Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: (Uni-commerce) generate Delivery Note and sync item fields #239

Merged
merged 1 commit into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ecommerce_integrations/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@
"*/5 * * * *": [
"ecommerce_integrations.unicommerce.order.sync_new_orders",
"ecommerce_integrations.unicommerce.inventory.update_inventory_on_unicommerce",
"ecommerce_integrations.unicommerce.delivery_note.prepare_delivery_note",
],
},
}
Expand Down
1 change: 1 addition & 0 deletions ecommerce_integrations/unicommerce/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
SHIPPING_PACKAGE_STATUS_FIELD = "unicommerce_shipping_package_status"
IS_COD_CHECKBOX = "unicommerce_is_cod"
SHIPPING_METHOD_FIELD = "unicommerce_shipping_method"
UNICOMMERCE_SHIPPING_ID = "unicommerce_shipment_id"
PICKLIST_ORDER_DETAILS_FIELD = "order_details"

GRN_STOCK_ENTRY_TYPE = "GRN on Unicommerce"
Expand Down
99 changes: 99 additions & 0 deletions ecommerce_integrations/unicommerce/delivery_note.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import frappe

from ecommerce_integrations.unicommerce.api_client import UnicommerceAPIClient
from ecommerce_integrations.unicommerce.constants import ORDER_CODE_FIELD, SETTINGS_DOCTYPE
from ecommerce_integrations.unicommerce.utils import create_unicommerce_log


@frappe.whitelist()
def prepare_delivery_note():
try:
settings = frappe.get_cached_doc(SETTINGS_DOCTYPE)
if not settings.delivery_note:
return

client = UnicommerceAPIClient()

days_to_sync = min(settings.get("order_status_days") or 2, 14)
minutes = days_to_sync * 24 * 60

# find all Facilities
enabled_facilities = list(settings.get_integration_to_erpnext_wh_mapping().keys())
enabled_channels = frappe.db.get_list(
"Unicommerce Channel", filters={"enabled": 1}, pluck="channel_id"
)

for facility in enabled_facilities:
updated_packages = client.search_shipping_packages(
updated_since=minutes, facility_code=facility
)
valid_packages = [p for p in updated_packages if p.get("channel") in enabled_channels]
if not valid_packages:
continue
shipped_packages = [p for p in valid_packages if p["status"] in ["DISPATCHED"]]
for order in shipped_packages:
if not frappe.db.exists(
"Delivery Note", {"unicommerce_shipment_id": order["code"]}, "name"
) and frappe.db.exists("Sales Order", {ORDER_CODE_FIELD: order["saleOrderCode"]}):
sales_order = frappe.get_doc("Sales Order", {ORDER_CODE_FIELD: order["saleOrderCode"]})
if frappe.db.exists(
"Sales Invoice", {"unicommerce_order_code": sales_order.unicommerce_order_code}
):
sales_invoice = frappe.get_doc(
"Sales Invoice", {"unicommerce_order_code": sales_order.unicommerce_order_code}
)
create_delivery_note(sales_order, sales_invoice)
except Exception as e:
create_unicommerce_log(status="Error", exception=e, rollback=True)


def create_delivery_note(so, sales_invoice):
try:
# Create the delivery note
from frappe.model.mapper import make_mapped_doc

res = make_mapped_doc(
method="erpnext.selling.doctype.sales_order.sales_order.make_delivery_note", source_name=so.name
)
res.update({"items": []})
for item in sales_invoice.items:
res.append(
"items",
{
"item_code": item.item_code,
"item_name": item.item_name,
"description": item.description,
"qty": item.qty,
"uom": item.uom,
"rate": item.rate,
"amount": item.amount,
"warehouse": item.warehouse,
"against_sales_order": item.sales_order,
"batch_no": item.batch_no,
"so_detail": item.so_detail,
},
)
for item in sales_invoice.taxes:
res.append(
"taxes",
{
"charge_type": item.charge_type,
"account_head": item.account_head,
"tax_amount": item.tax_amount,
"description": item.description,
"item_wise_tax_detail": item.item_wise_tax_detail,
"dont_recompute_tax": item.dont_recompute_tax,
},
)
res.unicommerce_order_code = sales_invoice.unicommerce_order_code
res.unicommerce_shipment_id = sales_invoice.unicommerce_shipping_package_code
res.save()
res.submit()
log = create_unicommerce_log(method="create_delevery_note", make_new=True)
frappe.flags.request_id = log.name
except Exception as e:
create_unicommerce_log(status="Error", exception=e, rollback=True)
else:
create_unicommerce_log(status="Success")
frappe.flags.request_id = None
return res
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
"sales_order_series",
"sales_invoice_series",
"order_status_days",
"delivery_note_settings_section",
"delivery_note",
"inventory_sync_settings_section",
"enable_inventory_sync",
"inventory_sync_frequency",
Expand Down Expand Up @@ -249,12 +251,23 @@
"fieldname": "vendor_code",
"fieldtype": "Data",
"label": "Vendor Code"
},
{
"fieldname": "delivery_note_settings_section",
"fieldtype": "Section Break",
"label": "Delivery Note Settings"
},
{
"default": "0",
"fieldname": "delivery_note",
"fieldtype": "Check",
"label": "Import Delivery Notes from Unicommerce on Shipment"
}
],
"index_web_pages_for_search": 1,
"issingle": 1,
"links": [],
"modified": "2022-02-07 14:11:34.599710",
"modified": "2023-05-02 14:04:26.684256",
"modified_by": "Administrator",
"module": "unicommerce",
"name": "Unicommerce Settings",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
SHIPPING_PACKAGE_STATUS_FIELD,
SHIPPING_PROVIDER_CODE,
TRACKING_CODE_FIELD,
UNICOMMERCE_SHIPPING_ID,
)
from ecommerce_integrations.unicommerce.utils import create_unicommerce_log

Expand Down Expand Up @@ -202,6 +203,15 @@ def setup_custom_fields(update=True):
collapsible=1,
),
],
"Delivery Note": [
dict(
fieldname="unicommerce_section",
label="Unicommerce Details",
fieldtype="Section Break",
insert_after="instructions",
collapsible=1,
),
],
}

custom_fields = {
Expand Down Expand Up @@ -429,6 +439,22 @@ def setup_custom_fields(update=True):
read_only=1,
),
],
"Delivery Note": [
dict(
fieldname=ORDER_CODE_FIELD,
label="Unicommerce Order No",
fieldtype="Data",
insert_after="unicommerce_section",
read_only=1,
),
dict(
fieldname=UNICOMMERCE_SHIPPING_ID,
label="Unicommerce Shipment Id",
fieldtype="Data",
insert_after=ORDER_CODE_FIELD,
read_only=1,
),
],
"Pick List": [
dict(
fieldname=PICKLIST_ORDER_DETAILS_FIELD,
Expand Down
2 changes: 1 addition & 1 deletion ecommerce_integrations/unicommerce/invoice.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ def create_sales_invoice(
si.naming_series = channel_config.sales_invoice_series or settings.sales_invoice_series
si.delivery_date = so.delivery_date
si.ignore_pricing_rule = 1
si.update_stock = update_stock
si.update_stock = False if settings.delivery_note else update_stock
si.flags.raw_data = si_data
si.insert()

Expand Down
29 changes: 18 additions & 11 deletions ecommerce_integrations/unicommerce/order.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
CHANNEL_ID_FIELD,
CHANNEL_TAX_ACCOUNT_FIELD_MAP,
FACILITY_CODE_FIELD,
INVOICE_CODE_FIELD,
IS_COD_CHECKBOX,
MODULE_NAME,
ORDER_CODE_FIELD,
Expand Down Expand Up @@ -78,9 +79,8 @@ def _get_new_orders(
for order in uni_orders:
if order["channel"] not in configured_channels:
continue
if frappe.db.exists("Sales Order", {ORDER_CODE_FIELD: order["code"]}):
continue

# In case a sales invoice is not generated for some reason and is skipped, we need to create it manually. Therefore, I have commented out this line of code.
order = client.get_sales_order(order_code=order["code"])
if order:
yield order
Expand All @@ -95,12 +95,19 @@ def _create_sales_invoices(unicommerce_order, sales_order, client: UnicommerceAP
shipping_packages = unicommerce_order["shippingPackages"]
for package in shipping_packages:
try:
log = create_unicommerce_log(method="create_sales_invoice", make_new=True)
frappe.flags.request_id = log.name

# This code was added because the log statement below was being executed every time.
invoice_data = client.get_sales_invoice(
shipping_package_code=package["code"], facility_code=facility_code
)
existing_si = frappe.db.get_value(
"Sales Invoice", {INVOICE_CODE_FIELD: invoice_data["invoice"]["code"]}
)
if existing_si:
continue

log = create_unicommerce_log(method="create_sales_invoice", make_new=True)
frappe.flags.request_id = log.name

warehouse_allocations = _get_warehouse_allocations(sales_order)
create_sales_invoice(
invoice_data["invoice"],
Expand All @@ -121,18 +128,18 @@ def create_order(payload: UnicommerceOrder, request_id: Optional[str] = None, cl

order = payload

existing_so = frappe.db.get_value("Sales Order", {ORDER_CODE_FIELD: order["code"]})
if existing_so:
so = frappe.get_doc("Sales Order", existing_so)
return so

# If a sales order already exists, then every time it's executed
if request_id is None:
log = create_unicommerce_log(
method="ecommerce_integrations.unicommerce.order.create_order", request_data=payload
)
request_id = log.name

existing_so = frappe.db.get_value("Sales Order", {ORDER_CODE_FIELD: order["code"]})
if existing_so:
so = frappe.get_doc("Sales Order", existing_so)
create_unicommerce_log(status="Invalid", message="Sales Order already exists, skipped")
return so

if client is None:
client = UnicommerceAPIClient()

Expand Down
5 changes: 5 additions & 0 deletions ecommerce_integrations/unicommerce/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"width": ITEM_WIDTH_FIELD,
"height": ITEM_HEIGHT_FIELD,
"batchGroupCode": ITEM_BATCH_GROUP_FIELD,
"maxRetailPrice": "standard_rate",
"costPrice": "valuation_rate",
}

ERPNEXT_TO_UNI_ITEM_MAPPING = {v: k for k, v in UNI_TO_ERPNEXT_ITEM_MAPPING.items()}
Expand Down Expand Up @@ -287,6 +289,9 @@ def _build_unicommerce_item(item_code: ItemCode) -> JsonDict:
)
# append site prefix to image url
item_json["imageUrl"] = get_url(item.image)
item_json["maxRetailPrice"] = item.standard_rate
item_json["description"] = frappe.utils.strip_html_tags(item.description)
item_json["costPrice"] = item.valuation_rate

return item_json

Expand Down
65 changes: 65 additions & 0 deletions ecommerce_integrations/unicommerce/tests/test_delivery_note.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import base64
import unittest

import frappe
import responses
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry

from ecommerce_integrations.unicommerce.constants import (
FACILITY_CODE_FIELD,
INVOICE_CODE_FIELD,
ORDER_CODE_FIELD,
SHIPPING_PACKAGE_CODE_FIELD,
)
from ecommerce_integrations.unicommerce.delivery_note import create_delivery_note
from ecommerce_integrations.unicommerce.invoice import bulk_generate_invoices, create_sales_invoice
from ecommerce_integrations.unicommerce.order import create_order
from ecommerce_integrations.unicommerce.tests.test_client import TestCaseApiClient


class TestDeliveryNote(TestCaseApiClient):
@classmethod
def setUpClass(cls):
super().setUpClass()

def test_create_invoice_and_delivery_note(self):
"""Use mocked invoice json to create and assert synced fields"""
from ecommerce_integrations.unicommerce import invoice

# HACK to allow invoicing test
invoice.INVOICED_STATE.append("CREATED")
self.responses.add(
responses.POST,
"https://demostaging.unicommerce.com/services/rest/v1/oms/shippingPackage/createInvoiceAndAllocateShippingProvider",
status=200,
json=self.load_fixture("create_invoice_and_assign_shipper"),
match=[responses.json_params_matcher({"shippingPackageCode": "TEST00949"})],
)
self.responses.add(
responses.POST,
"https://demostaging.unicommerce.com/services/rest/v1/invoice/details/get",
status=200,
json=self.load_fixture("invoice-SDU0026"),
match=[responses.json_params_matcher({"shippingPackageCode": "TEST00949", "return": False})],
)
self.responses.add(
responses.GET,
"https://example.com",
status=200,
body=base64.b64decode(self.load_fixture("invoice_label_response")["label"]),
)

order = self.load_fixture("order-SO5906")["saleOrderDTO"]
so = create_order(order, client=self.client)
make_stock_entry(item_code="MC-100", qty=15, to_warehouse="Stores - WP", rate=42)

bulk_generate_invoices(sales_orders=[so.name], client=self.client)

sales_invoice_code = frappe.db.get_value("Sales Invoice", {INVOICE_CODE_FIELD: "SDU0026"})

if not sales_invoice_code:
self.fail("Sales invoice not generated")

si = frappe.get_doc("Sales Invoice", sales_invoice_code)
dn = create_delivery_note(so, si)
self.assertEqual(dn.unicommerce_order_code, so.unicommerce_order_code)