diff --git a/india_compliance/gst_india/client_scripts/e_waybill_actions.js b/india_compliance/gst_india/client_scripts/e_waybill_actions.js index 12f5e7148c..1b95a8448d 100644 --- a/india_compliance/gst_india/client_scripts/e_waybill_actions.js +++ b/india_compliance/gst_india/client_scripts/e_waybill_actions.js @@ -558,6 +558,14 @@ function get_sub_suppy_type_options(frm, is_foreign_transaction) { if (frm.doc.purpose === "Send to Subcontractor") { supply_type = "Outward"; sub_supply_type = ["Job Work"]; + } else if (frm.doc.purpose === "Subcontracting Delivery") { + supply_type = "Outward"; + sub_supply_type = ["Others"]; + sub_supply_desc = "Job Work Delivery"; + } else if (frm.doc.purpose === "Return Raw Material to Customer") { + supply_type = "Outward"; + sub_supply_type = ["Others"]; + sub_supply_desc = "Return Raw Material"; } else if (["Material Transfer", "Material Issue"].includes(frm.doc.purpose)) { const same_gstin = frm.doc.bill_from_gstin === frm.doc.bill_to_gstin; diff --git a/india_compliance/gst_india/client_scripts/e_waybill_applicability.js b/india_compliance/gst_india/client_scripts/e_waybill_applicability.js index d727c9cbba..00d4517687 100644 --- a/india_compliance/gst_india/client_scripts/e_waybill_applicability.js +++ b/india_compliance/gst_india/client_scripts/e_waybill_applicability.js @@ -243,9 +243,13 @@ class StockEntryEwaybill extends EwaybillApplicability { if ( !gst_settings.enable_e_waybill || !gst_settings.enable_e_waybill_for_sc || - !["Material Transfer", "Material Issue", "Send to Subcontractor"].includes( - this.frm.doc.purpose - ) + ![ + "Material Transfer", + "Material Issue", + "Send to Subcontractor", + "Subcontracting Delivery", + "Return Raw Material to Customer", + ].includes(this.frm.doc.purpose) ) return false; @@ -269,7 +273,12 @@ class StockEntryEwaybill extends EwaybillApplicability { const same_gstin = this.frm.doc.bill_from_gstin === this.frm.doc.bill_to_gstin; const applicable_for_same_gstin = !( - is_return || this.frm.doc.purpose === "Send to Subcontractor" + is_return || + [ + "Send to Subcontractor", + "Subcontracting Delivery", + "Return Raw Material to Customer", + ].includes(this.frm.doc.purpose) ); if (same_gstin && !applicable_for_same_gstin) { @@ -319,9 +328,13 @@ class StockEntryEwaybill extends EwaybillApplicability { is_e_waybill_api_enabled() { return ( - ["Material Transfer", "Material Issue", "Send to Subcontractor"].includes( - this.frm.doc.purpose - ) && + [ + "Material Transfer", + "Material Issue", + "Send to Subcontractor", + "Subcontracting Delivery", + "Return Raw Material to Customer", + ].includes(this.frm.doc.purpose) && super.is_e_waybill_api_enabled() && gst_settings.enable_e_waybill_for_sc ); diff --git a/india_compliance/gst_india/client_scripts/stock_entry.js b/india_compliance/gst_india/client_scripts/stock_entry.js index f5d2a70a0f..21f817e47e 100644 --- a/india_compliance/gst_india/client_scripts/stock_entry.js +++ b/india_compliance/gst_india/client_scripts/stock_entry.js @@ -76,7 +76,7 @@ frappe.ui.form.on(DOCTYPE, { if (is_e_waybill_applicable(frm) && !is_e_waybill_generatable(frm)) frappe.show_alert( { - message: __("Supplier Address is required to create e-Waybill"), + message: __("Party Address is required to create e-Waybill"), indicator: "yellow", }, 10 @@ -112,7 +112,7 @@ frappe.ui.form.on(DOCTYPE, { }, company(frm) { - if (frm.doc.company && frm.doc.purpose === "Send to Subcontractor") { + if (frm.doc.company && is_subcontracting_entry(frm)) { frappe.call({ method: "frappe.contacts.doctype.address.address.get_default_address", args: { @@ -207,6 +207,20 @@ function get_items(doc) { return Array.from(new Set(doc.items.map(row => row.item_code))); } +function is_subcontracting_entry(frm) { + return [ + "Send to Subcontractor", + "Subcontracting Delivery", + "Return Raw Material to Customer", + ].includes(frm.doc.purpose); +} + +function is_subcontracting_inward_entry(frm) { + return ["Subcontracting Delivery", "Return Raw Material to Customer"].includes( + frm.doc.purpose + ); +} + function get_field_and_label(frm, field) { let field_label_dict = {}; @@ -219,6 +233,17 @@ function get_field_and_label(frm, field) { ], company_field: ["bill_to_address", __("Bill To")], }; + } else if (is_subcontracting_inward_entry(frm)) { + // For Subcontracting Inward related entries + // company bills to the customer + field_label_dict = { + party_field: [ + "bill_to_address", + __("Bill To (same as Customer Address)"), + __("Bill To"), + ], + company_field: ["bill_from_address", __("Bill From")], + }; } else { field_label_dict = { party_field: [ diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index 2757fd8b0b..6218c7383a 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -753,6 +753,20 @@ "print_hide": 1, "hidden": 0, }, + { + "fieldname": "additional_taxable_value", + "label": "Additional Taxable Value", + "fieldtype": "Currency", + "insert_after": "taxable_value", + "options": "Company:company:default_currency", + "read_only": 1, + "translatable": 0, + "no_copy": 1, + "print_hide": 1, + "hidden": 0, + "depends_on": 'eval:["Subcontracting Delivery", "Return Raw Material to Customer"].includes(parent.purpose)', + "description": "Value of customer-provided materials for Subcontracting Inward", + }, ], "Subcontracting Receipt Item": [ { diff --git a/india_compliance/gst_india/overrides/subcontracting_transaction.py b/india_compliance/gst_india/overrides/subcontracting_transaction.py index 2beae58207..72d17db5a3 100644 --- a/india_compliance/gst_india/overrides/subcontracting_transaction.py +++ b/india_compliance/gst_india/overrides/subcontracting_transaction.py @@ -178,6 +178,7 @@ def onload(doc, method=None): def validate(doc, method=None): + field_map = ( STOCK_ENTRY_FIELD_MAP if doc.doctype == "Stock Entry" @@ -539,6 +540,8 @@ def is_e_waybill_applicable(doc): "Material Transfer", "Material Issue", "Send to Subcontractor", + "Subcontracting Delivery", + "Return Raw Material to Customer", ]: return False diff --git a/india_compliance/gst_india/utils/taxes_controller.py b/india_compliance/gst_india/utils/taxes_controller.py index b986bf560c..6747283715 100644 --- a/india_compliance/gst_india/utils/taxes_controller.py +++ b/india_compliance/gst_india/utils/taxes_controller.py @@ -1,4 +1,5 @@ import json +from collections import defaultdict import frappe from frappe import _ @@ -117,6 +118,7 @@ def __init__(self, doc, field_map=None): def set_taxes_and_totals(self): self.set_item_wise_tax_rates() + self.set_additional_taxable_value() self.update_item_taxable_value() self.update_tax_amount() self.update_base_grand_total() @@ -154,7 +156,24 @@ def set_item_wise_tax_rates(self, item_name=None, tax_name=None): def update_item_taxable_value(self): for item in self.doc.get("items"): - item.taxable_value = self.get_value("amount", item) + taxable_value = self.get_value("amount", item) + taxable_value += flt( + item.get("additional_taxable_value", 0), item.precision("taxable_value") + ) + + item.taxable_value = taxable_value + + def set_additional_taxable_value(self): + if self.doc.doctype != "Stock Entry" or not self.doc.items: + return + + for item in self.doc.items: + item.additional_taxable_value = 0 + + if self.doc.purpose == "Subcontracting Delivery": + _set_subcontracting_delivery_additional_value(self.doc) + elif self.doc.purpose == "Return Raw Material to Customer": + _set_return_raw_material_additional_value(self.doc) def update_tax_amount(self): total_taxes = 0 @@ -272,3 +291,92 @@ def validate_taxes(doc): tax.idx, doc.doctype ) ) + + +def _set_subcontracting_delivery_additional_value(doc): + """ + additional_taxable_value = SUM(received_items.rate * received_items.consumed_qty) + """ + scio_details = [item.scio_detail for item in doc.items if item.get("scio_detail")] + + if not scio_details: + return + + quantity_processed = frappe._dict( + frappe.get_all( + "Subcontracting Inward Order Item", + filters={"name": ["in", scio_details]}, + fields=["name", "produced_qty"], + as_list=True, + ) + ) + + received_items = frappe.get_all( + "Subcontracting Inward Order Received Item", + filters={ + "reference_name": ["in", scio_details], + "is_customer_provided_item": 1, + }, + fields=["reference_name", "rate", "consumed_qty"], + ) + + if not received_items: + return + + # Calculate total material cost per FG item + fg_material_cost = defaultdict(float) + for received_item in received_items: + key = received_item.reference_name + cost = flt(received_item.rate) * flt(received_item.consumed_qty) + fg_material_cost[key] += cost + + precision = doc.precision("additional_taxable_value", "items") + + # Set additional_taxable_value for each item + for item in doc.items: + if not item.get("scio_detail"): + continue + item.additional_taxable_value = flt( + fg_material_cost.get(item.scio_detail) + / quantity_processed.get(item.scio_detail, 1) + * item.qty, + precision, + ) + + +def _set_return_raw_material_additional_value(doc): + """ + - additional_taxable_value = (SCIO Received Item rate * qty) - Stock Entry amount + """ + scio_details = [item.scio_detail for item in doc.items if item.get("scio_detail")] + + if not scio_details: + return + + received_items = frappe._dict( + frappe.get_all( + "Subcontracting Inward Order Received Item", + filters={ + "name": ["in", scio_details], + }, + fields=["name", "rate"], + as_list=True, + ) + ) + + if not received_items: + return + + precision = doc.precision("additional_taxable_value", "items") + + for item in doc.items: + scio_detail = item.get("scio_detail") + if not scio_detail: + continue + + scio_rate = received_items.get(scio_detail) + if not scio_rate: + continue + + scio_value = flt(scio_rate) * flt(item.qty) + item.additional_taxable_value = flt(scio_value - flt(item.amount), precision) diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 1f92e2b7b5..13e515e2c8 100644 --- a/india_compliance/patches.txt +++ b/india_compliance/patches.txt @@ -5,7 +5,7 @@ execute:from frappe.installer import add_module_defs; add_module_defs("india_com [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() #69 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #67 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #11 execute:from india_compliance.income_tax_india.setup import create_custom_fields; create_custom_fields() #4 india_compliance.patches.post_install.remove_old_fields #2 diff --git a/india_compliance/public/js/taxes_controller.js b/india_compliance/public/js/taxes_controller.js index 0d95ff687a..9c4a6095fd 100644 --- a/india_compliance/public/js/taxes_controller.js +++ b/india_compliance/public/js/taxes_controller.js @@ -133,7 +133,12 @@ india_compliance.taxes_controller = class TaxesController { // Function to calculate amount const calculateAmount = (qty, rate, precisionType) => { - return flt(flt(qty) * flt(rate), precision(precisionType, row)); + let amount = flt(flt(qty) * flt(rate), precision(precisionType, row)); + + if (this.frm.doc.doctype === "Stock Entry") { + amount += flt(row.additional_taxable_value, precision(precisionType, row)); + } + return amount; }; if (this.frm.doc.doctype === "Subcontracting Receipt") { diff --git a/india_compliance/public/js/utils.js b/india_compliance/public/js/utils.js index 8de9c7bf3a..4740de0a93 100644 --- a/india_compliance/public/js/utils.js +++ b/india_compliance/public/js/utils.js @@ -593,9 +593,13 @@ Object.assign(india_compliance, { if (doc.doctype != "Stock Entry") return true; if ( - !["Material Transfer", "Material Issue", "Send to Subcontractor"].includes( - doc.purpose - ) + ![ + "Material Transfer", + "Material Issue", + "Send to Subcontractor", + "Subcontracting Delivery", + "Return Raw Material to Customer", + ].includes(doc.purpose) ) { return false; }