diff --git a/india_compliance/gst_india/client_scripts/stock_entry.js b/india_compliance/gst_india/client_scripts/stock_entry.js index 77cb49b8e..5c64619f0 100644 --- a/india_compliance/gst_india/client_scripts/stock_entry.js +++ b/india_compliance/gst_india/client_scripts/stock_entry.js @@ -5,31 +5,35 @@ setup_e_waybill_actions(DOCTYPE); frappe.ui.form.on(DOCTYPE, { setup(frm) { - frm.set_query("taxes_and_charges", function () { - return { - filters: [ - ["disabled", "=", 0], - ["company", "=", frm.doc.company], - ], - }; + frm.set_query("taxes_and_charges", { + filters: [ + ["disabled", "=", 0], + ["company", "=", frm.doc.company], + ], }); - frm.set_query("transporter", function () { - return { - filters: [ - ["disabled", "=", 0], - ["is_transporter", "=", 1], - ], - }; + frm.set_query("transporter", { + filters: [ + ["disabled", "=", 0], + ["is_transporter", "=", 1], + ], }); ["ship_from_address", "ship_to_address"].forEach(field => { - frm.set_query(field, function () { - return { filters: { country: "India", disabled: 0 } }; - }); + frm.set_query(field, { filters: { country: "India", disabled: 0 } }); }); set_address_display_events(); + + frm.set_query("link_doctype", "doc_references", { + name: ["=", "Stock Entry"], + }); + + frm.set_query("link_name", "doc_references", function (doc) { + return { + filters: get_filters_for_relevant_stock_entries(doc), + }; + }); }, onload(frm) { @@ -46,7 +50,7 @@ frappe.ui.form.on(DOCTYPE, { ); }, - refresh(frm) { + refresh() { if (!gst_settings.enable_e_waybill || !gst_settings.enable_e_waybill_for_sc) return; @@ -112,6 +116,24 @@ frappe.ui.form.on(DOCTYPE, { taxes_and_charges(frm) { frm.taxes_controller.update_taxes(frm); }, + + async fetch_original_doc_ref(frm) { + let existing_references = frm.doc.doc_references.map(row => row.link_name); + + data = await frappe.db.get_list(DOCTYPE, { + filters: get_filters_for_relevant_stock_entries(frm.doc), + group_by: "name", + }); + + data.forEach(doc => { + if (existing_references.includes(doc.name)) return; + var row = frm.add_child("doc_references"); + row.link_doctype = DOCTYPE; + row.link_name = doc.name; + }); + + frm.refresh_field("doc_references"); + }, }); function set_address_display_events() { @@ -155,3 +177,16 @@ function on_change_set_address(frm, source_field, target_field, label1, label2) } frappe.ui.form.on("Stock Entry Detail", india_compliance.taxes_controller_events); + +function get_filters_for_relevant_stock_entries(doc) { + return [ + ["docstatus", "=", 1], + ["purpose", "=", "Send to Subcontractor"], + ["subcontracting_order", "=", doc.subcontracting_order], + ["Stock Entry Detail", "item_code", "in", get_items(doc)], + ]; +} + +function get_items(doc) { + return Array.from(new Set(doc.items.map(row => row.item_code))); +} diff --git a/india_compliance/gst_india/client_scripts/subcontracting_receipt.js b/india_compliance/gst_india/client_scripts/subcontracting_receipt.js index 77118afac..ddc1be687 100644 --- a/india_compliance/gst_india/client_scripts/subcontracting_receipt.js +++ b/india_compliance/gst_india/client_scripts/subcontracting_receipt.js @@ -1,29 +1,70 @@ -setup_e_waybill_actions("Subcontracting Receipt"); +DOCTYPE = "Subcontracting Receipt"; +setup_e_waybill_actions(DOCTYPE); -frappe.ui.form.on("Subcontracting Receipt", { +frappe.ui.form.on(DOCTYPE, { setup(frm) { - frm.set_query("taxes_and_charges", function () { - return { - filters: [ - ["disabled", "=", 0], - ["company", "=", frm.doc.company], - ], - }; + frm.set_query("taxes_and_charges", { + filters: [ + ["disabled", "=", 0], + ["company", "=", frm.doc.company], + ], }); - frm.set_query("transporter", function () { - return { - filters: [ - ["disabled", "=", 0], - ["is_transporter", "=", 1], - ], - }; + frm.set_query("transporter", { + filters: [ + ["disabled", "=", 0], + ["is_transporter", "=", 1], + ], }); ["supplier_address", "shipping_address"].forEach(field => { - frm.set_query(field, function () { - return { filters: { country: "India", disabled: 0 } }; - }); + frm.set_query(field, { filters: { country: "India", disabled: 0 } }); + }); + + frm.set_query("link_doctype", "doc_references", { + filters: { + name: ["in", ["Subcontracting Receipt", "Stock Entry"]], + }, + }); + + frm.set_query("link_name", "doc_references", function (doc, cdt, cdn) { + const row = locals[cdt][cdn]; + const subcontracting_orders = get_subcontracting_orders(doc); + + // same as backend query in get_relevant_references + if (row.link_doctype == "Stock Entry") { + const supplied_items = get_supplied_items(doc); + return { + filters: [ + ["docstatus", "=", 1], + ["purpose", "=", "Send to Subcontractor"], + ["subcontracting_order", "in", subcontracting_orders], + ["supplier", "=", doc.supplier], + ["Stock Entry Detail", "item_code", "in", supplied_items], + ], + }; + } else if (row.link_doctype == "Subcontracting Receipt") { + const received_items = get_received_items(doc); + return { + filters: [ + ["docstatus", "=", 1], + ["is_return", "=", 1], + ["supplier", "=", doc.supplier], + [ + "Subcontracting Receipt Item", + "item_code", + "in", + received_items, + ], + [ + "Subcontracting Receipt Item", + "subcontracting_order", + "in", + subcontracting_orders, + ], + ], + }; + } }); }, onload(frm) { @@ -32,9 +73,10 @@ frappe.ui.form.on("Subcontracting Receipt", { }); }, - refresh(frm) { + refresh() { if (!gst_settings.enable_e_waybill || !gst_settings.enable_e_waybill_for_sc) return; + show_sandbox_mode_indicator(); }, @@ -49,6 +91,35 @@ frappe.ui.form.on("Subcontracting Receipt", { ); }, + fetch_original_doc_ref(frm) { + let existing_references = get_existing_references(frm); + + frappe.call({ + method: "india_compliance.gst_india.overrides.subcontracting_transaction.get_relevant_references", + args: { + supplier: frm.doc.supplier, + supplied_items: get_supplied_items(frm.doc), + received_items: get_received_items(frm.doc), + subcontracting_orders: get_subcontracting_orders(frm.doc), + }, + callback: function (r) { + if (!r.message) return; + + Object.entries(r.message).forEach(([doctype, docnames]) => { + docnames.forEach(docname => { + if (existing_references[doctype]?.includes(docname)) return; + + let row = frm.add_child("doc_references"); + row.link_doctype = doctype; + row.link_name = docname; + }); + }); + + frm.refresh_field("doc_references"); + }, + }); + }, + taxes_and_charges(frm) { frm.taxes_controller.update_taxes(frm); }, @@ -58,3 +129,27 @@ frappe.ui.form.on( "Subcontracting Receipt Item", india_compliance.taxes_controller_events ); + +function get_existing_references(frm) { + let existing_references = {}; + + frm.doc.doc_references.forEach(row => { + if (!existing_references[row.link_doctype]) + existing_references[row.link_doctype] = []; + existing_references[row.link_doctype].push(row.link_name); + }); + + return existing_references; +} + +function get_supplied_items(doc) { + return Array.from(new Set(doc.supplied_items.map(row => row.rm_item_code))); +} + +function get_received_items(doc) { + return Array.from(new Set(doc.items.map(row => row.item_code))); +} + +function get_subcontracting_orders(doc) { + return Array.from(new Set(doc.items.map(row => row.subcontracting_order))); +} diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index 65ed9e177..f543a12a0 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -67,6 +67,26 @@ "insert_after": "total", "hide_border": 1, }, + { + "fieldname": "section_break_ref_doc", + "label": "References", + "fieldtype": "Section Break", + "insert_after": "bill_date", + "depends_on": "eval:doc.is_return !== 1", + }, + { + "fieldname": "fetch_original_doc_ref", + "label": "Fetch Original Document Reference", + "fieldtype": "Button", + "insert_after": "section_break_ref_doc", + }, + { + "fieldname": "doc_references", + "label": "Original Document References", + "fieldtype": "Table", + "insert_after": "fetch_original_doc_ref", + "options": "Dynamic Link", + }, ], ("Subcontracting Order", "Subcontracting Receipt"): [ { @@ -161,7 +181,7 @@ "label": "Taxes", "fieldtype": "Section Break", "insert_after": "get_stock_and_rate", - "depends_on": "eval:doc.purpose === 'Send to Subcontractor'", + "depends_on": "eval:doc.subcontracting_order", }, { "label": "E-Waybill Info", @@ -312,6 +332,26 @@ "read_only": 1, "is_virtual": 1, }, + { + "fieldname": "section_break_ref_doc", + "label": "References", + "fieldtype": "Section Break", + "insert_after": "value_difference", + "depends_on": "eval:doc.purpose === 'Material Transfer' && doc.subcontracting_order", + }, + { + "fieldname": "fetch_original_doc_ref", + "label": "Fetch Original Document Reference", + "fieldtype": "Button", + "insert_after": "section_break_ref_doc", + }, + { + "fieldname": "doc_references", + "label": "Original Document References", + "fieldtype": "Table", + "insert_after": "fetch_original_doc_ref", + "options": "Dynamic Link", + }, ], "Company": [ { diff --git a/india_compliance/gst_india/overrides/subcontracting_transaction.py b/india_compliance/gst_india/overrides/subcontracting_transaction.py index e350291e1..390e4d1a9 100644 --- a/india_compliance/gst_india/overrides/subcontracting_transaction.py +++ b/india_compliance/gst_india/overrides/subcontracting_transaction.py @@ -158,6 +158,17 @@ def validate(doc, method=None): if ignore_gst_validation_for_subcontracting(doc): return + if (doc.doctype == "Stock Entry" and doc.purpose == "Material Transfer") or ( + doc.doctype == "Subcontracting Receipt" and not doc.is_return + ): + if not doc.doc_references: + frappe.throw( + _("Please Select Original Document Reference for ITC-04 Reporting"), + title=_("Mandatory Field"), + ) + else: + remove_duplicates(doc) + field_map = ( STOCK_ENTRY_FIELD_MAP if doc.doctype == "Stock Entry" @@ -280,8 +291,8 @@ def validate_for_charge_type(self): def ignore_gst_validation_for_subcontracting(doc): - if doc.doctype == "Stock Entry" and doc.purpose != "Send to Subcontractor": - return True + if doc.doctype == "Stock Entry" and not doc.subcontracting_order: + return return ignore_gst_validations(doc) @@ -297,3 +308,69 @@ def set_address_display(doc): for address in adddress_fields: if doc.get(address): setattr(doc, address + "_display", get_address_display(doc.get(address))) + + +@frappe.whitelist() +def get_relevant_references( + supplier, supplied_items, received_items, subcontracting_orders +): + + if isinstance(supplied_items, str): + supplied_items = frappe.parse_json(supplied_items) + received_items = frappe.parse_json(received_items) + subcontracting_orders = frappe.parse_json(subcontracting_orders) + + # same filters used for set_query in JS + + receipt_returns = frappe.db.get_all( + "Subcontracting Receipt", + filters=[ + ["docstatus", "=", 1], + ["is_return", "=", 1], + ["supplier", "=", supplier], + ["Subcontracting Receipt Item", "item_code", "in", received_items], + [ + "Subcontracting Receipt Item", + "subcontracting_order", + "in", + subcontracting_orders, + ], + ], + pluck="name", + group_by="name", + ) + + stock_entries = frappe.db.get_all( + "Stock Entry", + filters=[ + ["docstatus", "=", 1], + ["purpose", "=", "Send to Subcontractor"], + ["subcontracting_order", "in", subcontracting_orders], + ["supplier", "=", supplier], + ["Stock Entry Detail", "item_code", "in", supplied_items], + ], + pluck="name", + group_by="name", + ) + + data = {"Subcontracting Receipt": receipt_returns, "Stock Entry": stock_entries} + + return data + + +def remove_duplicates(doc): + references = [] + has_duplicates = False + + for row in doc.doc_references: + ref = (row.link_doctype, row.link_name) + + if ref not in references: + references.append(ref) + else: + has_duplicates = True + + if has_duplicates: + doc.doc_references = [] + for row in references: + doc.append("doc_references", dict(link_doctype=row[0], link_name=row[1])) diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 380fb2b93..eb89d491a 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -4,7 +4,7 @@ india_compliance.patches.v15.remove_duplicate_web_template [post_model_sync] india_compliance.patches.v14.set_default_for_overridden_accounts_setting -execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #53 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #54 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #8 execute:from india_compliance.income_tax_india.setup import create_custom_fields; create_custom_fields() #2 india_compliance.patches.post_install.remove_old_fields #1