diff --git a/bloomstack_core/__init__.py b/bloomstack_core/__init__.py index 1270adaa4..5d136a516 100644 --- a/bloomstack_core/__init__.py +++ b/bloomstack_core/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -__version__ = '2.0.13' +__version__ = '2.1.0' diff --git a/bloomstack_core/bloomtrace/utils.py b/bloomstack_core/bloomtrace/utils.py index a1656b448..0b3afe4d6 100644 --- a/bloomstack_core/bloomtrace/utils.py +++ b/bloomstack_core/bloomtrace/utils.py @@ -1,3 +1,7 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Bloomstack Inc. and contributors +# For license information, please see license.txt + import frappe from frappe.frappeclient import FrappeClient, AuthError @@ -20,14 +24,29 @@ def get_bloomtrace_client(): return client -def make_integration_request(doctype, docname): - if frappe.conf.enable_bloomtrace: - integration_request = frappe.new_doc("Integration Request") - integration_request.update({ - "integration_type": "Remote", - "integration_request_service": "BloomTrace", - "status": "Queued", - "reference_doctype": doctype, - "reference_docname": docname - }) - integration_request.save(ignore_permissions=True) +def make_integration_request(doctype, docname, endpoint): + settings = frappe.get_cached_doc("Compliance Settings") + if not (frappe.conf.enable_bloomtrace and settings.is_compliance_enabled) or \ + frappe.db.exists("Integration Request", {"reference_doctype": doctype, "reference_docname": docname, "endpoint": endpoint}): + return + + doc = frappe.get_doc(doctype, docname) + company = settings.get("company", {"company": doc.company}) and settings.get("company", {"company": doc.company})[0] + fieldname = "push_{0}".format(frappe.scrub(endpoint)) + + if not company or not company.get(fieldname): + return + + integration_request = frappe.get_doc({ + "doctype": "Integration Request", + "integration_type": "Remote", + "integration_request_service": "BloomTrace", + "status": "Queued", + "reference_doctype": doctype, + "reference_docname": docname, + "endpoint": endpoint + }).save(ignore_permissions=True) + + +def create_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name) diff --git a/bloomstack_core/compliance/item.py b/bloomstack_core/compliance/item.py deleted file mode 100644 index f1d0b51d5..000000000 --- a/bloomstack_core/compliance/item.py +++ /dev/null @@ -1,91 +0,0 @@ -import frappe -from bloomstack_core.bloomtrace import get_bloomtrace_client -from frappe import _ -from frappe.utils import cstr, get_host_name - - -def execute_bloomtrace_integration_request(): - frappe_client = get_bloomtrace_client() - if not frappe_client: - return - - pending_requests = frappe.get_all("Integration Request", filters={ - "status": ["IN", ["Queued", "Failed"]], - "reference_doctype": "Item", - "integration_request_service": "BloomTrace" - }, order_by="creation ASC", limit=50) - - for request in pending_requests: - integration_request = frappe.get_doc("Integration Request", request.name) - item = frappe.get_doc("Item", integration_request.reference_docname) - - try: - if not item.bloomtrace_id: - insert_compliance_item(item, frappe_client) - else: - update_compliance_item(item, frappe_client) - - integration_request.error = "" - integration_request.status = "Completed" - except Exception as e: - integration_request.error = cstr(e) - integration_request.status = "Failed" - - integration_request.save(ignore_permissions=True) - - -def insert_compliance_item(item, frappe_client): - bloomtrace_compliance_item_dict = make_compliance_item(item) - bloomtrace_compliance_item = frappe_client.insert(bloomtrace_compliance_item_dict) - bloomtrace_id = bloomtrace_compliance_item.get('name') - frappe.db.set_value("Item", item.name, "bloomtrace_id", bloomtrace_id) - - -def update_compliance_item(item, frappe_client): - bloomtrace_compliance_item_dict = make_compliance_item(item) - bloomtrace_compliance_item_dict.update({ - "name": item.bloomtrace_id - }) - frappe_client.update(bloomtrace_compliance_item_dict) - - -def make_compliance_item(item): - bloomtrace_compliance_item_dict = { - "doctype": "Compliance Item", - "bloomstack_site": get_host_name(), - "item_code": item.item_code, - "item_name": item.item_name, - "enable_metrc": item.enable_metrc, - "metrc_id": item.metrc_id, - "metrc_item_category": item.metrc_item_category, - "metrc_unit_value": item.metrc_unit_value, - "metrc_uom": item.metrc_uom, - "metrc_unit_uom": item.metrc_unit_uom - } - return bloomtrace_compliance_item_dict - - -# Search querries -def metrc_item_category_query(doctype, txt, searchfield, start, page_len, filters): - metrc_uom = filters.get("metrc_uom") - quantity_type = frappe.db.get_value("Compliance UOM", metrc_uom, "quantity_type") - - return frappe.get_all("Compliance Item Category", filters={"quantity_type": quantity_type}, as_list=1) - - -def metrc_uom_query(doctype, txt, searchfield, start, page_len, filters): - metrc_item_category = filters.get("metrc_item_category") - quantity_type = frappe.db.get_value("Compliance Item Category", metrc_item_category, "quantity_type") - - return frappe.get_all("Compliance UOM", filters={"quantity_type": quantity_type}, as_list=1) - - -def metrc_unit_uom_query(doctype, txt, searchfield, start, page_len, filters): - metrc_item_category = filters.get("metrc_item_category") - mandatory_unit = frappe.db.get_value("Compliance Item Category", metrc_item_category, "mandatory_unit") - - quantity_type = "VolumeBased" - if mandatory_unit == "Weight": - quantity_type = "WeightBased" - - return frappe.get_all("Compliance UOM", filters={"quantity_type": quantity_type}, as_list=1) diff --git a/bloomstack_core/compliance/package.py b/bloomstack_core/compliance/package.py deleted file mode 100644 index 9546eec36..000000000 --- a/bloomstack_core/compliance/package.py +++ /dev/null @@ -1,174 +0,0 @@ -import frappe -from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request -from bloomstack_core.compliance.utils import log_request -from frappe import _ -from frappe.utils import cstr - - -# From Stock Entry -def create_package_from_stock(stock_entry, method): - # TODO: Handle non-manufacture Stock Entries for intermediate packages - stock_entry_purpose = frappe.db.get_value("Stock Entry Type", stock_entry.stock_entry_type, "purpose") - if stock_entry_purpose not in ["Manufacture", "Repack"]: - return - - make_integration_request("Stock Entry", stock_entry.name) - - -def adjust_package_from_stock(stock_entry, method): - # TODO: Handle non-manufacture Stock Entries for intermediate packages - if stock_entry.stock_entry_type != "Manufacture": - return - - -def build_stock_payload(stock_entry): - """ - Create the request body for package doctype in bloomtrace from a Stock Entry. - - Args: - stock_entry (object): The `Stock Entry` Frappe object. - - Returns: - payload (list of dict): The `Stock Entry` payload, if an Item is moved / created, otherwise `None`. - """ - - payload = {} - package_ingredients = [] - - for item in stock_entry.items: - if not frappe.db.get_value("Item", item.item_code, "is_compliance_item"): - continue - - if item.s_warehouse: - package_ingredients.append({ - "package": item.package_tag, - "quantity": item.qty, - "unit_of_measure": item.uom, - }) - elif item.t_warehouse: - payload = { - "tag": item.package_tag, - "item": item.item_name, - "quantity": item.qty, - "unit_of_measure": item.uom, - "patient_license_number": "", - "actual_date": stock_entry.posting_date, - } - - if not payload: - return - - payload["doctype"] = "Package" - payload["ingredients"] = package_ingredients - payload["company"] = stock_entry.company - - return [payload] - - -def execute_bloomtrace_integration_request_for_stock_entry(): - frappe_client = get_bloomtrace_client() - if not frappe_client: - return - - pending_requests = frappe.get_all("Integration Request", filters={ - "status": ["IN", ["Queued", "Failed"]], - "reference_doctype": "Stock Entry", - "integration_request_service": "BloomTrace" - }, order_by="creation ASC", limit=50) - - for request in pending_requests: - integration_request = frappe.get_doc("Integration Request", request.name) - stock_entry = frappe.get_doc("Stock Entry", integration_request.reference_docname) - - try: - package = build_stock_payload(stock_entry) - frappe_client.insert(package) - - integration_request.error = "" - integration_request.status = "Completed" - except Exception as e: - integration_request.error = cstr(e) - integration_request.status = "Failed" - - integration_request.save(ignore_permissions=True) - - -def create_package_from_delivery(delivery_note, method): - if delivery_note.is_return: - return - - make_integration_request("Delivery Note", delivery_note.name) - - -def build_delivery_payload(delivery_note, item): - """ - Create the request body for package doctype in bloomtrace from a Delivery Note. - - Args: - delivery_note (object): The `Delivery Note` Frappe object. - item (object): The `Delivery Note Item` Frappe object. - - Returns: - payload (list of dict): The `Delivery Note` payload, if an Item is moved / created, otherwise `None`. - """ - - payload = {} - package_ingredients = [] - - if item.package_tag: - source_package_tag = frappe.db.get_value("Package Tag", item.package_tag, "source_package_tag") - if source_package_tag: - package_ingredients.append({ - "package": source_package_tag, - "quantity": item.qty, - "unit_of_measure": item.uom, - }) - elif item.warehouse: - payload = { - "tag": item.package_tag, - "item": item.item_name, - "quantity": item.qty, - "unit_of_measure": item.uom, - "patient_license_number": "", - "actual_date": delivery_note.lr_date or delivery_note.posting_date - } - - if not payload: - return - - payload["doctype"] = "Package" - payload["Ingredients"] = package_ingredients - payload["company"] = delivery_note.company - - return [payload] - -def execute_bloomtrace_integration_request_for_delivery_note(): - frappe_client = get_bloomtrace_client() - if not frappe_client: - return - - pending_requests = frappe.get_all("Integration Request", filters={ - "status": ["IN", ["Queued", "Failed"]], - "reference_doctype": "Delivery Note", - "integration_request_service": "BloomTrace" - }, order_by="creation ASC", limit=50) - - for request in pending_requests: - integration_request = frappe.get_doc("Integration Request", request.name) - delivery_note = frappe.get_doc("Delivery Note", integration_request.reference_docname) - - for item in delivery_note.items: - if not frappe.db.get_value("Item", item.item_code, "is_compliance_item") or not item.package_tag: - continue - - try: - package = build_delivery_payload(delivery_note, item) - frappe_client.insert(package) - - integration_request.error = "" - integration_request.status = "Completed" - except Exception as e: - integration_request.error = cstr(e) - integration_request.status = "Failed" - - integration_request.save(ignore_permissions=True) diff --git a/bloomstack_core/hook_events/compliance_settings.py b/bloomstack_core/hook_events/compliance_settings.py index 0737632e0..c35a4d083 100644 --- a/bloomstack_core/hook_events/compliance_settings.py +++ b/bloomstack_core/hook_events/compliance_settings.py @@ -1,3 +1,4 @@ +import frappe from bloomstack_core.bloomtrace import get_bloomtrace_client from frappe.utils import get_host_name @@ -11,16 +12,37 @@ def sync_bloomtrace(compliance_settings, method): return site_url = get_host_name() - frappe_client.update({ - "doctype": "Bloomstack Site", - "name": site_url - }) - for company in compliance_settings.company: + try: frappe_client.update({ - "doctype": "Bloomstack Company", - "name": company.company, - "metrc_push_data": company.push_data, - "metrc_pull_data": company.pull_data, - "pull_incoming_transfer": company.pull_incoming_transfer + "doctype": "Bloomstack Site", + "name": site_url, + "metrc_user_key": compliance_settings.get_password("metrc_user_key") }) + except Exception as e: + frappe.log_error(e) + + for company in compliance_settings.company: + try: + frappe_client.update({ + "doctype": "Bloomstack Company", + "name": company.company, + "push_item": company.push_item, + "pull_item": company.pull_item, + "push_package_tag": company.push_package_tag, + "pull_package_tag": company.pull_package_tag, + "pull_transfer": company.pull_transfer, + "push_transfer": company.push_transfer, + "pull_plant": company.pull_plant, + "push_plant": company.push_plant, + "pull_plant_batch": company.pull_plant_batch, + "push_plant_batch": company.push_plant_batch, + "pull_strain": company.pull_strain, + "push_strain": company.push_strain, + "pull_harvest": company.pull_harvest, + "push_harvest": company.push_harvest, + "pull_package": company.pull_package, + "push_package": company.push_package + }) + except Exception as e: + frappe.log_error(e) \ No newline at end of file diff --git a/bloomstack_core/hook_events/delivery_note.py b/bloomstack_core/hook_events/delivery_note.py index a3467576b..1c3b2b671 100644 --- a/bloomstack_core/hook_events/delivery_note.py +++ b/bloomstack_core/hook_events/delivery_note.py @@ -5,8 +5,11 @@ import frappe from frappe import _ from frappe.utils import cstr, get_host_name -from bloomstack_core.bloomtrace import get_bloomtrace_client +from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request +def create_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name, "Package") + make_integration_request(doc.doctype, doc.name, "Transfer") def link_invoice_against_delivery_note(delivery_note, method): for item in delivery_note.items: @@ -38,30 +41,57 @@ def execute_bloomtrace_integration_request(): for request in pending_requests: integration_request = frappe.get_doc("Integration Request", request.name) delivery_note = frappe.get_doc("Delivery Note", integration_request.reference_docname) + try: - insert_transfer_template(delivery_note, frappe_client) - integration_request.error = "" - integration_request.status = "Completed" + error, status = "", "Completed" + + if integration_request.endpoint == "Package": + if not delivery_note.is_return: + insert_delivery_payload(delivery_note, frappe_client) + else: + error, status = "Delivery Note is marked as return", "Failed" + + if integration_request.endpoint == "Transfer": + if delivery_note.lr_no or (delivery_note.estimated_arrival and delivery_note.departure_time): + # If delivery trip is created or estimated_arrival and departure_time is present, only then move forward to integrate with BloomTrace + insert_transfer_template(delivery_note, frappe_client) + else: + error, status = "Delivery Trip / Estimated Departure / Estimated Arrival is missing", "Failed" + + integration_request.error = error + integration_request.status = status integration_request.save(ignore_permissions=True) except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) def insert_transfer_template(delivery_note, frappe_client): - delivery_trip = frappe.get_doc("Delivery Trip", delivery_note.lr_no) - estimated_arrival = '' - for stop in delivery_trip.delivery_stops: - if stop.delivery_note == delivery_note.name: - estimated_arrival = stop.estimated_arrival + estimated_arrival = delivery_note.estimated_arrival + departure_time = delivery_note.departure_time + + if delivery_note.lr_no: + delivery_trip = frappe.get_doc("Delivery Trip", delivery_note.lr_no) + for stop in delivery_trip.delivery_stops: + if stop.delivery_note == delivery_note.name: + estimated_arrival = stop.estimated_arrival + + if not estimated_arrival: + try: + delivery_trip.process_route(False) + except Exception: + frappe.throw(_("Estimated Arrival Times are not present.")) + + if not departure_time: + departure_time = delivery_trip.departure_time transfer_template_packages = [] for item in delivery_note.items: if item.package_tag: transfer_template_packages.append({ "package_tag": item.package_tag, - "wholesale_price": item.rate + "wholesale_price": item.amount }) site_url = get_host_name() @@ -77,8 +107,50 @@ def insert_transfer_template(delivery_note, frappe_client): "vehicle_license_plate_number": delivery_note.vehicle_no, "driver_name": delivery_note.driver_name, "driver_license_number": frappe.db.get_value("Driver", delivery_note.driver, "license_number"), - "estimated_departure": delivery_trip.departure_time, + "estimated_departure": departure_time, "estimated_arrival": estimated_arrival, "packages": transfer_template_packages } frappe_client.insert(transfer_template) + +def insert_delivery_payload(delivery_note, frappe_client): + """ + Create the request body for package doctype in bloomtrace from a Delivery Note. + + Args: + delivery_note (object): The `Delivery Note` Frappe object. + + Returns: + payload (list of dict): The `Delivery Note` payload, if an Item is moved / created, otherwise `None` is reported to BloomTrace + """ + + for item in delivery_note.items: + payload = {} + package_ingredients = [] + + if item.package_tag: + source_package_tag = frappe.db.get_value("Package Tag", item.package_tag, "source_package_tag") + if source_package_tag: + package_ingredients.append({ + "package": source_package_tag, + "quantity": item.qty, + "unit_of_measure": item.uom, + }) + elif item.warehouse: + payload = { + "tag": item.package_tag, + "item": item.item_name, + "quantity": item.qty, + "unit_of_measure": item.uom, + "patient_license_number": "", + "actual_date": delivery_note.lr_date or delivery_note.posting_date + } + + if not payload: + return + + payload["doctype"] = "Package" + payload["Ingredients"] = package_ingredients + payload["bloomstack_company"] = delivery_note.company + + frappe_client.insert(payload) diff --git a/bloomstack_core/hook_events/delivery_trip.py b/bloomstack_core/hook_events/delivery_trip.py index 0a6e05d28..7ddfc6ede 100644 --- a/bloomstack_core/hook_events/delivery_trip.py +++ b/bloomstack_core/hook_events/delivery_trip.py @@ -65,7 +65,7 @@ def make_transfer_templates(delivery_trip, method): for item in frappe.get_doc("Delivery Note", stop.delivery_note).items: if frappe.db.get_value("Item", item.item_code, "is_compliance_item"): - make_integration_request("Delivery Note", stop.delivery_note) + make_integration_request("Delivery Note", stop.delivery_note, "Transfer") break diff --git a/bloomstack_core/hook_events/harvest.py b/bloomstack_core/hook_events/harvest.py index c8df40e02..33c8bac41 100644 --- a/bloomstack_core/hook_events/harvest.py +++ b/bloomstack_core/hook_events/harvest.py @@ -6,6 +6,8 @@ from frappe.utils import cstr from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request +def create_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name, "Harvest") def execute_bloomtrace_integration_request(): frappe_client = get_bloomtrace_client() @@ -30,7 +32,7 @@ def execute_bloomtrace_integration_request(): integration_request.status = "Completed" integration_request.save(ignore_permissions=True) except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) diff --git a/bloomstack_core/hook_events/item.py b/bloomstack_core/hook_events/item.py index 7741507f5..1a977b79a 100644 --- a/bloomstack_core/hook_events/item.py +++ b/bloomstack_core/hook_events/item.py @@ -1,12 +1,20 @@ -import json +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Bloomstack Inc. and contributors +# For license information, please see license.txt import frappe +import json from bloomstack_core.utils import get_abbr from erpnext import get_default_company from erpnext.accounts.utils import get_company_default -from frappe.utils import cstr +from frappe.utils import cstr, get_host_name +from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request +from frappe import _ +def create_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name, "Item") + @frappe.whitelist() def autoname_item(item): item = frappe._dict(json.loads(item)) @@ -54,3 +62,89 @@ def autoname(item, method=None): if not method: return item.item_code + +def execute_bloomtrace_integration_request(): + frappe_client = get_bloomtrace_client() + if not frappe_client: + return + + pending_requests = frappe.get_all("Integration Request", filters={ + "status": ["IN", ["Queued", "Failed"]], + "reference_doctype": "Item", + "integration_request_service": "BloomTrace" + }, order_by="creation ASC", limit=50) + + for request in pending_requests: + integration_request = frappe.get_doc("Integration Request", request.name) + item = frappe.get_doc("Item", integration_request.reference_docname) + + try: + if not item.bloomtrace_id: + insert_compliance_item(item, frappe_client) + else: + update_compliance_item(item, frappe_client) + + integration_request.error = "" + integration_request.status = "Completed" + except Exception as e: + integration_request.error = cstr(frappe.get_traceback()) + integration_request.status = "Failed" + + integration_request.save(ignore_permissions=True) + + +def insert_compliance_item(item, frappe_client): + bloomtrace_compliance_item_dict = make_compliance_item(item) + bloomtrace_compliance_item = frappe_client.insert(bloomtrace_compliance_item_dict) + bloomtrace_id = bloomtrace_compliance_item.get('name') + frappe.db.set_value("Item", item.name, "bloomtrace_id", bloomtrace_id) + + +def update_compliance_item(item, frappe_client): + bloomtrace_compliance_item_dict = make_compliance_item(item) + bloomtrace_compliance_item_dict.update({ + "name": item.bloomtrace_id + }) + frappe_client.update(bloomtrace_compliance_item_dict) + + +def make_compliance_item(item): + bloomtrace_compliance_item_dict = { + "doctype": "Compliance Item", + "bloomstack_site": get_host_name(), + "item_code": item.item_code, + "item_name": item.item_name, + "enable_metrc": item.enable_metrc, + "metrc_id": item.metrc_id, + "metrc_item_category": item.metrc_item_category, + "metrc_unit_value": item.metrc_unit_value, + "metrc_uom": item.metrc_uom, + "metrc_unit_uom": item.metrc_unit_uom + } + return bloomtrace_compliance_item_dict + + +# Search querries +def metrc_item_category_query(doctype, txt, searchfield, start, page_len, filters): + metrc_uom = filters.get("metrc_uom") + quantity_type = frappe.db.get_value("Compliance UOM", metrc_uom, "quantity_type") + + return frappe.get_all("Compliance Item Category", filters={"quantity_type": quantity_type}, as_list=1) + + +def metrc_uom_query(doctype, txt, searchfield, start, page_len, filters): + metrc_item_category = filters.get("metrc_item_category") + quantity_type = frappe.db.get_value("Compliance Item Category", metrc_item_category, "quantity_type") + + return frappe.get_all("Compliance UOM", filters={"quantity_type": quantity_type}, as_list=1) + + +def metrc_unit_uom_query(doctype, txt, searchfield, start, page_len, filters): + metrc_item_category = filters.get("metrc_item_category") + mandatory_unit = frappe.db.get_value("Compliance Item Category", metrc_item_category, "mandatory_unit") + + quantity_type = "VolumeBased" + if mandatory_unit == "Weight": + quantity_type = "WeightBased" + + return frappe.get_all("Compliance UOM", filters={"quantity_type": quantity_type}, as_list=1) diff --git a/bloomstack_core/hook_events/package_tag.py b/bloomstack_core/hook_events/package_tag.py index fec81a061..4db4277c4 100644 --- a/bloomstack_core/hook_events/package_tag.py +++ b/bloomstack_core/hook_events/package_tag.py @@ -3,9 +3,12 @@ # For license information, please see license.txt import frappe -from bloomstack_core.bloomtrace import get_bloomtrace_client +from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request from frappe.utils import cstr, get_host_name +def insert_bloomtrace_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name, "Package Tag") + def execute_bloomtrace_integration_request(): frappe_client = get_bloomtrace_client() if not frappe_client: @@ -20,6 +23,7 @@ def execute_bloomtrace_integration_request(): integration_request = frappe.get_doc("Integration Request", request.name) package_tag = frappe.get_doc("Package Tag", integration_request.reference_docname) bloomtrace_package_tag = frappe_client.get_doc("Package Tag", integration_request.reference_docname) + try: if not bloomtrace_package_tag: insert_package_tag(package_tag, frappe_client) @@ -29,7 +33,7 @@ def execute_bloomtrace_integration_request(): integration_request.status = "Completed" integration_request.save(ignore_permissions=True) except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) @@ -56,7 +60,7 @@ def make_package_tag(package_tag): bloomtrace_package_tag = { "doctype": "Package Tag", "bloomstack_site": site_url, - "bloomstack_company": package_tag.bloomstack_company, + "bloomstack_company": package_tag.company, "item": item, "uid_number": package_tag.name, "batch_number": package_tag.batch_no, diff --git a/bloomstack_core/hook_events/plant.py b/bloomstack_core/hook_events/plant.py index 6ae551920..d7636b9d0 100644 --- a/bloomstack_core/hook_events/plant.py +++ b/bloomstack_core/hook_events/plant.py @@ -5,8 +5,10 @@ from urllib.parse import urlparse import frappe from frappe.utils import cstr, get_url -from bloomstack_core.bloomtrace import get_bloomtrace_client +from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request +def create_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name, "Plant") def execute_bloomtrace_integration_request(): frappe_client = get_bloomtrace_client() @@ -31,7 +33,7 @@ def execute_bloomtrace_integration_request(): integration_request.status = "Completed" integration_request.save(ignore_permissions=True) except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) diff --git a/bloomstack_core/hook_events/plant_additive_log.py b/bloomstack_core/hook_events/plant_additive_log.py index 836129400..261fcde07 100644 --- a/bloomstack_core/hook_events/plant_additive_log.py +++ b/bloomstack_core/hook_events/plant_additive_log.py @@ -4,8 +4,10 @@ import frappe from frappe.utils import cstr, get_url -from bloomstack_core.bloomtrace import get_bloomtrace_client +from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request +def create_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name, "Plant Additive Log") def execute_bloomtrace_integration_request(): frappe_client = get_bloomtrace_client() @@ -30,7 +32,7 @@ def execute_bloomtrace_integration_request(): integration_request.status = "Completed" integration_request.save(ignore_permissions=True) except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) diff --git a/bloomstack_core/hook_events/plant_batch.py b/bloomstack_core/hook_events/plant_batch.py index 21e3dbe06..1237d899e 100644 --- a/bloomstack_core/hook_events/plant_batch.py +++ b/bloomstack_core/hook_events/plant_batch.py @@ -5,8 +5,10 @@ from urllib.parse import urlparse import frappe from frappe.utils import cstr, get_url -from bloomstack_core.bloomtrace import get_bloomtrace_client +from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request +def create_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name, "Plant Batch") def execute_bloomtrace_integration_request(): frappe_client = get_bloomtrace_client() @@ -31,7 +33,7 @@ def execute_bloomtrace_integration_request(): integration_request.status = "Completed" integration_request.save(ignore_permissions=True) except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) diff --git a/bloomstack_core/hook_events/stock_entry.py b/bloomstack_core/hook_events/stock_entry.py index 22d2dfe44..10d9603d6 100644 --- a/bloomstack_core/hook_events/stock_entry.py +++ b/bloomstack_core/hook_events/stock_entry.py @@ -1,5 +1,6 @@ import frappe - +from bloomstack_core.bloomtrace import make_integration_request, get_bloomtrace_client +from frappe.utils import cstr def add_comment_to_batch(stock_entry, method): for item in stock_entry.items: @@ -17,3 +18,79 @@ def add_comment_to_batch(stock_entry, method): comment.save() frappe.db.commit() + +def create_package_from_stock(stock_entry, method): + # TODO: Handle non-manufacture Stock Entries for intermediate packages + stock_entry_purpose = frappe.db.get_value("Stock Entry Type", stock_entry.stock_entry_type, "purpose") + if stock_entry_purpose not in ["Manufacture", "Repack"]: + return + + make_integration_request("Stock Entry", stock_entry.name, "Package") + +def execute_bloomtrace_integration_request(): + frappe_client = get_bloomtrace_client() + if not frappe_client: + return + + pending_requests = frappe.get_all("Integration Request", filters={ + "status": ["IN", ["Queued", "Failed"]], + "reference_doctype": "Stock Entry", + "integration_request_service": "BloomTrace" + }, order_by="creation ASC", limit=50) + + for request in pending_requests: + integration_request = frappe.get_doc("Integration Request", request.name) + stock_entry = frappe.get_doc("Stock Entry", integration_request.reference_docname) + + try: + package = build_stock_payload(stock_entry) + frappe_client.insert(package) + + integration_request.error = "" + integration_request.status = "Completed" + except Exception as e: + integration_request.error = cstr(frappe.get_traceback()) + integration_request.status = "Failed" + + integration_request.save(ignore_permissions=True) + +def build_stock_payload(stock_entry): + """ + Create the request body for package doctype in bloomtrace from a Stock Entry. + Args: + stock_entry (object): The `Stock Entry` Frappe object. + Returns: + payload (list of dict): The `Stock Entry` payload, if an Item is moved / created, otherwise `None`. + """ + + payload = {} + package_ingredients = [] + + for item in stock_entry.items: + if not frappe.db.get_value("Item", item.item_code, "is_compliance_item"): + continue + + if item.s_warehouse: + package_ingredients.append({ + "package": item.package_tag, + "quantity": item.qty, + "unit_of_measure": item.uom, + }) + elif item.t_warehouse: + payload = { + "tag": item.package_tag, + "item": item.item_name, + "quantity": item.qty, + "unit_of_measure": item.uom, + "patient_license_number": "", + "actual_date": stock_entry.posting_date, + } + + if not payload: + return + + payload["doctype"] = "Package" + payload["ingredients"] = package_ingredients + payload["bloomstack_company"] = stock_entry.company + + return payload diff --git a/bloomstack_core/hook_events/stock_reconciliation.py b/bloomstack_core/hook_events/stock_reconciliation.py index 6df60deed..b8befe428 100644 --- a/bloomstack_core/hook_events/stock_reconciliation.py +++ b/bloomstack_core/hook_events/stock_reconciliation.py @@ -4,22 +4,15 @@ import frappe from frappe.utils import cstr, today -from bloomstack_core.bloomtrace import get_bloomtrace_client +from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request def create_integration_request(doc, method): - if doc.items[0].package_tag: - integration_request = frappe.new_doc("Integration Request") - integration_request.update({ - "integration_type": "Remote", - "integration_request_service": "BloomTrace", - "method": "POST", - "status": "Queued", - "endpoint": "adjust", - "reference_doctype": "Stock Reconciliation", - "reference_docname": doc.name - }) - integration_request.save(ignore_permissions=True) + for item in doc.items: + if item.package_tag: + # UID Transaction Log is used to make changes in Package using adjust endpoint + make_integration_request("Stock Reconciliation", doc.name, "Package") + break def execute_bloomtrace_integration_request(): frappe_client = get_bloomtrace_client() @@ -44,7 +37,7 @@ def execute_bloomtrace_integration_request(): integration_request.status = "Completed" integration_request.save(ignore_permissions=True) except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) diff --git a/bloomstack_core/hook_events/strain.py b/bloomstack_core/hook_events/strain.py index afed988a7..e413a5de2 100644 --- a/bloomstack_core/hook_events/strain.py +++ b/bloomstack_core/hook_events/strain.py @@ -5,8 +5,10 @@ from urllib.parse import urlparse import frappe from frappe.utils import cstr, get_url -from bloomstack_core.bloomtrace import get_bloomtrace_client +from bloomstack_core.bloomtrace import get_bloomtrace_client, make_integration_request +def create_integration_request(doc, method): + make_integration_request(doc.doctype, doc.name, "Strain") def execute_bloomtrace_integration_request(): frappe_client = get_bloomtrace_client() @@ -31,7 +33,7 @@ def execute_bloomtrace_integration_request(): integration_request.status = "Completed" integration_request.save(ignore_permissions=True) except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) diff --git a/bloomstack_core/hook_events/user.py b/bloomstack_core/hook_events/user.py index 1db1b9bbc..1291fb1e2 100644 --- a/bloomstack_core/hook_events/user.py +++ b/bloomstack_core/hook_events/user.py @@ -39,7 +39,7 @@ def execute_bloomtrace_integration_request(): integration_request.error = "" integration_request.status = "Completed" except Exception as e: - integration_request.error = cstr(e) + integration_request.error = cstr(frappe.get_traceback()) integration_request.status = "Failed" integration_request.save(ignore_permissions=True) diff --git a/bloomstack_core/hook_events/utils.py b/bloomstack_core/hook_events/utils.py index 9b9f0d5f0..a2f2de950 100644 --- a/bloomstack_core/hook_events/utils.py +++ b/bloomstack_core/hook_events/utils.py @@ -85,11 +85,3 @@ def validate_delivery_window(doc, method): )) frappe.sendmail(recipients=recipients, subject=subject, message=message) - - -def create_integration_request(doc, method): - companies = frappe.cache().hget("compliance", "companies") or [] - if not doc.company in companies: - return - - make_integration_request(doc.doctype, doc.name) diff --git a/bloomstack_core/hooks.py b/bloomstack_core/hooks.py index 7d1beb41e..4fb1b3cba 100755 --- a/bloomstack_core/hooks.py +++ b/bloomstack_core/hooks.py @@ -183,21 +183,20 @@ "Delivery Note": { "validate": "bloomstack_core.hook_events.delivery_note.link_invoice_against_delivery_note", "before_submit": [ - "bloomstack_core.hook_events.delivery_note.link_invoice_against_delivery_note", - "bloomstack_core.compliance.package.create_package_from_delivery" + "bloomstack_core.hook_events.delivery_note.link_invoice_against_delivery_note" ], + "on_submit": "bloomstack_core.hook_events.delivery_note.create_integration_request", "on_update_after_submit": "bloomstack_core.hook_events.delivery_note.link_invoice_against_delivery_note" }, "Package Tag": { - "validate": "bloomstack_core.hook_events.utils.create_integration_request", - "after_insert": "bloomstack_core.hook_events.utils.create_integration_request" + "on_update": "bloomstack_core.hook_events.package_tag.insert_bloomtrace_integration_request" }, "Sales Order": { "validate": "bloomstack_core.hook_events.sales_order.validate_batch_item", "on_update_after_submit": "bloomstack_core.hook_events.sales_order.check_overdue_status" }, "Stock Entry": { - "on_submit": "bloomstack_core.compliance.package.create_package_from_stock" + "on_submit": "bloomstack_core.hook_events.stock_entry.create_package_from_stock" }, "Delivery Trip": { "validate": [ @@ -214,7 +213,7 @@ "validate": "bloomstack_core.hook_events.employee.update_driver_employee" }, "Item": { - "on_update": "bloomstack_core.hook_events.utils.create_integration_request" + "on_update": "bloomstack_core.hook_events.item.create_integration_request" }, "Packing Slip": { "on_submit": "bloomstack_core.hook_events.packing_slip.create_stock_entry" @@ -234,20 +233,20 @@ "validate": "bloomstack_core.hook_events.production_plan.set_workstations" }, "Plant Batch": { - "on_update": "bloomstack_core.hook_events.utils.create_integration_request" + "on_update": "bloomstack_core.hook_events.plant_batch.create_integration_request" }, "Plant": { - "on_update": "bloomstack_core.hook_events.utils.create_integration_request" + "on_update": "bloomstack_core.hook_events.plant.create_integration_request" }, "Strain": { - "on_update": "bloomstack_core.hook_events.utils.create_integration_request" + "on_update": "bloomstack_core.hook_events.strain.create_integration_request" }, "Harvest": { - "on_submit": "bloomstack_core.hook_events.utils.create_integration_request", - "on_update_after_submit": "bloomstack_core.hook_events.utils.create_integration_request" + "on_submit": "bloomstack_core.hook_events.harvest.create_integration_request", + "on_update_after_submit": "bloomstack_core.hook_events.harvest.create_integration_request" }, "Plant Additive Log": { - "on_update": "bloomstack_core.hook_events.utils.create_integration_request" + "on_update": "bloomstack_core.hook_events.plant_additive_log.create_integration_request" }, "Stock Reconciliation": { "on_submit": "bloomstack_core.hook_events.stock_reconciliation.create_integration_request" @@ -259,16 +258,16 @@ scheduler_events = { "all": [ - "bloomstack_core.compliance.item.execute_bloomtrace_integration_request", + "bloomstack_core.hook_events.item.execute_bloomtrace_integration_request", "bloomstack_core.hook_events.package_tag.execute_bloomtrace_integration_request", "bloomstack_core.hook_events.delivery_note.execute_bloomtrace_integration_request", "bloomstack_core.hook_events.plant_batch.execute_bloomtrace_integration_request", "bloomstack_core.hook_events.plant.execute_bloomtrace_integration_request", "bloomstack_core.hook_events.strain.execute_bloomtrace_integration_request", - "bloomstack_core.compliance.package.execute_bloomtrace_integration_request_for_stock_entry", - "bloomstack_core.compliance.package.execute_bloomtrace_integration_request_for_delivery_note", + "bloomstack_core.hook_events.stock_entry.execute_bloomtrace_integration_request", "bloomstack_core.hook_events.plant_additive_log.execute_bloomtrace_integration_request", - "bloomstack_core.hook_events.harvest.execute_bloomtrace_integration_request" + "bloomstack_core.hook_events.harvest.execute_bloomtrace_integration_request", + "bloomstack_core.hook_events.stock_entry.execute_bloomtrace_integration_request" ], "hourly": [ "bloomstack_core.hook_events.user.execute_bloomtrace_integration_request" diff --git a/bloomstack_core/public/js/item.js b/bloomstack_core/public/js/item.js index be8112307..342f9f09f 100644 --- a/bloomstack_core/public/js/item.js +++ b/bloomstack_core/public/js/item.js @@ -5,7 +5,7 @@ frappe.ui.form.on('Item', { frm.set_query("metrc_item_category", () => { if (frm.doc.metrc_uom) { return { - query: "bloomstack_core.compliance.item.metrc_item_category_query", + query: "bloomstack_core.hook_events.item.metrc_item_category_query", filters: { metrc_uom: frm.doc.metrc_uom } @@ -16,7 +16,7 @@ frappe.ui.form.on('Item', { frm.set_query("metrc_uom", () => { if (frm.doc.metrc_item_category) { return { - query: "bloomstack_core.compliance.item.metrc_uom_query", + query: "bloomstack_core.hook_events.item.metrc_uom_query", filters: { metrc_item_category: frm.doc.metrc_item_category } @@ -27,7 +27,7 @@ frappe.ui.form.on('Item', { frm.set_query("metrc_unit_uom", () => { if (frm.doc.metrc_item_category) { return { - query: "bloomstack_core.compliance.item.metrc_unit_uom_query", + query: "bloomstack_core.hook_events.item.metrc_unit_uom_query", filters: { metrc_item_category: frm.doc.metrc_item_category }