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:
+
+ Request api credentials at
+
+ https://www.biotrack.com
+
+
+
+ Setup credentials on BioTrack Settings doctype.
+ Click on
+ BioTrack Settings
+ top right corner
+
+
+
+ After saving settings,
+
+
+
+
+ Enable
+
+ BioTrack Integration Service and Save a document.
+
+
+
+ To view api call logs,
+ Show Log
+
+
+
+ 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.
+
+
+
\ 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]