diff --git a/erpnext_biotrack/biotrackthc/__init__.py b/erpnext_biotrack/biotrackthc/__init__.py index e2f71f7..65b5f8a 100644 --- a/erpnext_biotrack/biotrackthc/__init__.py +++ b/erpnext_biotrack/biotrackthc/__init__.py @@ -1,64 +1,104 @@ from __future__ import unicode_literals from erpnext_biotrack.biotrackthc.client import BioTrackClientError -from erpnext_biotrack.utils import make_log from .client import post import frappe +from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller + +def sync_up_enabled(): + if frappe.flags.in_import or frappe.flags.in_test: + return False + + settings = frappe.get_doc("BioTrack Settings") + return settings.is_sync_up_enabled() + +def get_location(): + return frappe.get_value("BioTrack Settings", None, "location") def call(fn, *args, **kwargs): - if frappe.conf.get("biotrack.developer_mode"): - from .client_dev import post as post_dev - return post_dev(fn, *args, **kwargs) - - return post(fn, *args, **kwargs) - - -def sync(resources=None, force_sync=False, verbose=False): - if isinstance(resources, basestring): - resources = [resources] - - if not resources: - resources = [ - "employee", - "plant_room", - "inventory_room", - "vendor", - "inventory", - "plant", - "manifest", - "qa_lab", - "qa_sample", - ] - - frappe.flags.force_sync = force_sync - frappe.flags.in_import = True - - make_log(title="BioTrackTHC: Sync started...", status="Queued", method="sync", message="Started") - for name in resources: - action = "sync_{}".format(name) - method = frappe.get_attr("erpnext_biotrack.biotrackthc.{}.sync".format(name)) - if verbose: - print 'Sync "{}"'.format(name) - try: - result = method() - if not isinstance(result, tuple): - result = (result, 0) - - success, fail = result - make_log(method=action, message="synced {}, failed: {}".format(success, fail or 0)) - - except BioTrackClientError as e: - make_log( - title="{} has terminated".format(name), - status="Error", - method=name, - message=frappe.get_traceback(), - exception=True - ) - - frappe.db.set_value("BioTrack Settings", None, "last_sync_datetime", frappe.utils.now()) - make_log(title="BioTrackTHC: Sync Completed", status="Success", method="sync", message="Completed") - - frappe.flags.force_sync = False - frappe.flags.in_import = False + if frappe.conf.get("biotrack.developer_mode"): + from .client_dev import post as post_dev + return post_dev(fn, *args, **kwargs) + + return post(fn, *args, **kwargs) + + +def map_resources(doctype): + resources = [] + + if doctype == "Plant": + resources.append("plant") + elif doctype == "Plant Room": + resources.append("plant_room") + elif doctype == "Item": + resources.append("inventory") + resources.append("plant") + elif doctype == "Customer": + resources.append("vendor") + elif doctype == "Employee": + resources.append("employee") + elif doctype == "Quality Inspection": + resources.append("qa_sample") + elif doctype == "Warehouse": + resources.append("inventory_room") + + return resources + +def sync(doctype=None, resources=None, force_sync=False, async_notify=False): + main_resources = [] + + if not resources: + resources = [] + + elif isinstance(resources, basestring): + resources = [resources] + + if doctype: + main_resources = map_resources(doctype) + main_resources += resources + + if not main_resources: + main_resources = [ + "employee", + "plant_room", + "inventory_room", + "vendor", + "inventory", + "plant", + "manifest", + "qa_lab", + "qa_sample", + ] + + frappe.flags.force_sync = force_sync + frappe.flags.in_import = True + + if async_notify: + frappe.publish_realtime("msgprint", {"message": "Sync started", "alert": True}) + + for name in main_resources: + method = frappe.get_attr("erpnext_biotrack.biotrackthc.{}.sync".format(name)) + try: + method() + except Exception as e: + make_log(name, frappe.get_traceback(), "Failed") + + if not doctype: + frappe.db.set_value("BioTrack Settings", None, "last_sync_datetime", frappe.utils.now()) + make_log("Sync Completed") + + if async_notify: + frappe.publish_realtime("msgprint", {"message": "Sync completed", "alert": True}) + if doctype: + frappe.publish_realtime("list_update", {"doctype": doctype}) + + frappe.flags.force_sync = False + frappe.flags.in_import = False + +def make_log(action, data=None, status='Completed'): + service = get_integration_controller("BioTrack") + integration_req = service.create_request(data) + integration_req.action = action + integration_req.status = status + integration_req.save() \ No newline at end of file diff --git a/erpnext_biotrack/biotrackthc/bootinfo.py b/erpnext_biotrack/biotrackthc/bootinfo.py new file mode 100644 index 0000000..e7670e1 --- /dev/null +++ b/erpnext_biotrack/biotrackthc/bootinfo.py @@ -0,0 +1,8 @@ +import frappe + +def boot(bootinfo): + settings = frappe.get_doc("BioTrack Settings") + + bootinfo.biotrackthc_enabled = settings.is_enabled() + bootinfo.biotrackthc_sync_down = settings.is_sync_down_enabled() + bootinfo.biotrackthc_sync_up = settings.is_sync_up_enabled() \ No newline at end of file diff --git a/erpnext_biotrack/biotrackthc/client.py b/erpnext_biotrack/biotrackthc/client.py index 46e1717..82edd87 100644 --- a/erpnext_biotrack/biotrackthc/client.py +++ b/erpnext_biotrack/biotrackthc/client.py @@ -2,236 +2,144 @@ from frappe.modules.import_file import read_doc_from_file from frappe.utils import get_request_session, encode -from frappe.utils.data import flt, cint +from frappe.integration_broker.doctype.integration_service.integration_service import get_integration_controller class BioTrackClientError(frappe.ValidationError): - http_status_code = 500 + http_status_code = 500 - def __init__(self, *args, **kwargs): - if len(args) and isinstance(args[0], basestring): - frappe.local.message_log.append(args[0]) + def __init__(self, *args, **kwargs): + if len(args) and isinstance(args[0], basestring): + frappe.local.message_log.append(args[0]) - super(frappe.ValidationError, self).__init__(*args, **kwargs) + super(frappe.ValidationError, self).__init__(*args, **kwargs) class BioTrackEmptyDataError(BioTrackClientError): pass class BioTrackClient: - __API__ = "4.0" - __API_URL__ = "https://wslcb.mjtraceability.com/serverjson.asp" + __API__ = "4.0" + __API_URL__ = "https://wslcb.mjtraceability.com/serverjson.asp" - def __init__(self, license_number, username, password, is_training=0): - self.license_number = license_number - self.username = username - self.password = password - self.is_training = is_training + def __init__(self, license_number, username, password, is_training=0): + self.license_number = license_number + self.username = username + self.password = password + self.is_training = is_training - def post(self, action, data, raise_on_empty=True): - if not isinstance(data, dict): - raise BioTrackClientError("data must be instance of dict") + def post(self, action, data, raise_on_empty=False): + if not isinstance(data, dict): + raise BioTrackClientError("data must be instance of dict") - data["action"] = action - action_data = data.copy() + log = {} + data["action"] = action + action_data = data.copy() - data.update({ - "license_number": self.license_number, - "username": self.username, - "password": self.password, - "training": self.is_training, - "API": self.__API__, - }) + data.update({ + "license_number": self.license_number, + "username": self.username, + "password": self.password, + "training": self.is_training, + "API": self.__API__, + }) - if action != 'login': - data["nosession"] = 1 + if action != 'login': + data["nosession"] = 1 - print_log(data, " - Request Data") + log["request"] = action_data + print_log(data, " - Request Data") - request = get_request_session() - response = request.post(self.__API_URL__, data=json.dumps(data), headers={'Content-Type': 'application/json'}) - response.raise_for_status() - result = response.json() + service = get_integration_controller("BioTrack") + integration_req = service.create_request(log) + integration_req.action = action + integration_req.integration_type = "Remote" - if not result.get('success'): - raise BioTrackClientError(encode(result.get('error'))) + request = get_request_session() + response = request.post(self.__API_URL__, data=json.dumps(data), headers={'Content-Type': 'application/json'}) + response.raise_for_status() + result = response.json() - if raise_on_empty and len(result) == 1: - raise BioTrackEmptyDataError( - 'BioTrackTHC request was response empty data: {}'.format(json.dumps(action_data)) - ) + if not action.startswith("sync_"): + log["response"] = result + else: + log["response"] = {"success": result.get('success')} - print_log(result, " - Response") + print_log(result, " - Response") + integration_req.data = json.dumps(log) - return result + if not result.get('success'): + integration_req.status = 'Failed' + integration_req.save() + raise BioTrackClientError(encode(result.get('error'))) - def login(self): - return self.post('login', {}) + if raise_on_empty and len(result) == 1: + integration_req.status = 'Failed' + integration_req.save() + + raise BioTrackEmptyDataError( + 'BioTrackTHC request was response with empty data: {}'.format(json.dumps(action_data)) + ) + + integration_req.status = 'Completed' + integration_req.save() + + return result + + def login(self): + return self.post('login', {}) def get_client(license_number, username, password, is_training=0): - """ - :return BioTrackClient: - """ - return BioTrackClient(license_number, username, password, is_training) + """ + :return BioTrackClient: + """ + return BioTrackClient(license_number, username, password, is_training) def get_data(action, params=None, key=None): - result = post(action, data=params) - if key and key in result: - return result[key] + result = post(action, data=params) + if key and key in result: + return result[key] - return result + return result def post(action, data): - settings = frappe.get_doc("BioTrack Settings") - if not settings.enable_biotrack: - raise BioTrackClientError('BioTrackTHC integration is not enabled') - - client = get_client(settings.license_number, settings.username, settings.get_password(), settings.is_training) - - def try_from_cache(): - filename = action + '.json' - training_dir = '/training' if settings.is_training else '' - cache_dir = frappe.get_app_path("erpnext_biotrack", - "fixtures/offline_sync{training_dir}".format(training_dir=training_dir)) - - if not os.path.exists(cache_dir): - os.mkdir(cache_dir) - - f = frappe.get_app_path("erpnext_biotrack", cache_dir, filename) - - if os.path.exists(f): - result = read_doc_from_file(f) - else: - result = client.post(action, data) - with open(f, "w") as outfile: - outfile.write(frappe.as_json(result)) - - return result - - offline_sync = frappe.conf.get('erpnext_biotrack.offline_sync') or 0 - if action.startswith("sync_") and offline_sync: - return try_from_cache() - - return client.post(action, data) - - -def create_lot(stock_entry): - qty = 0 - data = [] - for entry in stock_entry.get("items"): - data.append({ - "barcodeid": entry.item_code, - "remove_quantity": entry.qty, - "remove_quantity_uom": "g", - }) - qty += entry.qty - - response = {} - try: - response = post("inventory_create_lot", {"data": data}) - except BioTrackClientError as ex: - frappe.local.message_log.pop() - frappe.throw(ex.message, title="BioTrack Request Failed") - - try: - from ..item_utils import make_lot_item - strain = frappe.get_value("Item", stock_entry.get("items")[0].item_code, "strain") - item = make_lot_item({ - "item_code": response.get("barcode_id"), - "barcode": response.get("barcode_id"), - "item_group": stock_entry.lot_group, - "default_warehouse": stock_entry.from_warehouse, - "strain": strain, - }, qty) - - stock_entry.lot_item = item.item_code - stock_entry.save() - except Exception: - post("inventory_convert_undo", {"barcodeid": [response.get("barcode_id")]}) - raise - -def create_product(stock_entry): - conversion_type = frappe.get_value("Item Group", stock_entry.product_group, "external_id") - - if not conversion_type: - frappe.throw("Inventory Type not found") - - qty = 0 - data = [] - request_data = {} - - for entry in stock_entry.get("items"): - data.append({ - "barcodeid": entry.item_code, - "remove_quantity": flt(entry.qty), - "remove_quantity_uom": "g", - }) - qty += entry.qty - - request_data["data"] = data - request_data["derivative_type"] = cint(conversion_type) - request_data["derivative_quantity"] = flt(stock_entry.product_qty) - request_data["derivative_quantity_uom"] = "g" - request_data["waste"] = flt(stock_entry.product_waste) - request_data["waste_uom"] = "g" - - product_usable = flt(stock_entry.product_usable) - if product_usable: - request_data["derivative_usable"] = product_usable - - if stock_entry.product_name: - request_data["derivative_product"] = stock_entry.product_name - - response = {} - try: - response = post("inventory_convert", request_data) - except BioTrackClientError as ex: - frappe.local.message_log.pop() - frappe.throw(ex.message, title="BioTrack Request Failed") - - derivatives = response.get("derivatives", []) - try: - from ..item_utils import make_item - for derivative in derivatives: - item_type = derivative.get("barcode_type") - barcode = derivative.get("barcode_id") - - # Waste - if item_type == 27: - make_item(properties={ - "item_name": "Waste", - "item_code": barcode, - "barcode": barcode, - "item_group": "Waste", - "default_warehouse": stock_entry.from_warehouse, - }, qty=stock_entry.product_waste) - stock_entry.waste_item = barcode - - if item_type == conversion_type: - make_item(properties={ - "item_name": stock_entry.product_name or stock_entry.product_group, - "item_code": barcode, - "barcode": barcode, - "item_group": stock_entry.product_group, - "default_warehouse": stock_entry.from_warehouse, - }, qty=stock_entry.product_qty) - - stock_entry.product_item = barcode - - stock_entry.save() - - except Exception: - barcodeid = [] - for derivative in derivatives: - barcodeid.append(derivative.get("barcode_id")) - post("inventory_convert_undo", {"barcodeid": barcodeid}) - raise + settings = frappe.get_doc("BioTrack Settings") + if not settings.is_enabled(): + raise BioTrackClientError('BioTrackTHC integration is not enabled') + + client = get_client(settings.license_number, settings.username, settings.get_password(), settings.is_training) + + def try_from_cache(): + filename = action + '.json' + training_dir = '/training' if settings.is_training else '' + cache_dir = frappe.get_app_path("erpnext_biotrack", + "fixtures/offline_sync{training_dir}".format(training_dir=training_dir)) + + if not os.path.exists(cache_dir): + os.mkdir(cache_dir) + + f = frappe.get_app_path("erpnext_biotrack", cache_dir, filename) + + if os.path.exists(f): + result = read_doc_from_file(f) + else: + result = client.post(action, data) + with open(f, "w") as outfile: + outfile.write(frappe.as_json(result)) + + return result + + offline_sync = frappe.conf.get('erpnext_biotrack.offline_sync') or 0 + if action.startswith("sync_") and offline_sync: + return try_from_cache() + + return client.post(action, data) def print_log(data, description=None): - if (frappe.conf.get("logging") or 0) > 0: - frappe.log("<<<< BioTrackTHC{description}".format(description=description)) - frappe.log(json.dumps(data)) - frappe.log(">>>>") + if (frappe.conf.get("logging") or 0) > 0: + frappe.log("<<<< BioTrackTHC{description}".format(description=description)) + frappe.log(json.dumps(data)) + frappe.log(">>>>") diff --git a/docs/conversion_specs.md b/erpnext_biotrack/biotrackthc/docs/conversion_specs.md similarity index 100% rename from docs/conversion_specs.md rename to erpnext_biotrack/biotrackthc/docs/conversion_specs.md diff --git a/erpnext_biotrack/erpnext_biotrack/__init__.py b/erpnext_biotrack/biotrackthc/doctype/__init__.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/__init__.py rename to erpnext_biotrack/biotrackthc/doctype/__init__.py diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/__init__.py b/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/__init__.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/__init__.py rename to erpnext_biotrack/biotrackthc/doctype/biotrack_settings/__init__.py diff --git a/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.js b/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.js new file mode 100644 index 0000000..e64f0c6 --- /dev/null +++ b/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.js @@ -0,0 +1,33 @@ +// Copyright (c) 2016, Webonyx and contributors +// For license information, please see license.txt + +frappe.ui.form.on('BioTrack Settings', { + refresh: function (frm) { + if (frm.doc.username) { + if (frm.doc.synchronization == 'All' || frm.doc.synchronization == 'Down') { + cur_frm.add_custom_button(' ' + __('Sync Now'), + function () { + frappe.call({ + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_now" + }) + } + ) + } + + if (frm.doc.is_training) { + cur_frm.set_intro('Training mode is enabled') + } + } + + cur_frm.add_custom_button(' ' + __("Show Log"), function () { + frappe.route_options = {"integration_request_service": "BioTrack"}; + frappe.set_route("List", "Integration Request"); + }); + + $(' ' + __("Help") + '') + .appendTo(cur_frm.page.inner_toolbar); + } + +}); \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.json b/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.json similarity index 65% rename from erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.json rename to erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.json index 564dcbe..e4fe3a7 100644 --- a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.json +++ b/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.json @@ -7,7 +7,7 @@ "custom": 0, "docstatus": 0, "doctype": "DocType", - "document_type": "System", + "document_type": "", "editable_grid": 1, "engine": "InnoDB", "fields": [ @@ -16,16 +16,17 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "sb0", - "fieldtype": "Section Break", + "fieldname": "synchronization", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "BioTrackTHC Integration", + "label": "Synchronization", "length": 0, "no_copy": 0, + "options": "\nUp\nDown\nAll", "permlevel": 0, "precision": "", "print_hide": 0, @@ -33,7 +34,7 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -43,17 +44,19 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "0", - "fieldname": "enable_biotrack", - "fieldtype": "Check", + "default": "Daily", + "depends_on": "", + "fieldname": "sync_frequency", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Enable", + "label": "Sync Frequency", "length": 0, "no_copy": 0, + "options": "Daily\nWeekly", "permlevel": 0, "precision": "", "print_hide": 0, @@ -61,6 +64,33 @@ "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "last_sync_datetime", + "fieldtype": "Datetime", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Last Sync", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, "reqd": 0, "search_index": 0, "set_only_once": 0, @@ -97,7 +127,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_biotrack", + "depends_on": "", "fieldname": "license_number", "fieldtype": "Data", "hidden": 0, @@ -125,7 +155,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_biotrack", + "depends_on": "", "fieldname": "username", "fieldtype": "Data", "hidden": 0, @@ -153,7 +183,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_biotrack", + "depends_on": "", "fieldname": "password", "fieldtype": "Password", "hidden": 0, @@ -181,7 +211,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_biotrack", + "depends_on": "", "description": "License number of location", "fieldname": "location", "fieldtype": "Data", @@ -210,7 +240,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "enable_biotrack", + "depends_on": "", "description": "", "fieldname": "is_training", "fieldtype": "Check", @@ -260,59 +290,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "warehouse", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Warehouse", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_13", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -342,169 +319,6 @@ "set_only_once": 0, "unique": 0 }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb4", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "sb3", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "depends_on": "enable_biotrack", - "fieldname": "sync_enabled", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Enable Background Syncing", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "Daily", - "depends_on": "sync_enabled", - "fieldname": "schedule_in", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Sync Frequency", - "length": 0, - "no_copy": 0, - "options": "Hourly\nDaily\nWeekly", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "cb3", - "fieldtype": "Column Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "last_sync_datetime", - "fieldtype": "Datetime", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Last Sync", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, { "allow_on_submit": 0, "bold": 0, @@ -543,9 +357,9 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2016-11-10 16:11:56.363177", + "modified": "2016-11-24 17:36:04.576458", "modified_by": "Administrator", - "module": "ERPNext BioTrack", + "module": "BioTrackTHC", "name": "BioTrack Settings", "name_case": "", "owner": "Administrator", diff --git a/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.py b/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.py new file mode 100644 index 0000000..1bacc96 --- /dev/null +++ b/erpnext_biotrack/biotrackthc/doctype/biotrack_settings/biotrack_settings.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Webonyx and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from erpnext_biotrack.biotrackthc.client import get_client, BioTrackClientError +from frappe.integration_broker.doctype.integration_service.integration_service import IntegrationService +from frappe.utils import call_hook_method +from frappe.utils.background_jobs import enqueue + +class BioTrackSettings(IntegrationService): + service_name = "BioTrack" + scheduler_events = { + "daily_long": [ + "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_daily" + ], + "weekly_long": [ + "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_weekly" + ] + } + + def install_fixtures(self): + pass + + def validate(self): + if not self.flags.ignore_mandatory: + self.validate_biotrack_credentails() + + def on_update(self): + pass + + def enable(self): + """ enable service """ + if not self.flags.ignore_mandatory: + self.validate_biotrack_credentails() + + def validate_biotrack_credentails(self): + client = get_client(self.license_number, self.username, self.get_password(), self.is_training) + try: + client.login() + except BioTrackClientError as ex: + frappe.local.message_log = [] + frappe.msgprint(ex.message, indicator='red', title='Invalid access credentials') + + def is_enabled(self): + if not frappe.db.exists("Integration Service", self.service_name): + return False + + service = frappe.get_doc("Integration Service", self.service_name) + + return service.enabled + + def is_sync_down_enabled(self): + return True if self.is_enabled() and (self.synchronization == "All" or self.synchronization == "Down") else False + + def is_sync_up_enabled(self): + return True if self.is_enabled() and (self.synchronization == "All" or self.synchronization == "Up") else False + + def create_request(self, data): + return super(BioTrackSettings, self).create_request(data, "Host", self.service_name) + + +@frappe.whitelist() +def get_service_details(): + return """ +
+

Steps to enable BioTrack service:

+
    +
  1. Request api credentials at + + https://www.biotrack.com + +
  2. +
    +
  3. Setup credentials on BioTrack Settings doctype. + Click on + + top right corner +
  4. +
    +
  5. + After saving settings, + + BioTrack Integration Service and Save a document. +
  6. +
    +
  7. + To view api call logs, + +
  8. +
+

+ After enabling service, system will synchrony data from BioTrack daily or weekly basis + as per set on BioTrack Settings page. For detail what's data synced, see + + Doctype Mappings + document. +

+
+ """ + +@frappe.whitelist() +def sync_now(doctype=None): + "Enqueue longjob for syncing biotrack." + settings = frappe.get_doc("BioTrack Settings") + if not settings.is_sync_down_enabled(): + frappe.msgprint('BioTrack service is not enabled.', title='Error', indicator='red') + return + + from erpnext_biotrack.biotrackthc import sync + + force_sync = False + if doctype: + force_sync = True + + enqueue(sync, queue="long", doctype=doctype, force_sync=force_sync, async_notify=True) + + +def sync_daily(): + return sync_if('Daily') + + +def sync_weekly(): + return sync_if('Weekly') + + +def sync_if(frequency, default='Daily'): + settings = frappe.get_doc("BioTrack Settings") + if settings.is_sync_down_enabled() and (settings.sync_frequency or default) == frequency: + from erpnext_biotrack.biotrackthc import sync + sync() + call_hook_method('biotrack_synced') \ No newline at end of file diff --git a/erpnext_biotrack/biotrackthc/employee.py b/erpnext_biotrack/biotrackthc/employee.py index 808c22d..60f96c1 100644 --- a/erpnext_biotrack/biotrackthc/employee.py +++ b/erpnext_biotrack/biotrackthc/employee.py @@ -2,7 +2,6 @@ import frappe from datetime import date from frappe.defaults import get_defaults -from erpnext_biotrack.utils import make_log from .client import get_data @@ -43,27 +42,22 @@ def sync_employee(biotrack_employee, company, biotrack_employee_list): naming_series = frappe.get_meta("Employee").get_options("naming_series") or "EMP/" - try: - employee.update({ - "naming_series": naming_series, - "employee_name": employee_name, - "status": "Active", - "external_id": employee_id, - "external_transaction_id": transactionid, - "company": company, - "date_of_birth": date_of_birth, - "date_of_joining": date_of_joining, - }) - - employee.flags.ignore_mandatory = True - employee.save() - - biotrack_employee_list.append(biotrack_employee.get("employee_id")) - frappe.db.commit() - - except Exception as e: - make_log(title=e.message, status="Error", method="sync_employee", message=frappe.get_traceback(), - request_data=biotrack_employee, exception=True) + employee.update({ + "naming_series": naming_series, + "employee_name": employee_name, + "status": "Active", + "external_id": employee_id, + "external_transaction_id": transactionid, + "company": company, + "date_of_birth": date_of_birth, + "date_of_joining": date_of_joining, + }) + + employee.flags.ignore_mandatory = True + employee.save() + + biotrack_employee_list.append(biotrack_employee.get("employee_id")) + frappe.db.commit() def lookup_employee(name, external_id): diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/__init__.py b/erpnext_biotrack/biotrackthc/hooks/__init__.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/__init__.py rename to erpnext_biotrack/biotrackthc/hooks/__init__.py diff --git a/erpnext_biotrack/biotrackthc/hooks/plant.py b/erpnext_biotrack/biotrackthc/hooks/plant.py new file mode 100644 index 0000000..9bf3894 --- /dev/null +++ b/erpnext_biotrack/biotrackthc/hooks/plant.py @@ -0,0 +1,216 @@ +import sys +import frappe +from erpnext_biotrack.biotrackthc import sync_up_enabled, get_location, call +from frappe.utils.data import cstr, cint, flt + + +def call_hook(plant, method, *args, **kwargs): + if not sync_up_enabled(): + return + + return getattr(sys.modules[__name__], method)(plant, method, *args, **kwargs) + + +def is_bio_plant(plant): + return cstr(plant.get("bio_barcode")) != "" + + +def on_submit(plant, method): + # only root plant get handled + if plant.flags.in_bulk: + return + + plants = plant.flags.bulk_plants or [] + plants.append(plant) + + if len(plants) != plant.get("qty"): + frappe.throw("Bulk adding qty mismatch") + + plant_room = frappe.get_doc("Plant Room", plant.get("plant_room")) + result = call("plant_new", { + "room": plant_room.external_id, + "quantity": plant.get("qty"), + "strain": plant.get("strain"), + "source": plant.get("item_code"), + "mother": cint(plant.get("is_mother")), + "location": get_location() + }) + + for idx, barcode in enumerate(result.get("barcode_id")): + doc = plants[idx] + doc.set("bio_barcode", barcode) + doc.flags.ignore_validate_update_after_submit = True + doc.save() + + +def on_cancel(plant, method): + """Call plant_new_undo api""" + if not is_bio_plant(plant): + return + + # Barcode 9160883599199700 is no longer in a state where it can be un-done. + call("plant_new_undo", { + "barcodeid": [plant.bio_barcode], + }) + +def on_trash(plant, method): + if not is_bio_plant(plant): + return + + try: + call("plant_destroy", { + "barcodeid": [plant.bio_barcode], + }) + except Exception as e: + frappe.local.message_log.pop() + +def before_harvest_schedule(plant, method): + if not is_bio_plant(plant): + return + + # Barcode 9160883599199700 is no longer in a state where it can be harvested + call("plant_harvest_schedule", { + "barcodeid": [plant.bio_barcode], + }) + +def before_harvest_schedule_undo(plant, method): + if not is_bio_plant(plant): + return + + try: + call("plant_harvest_schedule_undo", { + "barcodeid": [plant.bio_barcode], + }) + except Exception as e: + frappe.local.message_log.pop() + # ignore error + pass + +def before_destroy_schedule(plant, method, *args, **kwargs): + if not is_bio_plant(plant): + return + + if not "reason_key" in kwargs: + frappe.throw('"reason_key" is missing') + + call("plant_destroy_schedule", { + "barcodeid": [plant.bio_barcode], + "reason_extended": kwargs.get("reason_key"), + "reason": kwargs.get("reason"), + "override": 1 if kwargs.get("override") else 0 + }) + +def before_destroy_schedule_undo(plant, method): + if not is_bio_plant(plant): + return + + try: + call("plant_destroy_schedule_undo", { + "barcodeid": [plant.bio_barcode], + }) + except Exception as e: + frappe.local.message_log.pop() + # ignore error + pass + +def after_harvest(plant, method, items, flower, other_material=None, waste=None, additional_collection=None): + if not is_bio_plant(plant): + return + + res = call("plant_harvest", { + "barcodeid": plant.bio_barcode, + "location": get_location(), + "weights": make_weights_data(flower, other_material, waste), + "collectadditional": cint(additional_collection), + }) + + map_item_derivatives(items, res.get("derivatives", [])) + + plant.set("bio_transaction_id", res.get("transactionid")) + plant.flags.ignore_validate_update_after_submit = True + plant.save() + +def before_harvest_undo(plant, method): + if not is_bio_plant(plant): + return + + if not plant.get("bio_transaction_id"): + return + + try: + call("plant_harvest_undo", { + "transactionid": plant.get("bio_transaction_id"), + }) + except Exception as e: + frappe.local.message_log.pop() + # ignore error + pass + + plant.set("bio_transaction_id", None) + plant.flags.ignore_validate_update_after_submit = True + plant.save() + + +def after_cure(plant, method, items, flower, other_material=None, waste=None, additional_collection=None): + if not is_bio_plant(plant): + return + + res = call("plant_cure", { + "barcodeid": plant.bio_barcode, + "location": get_location(), + "weights": make_weights_data(flower, other_material, waste), + "collectadditional": cint(additional_collection), + }) + + map_item_derivatives(items, res.get("derivatives", [])) + frappe.set_value("Plant", plant.name, "bio_transaction_id", res.get("transactionid")) + +def after_convert_to_inventory(plant, method, item): + if not is_bio_plant(plant): + return + + res = call("plant_convert_to_inventory", { + "barcodeid": plant.bio_barcode + }) + + frappe.set_value("Item", item.name, "bio_barcode", plant.bio_barcode) + frappe.set_value("Plant", plant.name, "bio_transaction_id", res.get("transactionid")) + +def make_weights_data(flower, other_material=None, waste=None): + amount_map = { + 6: flt(flower), + 9: flt(other_material), + 27: flt(waste), + } + + weights = [ + { + "amount": amount_map[6], + "invtype": 6, + "uom": "g" + } + ] + + if other_material: + weights.append({ + "amount": amount_map[9], + "invtype": 9, + "uom": "g" + }) + + if waste: + weights.append({ + "amount": amount_map[27], + "invtype": 27, + "uom": "g" + }) + + return weights + +def map_item_derivatives(items, derivatives): + for derivative in derivatives: + item_group = frappe.get_doc("Item Group", {"external_id": derivative.get("barcode_type")}) + for item in items: + if item.item_group == item_group.name: + item.set("barcode", derivative.get("barcode_id")) + item.save() \ No newline at end of file diff --git a/erpnext_biotrack/biotrackthc/hooks/stock_entry.py b/erpnext_biotrack/biotrackthc/hooks/stock_entry.py new file mode 100644 index 0000000..d27e6d2 --- /dev/null +++ b/erpnext_biotrack/biotrackthc/hooks/stock_entry.py @@ -0,0 +1,159 @@ +import sys +import frappe +from erpnext_biotrack.biotrackthc import sync_up_enabled, get_location, call +from erpnext_biotrack.biotrackthc.client import BioTrackClientError +from frappe.utils.data import cstr, cint, flt + + +def call_hook(plant, method, *args, **kwargs): + if not sync_up_enabled(): + return + + return getattr(sys.modules[__name__], method)(plant, method, *args, **kwargs) + + +def is_bio_item(item): + return cstr(item.get("bio_barcode")) != "" + + +def on_submit(doc, method): + """BioTrack sync up: inventory_new or inventory_adjust""" + if doc.purpose == "Material Issue" and not doc.conversion: + for item_entry in doc.get("items"): + item = frappe.get_doc("Item", item_entry.item_code) + if is_bio_item(item): + if not item_entry.t_warehouse: + _inventory_adjust(item, remove_quantity=item_entry.qty) + # else: + # inventory split and inventory_move + + elif doc.purpose == "Material Receipt": + for item_entry in doc.get("items"): + item = frappe.get_doc("Item", item_entry.item_code) + + if is_bio_item(item): + _inventory_adjust(item, additional_quantity=item_entry.qty) + + # only sync up marijuana item + elif item.is_marijuana_item: + _inventory_new(item, item_entry.qty) + +def _inventory_new(item, qty): + item_group = frappe.get_doc("Item Group", item.item_group) + if not item_group.external_id: + frappe.throw("Invalid inventory type") + + if not item.strain: + frappe.throw("strain is missing for item {0}".format(item.item_code)) + + call_data = { + "invtype": item_group.external_id, + "quantity": qty, + "strain": item.strain, + } + + if item.plant: + call_data["source_id"] = item.plant + + res = call("inventory_new", data={ + "data": call_data, + "location": get_location() + }) + + item.update({ + "bio_barcode": res.get("barcode_id")[0], + "bio_remaining_quantity": qty + }) + + item.save() + +def _inventory_adjust(item, additional_quantity=None, remove_quantity=None): + data = { + "barcodeid": item.bio_barcode, + "reason": "Client Adjustment", + "type": 1, + "remove_quantity_uom": "g", + } + + if additional_quantity: + item.bio_remaining_quantity = item.bio_remaining_quantity + additional_quantity + + elif remove_quantity: + item.bio_remaining_quantity = item.bio_remaining_quantity - remove_quantity + + data["quantity"] = item.bio_remaining_quantity + call("inventory_adjust", data={ + "data": data, + }) + + item.save() + +def after_conversion(doc, method): + qty = 0 + data = [] + + for entry in doc.get("items"): + bio_barcode = frappe.get_value("Item", entry.item_code, "bio_barcode") + if not bio_barcode: + frappe.throw(_("{0} is not a BioTrack Item. Consider to select BioTrack items only or turn off BioTrack synchronization").format(entry.item_code)) + + data.append({ + "barcodeid": bio_barcode, + "remove_quantity": entry.qty, + "remove_quantity_uom": "g", + }) + qty += entry.qty + + if doc.conversion == 'Create Lot': + _create_lot(doc, data) + else: + _create_product(doc, data) + +def _create_lot(stock_entry, data): + try: + res = call("inventory_create_lot", {"data": data}) + frappe.set_value("Item", stock_entry.lot_item, "bio_barcode", res.get("barcode_id")) + except BioTrackClientError as ex: + frappe.local.message_log.pop() + frappe.throw(ex.message, title="BioTrack synchrony failed") + +def _create_product(stock_entry, data): + product_type = frappe.get_value("Item Group", stock_entry.product_group, "external_id") + request_data = {} + + if not product_type: + frappe.throw("Inventory type not found") + + request_data["data"] = data + request_data["derivative_type"] = cint(product_type) + request_data["derivative_quantity"] = flt(stock_entry.product_qty) + request_data["derivative_quantity_uom"] = "g" + request_data["waste"] = flt(stock_entry.product_waste) + request_data["waste_uom"] = "g" + + product_usable = flt(stock_entry.product_usable) + + if product_usable > 0: + request_data["derivative_usable"] = product_usable + + if stock_entry.product_name: + request_data["derivative_product"] = stock_entry.product_name + + response = {} + try: + response = call("inventory_convert", request_data) + except BioTrackClientError as ex: + frappe.local.message_log.pop() + frappe.throw(ex.message, title="BioTrack synchrony failed") + + derivatives = response.get("derivatives", []) + + for derivative in derivatives: + item_type = derivative.get("barcode_type") + barcode = derivative.get("barcode_id") + + if item_type == 27 and stock_entry.waste_item: + frappe.set_value("Item", stock_entry.waste_item, "bio_barcode", barcode) + + elif item_type == product_type and stock_entry.product_item: + frappe.set_value("Item", stock_entry.product_item, "bio_barcode", barcode) diff --git a/erpnext_biotrack/biotrackthc/inventory.py b/erpnext_biotrack/biotrackthc/inventory.py index 29c064c..dc3fa71 100644 --- a/erpnext_biotrack/biotrackthc/inventory.py +++ b/erpnext_biotrack/biotrackthc/inventory.py @@ -10,7 +10,7 @@ from .client import get_data from erpnext_biotrack.config import get_default_stock_warehouse -from erpnext_biotrack.erpnext_biotrack.doctype.strain import find_strain +from erpnext_biotrack.traceability_system.doctype.strain import find_strain from frappe.utils.data import flt, nowdate, nowtime, now, cint @@ -21,38 +21,38 @@ def sync(): sync_time = now() samples = [] - data = normalize(get_biotrack_inventories()) - plant_data = get_normalized_plants() + # data = normalize(get_biotrack_inventories()) + # plant_data = get_normalized_plants() - for _, inventory in data.items(): + for inventory in get_biotrack_inventories(): # filter to keep available parent only - ids = inventory.get("parentid") or [] - parent_ids = [] - if ids: - for key in (inventory.get("parentid") or []): - if key in data: - parent_ids.append(key) - - # lookup again database - if not parent_ids: - parent_ids = [r[0] for r in frappe.get_list("Item", filters=[["barcode", "in", ids]], as_list=True)] - - # plants filter - ids = inventory.get("plantid") or [] - plant_ids = [] - if ids: - for key in (inventory.get("plantid") or []): - if key in plant_data: - plant_ids.append(key) - - # lookup again database - if not plant_ids: - plant_ids = [r[0] for r in frappe.get_list("Plant", filters=[["name", "in", ids]], as_list=True)] - - inventory["parentid"] = parent_ids - inventory["plantid"] = plant_ids - - if inventory.get("is_sample") and parent_ids: + # ids = inventory.get("parentid") or [] + # parent_ids = [] + # if ids: + # for key in (inventory.get("parentid") or []): + # if key in data: + # parent_ids.append(key) + # + # # lookup again database + # if not parent_ids: + # parent_ids = [r[0] for r in frappe.get_list("Item", filters=[["barcode", "in", ids]], as_list=True)] + # + # # plants filter + # ids = inventory.get("plantid") or [] + # plant_ids = [] + # if ids: + # for key in (inventory.get("plantid") or []): + # if key in plant_data: + # plant_ids.append(key) + # + # # lookup again database + # if not plant_ids: + # plant_ids = [r[0] for r in frappe.get_list("Plant", filters=[["name", "in", ids]], as_list=True)] + # + # inventory["parentid"] = parent_ids + # inventory["plantid"] = plant_ids + + if inventory.get("is_sample"): samples.append(inventory) continue @@ -65,27 +65,28 @@ def sync(): return success, 0 -def sync_item(biotrack_inventory): - barcode = str(biotrack_inventory.get("id")) - remaining_quantity = flt(biotrack_inventory.get("remaining_quantity")) +def sync_item(data): + barcode = str(data.get("id")) + remaining_quantity = flt(data.get("remaining_quantity")) name = None item_values = get_item_values(barcode, ["name", "transaction_id"]) if item_values: name, transaction_id = item_values - if not (frappe.flags.force_sync or False) and transaction_id == biotrack_inventory.get("transactionid"): - frappe.db.set_value("Item", name, "last_sync", now(), update_modified=False) + if not (frappe.flags.force_sync or False) and transaction_id == data.get("transactionid"): + frappe.db.set_value("Item", name, "bio_last_sync", now(), update_modified=False) return False # inventory type - item_group = find_item_group(biotrack_inventory) - warehouse = find_warehouse(biotrack_inventory) + item_group = find_item_group(data) + warehouse = find_warehouse(data) + current_remaining_quantity = 0 # product (Item) mapping - if biotrack_inventory.get("productname"): - item_name = biotrack_inventory.get("productname") + if data.get("productname"): + item_name = data.get("productname") else: - item_name = " ".join(filter(None, [biotrack_inventory.get("strain"), item_group.name])) + item_name = " ".join(filter(None, [data.get("strain"), item_group.name])) if not name: item_code = barcode @@ -93,7 +94,7 @@ def sync_item(biotrack_inventory): "doctype": "Item", "item_code": item_code, "item_name": item_name, - "barcode": barcode, + "bio_barcode": barcode, "is_stock_item": 1, "stock_uom": "Gram", "item_group": item_group.name, @@ -101,33 +102,47 @@ def sync_item(biotrack_inventory): }) else: item = frappe.get_doc("Item", name) + current_remaining_quantity = item.bio_remaining_quantity strain = "" - if biotrack_inventory.get("strain"): - strain = find_strain(biotrack_inventory.get("strain")) + if data.get("strain"): + strain = find_strain(data.get("strain")) # Post task will do on biotrack_after_sync hook - parent_ids = biotrack_inventory.get("parentid") - plant_ids = biotrack_inventory.get("plantid") + parent_ids = data.get("parentid") + plant_ids = data.get("plantid") if not item.is_lot_item and (parent_ids or plant_ids): item.set("linking_data", json.dumps({"parent_ids": parent_ids, "plant_ids": plant_ids})) item.update({ "item_name": item_name, + "bio_barcode": barcode, "strain": strain, - "actual_qty": remaining_quantity, - "transaction_id": biotrack_inventory.get("transactionid"), - "last_sync": now(), + "bio_remaining_quantity": remaining_quantity, + "transaction_id": data.get("transactionid"), + "bio_last_sync": now(), "disabled": 1 if remaining_quantity == 0 else 0, }) + item.flags.ignore_links = True item.save() - if remaining_quantity and item.is_stock_item: - adjust_stock(item, remaining_quantity) + # adjust_stock + if item.is_stock_item: + if remaining_quantity > current_remaining_quantity: + make_stock_entry(item_code=item.name, target=item.default_warehouse, qty=remaining_quantity - current_remaining_quantity) + + # Consider to not modified down item's balance because it's hard to figure out the correct warehouse and its balance to deduct + # elif remaining_quantity < current_remaining_quantity: + # posting_date, posting_time = nowdate(), nowtime() + # balance = get_stock_balance_for(item.name, item.default_warehouse, posting_date, posting_time) + # + # if balance["qty"] >= remaining_quantity: + # make_stock_entry(item_code=item.name, source=item.default_warehouse, + # qty=current_remaining_quantity - remaining_quantity) # Disable Usable Marijuana item does not have product name - if not biotrack_inventory.get("productname") and item_group.external_id == 28: + if not data.get("productname") and item_group.external_id == 28: frappe.db.set_value("Item", item.name, "disabled", 1) log_invalid_item(item) @@ -136,26 +151,6 @@ def sync_item(biotrack_inventory): return True -def adjust_stock(item, remaining_quantity): - posting_date, posting_time = nowdate(), nowtime() - balance = get_stock_balance_for(item.name, item.default_warehouse, posting_date, posting_time) - qty = flt(balance["qty"]) - rate = flt(balance["rate"]) - - # Material Receipt - if remaining_quantity > qty: - make_stock_entry(item_code=item.name, target=item.default_warehouse, qty=remaining_quantity - qty) - - if remaining_quantity < qty: - create_stock_reconciliation( - item_code=item.name, - item_name=item.item_name, - warehouse=item.default_warehouse, - qty=remaining_quantity, - rate=rate if rate > 0 else 1 - ) - - def syn_samples(samples): for inventory in samples: parent_ids = inventory.get("parentid") or [] @@ -184,23 +179,6 @@ def syn_samples(samples): -def create_stock_reconciliation(**args): - args = frappe._dict(args) - sr = frappe.new_doc("Stock Reconciliation") - sr.posting_date = args.posting_date or nowdate() - sr.posting_time = args.posting_time or nowtime() - sr.company = args.company or get_default_company() - sr.append("items", { - "item_code": args.item_code, - "item_name": args.item_name, - "warehouse": args.warehouse, - "qty": args.qty, - "valuation_rate": args.rate - }) - - sr.submit() - return sr - def find_warehouse(data): if not data.get("currentroom"): @@ -226,8 +204,8 @@ def disable_deleted_items(sync_time=None): sync_time = now() return frappe.db.sql( - "update tabItem set `actual_qty` = 0, `disabled` = 1 where transaction_id IS NOT NULL and (`last_sync` IS NULL or `last_sync` < %(last_sync)s)", - {"last_sync": sync_time}) + "update tabItem set `bio_remaining_quantity` = 0, `disabled` = 1 where transaction_id IS NOT NULL and (`bio_last_sync` IS NULL or `bio_last_sync` < %(bio_last_sync)s)", + {"bio_last_sync": sync_time}) def log_invalid_item(item): diff --git a/erpnext_biotrack/biotrackthc/plant.py b/erpnext_biotrack/biotrackthc/plant.py index e8e1475..a8d2b3e 100644 --- a/erpnext_biotrack/biotrackthc/plant.py +++ b/erpnext_biotrack/biotrackthc/plant.py @@ -1,7 +1,13 @@ from __future__ import unicode_literals + +import json + import frappe, datetime +from erpnext_biotrack.traceability_system.doctype.strain import find_strain +from erpnext_biotrack.item_utils import get_item_values + from .client import get_data -from frappe.utils.data import get_datetime_str, now +from frappe.utils.data import now, cint, DATE_FORMAT, TIME_FORMAT, DATETIME_FORMAT, get_datetime_str @frappe.whitelist() @@ -19,22 +25,72 @@ def sync(): return len(biotrack_plants) -def sync_plant(biotrack_plant): - barcode = biotrack_plant.get("id") - if frappe.db.exists("Plant", barcode): - doc = frappe.get_doc("Plant", barcode) +def sync_plant(data): + barcode = data.get("id") + name = frappe.db.sql_list("select name from tabPlant where name=%(barcode)s or bio_barcode=%(barcode)s", {"barcode": barcode}) + + if name: + name = name.pop() + doc = frappe.get_doc("Plant", name) + if doc.get("bio_transaction_id") == data.get("transactionid"): + frappe.db.set_value("Plant", name, "bio_last_sync", now(), update_modified=False) + return False + else: + sessiontime = datetime.datetime.fromtimestamp(cint(data.get("sessiontime"))) + doc = frappe.get_doc({ + "__islocal": 1, + "doctype": "Plant", + "bio_barcode": barcode, + "posting_date": sessiontime.strftime(DATE_FORMAT), + "posting_time": sessiontime.strftime(TIME_FORMAT) + }) + + + plant_room = frappe.get_doc("Plant Room", {"external_id": data.get("room")}) + doc.update({ + "strain": find_strain(data.get("strain")), + "plant_room": plant_room.get("name") if plant_room else "", + "is_mother_plant": cint(data.get("mother")), + "destroy_scheduled": cint(data.get("removescheduled")), + "harvest_collect": cint(data.get("harvestcollect")), + "cure_collect": cint(data.get("curecollect")), + "bio_transaction_id": cint(data.get("transactionid")), + "bio_last_sync": now(), + "disabled": 0, + }) + + item_values = get_item_values(data.get("parentid"), ["name", "item_group"]) + if item_values: + item, item_group = item_values + doc.item = item + doc.item_group = item_group + + if doc.get("destroy_scheduled") and data.get("removescheduletime"): + doc.remove_time = datetime.datetime.fromtimestamp(cint(data.get("removescheduletime"))).strftime(DATETIME_FORMAT) + + if data.get("removereason"): + doc.remove_reason = data.get("removereason") + + state = cint(data.get("state")) + doc.state = "Drying" if state == 1 else ("Cured" if state == 2 else "Growing") + + doc.flags.in_import = True + doc.flags.ignore_validate_update_after_submit = True + doc.flags.ignore_mandatory = True + + if doc.is_new(): + doc.submit() else: - creation_datetime = datetime.datetime.fromtimestamp(int(biotrack_plant.get("sessiontime"))) - doc = frappe.get_doc({"doctype": "Plant", "barcode": barcode, "creation": get_datetime_str(creation_datetime)}) + doc.save() - doc.biotrack_sync_down(biotrack_plant) frappe.db.commit() def disable_deleted_plants(sync_time): return frappe.db.sql( - "update tabPlant set `disabled` = 1, remove_scheduled = 1 where transaction_id IS NOT NULL and (`last_sync` IS NULL or `last_sync` < %(last_sync)s)", - {"last_sync": sync_time}) + "update tabPlant set `disabled` = 1, destroy_scheduled = 1 where bio_transaction_id IS NOT NULL and (`bio_last_sync` IS NULL or `bio_last_sync` < %s)", + sync_time + ) def get_normalized(): diff --git a/erpnext_biotrack/config/desktop.py b/erpnext_biotrack/config/desktop.py index 6dca346..ebd740d 100644 --- a/erpnext_biotrack/config/desktop.py +++ b/erpnext_biotrack/config/desktop.py @@ -5,11 +5,10 @@ def get_data(): return [ { "module_name": "Traceability System", - "label": _("Traceability System"), + "label": _("Traceability"), "color": "green", "icon": "icon-leaf", "type": "module", - "system_manager": 1, "hidden": 1 }, ] diff --git a/erpnext_biotrack/config/docs.py b/erpnext_biotrack/config/docs.py index e7701fe..220a46c 100644 --- a/erpnext_biotrack/config/docs.py +++ b/erpnext_biotrack/config/docs.py @@ -2,6 +2,14 @@ Configuration for docs """ +source_link = "https://github.com/webonyx/erpnext_biotrack" +docs_base_url = "https://webonyx.github.io/erpnext_biotrack" +headline = "ERPNext Canabis" +sub_heading = "ERPNext solution for Cannabis Industry" +long_description = """ A very long description """ + +docs_version = "1.x.x" +splash_light_background = True # source_link = "https://github.com/[org_name]/erpnext_biotrack" # docs_base_url = "https://[org_name].github.io/erpnext_biotrack" @@ -9,4 +17,5 @@ # sub_heading = "Yes, you got that right the first time, everything" def get_context(context): - context.brand_html = "ERPNext BioTrack" + context.brand_html = "ERPNext Canabis" + context.app.splash_light_background = True diff --git a/erpnext_biotrack/config/traceability_system.py b/erpnext_biotrack/config/traceability_system.py index fed72bf..9978a77 100644 --- a/erpnext_biotrack/config/traceability_system.py +++ b/erpnext_biotrack/config/traceability_system.py @@ -5,12 +5,8 @@ def get_data(): return [ { - "label": _("Traceability System"), + "label": _("Cultivation"), "items": [ - { - "type": "doctype", - "name": "Item", - }, { "type": "doctype", "name": "Plant", @@ -24,6 +20,23 @@ def get_data(): "name": "Strain", } ] + }, + { + "label": _("Inventory"), + "items": [ + { + "type": "doctype", + "name": "Item", + }, + { + "type": "doctype", + "name": "Item Group", + }, + { + "type": "doctype", + "name": "Warehouse", + } + ] }, { "label": _("Integrations"), @@ -32,11 +45,6 @@ def get_data(): "type": "doctype", "name": "BioTrack Settings", "label": "BioTrackTHC Settings", - }, - { - "type": "doctype", - "name": "BioTrack Log", - "label": "BioTrackTHC Sync Log", } ] } diff --git a/erpnext_biotrack/custom_scripts/customer_list.js b/erpnext_biotrack/custom_scripts/customer_list.js index e901665..73448b4 100644 --- a/erpnext_biotrack/custom_scripts/customer_list.js +++ b/erpnext_biotrack/custom_scripts/customer_list.js @@ -1,12 +1,14 @@ $.extend(frappe.listview_settings['Customer'], { onload: function (DocListView) { DocListView.listview.stats.push("customer_group"); - DocListView.page.add_action_item(__("Synchronization"), function () { - frappe.call({ - method: "erpnext_biotrack.tasks.client_sync", - args: {"doctype": "Customer"} + if (frappe.boot.biotrackthc_sync_down) { + DocListView.page.add_action_item(__("BioTrackTHC Sync"), function () { + frappe.call({ + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_now", + args: {"doctype": "Customer"} + }) }) - }) + } } }); diff --git a/erpnext_biotrack/custom_scripts/delivery_note.js b/erpnext_biotrack/custom_scripts/delivery_note.js index 6169279..b402b5c 100644 --- a/erpnext_biotrack/custom_scripts/delivery_note.js +++ b/erpnext_biotrack/custom_scripts/delivery_note.js @@ -2,7 +2,7 @@ frappe.ui.form.on("Delivery Note", { onload: function (frm) { frm.set_indicator_formatter('item_code', function (doc) { - return (doc.docstatus == 1 || doc.qty <= doc.actual_qty) ? "green" : "orange" + return (doc.docstatus == 1) ? "green" : "orange" }, function (doc) { return frappe.form.link_formatters['Item'](doc.item_code, doc); diff --git a/erpnext_biotrack/custom_scripts/employee_list.js b/erpnext_biotrack/custom_scripts/employee_list.js index b20ba81..106df44 100644 --- a/erpnext_biotrack/custom_scripts/employee_list.js +++ b/erpnext_biotrack/custom_scripts/employee_list.js @@ -1,10 +1,12 @@ $.extend(frappe.listview_settings['Employee'], { onload: function (DocListView) { - DocListView.page.add_action_item(__("Synchronization"), function () { - frappe.call({ - method: "erpnext_biotrack.tasks.client_sync", - args: {"doctype": "Employee"} + if (frappe.boot.biotrackthc_sync_down) { + DocListView.page.add_action_item(__("BioTrackTHC Sync"), function () { + frappe.call({ + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_now", + args: {"doctype": "Employee"} + }) }) - }) + } } }); diff --git a/erpnext_biotrack/custom_scripts/item.js b/erpnext_biotrack/custom_scripts/item.js index ca5778a..bc0e17f 100644 --- a/erpnext_biotrack/custom_scripts/item.js +++ b/erpnext_biotrack/custom_scripts/item.js @@ -1,12 +1,4 @@ frappe.ui.form.on("Item", { - onload: function (frm) { - frm.fields_dict['item_group'].get_query = function (doc, cdt, cdn) { - return { - filters: {'parent_item_group': 'WA State Classifications'} - } - }; - }, - refresh: function (frm) { if (!frm.is_new()) { // Download certificate button @@ -33,41 +25,17 @@ frappe.ui.form.on("Item", { frm.set_value('certificate', attachment.file_url); frm.save('Save'); } - }) + }); dialog.set_title(__('Attach Certificate')); }); } - if (frm.doc.is_stock_item) { - var $btn = frm.add_custom_button(__("Sub Lot/Batch"), function () { - frappe.call({ - method: 'erpnext.stock.doctype.stock_reconciliation.stock_reconciliation.get_stock_balance_for', - args: { - item_code: frm.doc['item_code'], - warehouse: frm.doc['default_warehouse'], - posting_date: null, - posting_time: null - }, - callback: function (r) { - var actual_qty = r.message.qty; - if (actual_qty) { - erpnext.item.clone_item(frm.doc, actual_qty, r.message.rate) - } else { - frappe.msgprint( - { - message: __("Qty not available for {0} in warehouse {1}.

Available qty is 0", [frm.doc['item_name'], frm.doc['default_warehouse']]), - title: "Insufficient Stock", - indicator: 'red' - } - ); - $btn.prop('disabled', true); - } - } - }); - - }) + if (frm.doc.is_stock_item && (frm.doc.item_group === 'Flower' || frm.doc.item_group === 'Other Plant Material')) { + frm.add_custom_button(__('Create Lot'), function () { + erpnext.item.create_lot(frm.doc); + }); } } @@ -75,11 +43,150 @@ frappe.ui.form.on("Item", { }, is_marijuana_item: function (frm) { - erpnext.item.toggle_marijuana_attributes(frm) + erpnext.item.toggle_marijuana_attributes(frm); } }); $.extend(erpnext.item, { + create_lot: function (doc) { + frappe.call({ + method: 'erpnext.stock.dashboard.item_dashboard.get_data', + args: { + item_code: doc.item_code + }, + callback: function (r) { + var data = r.message || []; + if (data) { + open_dialog_form(r.message); + } else { + frappe.msgprint( + { + title: __('Error'), + message: __('Item {0} is out of stock', [doc.item_code]), + indicator: 'red' + } + ); + } + } + }); + + function open_dialog_form(data) { + frappe.model.with_doctype('Stock Entry', function () { + var ste = frappe.model.get_new_doc('Stock Entry'); + ste.purpose = "Material Issue"; + ste.conversion = 'Create Lot'; + ste.lot_group = doc.item_group + ' Lot'; + + var dialog = new frappe.ui.Dialog({ + title: __('{0} Lot Creation Tool', [doc.item_group]), + fields: [ + { + fieldname: 'item_code', label: __('Item'), + fieldtype: 'Link', options: 'Item', read_only: 1, 'default': doc.item_code + }, + { + fieldname: 'warehouse', label: __('Warehouse'), + fieldtype: 'Select', options: data.map(function (r) { + return r.warehouse + } + ), reqd: 1 + + }, + { + fieldname: 'qty', + label: __('Quantity'), + reqd: 1, + fieldtype: 'Float', + depends_on: 'warehouse' + }, + { + fieldname: 'rate', + label: __('Valuation Rate'), + fieldtype: 'Currency', + reqd: 1, + depends_on: 'warehouse' + } + ] + }); + + var update_doc = function () { + ste.from_warehouse = dialog.get_value('warehouse'); + ste.items = []; + + var row = frappe.model.add_child(ste, 'items'); + row.item_code = dialog.get_value('item_code'); + row.f_warehouse = dialog.get_value('warehouse'); + row.qty = dialog.get_value('qty'); + }; + + var open_doc = function () { + dialog.hide(); + update_doc(); + frappe.set_route('Form', 'Stock Entry', ste.name); + }; + + dialog.show(); + var $body = $(dialog.body); + + $body.find('select[data-fieldname="warehouse"]').on("change", function () { + var val = $(this).val(), qty = 0, rate, r; + if (val) { + r = data.filter(function (d) { + return d.warehouse === val; + }); + + if (r) { + qty = r[0].actual_qty; + rate = r[0].valuation_rate; + } + } + + dialog.get_field('qty').$wrapper.find(".help-box").html(__('Available {0}', [qty])); + // dialog.set_value('qty', qty); + dialog.set_value('rate', rate); + }); + + dialog.set_primary_action(__('Save'), function () { + if (dialog.working) return; + var data = dialog.get_values(); + if (data) { + update_doc(); + + dialog.working = true; + frappe.call({ + method: "frappe.client.submit", + args: { + doc: ste + }, + callback: function (r) { + dialog.hide(); + // delete the old doc + frappe.model.clear_doc(ste.doctype, ste.name); + frappe.ui.form.update_calling_link(r.message.name); + cur_frm.reload_doc(); + }, + error: function () { + open_doc(); + }, + always: function () { + dialog.working = false; + }, + freeze: true + }); + } + }); + + $('

' + + __("Add more items or open full form") + '

') + .appendTo($body) + .find('.link-open') + .on('click', function () { + open_doc(); + }); + + }); + } + }, clone_item: function (doc, actual_qty, rate) { var dialog = new frappe.ui.Dialog({ title: __('Sub Lot/Batch'), @@ -148,11 +255,20 @@ $.extend(erpnext.item, { frm.toggle_display("is_marijuana_item", frm.doc.__islocal); frm.toggle_reqd("strain", frm.doc.is_marijuana_item); - frm.toggle_reqd("default_warehouse", frm.doc.is_marijuana_item); - - frm.toggle_display("actual_qty", frm.doc.is_marijuana_item); - frm.toggle_reqd("actual_qty", frm.doc.is_marijuana_item); - frm.toggle_display("barcode", !frm.doc.__islocal || (frm.doc.__islocal && !frm.doc.is_marijuana_item)); + // frm.toggle_reqd("default_warehouse", frm.doc.is_marijuana_item); + if (frm.doc.is_marijuana_item || frm.doc.bio_barcode) { + frm.fields_dict['item_group'].get_query = function (doc, cdt, cdn) { + return { + filters: [["Item Group","docstatus","!=",2], ["Item Group","parent_item_group","=","WA State Classifications"]] + } + }; + } else { + frm.fields_dict['item_group'].get_query = function (doc, cdt, cdn) { + return { + filters: [["Item Group","docstatus","!=",2]] + } + }; + } } }); \ No newline at end of file diff --git a/erpnext_biotrack/custom_scripts/item_list.js b/erpnext_biotrack/custom_scripts/item_list.js index 3f4d53c..c2ecec4 100644 --- a/erpnext_biotrack/custom_scripts/item_list.js +++ b/erpnext_biotrack/custom_scripts/item_list.js @@ -4,24 +4,24 @@ if (settings.add_fields.indexOf('test_result') === -1) { } frappe.listview_settings['Item'] = $.extend({}, settings, { - get_indicator: function(doc) { + get_indicator: function (doc) { var indicator; if (settings.get_indicator) { indicator = settings.get_indicator(doc); } - if(indicator) { - return indicator; - } else if (doc.test_result){ + if (indicator) { + return indicator; + } else if (doc.test_result) { var indicators = { 'Failed': 'red', 'Pending': 'grey', 'Passed': 'green', 'Rejected': 'red' }; - return [__(doc.test_result), indicators[doc.test_result], "test_result,=," + doc.test_result]; + return [__(doc.test_result), indicators[doc.test_result], "test_result,=," + doc.test_result]; } - }, + }, onload: function (list) { if (settings.onload) { @@ -39,12 +39,14 @@ frappe.listview_settings['Item'] = $.extend({}, settings, { new_stock_entry('Create Product'); }); - list.page.add_action_item(__("Synchronization"), function () { - frappe.call({ - method: "erpnext_biotrack.tasks.client_sync", - args: {"doctype": "Item"} + if (frappe.boot.biotrackthc_sync_down) { + list.page.add_action_item(__("BioTrackTHC Sync"), function () { + frappe.call({ + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_now", + args: {"doctype": "Item"} + }) }) - }) + } } }); diff --git a/erpnext_biotrack/custom_scripts/quality_inspection_list.js b/erpnext_biotrack/custom_scripts/quality_inspection_list.js index 5f87ad1..57b83ee 100644 --- a/erpnext_biotrack/custom_scripts/quality_inspection_list.js +++ b/erpnext_biotrack/custom_scripts/quality_inspection_list.js @@ -1,8 +1,8 @@ frappe.listview_settings['Quality Inspection'] = { onload: function (DocListView) { - DocListView.page.add_action_item(__("Synchronization"), function () { + DocListView.page.add_action_item(__("BioTrackTHC Sync"), function () { frappe.call({ - method: "erpnext_biotrack.tasks.client_sync", + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_now", args: {"doctype": "Quality Inspection"} }) }) diff --git a/erpnext_biotrack/custom_scripts/warehouse_list.js b/erpnext_biotrack/custom_scripts/warehouse_list.js index c131848..bb6c49b 100644 --- a/erpnext_biotrack/custom_scripts/warehouse_list.js +++ b/erpnext_biotrack/custom_scripts/warehouse_list.js @@ -6,9 +6,9 @@ frappe.listview_settings['Warehouse'] = { } }, onload: function (DocListView) { - DocListView.page.add_action_item(__("Synchronization"), function () { + DocListView.page.add_action_item(__("BioTrackTHC Sync"), function () { frappe.call({ - method: "erpnext_biotrack.tasks.client_sync", + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_now", args: {"doctype": "Warehouse"} }) }) diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/__init__.py b/erpnext_biotrack/docs/__init__.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/__init__.py rename to erpnext_biotrack/docs/__init__.py diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/item_sub_lot/__init__.py b/erpnext_biotrack/docs/assets/__init__.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/item_sub_lot/__init__.py rename to erpnext_biotrack/docs/assets/__init__.py diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/plant/__init__.py b/erpnext_biotrack/docs/assets/img/__init__.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/plant/__init__.py rename to erpnext_biotrack/docs/assets/img/__init__.py diff --git a/erpnext_biotrack/docs/assets/img/biotrackthc.png b/erpnext_biotrack/docs/assets/img/biotrackthc.png new file mode 100644 index 0000000..e222b65 Binary files /dev/null and b/erpnext_biotrack/docs/assets/img/biotrackthc.png differ diff --git a/erpnext_biotrack/docs/user/__init__.py b/erpnext_biotrack/docs/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/erpnext_biotrack/docs/user/biotrackthc-integration.md b/erpnext_biotrack/docs/user/biotrackthc-integration.md new file mode 100644 index 0000000..ad1f733 --- /dev/null +++ b/erpnext_biotrack/docs/user/biotrackthc-integration.md @@ -0,0 +1,18 @@ +# BiotrackTHC Integration + +To Integrate BiotrackTHC with ERPNext Canabis, you need BiotrackTHC api credentials. + +####Step 1: Go to Setup + +`Traceability System > Integrations > BioTrack Settings` + +####Step 2: Activate + +In the BioTrack Settings, check "Enable" to enable BiotrackTHC. + +####Step 3: Settings + +Complete setting form with valid api credentials. + +BiotrackTHC Settings + \ No newline at end of file diff --git a/erpnext_biotrack/docs/user/cultivation-guides.md b/erpnext_biotrack/docs/user/cultivation-guides.md new file mode 100644 index 0000000..05fb6b3 --- /dev/null +++ b/erpnext_biotrack/docs/user/cultivation-guides.md @@ -0,0 +1,21 @@ + +#### Go to Plant list +`Traceability System > Cultivation > Plant` + +--- + +#### New Plant + +--- + +#### Harvest Plant + +--- + +#### Cure Plant + +--- + +#### Move Plant to inventory by convert to Mature Plant + +--- \ No newline at end of file diff --git a/erpnext_biotrack/docs/user/index.md b/erpnext_biotrack/docs/user/index.md new file mode 100644 index 0000000..ccd28db --- /dev/null +++ b/erpnext_biotrack/docs/user/index.md @@ -0,0 +1 @@ +{index} diff --git a/erpnext_biotrack/docs/user/index.txt b/erpnext_biotrack/docs/user/index.txt new file mode 100644 index 0000000..ebe2107 --- /dev/null +++ b/erpnext_biotrack/docs/user/index.txt @@ -0,0 +1,3 @@ +cultivation-guides +inventory-management +biotrackthc-integration \ No newline at end of file diff --git a/erpnext_biotrack/docs/user/inventory-management.md b/erpnext_biotrack/docs/user/inventory-management.md new file mode 100644 index 0000000..bd4598e --- /dev/null +++ b/erpnext_biotrack/docs/user/inventory-management.md @@ -0,0 +1,26 @@ +#### Go to Item list +`Traceability System > Inventory > Item` + +--- + +#### New Item + +--- + +#### Add Item quantity to a Warehouse + +--- + +#### Adjust Item quantity + +--- + +#### Move Item to another Warehouse + +--- + +#### Create Flower Lot + +--- + +#### Create Product \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.js b/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.js deleted file mode 100644 index afa0f29..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2016, Webonyx and contributors -// For license information, please see license.txt - -frappe.ui.form.on('BioTrack Log', { - refresh: function(frm) { - - } -}); diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.json b/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.json deleted file mode 100644 index 04fbfc8..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "creation": "2016-06-27 05:37:46.887717", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "title", - "fieldtype": "Data", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "default": "Queued", - "fieldname": "status", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Status", - "length": 0, - "no_copy": 0, - "options": "\nQueued\nError\nSuccess", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "method", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Method", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "message", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Message", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "request_date", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Request Data", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "in_create": 1, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2016-07-19 22:44:19.354584", - "modified_by": "Administrator", - "module": "ERPNext BioTrack", - "name": "BioTrack Log", - "name_case": "", - "owner": "Administrator", - "permissions": [ - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Administrator", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 1 - } - ], - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "title_field": "title" -} \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.py b/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.py deleted file mode 100644 index ceace54..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/biotrack_log.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Webonyx and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - - -class BioTrackLog(Document): - pass diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/test_biotrack_log.py b/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/test_biotrack_log.py deleted file mode 100644 index db11b3a..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_log/test_biotrack_log.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Webonyx and Contributors -# See license.txt -from __future__ import unicode_literals - -import frappe -import unittest - -# test_records = frappe.get_test_records('BioTrack Log') - -class TestBioTrackLog(unittest.TestCase): - pass diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.js b/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.js deleted file mode 100644 index d2978c5..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.js +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2016, Webonyx and contributors -// For license information, please see license.txt - -frappe.ui.form.on('BioTrack Settings', { - refresh: function(frm) { - if(!frm.doc.__islocal && frm.doc.enable_biotrack === 1){ - cur_frm.add_custom_button(' ' + __('Sync Now'), - function() { - frappe.call({ - method:"erpnext_biotrack.tasks.sync" - }) - } - ) - } - - if (frm.doc.is_training) { - cur_frm.set_intro('Training mode is enabled') - } - - cur_frm.add_custom_button(' ' + __("Sync Log"), function() { - frappe.set_route("List", "BioTrack Log"); - }); - - $(' ' + __("Help") + '') - .appendTo(cur_frm.page.inner_toolbar); - }, - - enable_biotrack: function(frm) { - if (!frm.enable_biotrack) { - frm.set_value('sync_enabled', 0) - } - } -}); \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.py b/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.py deleted file mode 100644 index 5f34617..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/biotrack_settings/biotrack_settings.py +++ /dev/null @@ -1,32 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Webonyx and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from erpnext_biotrack.biotrackthc.client import get_client, BioTrackClientError -from frappe.model.document import Document -from frappe.utils.data import cint - -class BioTrackSettings(Document): - def validate(self): - if self.enable_biotrack == 1: - self.validate_access() - - def validate_access(self): - client = get_client(self.license_number, self.username, self.get_password(), self.is_training) - try: - client.login() - except BioTrackClientError as ex: - frappe.local.message_log = [] - frappe.msgprint(ex.message, indicator='red', title='Access Error') - - def get_password(self, fieldname='password', raise_exception=True): - """ This fix because master branch is still storing raw password in database """ - try: - return super(Document, self).get_password(fieldname, raise_exception) - except AttributeError: - return self.get(fieldname) - - def is_sync_enabled(self): - return cint(self.enable_biotrack) and cint(self.sync_enabled) \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/item_sub_lot/item_sub_lot.json b/erpnext_biotrack/erpnext_biotrack/doctype/item_sub_lot/item_sub_lot.json deleted file mode 100644 index f99c109..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/item_sub_lot/item_sub_lot.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-08-22 20:41:15.707383", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Other", - "editable_grid": 1, - "fields": [ - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "item_code", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Item Code", - "length": 0, - "no_copy": 0, - "options": "Item", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, - { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 1, - "label": "Initial Quantity", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "report_hide": 0, - "reqd": 1, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "in_dialog": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 1, - "max_attachments": 0, - "modified": "2016-08-24 00:37:23.988897", - "modified_by": "Administrator", - "module": "ERPNext BioTrack", - "name": "Item Sub Lot", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_seen": 0 -} \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/item_sub_lot/item_sub_lot.py b/erpnext_biotrack/erpnext_biotrack/doctype/item_sub_lot/item_sub_lot.py deleted file mode 100644 index ffe4399..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/item_sub_lot/item_sub_lot.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Webonyx and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.model.document import Document - -class ItemSubLot(Document): - pass diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.js b/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.js deleted file mode 100644 index 9debab3..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.js +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright (c) 2016, Webonyx and contributors -// For license information, please see license.txt - -frappe.provide("erpnext_biotrack.plant"); -frappe.ui.form.on('Plant', { - refresh: function (frm) { - var is_new = frm.is_new(); - frm.toggle_display("qty", !is_new); - frm.toggle_display("remove_scheduled", !is_new); - frm.toggle_display("harvest_scheduled", !is_new); - frm.toggle_display("state", !is_new); - frm.toggle_display("barcode", !is_new); - frm.toggle_reqd("barcode", !is_new); - frm.toggle_reqd("item_group", is_new); - frm.toggle_reqd("source", is_new); - - erpnext_biotrack.plant.setup_actions(frm); - - function cal_remaining_time(d) { - var expire_d = moment(d).add(72, "hours"); - var diff = moment(expire_d).diff(moment(), "hours"); - - if (diff == 0) { - diff = moment(expire_d).diff(moment(), "minutes"); - if (diff < 0) { - diff = 0 - } else { - diff += ' minutes' - } - } else { - if (diff < 0) { - diff = 0; - } else { - diff += ' hours' - } - } - - if (diff == 0) { - diff = '72 hours remaining expired' - } else { - diff += ' remaining'; - } - - return diff - } - - - if (frm.doc.remove_scheduled) { - if (frm.doc.disabled) { - frm.dashboard.add_comment( - __("The Plant had been destroyed"), - true - ); - } else { - frm.dashboard.add_comment( - __("This Plant is scheduled for destruction. {0}", [cal_remaining_time(frm.doc.remove_time)]), - true - ); - } - - } - - frm.fields_dict['source'].get_query = function (doc, cdt, cdn) { - if (frm.doc.item_group) { - return { - filters: {'item_group': frm.doc.item_group} - } - } else { - return { - filters: {'item_group': ["in", frm.get_field('item_group').df.options.split("\n")]} - } - } - }; - }, - bulk_add: function (frm) { - frm.toggle_display("qty", frm.doc.bulk_add); - frm.toggle_reqd("qty", frm.doc.bulk_add); - } -}); - -$.extend(erpnext_biotrack.plant, { - setup_actions: function (frm) { - frm.page.clear_actions_menu(); - frm.page.clear_secondary_action(); - frm.page.btn_secondary - .removeClass('btn-primary') - .addClass('btn-default'); - - if (!frm.is_new()) { - frm.add_custom_button('Related Items', function () { - frappe.set_route("List", "Item", {plant: frm.doc.name}); - }) - } - - if (frm.doc.disabled) { - return; - } - - if (!frm.is_new()) { - if (!frm.doc.remove_scheduled) { - frm.page.set_secondary_action('Harvest/Cure', function () { - erpnext_biotrack.plant.harvest(frm); - }); - - if (frm.doc.state == 'Growing') { - frm.page.add_action_item(__('Move To Inventory'), function () { - erpnext_biotrack.plant.move_to_inventory(frm); - }) - } - - if (frm.doc.harvest_scheduled) { - if (frm.doc.state == 'Growing') { - frm.page.add_action_item(__("Undo Scheduled Harvest"), function () { - erpnext_biotrack.plant.harvest_schedule_undo(frm); - }) - } - } else { - - frm.page.add_action_item(__("Destroy Schedule"), function () { - erpnext_biotrack.plant.destroy_schedule(frm); - }); - - frm.page.add_action_item(__("Permanently Delete"), function () { - frappe.confirm( - "System will permanently remove this plant and restore it's source balance to inventory.", - function () { - frm.validate_form_action("Delete"); - erpnext_biotrack.plant.plant_new_undo(frm.doc); - }, - function () { - } - ) - }); - } - - - } else { - frm.page.add_action_item(__("Undo Scheduled Destruction"), function () { - erpnext_biotrack.plant.destroy_schedule_undo(frm); - }); - - frm.page.add_action_item(__("Re-Schedule Destruction"), function () { - erpnext_biotrack.plant.destroy_schedule(frm); - }); - } - - if (frm.doc.harvest_scheduled) { - frm.page.btn_secondary - .removeClass('btn-default') - .addClass('btn-primary'); - } - } - }, - plant_new_undo: function (doc) { - frappe.call({ - doc: doc, - method: 'undo', - callback: function (data) { - window.history.back(); - } - }); - }, - harvest: function (frm) { - if (!frm.doc.harvest_scheduled) { - var cf_dialog = frappe.msgprint({ - title: 'Harvest Schedule', - message: 'You will need to initiate harvest notification before beginning the harvest process. Would you like to' + - ' do so now?' - }); - - cf_dialog.set_primary_action(__('Yes'), function () { - cf_dialog.show_loading(); - frappe.call({ - doc: frm.doc, - method: 'harvest_schedule', - callback: function (data) { - cur_frm.reload_doc(); - cf_dialog.hide(); - msg_dialog = null; - } - }); - }); - - return; - } - - var doc = frm.doc, - fields = [ - { - fieldname: 'name', label: 'Barcode', fieldtype: 'Data', read_only: 1, default: doc.name - }, - { - fieldname: 'strain', label: 'Strain', fieldtype: 'Data', read_only: 1, default: doc.strain - }, - { - fieldname: 'uom', label: 'Unit of Measure', fieldtype: 'Select', options: ['Gram'], default: 'Gram' - }, - { - fieldname: 'flower_amount', - label: __('Flower {0} Weight', [doc.state == 'Growing' ? 'Wet' : 'Dry']), - fieldtype: 'Float', - reqd: 1 - }, - { - fieldname: 'other_material_amount', label: 'Other Plant Material', fieldtype: 'Float' - }, - { - fieldname: 'waste_amount', label: 'Waste', fieldtype: 'Float' - } - ], - dialog; - - if (doc.state == "Drying") { - fields.push( - { - fieldname: 'additional_collection', label: 'Additional Collections', fieldtype: 'Check' - } - ) - } - - dialog = new frappe.ui.Dialog({ - title: 'Plant ' + (doc.state == 'Growing' ? 'Harvest' : 'Cure'), - fields: fields, - onhide: function () { - cur_frm.reload_doc(); - } - }); - - dialog.set_primary_action(__('Submit'), function () { - var values = dialog.get_values(); - if (!values) { - return; - } - - delete values['name']; - delete values['strain']; - delete values['uom']; // discard and use Gram by default - - frappe.call({ - doc: doc, - method: 'harvest_cure', - args: values, - callback: function (data) { - if (data.message && data.message.transaction_id) { - var confirm_dialog = frappe.msgprint({ - title: 'Success', - message: __( - 'Plant {0} successfully. If you think this was an accidentally action, click Undo', - [(doc.state == 'Growing' ? 'harvested' : 'cured')] - ) - }); - - confirm_dialog.set_primary_action(__('Undo'), function () { - frappe.call({ - // doc: frm.doc, - // method: 'harvest_cure_undo', - // args: data.message, - method: 'erpnext_biotrack.erpnext_biotrack.doctype.plant.plant.harvest_cure_undo', - args: $.extend({name: doc.name}, data.message), - callback: function (data) { - confirm_dialog.hide(); - msg_dialog = null - } - }); - }); - - confirm_dialog.custom_onhide = function () { - dialog.hide(); - }; - } else { - dialog.hide(); - } - } - }); - }); - - dialog.show(); - }, - move_to_inventory: function (frm) { - var doc = frm.doc, - fields = [ - { - fieldname: 'name', label: 'Plant', fieldtype: 'Data', read_only: 1, default: doc.name - }, - { - fieldname: 'strain', label: 'Strain', fieldtype: 'Data', read_only: 1, default: doc.strain - } - ], - dialog; - - dialog = new frappe.ui.Dialog({ - title: __('Move to Inventory'), - fields: fields - }); - - dialog.set_primary_action(__('Submit'), function () { - frappe.call({ - doc: doc, - method: 'move_to_inventory', - callback: function (data) { - dialog.hide(); - frappe.set_route('List', 'Plant'); - } - }); - }); - - dialog.show(); - }, - destroy_schedule: function (frm) { - var doc = frm.doc, - fields = [ - { - fieldname: 'reason', label: __('Please choose a reason for scheduling this destruction'), - fieldtype: 'Select', options: [ - 'Other', - 'Waste', - 'Unhealthy or Died', - 'Infestation', - 'Product Return', - 'Mistake', - 'Spoilage', - 'Quality Control' - ] - }, - { - fieldname: 'reason_txt', label: __('Reason Detail'), - fieldtype: 'Text' - } - ], - dialog; - - if (doc.remove_scheduled) { - fields.push({ - fieldname: 'override', label: __('Reset Scheduled time'), - fieldtype: 'Check' - }) - } - - dialog = new frappe.ui.Dialog({ - title: __('Destruction Schedule'), - fields: fields - }); - - if (doc.remove_scheduled) { - dialog.get_field('override').set_input(1); - } - - dialog.set_primary_action(__('Submit'), function () { - var values = dialog.get_values(); - if (!values) { - return; - } - - if (!values.reason) { - frappe.msgprint({ - message: __('Please specify a reason'), - indicator: 'red', - title: 'Error' - }); - - return; - } - - if (values.reason == 'Other' && !values.reason_txt) { - frappe.msgprint({ - message: __('Please input a reason detail'), - indicator: 'red', - title: 'Error' - }); - - return; - } - - delete values['name']; - frappe.call({ - doc: doc, - method: 'destroy_schedule', - args: values, - callback: function (data) { - dialog.hide(); - cur_frm.reload_doc(); - } - }); - }); - - dialog.show_message('This will initiate the 72 hour waiting period.'); - dialog.message.removeClass('small text-muted'); - dialog.show(); - }, - - destroy_schedule_undo: function (frm) { - frappe.confirm( - 'Please confirm this action', - function () { - frappe.call({ - doc: frm.doc, - method: 'destroy_schedule_undo', - callback: function (data) { - cur_frm.reload_doc(); - } - }); - } - ); - }, - harvest_schedule_undo: function (frm) { - frappe.confirm( - 'Please confirm this action', - function () { - frappe.call({ - doc: frm.doc, - method: 'harvest_schedule_undo', - callback: function (data) { - cur_frm.reload_doc(); - } - }); - } - ); - }, -}); \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.py b/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.py deleted file mode 100644 index 2f3669c..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.py +++ /dev/null @@ -1,410 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2015, Webonyx and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe, datetime -from erpnext.stock.utils import get_stock_balance -from erpnext_biotrack.biotrackthc import call as biotrackthc_call -from erpnext_biotrack.biotrackthc.client import BioTrackClientError -from erpnext_biotrack.biotrackthc.inventory_room import get_default_warehouse -from erpnext_biotrack.item_utils import get_item_values, make_item -from frappe.desk.reportview import build_match_conditions -from frappe.utils.data import get_datetime_str, DATETIME_FORMAT, cint, now, flt, add_to_date -from frappe.model.document import Document -from erpnext_biotrack.erpnext_biotrack.doctype.strain import find_strain -from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry -from frappe.utils.background_jobs import enqueue - -removal_reasons = { - 0: 'Other', - 1: 'Waste', - 2: 'Unhealthy or Died', - 3: 'Infestation', - 4: 'Product Return', - 5: 'Mistake', - 6: 'Spoilage', - 7: 'Quality Control' -} - - -class Plant(Document): - def before_insert(self): - if self.qty: - item = frappe.get_doc("Item", self.source) - qty = get_stock_balance(item.item_code, item.default_warehouse) - if qty < self.qty: - frappe.throw("The provided quantity {0} exceeds stock balance. " - "Stock balance remaining {1}".format(self.qty, qty)) - - if not self.barcode: - self.biotrack_sync_up() - - def after_insert(self): - if frappe.flags.in_import or frappe.flags.in_test: - return - - source_item = frappe.get_doc("Item", self.get("source")) - make_stock_entry(item_code=source_item.name, source=source_item.default_warehouse, qty=1) - - if self.bulk_add and self.qty > 1: - frappe.db.commit() - enqueue(bulk_clone, name=self.name) - - def on_trash(self): - # able to delete new Plants - if self.state == "Growing" or not self.harvest_scheduled: - return - - if not self.remove_scheduled: - frappe.throw("Plant can not be deleted directly. Please schedule for destruction first") - - if not self.disabled: - frappe.throw("Plant can only be deleted once destroyed") - - def biotrack_sync_up(self): - if frappe.flags.in_import or frappe.flags.in_test: - return - - warehouse = frappe.get_doc("Warehouse", self.get("warehouse")) - location = frappe.get_value("BioTrack Settings", None, "location") - - result = biotrackthc_call("plant_new", { - "room": warehouse.external_id, - "quantity": 1, - "strain": self.get("strain"), - "source": self.get("source"), - "mother": cint(self.get("is_mother")), - "location": location - }) - - self.set("barcode", result.get("barcode_id")[0]) - - def biotrack_sync_down(self, data): - if not (frappe.flags.force_sync or False) and self.get("transaction_id") == data.get("transactionid"): - frappe.db.set_value("Plant", self.name, "last_sync", now(), update_modified=False) - return - - plant_room = frappe.get_doc("Plant Room", {"external_id": data.get("room")}) - properties = { - "strain": find_strain(data.get("strain")), - "warehouse": plant_room.get("name") if plant_room else "", - "is_mother_plant": cint(data.get("mother")), - "remove_scheduled": cint(data.get("removescheduled")), - "transaction_id": cint(data.get("transactionid")), - "last_sync": now(), - "disabled": 0, - } - - item_values = get_item_values(data.get("parentid"), ["name", "item_group"]) - if item_values: - properties["source"], properties["item_group"] = item_values - - if not self.get("birthdate"): - if isinstance(self.get("creation"), basestring): - properties["birthdate"] = self.get("creation") - else: - properties["birthdate"] = self.get("creation").strftime(DATETIME_FORMAT) - - if properties["remove_scheduled"]: - remove_datetime = datetime.datetime.fromtimestamp(cint(data.get("removescheduletime"))) - properties["remove_time"] = get_datetime_str(remove_datetime) - - if data.get("removereason"): - properties["remove_reason"] = data.get("removereason") - - state = int(data.get("state")) - properties["state"] = "Drying" if state == 1 else ("Cured" if state == 2 else "Growing") - - self.update(properties) - self.flags.ignore_mandatory = True - self.save(ignore_permissions=True) - - @Document.whitelist - def undo(self): - biotrackthc_call("plant_new_undo", { - "barcodeid": [self.name], - }) - - # Restore Item source balance - item = frappe.get_doc("Item", self.get("source")) - make_stock_entry(item_code=item.name, target=item.default_warehouse, qty=1) - self.delete() - - @Document.whitelist - def harvest_cure(self, flower_amount, other_material_amount=None, waste_amount=None, additional_collection=None): - if self.disabled: - frappe.throw("Plant {} is not available for harvesting.") - - if self.remove_scheduled: - frappe.throw("Plant {} is currently scheduled for destruction and cannot be harvested.") - - amount_map = { - 6: flt(flower_amount), - 9: flt(other_material_amount), - 27: flt(waste_amount), - } - - weights = [ - { - "amount": amount_map[6], - "invtype": 6, - "uom": "g" - } - ] - - if other_material_amount: - weights.append({ - "amount": amount_map[9], - "invtype": 9, - "uom": "g" - }) - - if waste_amount: - weights.append({ - "amount": amount_map[27], - "invtype": 27, - "uom": "g" - }) - - data = { - 'barcodeid': self.name, - 'collectadditional': cint(additional_collection), - 'weights': weights - } - - action = "plant_cure" if self.state == "Drying" else "plant_harvest" - flower_weight = amount_map[6] - - if action == "plant_harvest": - self.wet_weight = flower_weight - else: - self.dry_weight = flt(self.dry_weight) + flower_weight - if self.dry_weight > self.wet_weight: - frappe.throw( - "The provided dry weight {0} exceeds the previous wet weight {1}.". - format(self.dry_weight, self.wet_weight), title="Error") - - data["location"] = frappe.get_value("BioTrack Settings", None, "location") - - try: - res = biotrackthc_call(action, data) - except BioTrackClientError as ex: - frappe.local.message_log.pop() - frappe.throw(ex.message, title="Error") # response nicer error message - - # sync derivatives - items = [] - if res.get("derivatives"): - defaukt_warehouse = get_default_warehouse() - - try: - for derivative in res.get("derivatives"): - item_group = frappe.get_doc("Item Group", {"external_id": derivative.get("barcode_type")}) - qty = flt(amount_map[cint(derivative.get("barcode_type"))]) - - item = make_item(barcode=derivative.get("barcode_id"), properties={ - "item_group": item_group.name, - "default_warehouse": defaukt_warehouse.name, - "strain": self.strain, - "stock_uom": "Gram", - "is_stock_item": 1, - "actual_qty": qty, - "plant": self.name, - }, qty=qty) - - items.append(item.item_code) - except Exception as ex: - self.harvest_cure_undo({}, action, res.get("transactionid")) - raise - - if self.state == "Growing": - self.state = "Drying" - - # Remove from Cultivation - if self.dry_weight == self.wet_weight or (action == "plant_cure" and not additional_collection): - self.disabled = 1 - - self.transaction_id = res.get("transactionid") - self.save() - - return {"items": items, "action": action, "transaction_id": res.get("transactionid")} - - @Document.whitelist - def harvest_cure_undo(self, items, action, transaction_id): - action += "_undo" - res = biotrackthc_call(action, {'transactionid': transaction_id}) - - # cleanup items - if items: - for barcode in items: - item = frappe.get_doc("Item", {"barcode": barcode}) - entries = frappe.get_list("Stock Entry", {"item_code": item.item_code}) - for name in entries: - entry = frappe.get_doc("Stock Entry", name) - entry.cancel() - entry.delete() - item.delete() - - if action == "plant_harvest": - self.state = "Growing" - - if action == "plant_cure" and self.disabled: - self.disabled = 0 - - self.transaction_id = res.get("transactionid") - self.save() - - @Document.whitelist - def destroy_schedule(self, reason, reason_txt=None, override=None): - data = { - 'barcodeid': [self.name], - 'reason_extended': removal_reasons.keys()[removal_reasons.values().index(reason)], - 'reason': reason_txt - } - - if self.remove_scheduled and not override: - frappe.throw( - "Plant {} has already been scheduled for destruction. Check `Reset Scheduled time` to override.".format( - self.name)) - - if override: - data['override'] = 1 - - biotrackthc_call("plant_destroy_schedule", data) - - self.remove_scheduled = 1 - self.remove_reason = reason_txt or reason - self.remove_time = now() - self.save() - - @Document.whitelist - def destroy_schedule_undo(self): - biotrackthc_call("plant_destroy_schedule_undo", {'barcodeid': [self.name]}) - self.remove_scheduled = 0 - self.remove_reason = None - self.remove_time = None - self.save() - - @Document.whitelist - def harvest_schedule(self): - biotrackthc_call("plant_harvest_schedule", {'barcodeid': [self.name]}) - self.harvest_scheduled = 1 - self.harvest_schedule_time = now() - self.save() - - @Document.whitelist - def harvest_schedule_undo(self): - if self.state == "Drying": - frappe.throw("Plant {} was already on harvesting process.".format(self.name)) - - biotrackthc_call("plant_harvest_schedule_undo", {'barcodeid': [self.name]}) - self.harvest_scheduled = 0 - self.harvest_schedule_time = None - self.save() - - @Document.whitelist - def move_to_inventory(self): - items = [self.name] - res = biotrackthc_call("plant_convert_to_inventory", {'barcodeid': [self.name]}) - defaukt_warehouse = get_default_warehouse() - item_group = frappe.get_doc("Item Group", {"external_id": 12}) # Mature Plant - qty = 1 - - for barcode in items: - make_item(barcode=barcode, properties={ - "item_group": item_group.name, - "default_warehouse": defaukt_warehouse.name, - "strain": self.strain, - "is_stock_item": 1, - "actual_qty": qty, - "plant": self.name, - "parent_item": self.source, - }, qty=qty) - - # destroy plant as well - self.remove_scheduled = 1 - self.disabled = 1 - self.transaction_id = res.get("transactionid") - self.save() - - @Document.whitelist - def destroy(self): - biotrackthc_call("plant_destroy", {'barcodeid': [self.name]}) - self.delete() - - -@frappe.whitelist() -def harvest_cure_undo(name, items, action, transaction_id): - """Alias to harvest_cure_undo method to avoid check_if_latest exception""" - plant = frappe.get_doc("Plant", name) - - import json - try: - items = json.loads(items) - except ValueError: - items = [] - - return plant.harvest_cure_undo(items, action, transaction_id) - - -def get_plant_list(doctype, txt, searchfield, start, page_len, filters): - fields = ["name", "strain"] - match_conditions = build_match_conditions("Plant") - match_conditions = "and {}".format(match_conditions) if match_conditions else "" - - return frappe.db.sql("""select %s from `tabPlant` where docstatus < 2 - and (%s like %s or strain like %s) - {match_conditions} - order by - case when name like %s then 0 else 1 end, - case when strain like %s then 0 else 1 end, - name, strain limit %s, %s""".format(match_conditions=match_conditions) % - (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), - ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) - - -def bulk_clone(name): - source_plant = frappe.get_doc("Plant", name) - - if source_plant.qty > 1: - warehouse = frappe.get_doc("Warehouse", source_plant.get("warehouse")) - location = frappe.get_value("BioTrack Settings", None, "location") - remaining_qty = source_plant.qty - 1 - - result = biotrackthc_call("plant_new", { - "room": warehouse.external_id, - "quantity": remaining_qty, - "strain": source_plant.strain, - "source": source_plant.source, - "mother": cint(source_plant.get("is_mother")), - "location": location - }) - - for barcode in result.get("barcode_id"): - plant = frappe.new_doc("Plant") - plant.update({ - "barcode": barcode, - "item_group": source_plant.item_group, - "source": source_plant.source, - "strain": source_plant.strain, - "warehouse": source_plant.warehouse, - "state": source_plant.state, - "birthdate": now(), - }) - - plant.save() - - # save directly with sql to avoid mistimestamp check - frappe.db.set_value("Plant", source_plant.name, "qty", 1, update_modified=False) - frappe.publish_realtime("list_update", {"doctype": "Plant"}) - - -def destroy_scheduled_plants(): - """Destroy expired Plants""" - date = add_to_date(now(), days=-3) - for name in frappe.get_list("Plant", - [["disabled", "=", 0], ["remove_scheduled", "=", 1], ["remove_time", "<", date]]): - plant = frappe.get_doc("Plant", name) - plant.disabled = 1 - plant.save() diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant_list.js b/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant_list.js deleted file mode 100644 index a5e80e4..0000000 --- a/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant_list.js +++ /dev/null @@ -1,41 +0,0 @@ -frappe.listview_settings['Plant'] = { - add_fields: ['disabled', 'birthdate', 'remove_scheduled'], - filters: [["disabled", "=", "No"]], - get_indicator: function (doc) { - if (doc.disabled) { - return [__("Destroyed"), "grey", "disabled,=,Yes"]; - } else if(doc.remove_scheduled){ - return [__("Destroy Scheduled"), "orange", "remove_scheduled,=,Yes"]; - } else { - return [this.calculate_time_in_room(doc.birthdate), "green", "disabled,=,No"]; - } - }, - - onload: function (DocListView) { - DocListView.listview.stats.push("state"); - DocListView.listview.stats.push("plant_room"); - - DocListView.page.add_action_item(__("Synchronization"), function () { - frappe.call({ - method: "erpnext_biotrack.tasks.client_sync", - args: {"doctype": "Plant"} - }) - }) - }, - calculate_time_in_room: function (birthdate) { - var diff = frappe.datetime.get_diff(frappe.datetime.get_today(), birthdate.split(' ')[0]); - if (diff == 0) { - return comment_when(birthdate); - } - - if (diff == 1) { - return __('Yesterday') - } - - if (diff == 2) { - return __('2 days ago') - } - - return diff + ' days'; - } -}; diff --git a/erpnext_biotrack/fixtures/custom_field.json b/erpnext_biotrack/fixtures/custom_field.json index 26a3d07..bcb54c8 100644 --- a/erpnext_biotrack/fixtures/custom_field.json +++ b/erpnext_biotrack/fixtures/custom_field.json @@ -9,22 +9,127 @@ "docstatus": 0, "doctype": "Custom Field", "dt": "Item", - "fieldname": "parent_item", + "fieldname": "bio_barcode", + "fieldtype": "Data", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "insert_after": null, + "label": "BioTrack Barcode", + "modified": "2016-11-15 22:41:52.693746", + "name": "Item-bio_barcode", + "no_copy": 1, + "options": null, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "unique": 0, + "width": null + }, + { + "allow_on_submit": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Item", + "fieldname": "bio_remaining_quantity", + "fieldtype": "Float", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "insert_after": null, + "label": "BioTrack Remaining Quantity", + "modified": "2016-11-15 22:49:14.898139", + "name": "Item-bio_remaining_quantity", + "no_copy": 1, + "options": null, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 1, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "unique": 0, + "width": null + }, + { + "allow_on_submit": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Item", + "fieldname": "bio_last_sync", + "fieldtype": "Datetime", + "hidden": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "insert_after": null, + "label": "Bio Last Sync", + "modified": "2016-11-15 22:57:00.966149", + "name": "Item-bio_last_sync", + "no_copy": 1, + "options": "", + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "unique": 0, + "width": null + }, + { + "allow_on_submit": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "default": null, + "depends_on": "", + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Stock Entry", + "fieldname": "plant", "fieldtype": "Link", - "hidden": 0, + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "insert_after": "column_break0", - "label": "Parent Item", - "modified": "2016-10-06 00:24:26.452119", - "name": "Item-parent_item", + "insert_after": null, + "label": "Plant", + "modified": "2016-11-12 13:15:18.120111", + "name": "Stock Entry-plant", "no_copy": 0, - "options": "Item", + "options": "Plant", "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "print_width": null, "read_only": 1, @@ -44,25 +149,25 @@ "docstatus": 0, "doctype": "Custom Field", "dt": "Item", - "fieldname": "sub_items", - "fieldtype": "Table", + "fieldname": "parent_item", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "insert_after": "sub_lot_sec", - "label": "Sub Items", - "modified": "2016-08-24 00:47:31.947140", - "name": "Item-sub_items", + "insert_after": "column_break0", + "label": "Parent Item", + "modified": "2016-10-06 00:24:26.452119", + "name": "Item-parent_item", "no_copy": 0, - "options": "Item Sub Lot", + "options": "Item", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "print_width": null, - "read_only": 0, + "read_only": 1, "report_hide": 0, "reqd": 0, "search_index": 0, @@ -664,76 +769,6 @@ "unique": 0, "width": null }, - { - "allow_on_submit": 0, - "collapsible": 1, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "sub_lot_sec", - "fieldtype": "Section Break", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "reorder_levels", - "label": "Sub Items", - "modified": "2016-08-24 00:43:41.575351", - "name": "Item-sub_lot_sec", - "no_copy": 0, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": "", - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "actual_qty", - "fieldtype": "Float", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": "strain", - "label": "Actual Quantity", - "modified": "2016-08-25 23:55:10.123610", - "name": "Item-actual_qty", - "no_copy": 0, - "options": null, - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, { "allow_on_submit": 0, "collapsible": 0, @@ -804,41 +839,6 @@ "unique": 0, "width": null }, - { - "allow_on_submit": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "doctype": "Custom Field", - "dt": "Item", - "fieldname": "last_sync", - "fieldtype": "Datetime", - "hidden": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "insert_after": null, - "label": "Last Sync", - "modified": "2016-09-04 06:55:47.132512", - "name": "Item-last_sync", - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 1, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "unique": 0, - "width": null - }, { "allow_on_submit": 0, "collapsible": 0, @@ -1643,5 +1643,40 @@ "search_index": 0, "unique": 0, "width": null + }, + { + "allow_on_submit": 0, + "collapsible": 0, + "collapsible_depends_on": null, + "default": null, + "depends_on": null, + "description": null, + "docstatus": 0, + "doctype": "Custom Field", + "dt": "Integration Request", + "fieldname": "action", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 1, + "insert_after": "integration_request_service", + "label": "Action", + "modified": "2016-11-24 17:19:50.490976", + "name": "Integration Request-action", + "no_copy": 0, + "options": null, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "print_width": null, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "unique": 0, + "width": null } ] \ No newline at end of file diff --git a/erpnext_biotrack/hooks.py b/erpnext_biotrack/hooks.py index 69172dc..f72aab1 100644 --- a/erpnext_biotrack/hooks.py +++ b/erpnext_biotrack/hooks.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from . import __version__ as app_version app_name = "erpnext_biotrack" app_title = "ERPNext BioTrack" @@ -17,16 +18,15 @@ ["name", "in", ( # Item "Item-strain", - "Item-actual_qty", - "Item-sub_lot_sec", "Item-is_lot_item", "Item-parent_item", "Item-plant", - "Item-sub_items", "Item-test_result", "Item-sample_id", "Item-is_marijuana_item", - "Item-last_sync", + "Item-bio_last_sync", + "Item-bio_barcode", + "Item-bio_remaining_quantity", "Item-transaction_id", "Item-linking_data", "Item-certificate", @@ -54,6 +54,7 @@ "Delivery Note-depart_datetime", "Delivery Note-arrive_datetime", + "Stock Entry-plant", "Stock Entry-conversion", "Stock Entry-conversion_sec", "Stock Entry-product_group", @@ -79,6 +80,8 @@ "Quotation Item-test_result", "Quotation Item-potency", "Quotation Item-thca", + + "Integration Request-action" )] ] }, @@ -91,7 +94,10 @@ # include js, css files in header of desk.html # app_include_css = "/assets/erpnext_biotrack/css/erpnext_biotrack.css" -app_include_js = "/assets/erpnext_biotrack/js/erpnext_biotrack.js" +app_include_js = [ + "/assets/erpnext_biotrack/js/erpnext_biotrack.js", + "/assets/erpnext_biotrack/js/biotrackthc_integration.js", +] # include js, css files in header of web template # web_include_css = "/assets/erpnext_biotrack/css/erpnext_biotrack.css" @@ -108,6 +114,11 @@ # "Role": "home_page" # } +integration_services = ["BioTrack"] + +extend_bootinfo = [ + "erpnext_biotrack.biotrackthc.bootinfo.boot" +] # Form custom scripts doctype_js = { @@ -133,7 +144,7 @@ } standard_queries = { - "Plant": "erpnext_biotrack.erpnext_biotrack.doctype.plant.plant.get_plant_list" + "Plant": "erpnext_biotrack.traceability_system.doctype.plant.plant.get_plant_list" } # Website user home page (by function) @@ -151,7 +162,7 @@ # before_install = "erpnext_biotrack.install.before_install" after_install = "erpnext_biotrack.install.after_install" -biotrack_after_sync = [ +biotrack_synced = [ "erpnext_biotrack.item_utils.item_linking_correction", "erpnext_biotrack.item_utils.qa_result_population" ] @@ -180,15 +191,31 @@ doc_events = { "Item": { - "validate": "erpnext_biotrack.item_utils.on_validate", - "after_insert": "erpnext_biotrack.item_utils.after_insert", + "validate": "erpnext_biotrack.item_utils.on_validate" }, "Stock Entry": { - "on_submit": "erpnext_biotrack.stock_entry.on_submit", + "on_submit": [ + "erpnext_biotrack.stock_entry.on_submit", # for conversion handler + "erpnext_biotrack.biotrackthc.hooks.stock_entry.call_hook", # for new_inventory sync up + ], "get_item_details": "erpnext_biotrack.stock_entry.get_item_details", + "after_conversion": "erpnext_biotrack.biotrackthc.hooks.stock_entry.call_hook", }, "File": { "on_trash": "erpnext_biotrack.item_utils.remove_certificate_on_trash_file", + }, + "Plant": { + "on_submit": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "on_cancel": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "on_trash": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "before_harvest_schedule": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "before_harvest_schedule_undo": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "before_destroy_schedule": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "before_destroy_schedule_undo": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "after_convert_to_inventory": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "after_harvest": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "before_harvest_undo": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", + "after_cure": "erpnext_biotrack.biotrackthc.hooks.plant.call_hook", } } @@ -199,16 +226,13 @@ # "all": [ # "erpnext_biotrack.tasks.all" # ], - "daily": [ - "erpnext_biotrack.tasks.daily" - ], + # "daily": [ + # ], "hourly": [ - "erpnext_biotrack.tasks.hourly", - "erpnext_biotrack.erpnext_biotrack.doctype.plant.plant.destroy_scheduled_plants", + "erpnext_biotrack.traceability_system.doctype.plant.plant.destroy_scheduled_plants", ], - "weekly": [ - "erpnext_biotrack.tasks.weekly" - ] + # "weekly": [ + # ] # "monthly": [ # "erpnext_biotrack.tasks.monthly" # ] diff --git a/erpnext_biotrack/item_utils.py b/erpnext_biotrack/item_utils.py index ea0e993..293eb2f 100644 --- a/erpnext_biotrack/item_utils.py +++ b/erpnext_biotrack/item_utils.py @@ -54,23 +54,23 @@ def new_item(item_name, item_group, strain, actual_qty, default_warehouse, plant def make_item(**args): args = frappe._dict(args) - - if args.barcode: - frappe.flags.in_import = True # ignore custom validate and after_insert hooks - item = frappe.new_doc("Item") properties = frappe._dict(args.properties) or frappe._dict() - properties.item_name = properties.item_name or " ".join(filter(None, [properties.strain, properties.item_group])) - properties.is_stock_item = properties.is_stock_item or 1 - if args.barcode: + # ignore custom validate and after_insert hooks + frappe.flags.ignore_external_sync = True + frappe.flags.in_import = True + properties.item_code = args.barcode properties.barcode = args.barcode - # todo consider to remove actual_qty - if args.qty: - properties.actual_qty = args.qty + + if not properties.item_code: + properties.item_code = generate_item_code() + + properties.item_name = properties.item_name or " ".join(filter(None, [properties.strain, properties.item_group])) + properties.is_stock_item = properties.is_stock_item or 1 item.update(properties) item.insert() @@ -91,6 +91,7 @@ def make_lot_item(properties, qty): @frappe.whitelist() def clone_item(item_code, qty, rate, default_warehouse): + # todo data = biotrackthc_call("inventory_split", data={ "data": [ { @@ -150,18 +151,18 @@ def get_item_values(barcode, fields="name"): fl = ", ".join(fl) result = frappe.db.sql( - "select {0} from tabItem where `name` = %(barcode)s or `barcode` = %(barcode)s".format(fl), + "select {0} from tabItem where `bio_barcode` = %(barcode)s or `name` = %(barcode)s or `barcode` = %(barcode)s".format(fl), {"barcode": barcode}, as_list=True) return result[0] if result else None def on_validate(item, method): - if frappe.flags.in_import or frappe.flags.in_test: + if frappe.flags.ignore_external_sync or frappe.flags.in_import or frappe.flags.in_test: return if not item.is_marijuana_item: - return item + return missing = [] marijuana_req_fields = ["strain", "item_group"] @@ -169,9 +170,6 @@ def on_validate(item, method): if item.get(field) is None: missing.append(field) - if flt(item.get("actual_qty")) == 0: - missing.append("actual_qty") - if not missing: return @@ -181,38 +179,6 @@ def on_validate(item, method): name=item.name)) -def after_insert(item, method): - if frappe.flags.in_import or frappe.flags.in_test: - return - - if not item.is_marijuana_item: - return item - - item_group = frappe.get_doc("Item Group", item.item_group) - location = frappe.get_value("BioTrack Settings", None, "location") - - call_data = { - "invtype": item_group.external_id, - "quantity": item.actual_qty, - "strain": item.strain, - } - - if item.plant: - call_data["source_id"] = item.plant - - data = biotrackthc_call("inventory_new", data={ - "data": call_data, - "location": location - }) - - item.update({ - "barcode": data['barcode_id'][0] - }) - - make_stock_entry(item_code=item.item_code, target=item.default_warehouse, qty=item.actual_qty) - item.save() - - def remove_certificate_on_trash_file(file, method): if file.attached_to_name and file.attached_to_doctype == "Item": item = frappe.get_doc("Item", file.attached_to_name) @@ -342,28 +308,10 @@ def delete_item(name): item.delete() -def test_insert(): - item = frappe.get_doc({ - "doctype": "Item", - "item_name": "_Test Item", - "item_code": "_Test Item", - "item_group": "Usable Marijuana", - "strain": "Pineapple", - "stock_uom": "Gram", - "actual_qty": 50, - "default_warehouse": "Bulk Inventory room - EV", - "is_marijuana_item": 1, - }) +def generate_item_code(naming_series = None): + if not naming_series: + naming_series = frappe.get_meta("Item").get_options("naming_series") or "ITEM-" - item.save() - - # success and tear down - entries = frappe.get_list("Stock Entry", {"item_code": item.item_code}) - for name in entries: - entry = frappe.get_doc("Stock Entry", name) - entry.cancel() - entry.delete() + from frappe.model.naming import make_autoname + return make_autoname(naming_series + '.#####') - item.delete() - - return item.as_dict() diff --git a/erpnext_biotrack/modules.txt b/erpnext_biotrack/modules.txt index e6ec7c5..82be39b 100644 --- a/erpnext_biotrack/modules.txt +++ b/erpnext_biotrack/modules.txt @@ -1,3 +1,2 @@ -ERPNext BioTrack BioTrackTHC Traceability System \ No newline at end of file diff --git a/erpnext_biotrack/patches.txt b/erpnext_biotrack/patches.txt index a443c5c..f83e41f 100644 --- a/erpnext_biotrack/patches.txt +++ b/erpnext_biotrack/patches.txt @@ -16,3 +16,12 @@ execute:frappe.delete_doc_if_exists("Custom Field", "Item-item_parent") execute:frappe.delete_doc_if_exists("Custom Field", "Quality Inspection-sample_code") execute:frappe.db.sql("""UPDATE tabItem set test_result = NULL""") erpnext_biotrack.patches.migrate_plant_room +erpnext_biotrack.patches.v0_2.mirror_plant_to_traceability_system +erpnext_biotrack.patches.v0_2.mirror_strain_to_traceability_system +erpnext_biotrack.patches.v0_2.mirror_biotrackthc_doctypes +erpnext_biotrack.patches.v0_2.migrate_integration_service +execute:frappe.delete_doc_if_exists("DocType", "BioTrack Log") +execute:frappe.db.sql("""UPDATE `tabDesktop Icon` set label = 'Traceability' WHERE label = 'Traceability System'""") +execute:frappe.db.sql("""DELETE FROM `tabDesktop Icon` WHERE module_name = 'Plant'""") +execute:frappe.delete_doc_if_exists("Custom Field", "Item-sub_items") +execute:frappe.delete_doc_if_exists("Custom Field", "Item-sub_lot_sec") \ No newline at end of file diff --git a/erpnext_biotrack/patches/v0_2/__init__.py b/erpnext_biotrack/patches/v0_2/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/erpnext_biotrack/patches/v0_2/migrate_bio_items.py b/erpnext_biotrack/patches/v0_2/migrate_bio_items.py new file mode 100644 index 0000000..3cefc18 --- /dev/null +++ b/erpnext_biotrack/patches/v0_2/migrate_bio_items.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals + +import frappe +# bench --force --site abc run-patch erpnext_biotrack.patches.v0_2.migrate_bio_items +def execute(): + + frappe.db.sql("UPDATE tabItem SET `bio_barcode` = `barcode` WHERE `transaction_id` IS NOT NULL") + frappe.db.sql("UPDATE tabItem SET `bio_remaining_quantity` = `actual_qty`") + frappe.db.sql("UPDATE tabItem SET `bio_last_sync` = `last_sync`") + + frappe.delete_doc_if_exists("Custom Field", "Item-sub_items") + frappe.delete_doc_if_exists("Custom Field", "Item-sub_lot_sec") + frappe.delete_doc_if_exists("Custom Field", "Item-actual_qty") + frappe.delete_doc_if_exists("Custom Field", "Item-last_sync") + frappe.delete_doc_if_exists("Custom Field", "Item-external_qty") \ No newline at end of file diff --git a/erpnext_biotrack/patches/v0_2/migrate_integration_service.py b/erpnext_biotrack/patches/v0_2/migrate_integration_service.py new file mode 100644 index 0000000..be5ea65 --- /dev/null +++ b/erpnext_biotrack/patches/v0_2/migrate_integration_service.py @@ -0,0 +1,24 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + service_name="BioTrack" + if frappe.db.exists("Integration Service", service_name): + integration_service = frappe.get_doc("Integration Service", service_name) + else: + integration_service = frappe.new_doc("Integration Service") + integration_service.service = service_name + + integration_service.enabled = 1 + integration_service.flags.ignore_mandatory = True + integration_service.save(ignore_permissions=True) + + settings = frappe.get_doc("BioTrack Settings") + if not settings.synchronization: + settings.synchronization = "All" + + if not settings.sync_frequency: + settings.sync_frequency = "Daily" + + settings.flags.ignore_mandatory = True + settings.save() \ No newline at end of file diff --git a/erpnext_biotrack/patches/v0_2/mirror_biotrackthc_doctypes.py b/erpnext_biotrack/patches/v0_2/mirror_biotrackthc_doctypes.py new file mode 100644 index 0000000..27a9608 --- /dev/null +++ b/erpnext_biotrack/patches/v0_2/mirror_biotrackthc_doctypes.py @@ -0,0 +1,21 @@ +from __future__ import unicode_literals + +import frappe +# bench --force --site abc run-patch erpnext_biotrack.patches.v0_2.mirror_biotrackthc_doctypes +def execute(): + if not frappe.db.exists("Module Def", "BioTrackTHC"): + doc = frappe.get_doc({ + "doctype": "Module Def", + "app_name": "erpnext_biotrack", + "module_name": "BioTrackTHC", + }) + doc.save(ignore_permissions=True) + + frappe.db.sql("UPDATE tabDocType SET `module` = 'BioTrackTHC' WHERE `name` = 'BioTrack Settings'") + frappe.db.sql("UPDATE tabDocType SET `module` = 'BioTrackTHC' WHERE `name` = 'BioTrack Log'") + frappe.db.sql("UPDATE tabDocType SET `module` = 'Traceability System' WHERE `name` = 'Item Sub Lot'") + frappe.reload_doc('biotrackthc', 'doctype', 'biotrack_settings') + + frappe.delete_doc_if_exists("DocType", "Item Conversation Detail") + frappe.delete_doc_if_exists("DocType", "Item Conversation") + frappe.delete_doc_if_exists("Module Def", "ERPNext BioTrack") \ No newline at end of file diff --git a/erpnext_biotrack/patches/v0_2/mirror_plant_to_traceability_system.py b/erpnext_biotrack/patches/v0_2/mirror_plant_to_traceability_system.py new file mode 100644 index 0000000..f64fd68 --- /dev/null +++ b/erpnext_biotrack/patches/v0_2/mirror_plant_to_traceability_system.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals + +import frappe +# bench --force --site abc run-patch erpnext_biotrack.patches.v0_2.mirror_plant_to_traceability_system +def execute(): + frappe.db.sql("UPDATE tabDocType SET `module` = 'Traceability System' WHERE `name` = 'Plant'") + frappe.reload_doc('traceability_system', 'doctype', 'plant') + frappe.db.sql("UPDATE tabPlant SET `title` = `strain` WHERE title = '{strain}' or title IS NULL") + frappe.db.sql("UPDATE tabPlant SET `naming_series` = 'PLANT-' WHERE naming_series IS NULL") + frappe.db.sql("UPDATE tabPlant SET `docstatus` = 1 WHERE docstatus = 0") + frappe.db.sql("UPDATE tabPlant SET `item_code` = `source` WHERE source IS NOT NULL") + frappe.db.sql("UPDATE tabPlant SET `bio_barcode` = `barcode` WHERE barcode IS NOT NULL") + frappe.db.sql("UPDATE tabPlant SET `bio_transaction_id` = `transaction_id` WHERE transaction_id IS NOT NULL") + frappe.db.sql("UPDATE tabPlant SET `bio_last_sync` = `last_sync` WHERE last_sync IS NOT NULL") + frappe.db.sql("UPDATE tabPlant SET `destroy_scheduled` = `remove_scheduled` WHERE remove_scheduled IS NOT NULL") + frappe.db.sql("UPDATE tabPlant SET `posting_date` = DATE(`birthdate`) WHERE birthdate IS NOT NULL") + frappe.db.sql("UPDATE tabPlant SET `posting_time` = TIME(`birthdate`) WHERE birthdate IS NOT NULL") + + frappe.clear_cache() diff --git a/erpnext_biotrack/patches/v0_2/mirror_strain_to_traceability_system.py b/erpnext_biotrack/patches/v0_2/mirror_strain_to_traceability_system.py new file mode 100644 index 0000000..1aa21d6 --- /dev/null +++ b/erpnext_biotrack/patches/v0_2/mirror_strain_to_traceability_system.py @@ -0,0 +1,8 @@ +from __future__ import unicode_literals + +import frappe +# bench --force --site abc run-patch erpnext_biotrack.patches.v0_2.mirror_strain_to_traceability_system +def execute(): + frappe.db.sql("UPDATE tabDocType SET `module` = 'Traceability System' WHERE `name` = 'Strain'") + frappe.reload_doc('traceability_system', 'doctype', 'strain') + diff --git a/erpnext_biotrack/public/js/biotrackthc_integration.js b/erpnext_biotrack/public/js/biotrackthc_integration.js new file mode 100644 index 0000000..37a9421 --- /dev/null +++ b/erpnext_biotrack/public/js/biotrackthc_integration.js @@ -0,0 +1,25 @@ +frappe.provide("frappe.integration_service"); + +frappe.integration_service.biotrack_settings = Class.extend({ + init: function (frm) { + + }, + + get_scheduler_job_info: function () { + return { + "Daily": "Synchrony data from BioTrack on daily basis", + "Weekly": "Synchrony data from BioTrack on weekly basis" + } + }, + + get_service_info: function (frm) { + frappe.call({ + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.get_service_details", + callback: function (r) { + var integration_service_help = frm.fields_dict.integration_service_help.wrapper; + $(integration_service_help).empty(); + $(integration_service_help).append(r.message); + } + }) + } +}); \ No newline at end of file diff --git a/erpnext_biotrack/stock_entry.py b/erpnext_biotrack/stock_entry.py index f842738..f962dc8 100644 --- a/erpnext_biotrack/stock_entry.py +++ b/erpnext_biotrack/stock_entry.py @@ -5,18 +5,77 @@ from __future__ import unicode_literals import frappe from erpnext.stock.doctype.stock_entry.stock_entry import get_warehouse_details - +from .item_utils import make_lot_item, make_item def on_submit(doc, method): + """Item conversion such as Lot creation or Product Conversion""" if frappe.flags.in_import or frappe.flags.in_test: return - from .biotrackthc.client import create_lot, create_product + frappe.flags.ignore_external_sync = True + + if not doc.conversion: + return + + strain = frappe.get_value("Item", doc.get("items")[0].item_code, "strain") if doc.conversion == 'Create Lot': - create_lot(doc) + qty = 0 + for entry in doc.get("items"): + qty += entry.qty + + item = make_lot_item({ + "item_group": doc.lot_group, + "strain": strain, + "default_warehouse": doc.from_warehouse + }, qty) + + doc.lot_item = item.name + elif doc.conversion == 'Create Product': - create_product(doc) + if doc.product_waste: + item = make_item(properties={ + "item_group": "Waste", + "strain": strain, + "default_warehouse": doc.from_warehouse, + }, qty=doc.product_waste) + + doc.waste_item = item.name + + item = make_item(properties={ + "item_name": doc.product_name or doc.product_group, + "item_group": doc.product_group, + "strain": strain, + "default_warehouse": doc.from_warehouse, + }, qty=doc.product_qty) + + doc.product_item = item.name + + doc.flags.ignore_validate_update_after_submit = True + doc.save() + + # pass the ball to external adapters such as biotrackthc + doc.run_method('after_conversion') + + frappe.flags.ignore_external_sync = False + +def on_validate(doc, method): + if frappe.flags.in_import or frappe.flags.in_test: + return + + if not doc.conversion: + return + + missing = [] + if not doc.lot_group: + missing.append("lot_group") + + if not missing: + return + raise frappe.MandatoryError('[{doctype}, {name}]: {fields}'.format( + fields=", ".join((each for each in missing)), + doctype=doc.doctype, + name=doc.name)) def get_item_details(doc, method, args=None, for_update=False): """ Modify original method data to attach qty and strain """ diff --git a/erpnext_biotrack/tasks.py b/erpnext_biotrack/tasks.py deleted file mode 100644 index 86a8eb3..0000000 --- a/erpnext_biotrack/tasks.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright Webonyx and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.utils.background_jobs import enqueue - - -@frappe.whitelist() -def sync(): - "Enqueue longjob for syncing biotrack." - settings = frappe.get_doc("BioTrack Settings") - if not settings.is_sync_enabled(): - frappe.msgprint('BioTrackTHC Background Syncing is not enabled.', title='Sync Error', indicator='red') - return - - enqueue(sync_all, queue="long") - - frappe.msgprint("Queued for syncing. It may take a few minutes to an hour.") - - -@frappe.whitelist() -def client_sync(doctype): - "Enqueue longjob for syncing biotrack." - settings = frappe.get_doc("BioTrack Settings") - if not settings.enable_biotrack: - frappe.msgprint('BioTrackTHC is not enabled.', title='Error', indicator='red') - return - - enqueue(async_client_sync, queue="long", doctype=doctype) - frappe.msgprint("Synchronization is enqueued.") - - -def async_client_sync(doctype): - if doctype == "Plant": - from .biotrackthc.plant import sync - sync() - elif doctype == "Item": - from .biotrackthc.inventory import sync - from .biotrackthc.plant import sync as sync_plant # need to sync plant as well - sync_plant() - sync() - elif doctype == "Customer": - from .biotrackthc.vendor import sync - sync() - elif doctype == "Employee": - from .biotrackthc.employee import sync - sync() - elif doctype == "Quality Inspection": - from .biotrackthc.qa_sample import sync - sync() - elif doctype == "Warehouse": - from .biotrackthc.inventory_room import sync - from .biotrackthc.plant_room import sync as room_sync - sync() - room_sync() - - run_hooks() - frappe.publish_realtime("list_update", {"doctype": doctype}) - - -def sync_all(): - from .biotrackthc import sync - sync() - run_hooks() - - -def hourly(): - return sync_if('Hourly') - - -def daily(): - return sync_if('Daily') - - -def weekly(): - return sync_if('Weekly') - - -def sync_if(frequency, default='Daily'): - settings = frappe.get_doc("BioTrack Settings") - if settings.is_sync_enabled() and (settings.schedule_in or default) == frequency: - sync_all() - -def run_hooks(): - hooks = frappe.get_hooks(hook="biotrack_after_sync") - for hook in hooks or []: - frappe.get_attr(hook)() - diff --git a/erpnext_biotrack/traceability_system/doctype/plant/__init__.py b/erpnext_biotrack/traceability_system/doctype/plant/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/erpnext_biotrack/traceability_system/doctype/plant/plant.js b/erpnext_biotrack/traceability_system/doctype/plant/plant.js new file mode 100644 index 0000000..f5f4c19 --- /dev/null +++ b/erpnext_biotrack/traceability_system/doctype/plant/plant.js @@ -0,0 +1,500 @@ +// Copyright (c) 2016, Webonyx and contributors +// For license information, please see license.txt + +frappe.provide("erpnext_biotrack.plant"); + +frappe.ui.form.on('Plant', { + onload: function (frm) { + frm.set_df_property("from_warehouse", "only_select", true); + }, + refresh: function (frm) { + var is_new = frm.is_new(); + frm.toggle_display("qty", frm.doc.docstatus != 1); + frm.toggle_display("destroy_scheduled", !is_new); + frm.toggle_display("harvest_scheduled", !is_new); + frm.toggle_display("state", !is_new); + frm.toggle_display("disabled", !is_new); + frm.toggle_reqd("item_group", is_new); + frm.toggle_reqd("item_code", is_new); + frm.toggle_reqd("from_warehouse", frm.doc.docstatus != 1); + + erpnext_biotrack.plant.setup_actions(frm); + erpnext_biotrack.plant.setup_queries(frm); + + if (frm.doc.docstatus == 0) { + erpnext_biotrack.plant.get_warehouse_details(frm, function (actual_qty) { + }) + } + + function cal_remaining_time(d) { + var expire_d = moment(d).add(72, "hours"); + var diff = moment(expire_d).diff(moment(), "hours"); + + if (diff == 0) { + diff = moment(expire_d).diff(moment(), "minutes"); + if (diff < 0) { + diff = 0 + } else { + diff += ' minutes' + } + } else { + if (diff < 0) { + diff = 0; + } else { + diff += ' hours' + } + } + + if (diff == 0) { + diff = '72 hours remaining expired' + } else { + diff += ' remaining'; + } + + return diff + } + + + if (frm.doc.remove_scheduled) { + if (frm.doc.disabled) { + frm.dashboard.add_comment( + __("The Plant had been destroyed"), + true + ); + } else { + frm.dashboard.add_comment( + __("This Plant is scheduled for destruction. {0}", [cal_remaining_time(frm.doc.remove_time)]), + true + ); + } + + } + }, + item_group: function (frm) { + frm.set_value('item_code', ''); + }, + item_code: function (frm) { + erpnext_biotrack.plant.setup_queries(frm); + frm.set_value('from_warehouse', ''); + }, + from_warehouse: function (frm) { + erpnext_biotrack.plant.get_warehouse_details(frm, function (actual_qty) { + if (!actual_qty) { + frappe.msgprint( + { + message: __( + "Qty not available for {0} in warehouse {1}", + [frm.doc.item_code, frm.doc.from_warehouse] + ), + title: "Insufficient Stock", + indicator: 'red' + } + ); + } + }) + } +}); + +frappe.ui.form.on('Plant', 'before_submit', function (frm) { + var deferred = $.Deferred(); + if (frm.doc.qty > 5 && frappe.boot.biotrackthc_sync_up) { + frappe.confirm( + __('High quantity with BioTrackTHC synchronization enabled would increase network latency. Would you like to continue?'), + function () { + deferred.resolve() + }, function () { + validated = 0; + deferred.reject() + } + ); + } else { + deferred.resolve() + } + + return deferred.promise(); +}); + +frappe.ui.form.on('Plant', 'before_submit', function (frm) { + var deferred = $.Deferred(); + erpnext_biotrack.plant.get_warehouse_details(frm, function (actual_qty) { + if (frm.doc.qty > actual_qty) { + frappe.msgprint( + { + message: __('Available qty is {0}, you need {1}', [actual_qty, frm.doc.qty]), + title: "Insufficient Stock", + indicator: 'red' + } + ); + + validated = 0; + deferred.reject(); + } else { + deferred.resolve(); + } + + }); + + return deferred.promise(); +}); + +$.extend(erpnext_biotrack.plant, { + get_warehouse_details: function (frm, fn) { + frappe.call({ + method: 'erpnext.stock.doctype.stock_entry.stock_entry.get_warehouse_details', + args: { + "args": { + item_code: frm.doc.item_code, + warehouse: frm.doc.from_warehouse, + posting_date: frm.doc.posting_date, + posting_time: frm.doc.posting_time + } + }, + callback: function (r) { + var actual_qty = r.message.actual_qty || 0; + frm.field_map('qty', function (field) { + field.description = __('Available {0}', [actual_qty]); + }); + + fn(actual_qty); + } + }); + }, + setup_queries: function (frm) { + frm.fields_dict['item_code'].get_query = function (doc, cdt, cdn) { + if (frm.doc.item_group) { + return { + filters: {is_stock_item: 1, item_group: frm.doc.item_group} + } + } else { + return { + filters: {is_stock_item: 1, item_group: ["in", frm.get_field('item_group').df.options.split("\n")]} + } + } + }; + + if (frm.doc.item_code) { + frappe.call({ + method: 'erpnext.stock.dashboard.item_dashboard.get_data', + args: { + item_code: frm.doc.item_code + }, + callback: function (r) { + var data = r.message || []; + frm.fields_dict['from_warehouse'].get_query = function (doc, cdt, cdn) { + return { + filters: { + 'name': ["in", data.map(function (r) { + return r.warehouse + })] + } + } + }; + + if (!data) { + frappe.msgprint( + { + title: __('Insufficient Stock'), + message: __('Item {0} is not available in any warehouses', [frm.doc.item_code]), + indicator: 'red' + } + ); + } + } + }); + } + }, + setup_actions: function (frm) { + frm.page.clear_actions_menu(); + + if (frm.is_new() || frm.doc.disabled || frm.doc.docstatus != 1) { + return; + } + + if (frm.doc.wet_weight && !frm.doc.dry_weight) { + var $btn = frm.add_custom_button(__('Undo Harvest'), function () { + $btn.prop('disabled', true); + frappe.call({ + doc: frm.doc, + method: 'harvest_undo', + callback: function (data) { + cur_frm.reload_doc(); + } + }); + }) + } + + if (!frm.doc.destroy_scheduled) { + if (frm.doc.state == 'Growing') { + if (!frm.doc.harvest_scheduled) { + frm.page.add_action_item(__('Schedule for Harvesting'), function () { + erpnext_biotrack.plant.harvest_schedule(frm); + }); + } else { + frm.page.add_action_item(__('Harvest'), function () { + erpnext_biotrack.plant.harvest_cure(frm); + }); + } + + + frm.page.add_action_item(__('Convert to Mature Plant'), function () { + erpnext_biotrack.plant.move_to_inventory(frm); + }) + } else if (frm.doc.state == 'Drying') { + frm.page.add_action_item(__('Cure'), function () { + erpnext_biotrack.plant.harvest_cure(frm); + }); + } + + if (frm.doc.harvest_scheduled) { + if (frm.doc.state == 'Growing') { + frm.page.add_action_item(__("Undo Scheduled Harvest"), function () { + erpnext_biotrack.plant.harvest_schedule_undo(frm); + }) + } + } else { + + frm.page.add_action_item(__("Schedule for Destruction"), function () { + erpnext_biotrack.plant.destroy_schedule(frm); + }); + } + + } else { + frm.add_custom_button('Undo Destruction Notification', function () { + erpnext_biotrack.plant.destroy_schedule_undo(frm); + }); + + frm.add_custom_button('Override Destruction Notification', function () { + erpnext_biotrack.plant.destroy_schedule(frm); + }); + } + }, + plant_new_undo: function (doc) { + frappe.call({ + doc: doc, + method: 'undo', + callback: function (data) { + window.history.back(); + } + }); + }, + harvest_schedule: function (frm) { + frappe.call({ + doc: frm.doc, + method: 'harvest_schedule', + callback: function (data) { + cur_frm.reload_doc(); + } + }); + }, + harvest_cure: function (frm) { + var doc = frm.doc, + fields = [ + { + fieldname: 'name', label: 'Plant Identifier', fieldtype: 'Data', read_only: 1, 'default': doc.name + }, + { + fieldname: 'strain', label: 'Strain', fieldtype: 'Data', read_only: 1, 'default': doc.strain + }, + { + fieldname: 'uom', + label: 'UOM', + fieldtype: 'Select', + read_only: 1, + options: ['Gram'], + 'default': 'Gram' + }, + { + fieldname: 'flower', + label: __('Flower {0} Weight', [doc.state == 'Growing' ? 'Wet' : 'Dry']), + fieldtype: 'Float', + reqd: 1 + }, + { + fieldname: 'other_material', + label: 'Other Plant Material Weight', + fieldtype: 'Float', + 'default': 0.00 + }, + { + fieldname: 'waste', label: 'Waste Weight', fieldtype: 'Float', 'default': 0.00 + } + ], + dialog; + + fields.push( + { + fieldname: 'additional_collection', label: 'Additional Collections', fieldtype: 'Check' + } + ); + + dialog = new frappe.ui.Dialog({ + title: __((doc.state == 'Growing' ? 'Harvest' : 'Cure') + ' Plant'), + fields: fields, + onhide: function () { + cur_frm.reload_doc(); + } + }); + + dialog.set_primary_action(__('Submit'), function () { + var values = dialog.get_values(); + if (!values) { + return; + } + + delete values['name']; + delete values['strain']; + delete values['uom']; // discard and use Gram by default + + frappe.call({ + doc: doc, + method: (doc.state == 'Growing' ? 'harvest' : 'cure'), + args: values, + callback: function (data) { + dialog.hide(); + } + }); + }); + + dialog.show(); + }, + move_to_inventory: function (frm) { + var doc = frm.doc, + fields = [ + { + fieldname: 'name', label: 'Plant Identifier', fieldtype: 'Data', read_only: 1, default: doc.name + }, + { + fieldname: 'strain', label: 'Strain', fieldtype: 'Data', read_only: 1, default: doc.strain + } + ], + dialog; + + dialog = new frappe.ui.Dialog({ + title: __('Convert to Mature Plant'), + fields: fields + }); + + dialog.set_primary_action(__('Move'), function () { + frappe.call({ + doc: doc, + method: 'convert_to_inventory', + callback: function (data) { + dialog.hide(); + frappe.set_route('List', 'Plant'); + } + }); + }); + + dialog.show(); + }, + destroy_schedule: function (frm) { + var doc = frm.doc, + fields = [ + { + fieldname: 'reason', label: __('Please choose a reason for scheduling this destruction'), + fieldtype: 'Select', options: [ + 'Other', + 'Waste', + 'Unhealthy or Died', + 'Infestation', + 'Product Return', + 'Mistake', + 'Spoilage', + 'Quality Control' + ] + }, + { + fieldname: 'reason_txt', label: __('Reason Detail'), + fieldtype: 'Text' + } + ], + dialog; + + if (doc.destroy_scheduled) { + fields.push({ + fieldname: 'override', label: __('Reset Scheduled time'), + fieldtype: 'Check' + }) + } + + dialog = new frappe.ui.Dialog({ + title: __('Destruction Notification'), + fields: fields + }); + + if (doc.destroy_scheduled) { + dialog.get_field('override').set_input(1); + } + + dialog.set_primary_action(__('Submit'), function () { + var values = dialog.get_values(); + if (!values) { + return; + } + + if (!values.reason) { + frappe.msgprint({ + message: __('Please specify a reason'), + indicator: 'red', + title: 'Error' + }); + + return; + } + + if (values.reason == 'Other' && !values.reason_txt) { + frappe.msgprint({ + message: __('Please input a reason detail'), + indicator: 'red', + title: 'Error' + }); + + return; + } + + delete values['name']; + frappe.call({ + doc: doc, + method: 'destroy_schedule', + args: values, + callback: function (data) { + dialog.hide(); + cur_frm.reload_doc(); + } + }); + }); + + dialog.show_message('This will initiate the 72 hour waiting period.'); + dialog.message.removeClass('small text-muted'); + dialog.show(); + }, + + destroy_schedule_undo: function (frm) { + frappe.confirm( + 'You are going to cancel destruction notification?', + function () { + frappe.call({ + doc: frm.doc, + method: 'destroy_schedule_undo', + callback: function (data) { + cur_frm.reload_doc(); + } + }); + } + ); + }, + harvest_schedule_undo: function (frm) { + frappe.confirm( + 'Please confirm this action', + function () { + frappe.call({ + doc: frm.doc, + method: 'harvest_schedule_undo', + callback: function (data) { + cur_frm.reload_doc(); + } + }); + } + ); + }, +}); \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.json b/erpnext_biotrack/traceability_system/doctype/plant/plant.json similarity index 75% rename from erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.json rename to erpnext_biotrack/traceability_system/doctype/plant/plant.json index 89513a8..02621c9 100644 --- a/erpnext_biotrack/erpnext_biotrack/doctype/plant/plant.json +++ b/erpnext_biotrack/traceability_system/doctype/plant/plant.json @@ -2,13 +2,13 @@ "allow_copy": 0, "allow_import": 1, "allow_rename": 1, - "autoname": "field:barcode", + "autoname": "naming_series:", "beta": 0, "creation": "2016-08-01 02:45:07.048126", "custom": 0, "docstatus": 0, "doctype": "DocType", - "document_type": "Document", + "document_type": "", "editable_grid": 1, "engine": "InnoDB", "fields": [ @@ -17,19 +17,20 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "barcode", - "fieldtype": "Data", + "fieldname": "naming_series", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Barcode", + "label": "Series", "length": 0, "no_copy": 0, + "options": "PLANT-", "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, @@ -40,24 +41,24 @@ "unique": 0 }, { - "allow_on_submit": 0, + "allow_on_submit": 1, "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "__islocal", - "fieldname": "bulk_add", - "fieldtype": "Check", - "hidden": 0, + "default": "{strain}", + "fieldname": "title", + "fieldtype": "Data", + "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Bulk Add", + "label": "Title", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, @@ -72,26 +73,25 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "1", - "depends_on": "__islocal", - "fieldname": "qty", - "fieldtype": "Int", + "fieldname": "plant_room", + "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Quantity", + "label": "Plant Room", "length": 0, "no_copy": 0, + "options": "Plant Room", "permlevel": 0, "precision": "", - "print_hide": 1, + "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 0, + "reqd": 1, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -101,17 +101,17 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "plant_room", + "fieldname": "strain", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "label": "Plant Room", + "in_filter": 1, + "in_list_view": 1, + "label": "Strain", "length": 0, "no_copy": 0, - "options": "Plant Room", + "options": "Strain", "permlevel": 0, "precision": "", "print_hide": 0, @@ -120,7 +120,7 @@ "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 0, + "search_index": 1, "set_only_once": 0, "unique": 0 }, @@ -129,26 +129,27 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "strain", - "fieldtype": "Link", + "depends_on": "eval:doc.docstatus == 1", + "fieldname": "state", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 1, - "in_list_view": 1, - "label": "Strain", + "in_list_view": 0, + "label": "State", "length": 0, - "no_copy": 0, - "options": "Strain", + "no_copy": 1, + "options": "Growing\nDrying\nCured", "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, - "search_index": 1, + "search_index": 0, "set_only_once": 0, "unique": 0 }, @@ -183,25 +184,53 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "Now", - "fieldname": "birthdate", - "fieldtype": "Datetime", + "default": "Today", + "fieldname": "posting_date", + "fieldtype": "Date", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Birthdate", + "label": "Posting Date", "length": 0, "no_copy": 0, "permlevel": 0, "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, - "read_only": 1, + "read_only": 0, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "default": "", + "fieldname": "posting_time", + "fieldtype": "Time", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Posting Time", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -241,17 +270,17 @@ "depends_on": "", "fieldname": "disabled", "fieldtype": "Check", - "hidden": 1, + "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 1, "in_list_view": 0, "label": "Disabled", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, "remember_last_selected_value": 0, @@ -299,13 +328,13 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Source Type", + "label": "Item Group", "length": 0, "no_copy": 0, "options": "\nSeed\nClone\nMature Plant\nPlant Tissue", "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, @@ -320,20 +349,48 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "source", + "fieldname": "item_code", "fieldtype": "Link", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Source", + "label": "Item", "length": 0, "no_copy": 0, "options": "Item", "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "from_warehouse", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "From Warehouse", + "length": 0, + "no_copy": 1, + "options": "Warehouse", + "permlevel": 0, + "precision": "", + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, @@ -348,7 +405,36 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "section_break_5", + "default": "1", + "depends_on": "", + "fieldname": "qty", + "fieldtype": "Int", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Quantity", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "section_break_9", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -374,17 +460,17 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "state", - "fieldtype": "Select", + "depends_on": "eval:doc.docstatus == 1", + "fieldname": "harvest_collect", + "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, - "in_filter": 1, + "in_filter": 0, "in_list_view": 0, - "label": "State", + "label": "Harvest Collect", "length": 0, "no_copy": 0, - "options": "Growing\nDrying\nCured", "permlevel": 0, "precision": "", "print_hide": 0, @@ -392,7 +478,7 @@ "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, - "reqd": 1, + "reqd": 0, "search_index": 0, "set_only_once": 0, "unique": 0 @@ -402,20 +488,22 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "section_break_9", - "fieldtype": "Section Break", + "depends_on": "eval:doc.docstatus == 1", + "fieldname": "cure_collect", + "fieldtype": "Int", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, + "label": "Cure Collect", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, - "read_only": 0, + "read_only": 1, "remember_last_selected_value": 0, "report_hide": 0, "reqd": 0, @@ -437,10 +525,10 @@ "in_list_view": 0, "label": "Harvests Scheduled", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, "remember_last_selected_value": 0, @@ -455,7 +543,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "remove_scheduled", + "fieldname": "destroy_scheduled", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -464,10 +552,10 @@ "in_list_view": 0, "label": "Destroy Scheduled", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, "remember_last_selected_value": 0, @@ -508,7 +596,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "harvest_scheduled", + "depends_on": "eval:doc.docstatus == 1", "fieldname": "wet_weight", "fieldtype": "Float", "hidden": 0, @@ -516,12 +604,12 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Wet Weight", + "label": "Wet Weight Collected", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, "remember_last_selected_value": 0, @@ -536,7 +624,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "harvest_scheduled", + "depends_on": "eval:doc.docstatus == 1", "fieldname": "dry_weight", "fieldtype": "Float", "hidden": 0, @@ -544,12 +632,12 @@ "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Dry Weight", + "label": "Dry Weight Collected", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 1, "remember_last_selected_value": 0, @@ -592,7 +680,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "remove_scheduled", + "depends_on": "destroy_scheduled", "fieldname": "remove_time", "fieldtype": "Datetime", "hidden": 0, @@ -602,7 +690,7 @@ "in_list_view": 0, "label": "Destroy Scheduled at", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", "print_hide": 0, @@ -620,7 +708,7 @@ "bold": 0, "collapsible": 0, "columns": 0, - "depends_on": "remove_scheduled", + "depends_on": "destroy_scheduled", "fieldname": "remove_reason", "fieldtype": "Data", "hidden": 0, @@ -648,19 +736,46 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "transaction_id", - "fieldtype": "Int", + "fieldname": "bio_barcode", + "fieldtype": "Data", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "BioTrack Identifier", + "length": 0, + "no_copy": 1, + "permlevel": 0, + "precision": "", + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 1, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "bio_transaction_id", + "fieldtype": "Data", "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Transaction Id", + "label": "Bio Transaction Id", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, @@ -675,19 +790,19 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "last_sync", + "fieldname": "bio_last_sync", "fieldtype": "Datetime", "hidden": 1, "ignore_user_permissions": 0, "ignore_xss_filter": 0, "in_filter": 0, "in_list_view": 0, - "label": "Last Sync", + "label": "Bio Last Sync", "length": 0, - "no_copy": 0, + "no_copy": 1, "permlevel": 0, "precision": "", - "print_hide": 0, + "print_hide": 1, "print_hide_if_no_value": 0, "read_only": 0, "remember_last_selected_value": 0, @@ -696,6 +811,33 @@ "search_index": 1, "set_only_once": 0, "unique": 0 + }, + { + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "amended_from", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_list_view": 0, + "label": "Amended From", + "length": 0, + "no_copy": 1, + "options": "Plant", + "permlevel": 0, + "print_hide": 1, + "print_hide_if_no_value": 0, + "read_only": 1, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 } ], "hide_heading": 0, @@ -704,87 +846,45 @@ "image_view": 0, "in_create": 0, "in_dialog": 0, - "is_submittable": 0, + "is_submittable": 1, "issingle": 0, "istable": 0, "max_attachments": 1, - "modified": "2016-11-10 20:46:17.641987", + "modified": "2016-11-23 15:54:06.409398", "modified_by": "Administrator", - "module": "ERPNext BioTrack", + "module": "Traceability System", "name": "Plant", "name_case": "", "owner": "Administrator", "permissions": [ { - "amend": 0, + "amend": 1, "apply_user_permissions": 0, - "cancel": 0, + "cancel": 1, "create": 1, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Item Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, - "delete": 0, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, - "write": 0 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 0, "delete": 1, "email": 1, "export": 1, "if_owner": 0, - "import": 0, + "import": 1, "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "Manufacturing Manager", + "role": "Traceability System Manager", "set_user_permissions": 0, "share": 1, - "submit": 0, + "submit": 1, "write": 1 } ], "quick_entry": 0, "read_only": 0, "read_only_onload": 0, - "search_fields": "strain", + "search_fields": "strain, plant_room", "sort_field": "modified", "sort_order": "DESC", - "title_field": "barcode", + "title_field": "title", "track_seen": 0 } \ No newline at end of file diff --git a/erpnext_biotrack/traceability_system/doctype/plant/plant.py b/erpnext_biotrack/traceability_system/doctype/plant/plant.py new file mode 100644 index 0000000..c243082 --- /dev/null +++ b/erpnext_biotrack/traceability_system/doctype/plant/plant.py @@ -0,0 +1,352 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2015, Webonyx and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, datetime +from erpnext.stock.utils import get_stock_balance +from erpnext_biotrack.biotrackthc import call as biotrackthc_call +from erpnext_biotrack.biotrackthc.inventory_room import get_default_warehouse +from erpnext_biotrack.item_utils import get_item_values, make_item, generate_item_code +from frappe.desk.reportview import build_match_conditions +from frappe.utils.data import cint, now, flt, add_to_date +from frappe.model.document import Document +from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry +from frappe.utils.background_jobs import enqueue + +removal_reasons = { + 0: 'Other', + 1: 'Waste', + 2: 'Unhealthy or Died', + 3: 'Infestation', + 4: 'Product Return', + 5: 'Mistake', + 6: 'Spoilage', + 7: 'Quality Control' +} + + +class Plant(Document): + def on_submit(self): + if frappe.flags.in_import: + return + + self.validate_item_balance() + self.make_stock_entry() + + # bulk add + if self.qty > 1 and not self.flags.in_bulk: + self.flags.bulk_add = True + self.flags.bulk_plants = [] + + for i in xrange(self.qty - 1): + plant = frappe.copy_doc(self) + plant.qty = 1 + plant.flags.in_bulk = True + plant.submit() + self.flags.bulk_plants.append(plant) + + + def on_cancel(self): + if self.flags.in_import: + return + + self.make_stock_entry_cancel() + + def on_trash(self): + # able to delete new Plants + if self.state == "Growing" or not self.harvest_scheduled: + return + + if not self.destroy_scheduled: + frappe.throw("Plant can not be deleted directly. Please schedule for destruction first") + + if not self.disabled: + frappe.throw("Plant can only be deleted once destroyed") + + def validate_item_balance(self): + # able to delete new Plants + item = frappe.get_doc("Item", self.get("item_code")) + qty = get_stock_balance(item.item_code, self.get("from_warehouse")) + if qty < self.qty: + frappe.throw("The provided quantity {0} exceeds stock balance. " + "Stock balance remaining {1}".format(self.qty, qty)) + + def make_stock_entry(self): + item = frappe.get_doc("Item", self.get("item_code")) + ste = make_stock_entry(item_code=item.name, source=self.get("from_warehouse"), qty=1, do_not_save=True) + ste.plant = self.name + ste.submit() + + + def make_stock_entry_cancel(self): + name = frappe.db.exists("Stock Entry", {"plant": self.name}) + if name: + ste = frappe.get_doc("Stock Entry", name) + ste.cancel() + + + def collect_item(self, item_group, qty): + default_warehouse = get_default_warehouse() + return make_item(properties={ + "item_name": item_group.item_group_name, + "item_code": generate_item_code(), + "item_group": item_group.name, + "default_warehouse": default_warehouse.name, + "strain": self.get("strain"), + "stock_uom": "Gram", + "is_stock_item": 1, + "plant": self.name, + }, qty=qty) + + + def delete_related_items(self): + for item_name in frappe.get_all("Item", {"plant": self.name}): + item = frappe.get_doc("Item", item_name) + for name in frappe.get_list("Stock Entry", {"item_code": item.item_code}): + ste = frappe.get_doc("Stock Entry", name) + ste.cancel() + ste.delete() + item.delete() + + @Document.whitelist + def cure(self, flower, other_material=None, waste=None, additional_collection=None): + if self.disabled: + frappe.throw("Plant {} is not available for harvesting.") + + if self.destroy_scheduled: + frappe.throw("Plant {} is currently scheduled for destruction and cannot be harvested.") + + self.dry_weight = flt(self.dry_weight) + flt(flower) + if self.wet_weight and self.dry_weight > self.wet_weight: + frappe.throw( + "The provided dry weight {0} exceeds the previous wet weight {1}.". + format(self.dry_weight, self.wet_weight), title="Error") + + items = [] + frappe.flags.ignore_external_sync = True + + # collect Flower + item_group = frappe.get_doc("Item Group", {"external_id": 6}) + items.append(self.collect_item(item_group, flower)) + + if other_material: + item_group = frappe.get_doc("Item Group", {"external_id": 9}) + items.append(self.collect_item(item_group, other_material)) + + if waste: + item_group = frappe.get_doc("Item Group", {"external_id": 27}) + items.append(self.collect_item(item_group, waste)) + + + # Remove from Cultivation + if not additional_collection or self.dry_weight == self.wet_weight: + self.disabled = 1 + + self.cure_collect = self.cure_collect + 1 + self.flags.ignore_validate_update_after_submit = True + self.save() + + # hook + self.run_method("after_cure", items=items, flower=flower, other_material=other_material, waste=waste, additional_collection=additional_collection) + + return {"items": items} + + @Document.whitelist + def cure_undo(self): + if not self.disabled: + frappe.throw("Invalid action") + + self.run_method("before_cure_undo") + + # self.delete_related_items() + + self.disabled = 0 + self.cure_collect = self.cure_collect - 1 if self.cure_collect > 0 else 0 + self.flags.ignore_validate_update_after_submit = True + self.save() + + @Document.whitelist + def harvest(self, flower, other_material=None, waste=None, additional_collection=None): + if self.disabled: + frappe.throw("Plant {} is not available for harvesting.") + + if self.destroy_scheduled: + frappe.throw("Plant {} is currently scheduled for destruction and cannot be harvested.") + + items = [] + frappe.flags.ignore_external_sync = True + if other_material: + item_group = frappe.get_doc("Item Group", {"external_id": 9}) + items.append(self.collect_item(item_group, other_material)) + + if waste: + item_group = frappe.get_doc("Item Group", {"external_id": 27}) + items.append(self.collect_item(item_group, waste)) + + self.wet_weight = flt(self.wet_weight) + flt(flower) + if not additional_collection: + self.state = "Drying" + + # Reset harvest_scheduled status + self.harvest_scheduled = 0 + self.harvest_schedule_time = None + self.harvest_collect = self.harvest_collect + 1 + self.flags.ignore_validate_update_after_submit = True + self.save() + + self.run_method("after_harvest", items=items, flower=flower, other_material=other_material, waste=waste, additional_collection=additional_collection) + + return {"items": items} + + @Document.whitelist + def harvest_undo(self): + if self.state != "Drying": + frappe.throw("Invalid action") + + self.run_method("before_harvest_undo") + + self.delete_related_items() + + self.wet_weight = 0 + self.dry_weight = 0 + self.harvest_collect = self.harvest_collect - 1 if self.harvest_collect > 0 else 0 + self.state = "Growing" + + self.flags.ignore_validate_update_after_submit = True + self.save() + + @Document.whitelist + def destroy_schedule(self, reason, reason_txt=None, override=None): + if self.destroy_scheduled and not override: + frappe.throw( + "Plant {} has already been scheduled for destruction. Check `Reset Scheduled time` to reschedule.".format( + self.name)) + + reason_key = removal_reasons.keys()[removal_reasons.values().index(reason)] + self.run_method("before_destroy_schedule", reason_key=reason_key, reason=reason_txt, override=override) + + self.destroy_scheduled = 1 + self.remove_reason = reason_txt or reason + self.remove_time = now() + self.flags.ignore_validate_update_after_submit = True + self.save() + + @Document.whitelist + def destroy_schedule_undo(self): + if not self.destroy_scheduled: + return + + self.run_method("before_destroy_schedule_undo") + self.flags.ignore_validate_update_after_submit = True + self.destroy_scheduled = 0 + self.remove_reason = None + self.remove_time = None + self.save() + + @Document.whitelist + def harvest_schedule(self): + if self.harvest_scheduled: + frappe.throw("Plant {} has been scheduled for harvesting.".format(self.name)) + + self.run_method("before_harvest_schedule") + self.harvest_scheduled = 1 + self.harvest_schedule_time = now() + + self.flags.ignore_validate_update_after_submit = True + self.save() + + @Document.whitelist + def harvest_schedule_undo(self): + if not self.harvest_scheduled: + frappe.throw("Plant {} was not in scheduled state.".format(self.name)) + + if self.state == "Drying": + frappe.throw("Plant {} has already on harvesting process.".format(self.name)) + + self.run_method("before_harvest_schedule_undo") + + self.harvest_scheduled = 0 + self.harvest_schedule_time = None + + self.flags.ignore_validate_update_after_submit = True + self.save() + + @Document.whitelist + def convert_to_inventory(self): + item_group = frappe.get_doc("Item Group", {"external_id": 12}) # Mature Plant + qty = 1 + item = self.collect_item(item_group, qty) + + # destroy plant as well + self.destroy_scheduled = 1 + self.remove_time = now() + self.disabled = 1 + + self.flags.ignore_validate_update_after_submit = True + self.save() + + self.run_method('after_convert_to_inventory', item=item) + + +def get_plant_list(doctype, txt, searchfield, start, page_len, filters): + fields = ["name", "strain"] + match_conditions = build_match_conditions("Plant") + match_conditions = "and {}".format(match_conditions) if match_conditions else "" + + return frappe.db.sql("""select %s from `tabPlant` where docstatus < 2 + and (%s like %s or strain like %s) + {match_conditions} + order by + case when name like %s then 0 else 1 end, + case when strain like %s then 0 else 1 end, + name, strain limit %s, %s""".format(match_conditions=match_conditions) % + (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"), + ("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len)) + + +def bulk_clone(name): + source_plant = frappe.get_doc("Plant", name) + + if source_plant.qty > 1: + warehouse = frappe.get_doc("Warehouse", source_plant.get("warehouse")) + location = frappe.get_value("BioTrack Settings", None, "location") + remaining_qty = source_plant.qty - 1 + + result = biotrackthc_call("plant_new", { + "room": warehouse.external_id, + "quantity": remaining_qty, + "strain": source_plant.strain, + "source": source_plant.item_code, + "mother": cint(source_plant.get("is_mother")), + "location": location + }) + + for barcode in result.get("barcode_id"): + plant = frappe.new_doc("Plant") + plant.update({ + "barcode": barcode, + "item_group": source_plant.item_group, + "source": source_plant.item_code, + "strain": source_plant.strain, + "warehouse": source_plant.warehouse, + "state": source_plant.state, + "birthdate": now(), + }) + + plant.save() + + # save directly with sql to avoid mistimestamp check + frappe.db.set_value("Plant", source_plant.name, "qty", 1, update_modified=False) + frappe.publish_realtime("list_update", {"doctype": "Plant"}) + + +def destroy_scheduled_plants(): + """Destroy expired Plants""" + date = add_to_date(now(), days=-3) + for name in frappe.get_list("Plant", + [["disabled", "=", 0], ["destroy_scheduled", "=", 1], ["remove_time", "<", date]]): + plant = frappe.get_doc("Plant", name) + plant.disabled = 1 + plant.remove_time = now() + plant.save() diff --git a/erpnext_biotrack/traceability_system/doctype/plant/plant_dashboard.py b/erpnext_biotrack/traceability_system/doctype/plant/plant_dashboard.py new file mode 100644 index 0000000..cccaa5c --- /dev/null +++ b/erpnext_biotrack/traceability_system/doctype/plant/plant_dashboard.py @@ -0,0 +1,19 @@ +from frappe import _ + +def get_data(): + return { + 'non_standard_fieldnames': { + 'Stock Entry': 'plant', + 'Item': 'plant', + }, + 'transactions': [ + # { + # 'label': _('Traceability'), + # 'items': ['Item'] + # }, + { + 'label': _('Traceability'), + 'items': ['Stock Entry', 'Item'] + } + ] + } \ No newline at end of file diff --git a/erpnext_biotrack/traceability_system/doctype/plant/plant_list.js b/erpnext_biotrack/traceability_system/doctype/plant/plant_list.js new file mode 100644 index 0000000..56f3fcd --- /dev/null +++ b/erpnext_biotrack/traceability_system/doctype/plant/plant_list.js @@ -0,0 +1,43 @@ +frappe.listview_settings['Plant'] = { + add_fields: ['disabled', 'posting_date', 'destroy_scheduled'], + filters: [["disabled", "=", "No"]], + get_indicator: function (doc) { + if (doc.disabled) { + return [__("Destroyed"), "grey", "disabled,=,Yes"]; + } else if (doc.destroy_scheduled) { + return [__("Destroy Scheduled"), "orange", "destroy_scheduled,=,Yes"]; + } else { + return [this.calculate_time_in_room(doc.posting_date), "green", "disabled,=,No"]; + } + }, + + onload: function (DocListView) { + DocListView.listview.stats.push("state"); + DocListView.listview.stats.push("plant_room"); + + if (frappe.boot.biotrackthc_sync_down) { + DocListView.page.add_action_item(__("BioTrackTHC Sync"), function () { + frappe.call({ + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_now", + args: {"doctype": "Plant"} + }) + }) + } + }, + calculate_time_in_room: function (posting_date) { + var diff = frappe.datetime.get_diff(frappe.datetime.get_today(), posting_date); + if (diff == 0) { + return comment_when(posting_date); + } + + if (diff == 1) { + return __('Yesterday') + } + + if (diff == 2) { + return __('2 days ago') + } + + return diff + ' days'; + } +}; diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/plant/test_plant.py b/erpnext_biotrack/traceability_system/doctype/plant/test_plant.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/plant/test_plant.py rename to erpnext_biotrack/traceability_system/doctype/plant/test_plant.py diff --git a/erpnext_biotrack/traceability_system/doctype/plant_room/plant_room_list.js b/erpnext_biotrack/traceability_system/doctype/plant_room/plant_room_list.js new file mode 100644 index 0000000..f0dba9f --- /dev/null +++ b/erpnext_biotrack/traceability_system/doctype/plant_room/plant_room_list.js @@ -0,0 +1,15 @@ +// Copyright (c) 2016, Webonyx and contributors +// For license information, please see license.txt + +frappe.listview_settings['Plant Room'] = { + onload: function (ListView) { + if (frappe.boot.biotrackthc_sync_down) { + ListView.page.add_action_item(__("BioTrackTHC Sync"), function () { + frappe.call({ + method: "erpnext_biotrack.biotrackthc.doctype.biotrack_settings.biotrack_settings.sync_now", + args: {"doctype": "Plant Room"} + }) + }) + } + } +}; diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/strain/__init__.py b/erpnext_biotrack/traceability_system/doctype/strain/__init__.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/strain/__init__.py rename to erpnext_biotrack/traceability_system/doctype/strain/__init__.py diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/strain/strain.js b/erpnext_biotrack/traceability_system/doctype/strain/strain.js similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/strain/strain.js rename to erpnext_biotrack/traceability_system/doctype/strain/strain.js diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/strain/strain.json b/erpnext_biotrack/traceability_system/doctype/strain/strain.json similarity index 62% rename from erpnext_biotrack/erpnext_biotrack/doctype/strain/strain.json rename to erpnext_biotrack/traceability_system/doctype/strain/strain.json index a8da6fa..f5b3ad6 100644 --- a/erpnext_biotrack/erpnext_biotrack/doctype/strain/strain.json +++ b/erpnext_biotrack/traceability_system/doctype/strain/strain.json @@ -2,18 +2,22 @@ "allow_copy": 0, "allow_import": 0, "allow_rename": 0, - "autoname": "field:strain_name", - "creation": "2016-07-18 16:33:27.379232", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Other", + "autoname": "field:strain_name", + "beta": 0, + "creation": "2016-07-18 16:33:27.379232", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 0, + "engine": "InnoDB", "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "fieldname": "strain_name", + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "strain_name", "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, @@ -28,6 +32,7 @@ "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, + "remember_last_selected_value": 0, "report_hide": 0, "reqd": 1, "search_index": 0, @@ -38,15 +43,16 @@ "hide_heading": 0, "hide_toolbar": 0, "idx": 0, + "image_view": 0, "in_create": 0, "in_dialog": 0, "is_submittable": 0, "issingle": 0, "istable": 0, "max_attachments": 0, - "modified": "2016-07-18 16:50:37.011990", + "modified": "2016-11-14 15:24:48.916638", "modified_by": "Administrator", - "module": "ERPNext BioTrack", + "module": "Traceability System", "name": "Strain", "name_case": "", "owner": "Administrator", @@ -61,40 +67,23 @@ "export": 1, "if_owner": 0, "import": 0, + "is_custom": 0, "permlevel": 0, "print": 1, "read": 1, "report": 1, - "role": "System Manager", + "role": "Traceability System Manager", "set_user_permissions": 0, "share": 1, "submit": 0, "write": 1 - }, - { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Administrator", - "set_user_permissions": 0, - "share": 0, - "submit": 0, - "write": 1 } ], + "quick_entry": 1, "read_only": 0, "read_only_onload": 0, "sort_field": "modified", "sort_order": "DESC", - "title_field": "" + "title_field": "", + "track_seen": 0 } \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/strain/strain.py b/erpnext_biotrack/traceability_system/doctype/strain/strain.py similarity index 100% rename from erpnext_biotrack/erpnext_biotrack/doctype/strain/strain.py rename to erpnext_biotrack/traceability_system/doctype/strain/strain.py diff --git a/erpnext_biotrack/traceability_system/doctype/strain/test_records.json b/erpnext_biotrack/traceability_system/doctype/strain/test_records.json new file mode 100644 index 0000000..ea01d28 --- /dev/null +++ b/erpnext_biotrack/traceability_system/doctype/strain/test_records.json @@ -0,0 +1,6 @@ +[ + { + "doctype": "Strain", + "strain_name": "_Test Strain" + } +] \ No newline at end of file diff --git a/erpnext_biotrack/erpnext_biotrack/doctype/strain/test_strain.py b/erpnext_biotrack/traceability_system/doctype/strain/test_strain.py similarity index 80% rename from erpnext_biotrack/erpnext_biotrack/doctype/strain/test_strain.py rename to erpnext_biotrack/traceability_system/doctype/strain/test_strain.py index 0d0dd71..263a3b7 100644 --- a/erpnext_biotrack/erpnext_biotrack/doctype/strain/test_strain.py +++ b/erpnext_biotrack/traceability_system/doctype/strain/test_strain.py @@ -6,7 +6,7 @@ import frappe import unittest -# test_records = frappe.get_test_records('Strain') +test_records = frappe.get_test_records('Strain') class TestStrain(unittest.TestCase): pass diff --git a/erpnext_biotrack/utils.py b/erpnext_biotrack/utils.py index 87ca086..97ec672 100644 --- a/erpnext_biotrack/utils.py +++ b/erpnext_biotrack/utils.py @@ -3,85 +3,8 @@ # For license information, please see license.txt from __future__ import unicode_literals -import json, os import frappe from frappe.model.db_schema import DbTable -from frappe.utils.csvutils import read_csv_content -from .exceptions import BiotrackSetupError -from .exceptions import BiotrackError -from frappe.defaults import get_defaults -from frappe.desk.tags import DocTags -from frappe.exceptions import DoesNotExistError, ValidationError - -dumped_data = {} - - -def get_biotrack_settings(): - d = frappe.get_doc("BioTrack Settings") - d.password = d.get_password() - - if d.username and d.license_number: - return d.as_dict() - else: - frappe.throw(_("BioTrack credentials are not configured on BioTrack Settings"), BiotrackError) - - -def disable_biotrack_sync_on_exception(): - frappe.db.rollback() - frappe.db.set_value("BioTrack Settings", None, "enable_biotrack", 0) - frappe.db.set_value("BioTrack Settings", None, "session_id", '') - frappe.db.commit() - - -def is_biotrack_enabled(): - biotrack_settings = frappe.get_doc("BioTrack Settings") - if not biotrack_settings.enable_biotrack: - return False - try: - biotrack_settings.validate() - except BiotrackSetupError: - return False - - return True - - -def make_log(title=None, status="Queued", method="sync", message=None, exception=False, - name=None, request_data={}): - if not name: - name = frappe.db.get_value("BioTrack Log", {"status": "Queued"}) - - if name: - """ if name not provided by log calling method then fetch existing queued state log""" - log = frappe.get_doc("BioTrack Log", name) - - else: - """ if queued job is not found create a new one.""" - log = frappe.get_doc({"doctype": "BioTrack Log"}).insert(ignore_permissions=True) - - if exception: - frappe.db.rollback() - log = frappe.get_doc({"doctype": "BioTrack Log"}).insert(ignore_permissions=True) - - log.message = (log.message + "\n\n" if log.message else "") + "{}\n".format(json.dumps({"method": method - , "status": status, - "time": frappe.utils.now()})) - if status == "Error" and not exception: - status = "Queued" - - log.message += message if message else frappe.get_traceback() - log.title = title[0:140] if title else (log.title if log.title else "Sync log") - log.method = method - log.status = status - log.request_data = json.dumps(request_data) - - log.save(ignore_permissions=True) - frappe.db.commit() - - -def add_tag(doctype, name, tag): - DocTags(doctype).add(name, tag) - - return tag def rename_custom_field(doctype, old_fieldname, new_fieldname): @@ -103,33 +26,3 @@ def rename_custom_field(doctype, old_fieldname, new_fieldname): format(fieldname=new_fieldname, name="{}-{}".format(doctype, new_fieldname), doctype=doctype, old_fieldname=old_fieldname) frappe.db.sql(update_custom_field_sql) - - -def inventories_price_log(): - if not "inventories_price" in dumped_data: - inventories_price = {} - for row in load_dumped_data("inventorytransfers_log"): - inventory_id = row[2] - price = row[19] - - if inventory_id not in inventories_price: - inventories_price[inventory_id] = [] - - inventories_price[inventory_id].append(price) - dumped_data["inventories_price"] = inventories_price - - return dumped_data["inventories_price"] - - -def load_dumped_data(name): - if not name in dumped_data: - filename = name + '.csv' - file = frappe.get_app_path("erpnext_biotrack", "fixtures/dump", filename) - if os.path.exists(file): - with open(file, "r") as csvfile: - fcontent = csvfile.read() - dumped_data[name] = read_csv_content(fcontent, False) - else: - raise ValidationError, "Dumped file {} does not exists".format(filename) - - return dumped_data[name]