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(unicommerce): Sales Invoice creation through picklist #240

Merged
merged 16 commits into from
May 15, 2023
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"actions": [],
"creation": "2023-04-19 11:57:15.149202",
"default_view": "List",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"sales_order",
"sales_invoice",
"posting_date",
"pick_status",
"invoice_url",
"invoice_pdf"
],
"fields": [
{
"fieldname": "sales_order",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Sales Order",
"options": "Sales Order"
},
{
"fieldname": "sales_invoice",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Sales Invoice",
"options": "Sales Invoice"
},
{
"fetch_from": "sales_invoice.posting_date",
"fieldname": "posting_date",
"fieldtype": "Date",
"label": "Posting Date"
},
{
"fieldname": "pick_status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Pick Status",
"options": "\nPartially Picked\nFully Picked"
},
{
"fieldname": "invoice_url",
"fieldtype": "Data",
"hidden": 1,
"label": "Invoice URL"
},
{
"fieldname": "invoice_pdf",
"fieldtype": "Attach",
"in_list_view": 1,
"label": "Invoice Pdf"
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2023-04-20 10:58:07.144994",
"modified_by": "Administrator",
"module": "Ecommerce Integrations",
"name": "Pick List Sales Order Details",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2023, Frappe and contributors
# For license information, please see license.txt

# import frappe
from frappe.model.document import Document


class PickListSalesOrderDetails(Document):
pass
55 changes: 55 additions & 0 deletions ecommerce_integrations/fixtures/custom_field.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[
{
"allow_in_quick_entry": 0,
ankush marked this conversation as resolved.
Show resolved Hide resolved
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"collapsible_depends_on": null,
"columns": 0,
"default": null,
"depends_on": null,
"description": null,
"docstatus": 0,
"doctype": "Custom Field",
"dt": "Pick List",
"fetch_from": null,
"fetch_if_empty": 0,
"fieldname": "order_details",
"fieldtype": "Table",
"hidden": 0,
"hide_border": 0,
"hide_days": 0,
"hide_seconds": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_preview": 0,
"in_standard_filter": 0,
"insert_after": "prompt_qty",
"is_system_generated": 0,
"is_virtual": 0,
"label": "Order Details",
"length": 0,
"mandatory_depends_on": null,
"modified": "2023-04-19 12:01:29.008340",
"module": null,
"name": "Pick List-order_details",
"no_copy": 0,
"non_negative": 0,
"options": "Pick List Sales Order Details",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"print_width": null,
"read_only": 0,
"read_only_depends_on": null,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"translatable": 0,
"unique": 0,
"width": null
}
]
6 changes: 6 additions & 0 deletions ecommerce_integrations/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"Sales Invoice": "public/js/unicommerce/sales_invoice.js",
"Item": "public/js/unicommerce/item.js",
"Stock Entry": "public/js/unicommerce/stock_entry.js",
"Pick List": "public/js/unicommerce/pick_list.js",
}
# doctype_list_js = {"doctype" : "public/js/doctype_list.js"}
# doctype_tree_js = {"doctype" : "public/js/doctype_tree.js"}
Expand Down Expand Up @@ -114,6 +115,11 @@
"on_cancel": "ecommerce_integrations.unicommerce.grn.prevent_grn_cancel",
},
"Item Price": {"on_change": "ecommerce_integrations.utils.price_list.discard_item_prices"},
"Pick List": {"validate": "ecommerce_integrations.unicommerce.pick_list.validate"},
"Sales Invoice": {
"on_submit": "ecommerce_integrations.unicommerce.invoice.on_submit",
"on_cancel": "ecommerce_integrations.unicommerce.invoice.on_cancel",
},
}

# Scheduled Tasks
Expand Down
53 changes: 53 additions & 0 deletions ecommerce_integrations/public/js/unicommerce/pick_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
frappe.ui.form.on('Pick List', {
refresh(frm){
if (frm.doc.order_details){
cur_frm.add_custom_button(__('Generate Invoice'), () => frm.trigger('generate_invoice'))
}
},
generate_invoice(frm){
let selected_so = []
var tbl = cur_frm.doc.order_details || [];
for(var i = 0; i < tbl.length; i++) {
selected_so.push(tbl[i].sales_order)
}
let sales_orders = [];
let so_item_list = [];
const warehouse_allocation = {};
selected_so.forEach(function(so) {
const item_details = frm.doc.locations.map((item) => {
if (item.sales_order == so && item.picked_qty > 0){
so_item_list.push({so_item:item.sales_order_item,
qty:item.qty
});
return {
sales_order_row: item.sales_order_item,
item_code: item.item_code,
warehouse: item.warehouse,
shelf:item.shelf
}
}
else{
return {}
}
});
sales_orders.push(so);
warehouse_allocation[so] = item_details.filter(value => Object.keys(value).length !== 0);
});
frappe.call({
method: 'ecommerce_integrations.unicommerce.pick_list.validate_partial_picking',
ankush marked this conversation as resolved.
Show resolved Hide resolved
args: {
'so_item_list': so_item_list
}
})
frappe.call({
method: 'ecommerce_integrations.unicommerce.invoice.generate_unicommerce_invoices',
args: {
'sales_orders': sales_orders,
'warehouse_allocation': warehouse_allocation
},
freeze: true,
freeze_message: "Requesting Invoice generation. Once synced, invoice will appear in linked documents.",
ankush marked this conversation as resolved.
Show resolved Hide resolved
});

},
})
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,13 @@ def get_erpnext_warehouses(self, all_wh=False) -> List[ERPNextWarehouse]:
wh_map.erpnext_warehouse for wh_map in self.warehouse_mapping if wh_map.enabled or all_wh
]

def get_warehouses(self):
return [
{"erpnext_warehouse": wh_map.erpnext_warehouse, "shelf": wh_map.shelf_wise}
for wh_map in self.warehouse_mapping
if wh_map.enabled
]

def get_erpnext_to_integration_wh_mapping(
self, all_wh=False
) -> Dict[ERPNextWarehouse, IntegrationWarehouse]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"column_break_2",
"return_warehouse",
"company_address",
"dispatch_address"
"dispatch_address",
"shelf_wise"
],
"fields": [
{
Expand Down Expand Up @@ -57,18 +58,25 @@
"fieldtype": "Link",
"label": "Dispatch Address",
"options": "Address"
},
{
"default": "0",
"fieldname": "shelf_wise",
"fieldtype": "Check",
"label": "Shelf Wise"
ankush marked this conversation as resolved.
Show resolved Hide resolved
}
],
"index_web_pages_for_search": 1,
"istable": 1,
"links": [],
"modified": "2021-11-19 16:39:01.415522",
"modified": "2023-04-20 15:43:11.040252",
"modified_by": "Administrator",
"module": "unicommerce",
"name": "Unicommerce Warehouses",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
68 changes: 67 additions & 1 deletion ecommerce_integrations/unicommerce/inventory.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections import defaultdict
from datetime import datetime
from typing import Dict

import frappe
Expand Down Expand Up @@ -38,7 +39,7 @@ def update_inventory_on_unicommerce(client=None, force=False):
return

# get configured warehouses
warehouses = settings.get_erpnext_warehouses()
warehouses = settings.get_warehouses()
wh_to_facility_map = settings.get_erpnext_to_integration_wh_mapping()

if client is None:
Expand All @@ -49,6 +50,9 @@ def update_inventory_on_unicommerce(client=None, force=False):
inventory_synced_on = now()

for warehouse in warehouses:
if warehouse["shelf"]:
return shelf_bulk_update(warehouse, settings)
warehouse = warehouse["erpnext_warehouse"]
is_group_warehouse = cint(frappe.db.get_value("Warehouse", warehouse, "is_group"))

if is_group_warehouse:
Expand Down Expand Up @@ -86,3 +90,65 @@ def _update_inventory_sync_status(ecom_item_success_map: Dict[str, bool], timest
for ecom_item, status in ecom_item_success_map.items():
if status:
update_inventory_sync_status(ecom_item, timestamp)


def shelf_bulk_update(warehouse, settings):
warehouse = warehouse["erpnext_warehouse"]
shelves = frappe.get_list("Shelf", {"warehouse": warehouse, "disable": 0}, ["shelf_name", "type"])
ankush marked this conversation as resolved.
Show resolved Hide resolved
is_group_warehouse = cint(frappe.db.get_value("Warehouse", warehouse, "is_group"))
if is_group_warehouse:
erpnext_inventory = get_inventory_levels_of_group_warehouse(
warehouse=warehouse, integration=MODULE_NAME
)
else:
erpnext_inventory = get_inventory_levels(warehouses=(warehouse,), integration=MODULE_NAME)

if not erpnext_inventory:
return

# erpnext_inventory = erpnext_inventory[:MAX_INVENTORY_UPDATE_IN_REQUEST]
report = frappe.get_doc("Report", "Stock Ledger")
wh_to_facility_map = settings.get_erpnext_to_integration_wh_mapping()
facility_code = wh_to_facility_map[warehouse]
inventory_list = []
for shelf in shelves:
inventoryType = "GOOD_INVENTORY"
if shelf.type == "Unsellable":
inventoryType = "BAD_INVENTORY"
for item in erpnext_inventory:
custom_filter = {
"company": "Lifelong Online Retail Private Limited",
ankush marked this conversation as resolved.
Show resolved Hide resolved
"from_date": datetime.today().strftime("%Y-%m-%d"),
"to_date": datetime.today().strftime("%Y-%m-%d"),
"warehouse": warehouse,
"shelf": shelf.shelf_name,
"item_code": item.item_code,
}
columns, data = report.get_data(limit=1, filters=custom_filter, as_dict=True)
if len(data) > 0:
data = data[0]
inventory_list.append(
{
"itemSKU": item.item_code,
"quantity": data["qty_after_transaction"],
"shelfCode": shelf,
ankush marked this conversation as resolved.
Show resolved Hide resolved
"inventoryType": inventoryType,
"adjustmentType": "REPLACE",
"facilityCode": facility_code,
}
)
inventory_list = inventory_list[:MAX_INVENTORY_UPDATE_IN_REQUEST]
client = UnicommerceAPIClient()
response, status = client.bulk_inventory_update(
facility_code=facility_code, inventory_map={"sku": 1}, inventory_adjustments=inventory_list
)
if status:
success_map: Dict[str, bool] = defaultdict(lambda: True)
# update success_map
sku_to_ecom_item_map = {d.integration_item_code: d.ecom_item for d in erpnext_inventory}
for sku, status in response.items():
ecom_item = sku_to_ecom_item_map[sku]
# Any one warehouse sync failure should be considered failure
success_map[ecom_item] = success_map[ecom_item] and status

_update_inventory_sync_status(success_map, now())
Loading