diff --git a/india_compliance/gst_india/api_classes/taxpayer_returns.py b/india_compliance/gst_india/api_classes/taxpayer_returns.py index dffda10347..fb0df061bb 100644 --- a/india_compliance/gst_india/api_classes/taxpayer_returns.py +++ b/india_compliance/gst_india/api_classes/taxpayer_returns.py @@ -9,6 +9,7 @@ class ReturnsAPI(TaxpayerBaseAPI): IGNORED_ERROR_CODES = { **TaxpayerBaseAPI.IGNORED_ERROR_CODES, "RET11416": "no_docs_found", + "RET12501": "no_docs_found", # random `system failure` for CDNR "RET13508": "no_docs_found", "RET13509": "no_docs_found", "RET13510": "no_docs_found", diff --git a/india_compliance/gst_india/client_scripts/e_invoice_actions.js b/india_compliance/gst_india/client_scripts/e_invoice_actions.js index aff1439769..fbe167e446 100644 --- a/india_compliance/gst_india/client_scripts/e_invoice_actions.js +++ b/india_compliance/gst_india/client_scripts/e_invoice_actions.js @@ -21,7 +21,7 @@ frappe.ui.form.on("Sales Invoice", { ) return; - if(frm.doc.docstatus === 2) return; + if (frm.doc.docstatus === 2) return; const is_einv_generatable = is_e_invoice_generatable(frm, true); @@ -144,6 +144,10 @@ frappe.ui.form.on("Sales Invoice", { return; } + if (gst_settings.auto_cancel_e_invoice === 1) { + continueCancellation(); + return; + } return show_cancel_e_invoice_dialog(frm, continueCancellation); }); }, @@ -281,7 +285,10 @@ function get_cancel_e_invoice_dialog_fields(frm, manual_cancel = false) { fieldname: "reason", fieldtype: "Select", reqd: 1, - default: manual_cancel ? "Others" : "Data Entry Mistake", + default: manual_cancel + ? "Others" + : gst_settings.reason_for_e_invoice_cancellation || + "Data Entry Mistake", options: ["Duplicate", "Data Entry Mistake", "Order Cancelled", "Others"], }, { 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 cfcebd60a6..c580614568 100644 --- a/india_compliance/gst_india/client_scripts/e_waybill_actions.js +++ b/india_compliance/gst_india/client_scripts/e_waybill_actions.js @@ -221,6 +221,11 @@ function setup_e_waybill_actions(doctype) { return; } + if (gst_settings.auto_cancel_e_waybill === 1) { + continueCancellation(); + return; + } + return show_cancel_e_waybill_dialog(frm, continueCancellation); }); }, @@ -758,7 +763,8 @@ function get_cancel_e_waybill_dialog_fields(frm) { fieldname: "reason", fieldtype: "Select", reqd: 1, - default: "Data Entry Mistake", + default: + gst_settings.reason_for_e_waybill_cancellation || "Data Entry Mistake", options: ["Duplicate", "Order Cancelled", "Data Entry Mistake", "Others"], }, { diff --git a/india_compliance/gst_india/client_scripts/stock_entry.js b/india_compliance/gst_india/client_scripts/stock_entry.js index a1ac3fd457..f5d2a70a0f 100644 --- a/india_compliance/gst_india/client_scripts/stock_entry.js +++ b/india_compliance/gst_india/client_scripts/stock_entry.js @@ -56,11 +56,6 @@ frappe.ui.form.on(DOCTYPE, { "supplier_address", ...get_field_and_label(frm, "party_field") ); - - frm.get_docfield("taxes", "charge_type").options = [ - "On Net Total", - "On Item Quantity", - ]; }, refresh(frm) { diff --git a/india_compliance/gst_india/client_scripts/subcontracting_order.js b/india_compliance/gst_india/client_scripts/subcontracting_order.js index 9ea8504838..e2f463453d 100644 --- a/india_compliance/gst_india/client_scripts/subcontracting_order.js +++ b/india_compliance/gst_india/client_scripts/subcontracting_order.js @@ -13,11 +13,6 @@ frappe.ui.form.on("Subcontracting Order", { frm.taxes_controller = new india_compliance.taxes_controller(frm, { total_taxable_value: "total", }); - - frm.get_docfield("taxes", "charge_type").options = [ - "On Net Total", - "On Item Quantity", - ]; }, taxes_and_charges(frm) { diff --git a/india_compliance/gst_india/client_scripts/subcontracting_receipt.js b/india_compliance/gst_india/client_scripts/subcontracting_receipt.js index f0038c4d08..789a330913 100644 --- a/india_compliance/gst_india/client_scripts/subcontracting_receipt.js +++ b/india_compliance/gst_india/client_scripts/subcontracting_receipt.js @@ -42,28 +42,21 @@ frappe.ui.form.on(DOCTYPE, { ...filters, supplied_items: get_supplied_items(doc), }, - } - + }; else if (row.link_doctype == "Subcontracting Receipt") return { query: "india_compliance.gst_india.overrides.subcontracting_transaction.get_subcontracting_receipt_references", filters: { ...filters, received_items: get_received_items(doc), - } - } - + }, + }; }); }, onload(frm) { frm.taxes_controller = new india_compliance.taxes_controller(frm, { total_taxable_value: "total", }); - - frm.get_docfield("taxes", "charge_type").options = [ - "On Net Total", - "On Item Quantity", - ]; }, refresh() { diff --git a/india_compliance/gst_india/constants/custom_fields.py b/india_compliance/gst_india/constants/custom_fields.py index 6fed6fc111..08c0150fd7 100644 --- a/india_compliance/gst_india/constants/custom_fields.py +++ b/india_compliance/gst_india/constants/custom_fields.py @@ -56,7 +56,7 @@ "fieldtype": "Section Break", "insert_after": "total", "hide_border": 1, - "depends_on": "eval: doc.purchase_order && india_compliance.is_e_waybill_generatable_for_subcontracting(doc)", + "depends_on": "eval: doc.purchase_order && india_compliance.is_e_waybill_applicable_for_subcontracting(doc)", }, ], "Subcontracting Receipt": [ @@ -66,7 +66,7 @@ "fieldtype": "Section Break", "insert_after": "total", "hide_border": 1, - "depends_on": "eval: india_compliance.is_e_waybill_generatable_for_subcontracting(doc)", + "depends_on": "eval: india_compliance.is_e_waybill_applicable_for_subcontracting(doc)", }, { "fieldname": "section_break_ref_doc", @@ -134,11 +134,19 @@ }, ], ("Subcontracting Order", "Subcontracting Receipt", "Stock Entry"): [ + { + "fieldname": "tax_category", + "label": "Tax Category", + "fieldtype": "Link", + "insert_after": "section_break_taxes", + "options": "Tax Category", + "print_hide": 1, + }, { "fieldname": "taxes_and_charges", "label": "Taxes and Charges Template", "fieldtype": "Link", - "insert_after": "section_break_taxes", + "insert_after": "tax_category", "options": "Sales Taxes and Charges Template", "print_hide": 1, }, @@ -182,14 +190,14 @@ "label": "Taxes", "fieldtype": "Section Break", "insert_after": "get_stock_and_rate", - "depends_on": "eval: india_compliance.is_e_waybill_generatable_for_subcontracting(doc)", + "depends_on": "eval: india_compliance.is_e_waybill_applicable_for_subcontracting(doc)", }, { "label": "E-Waybill Info", "fieldname": "tab_break_ewaybill", "fieldtype": "Tab Break", "insert_after": "address_display", - "depends_on": "eval: india_compliance.is_e_waybill_generatable_for_subcontracting(doc)", + "depends_on": "eval: india_compliance.is_e_waybill_applicable_for_subcontracting(doc)", }, { "label": "e-Waybill Address", diff --git a/india_compliance/gst_india/data/test_e_invoice.json b/india_compliance/gst_india/data/test_e_invoice.json index 5859cc3f47..f5a2fb3eac 100644 --- a/india_compliance/gst_india/data/test_e_invoice.json +++ b/india_compliance/gst_india/data/test_e_invoice.json @@ -664,15 +664,6 @@ "Stcd": "02", "TrdNm": "_Test Indian Registered Company" }, - "ShipDtls": { - "Gstin": "URP", - "LglNm": "Test Indian Unregistered Company", - "TrdNm": "Test Indian Unregistered Company", - "Addr1": "Test Address - 2", - "Loc": "Test City", - "Pin": 380015, - "Stcd": "24" - }, "TranDtls": { "RegRev": "N", "SupTyp": "EXPWOP", diff --git a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.json b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.json index a30beee11a..316e143e6a 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.json +++ b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.json @@ -43,6 +43,7 @@ "column_break_wxlx", "total_taxable_value", "section_break_biay", + "tax_category", "taxes", "section_break_zcnz", "total_customs_duty", @@ -279,6 +280,13 @@ "options": "\nNot Applicable\nReconciled\nUnreconciled\nIgnored", "print_hide": 1, "read_only": 1 + }, + { + "fieldname": "tax_category", + "fieldtype": "Link", + "label": "Tax Category", + "options": "Tax Category", + "print_hide": 1 } ], "in_create": 1, @@ -297,7 +305,7 @@ "link_fieldname": "link_name" } ], - "modified": "2024-08-12 15:48:43.769450", + "modified": "2024-12-20 15:17:36.898844", "modified_by": "Administrator", "module": "GST India", "name": "Bill of Entry", diff --git a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py index aba4d59f59..ec270d2c91 100644 --- a/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py +++ b/india_compliance/gst_india/doctype/bill_of_entry/bill_of_entry.py @@ -11,6 +11,7 @@ import erpnext from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries from erpnext.controllers.accounts_controller import AccountsController +from erpnext.stock.get_item_details import ItemDetailsCtx, _get_item_tax_template from india_compliance.gst_india.overrides.ineligible_itc import ( update_landed_cost_voucher_for_gst_expense, @@ -114,6 +115,7 @@ def set_default_accounts(self): self.customs_payable_account = company.default_customs_payable_account def set_taxes_and_totals(self): + self.validate_item_tax_template() self.taxes_controller = CustomTaxController(self) self.taxes_controller.set_item_wise_tax_rates() @@ -234,6 +236,47 @@ def validate_taxes(self): ).format(row.idx, tax.tax_amount, column) ) + def validate_item_tax_template(self): + for item in self.items: + if item.item_code and item.get("item_tax_template"): + item_doc = frappe.get_cached_doc("Item", item.item_code) + item_details = ItemDetailsCtx( + { + "net_rate": item.get("taxable_value"), + "base_net_rate": item.get("taxable_value"), + "tax_category": self.get("tax_category"), + "bill_date": self.bill_of_entry_date, + "company": self.get("company"), + } + ) + + item_group = item_doc.item_group + item_group_taxes = [] + + while item_group: + item_group_doc = frappe.get_cached_doc("Item Group", item_group) + item_group_taxes += item_group_doc.taxes or [] + item_group = item_group_doc.parent_item_group + + item_taxes = item_doc.taxes or [] + + if not item_group_taxes and (not item_taxes): + # No validation if no taxes in item or item group + continue + + taxes = _get_item_tax_template( + item_details, item_taxes + item_group_taxes, for_validate=True + ) + + if taxes: + if item.item_tax_template not in taxes: + item.item_tax_template = taxes[0] + frappe.msgprint( + _( + "Row {0}: Item Tax template updated as per validity and rate applied" + ).format(item.idx, frappe.bold(item.item_code)) + ) + def get_gl_entries(self): # company_currency is required by get_gl_dict # nosemgrep diff --git a/india_compliance/gst_india/doctype/e_invoice_log/e_invoice_log.js b/india_compliance/gst_india/doctype/e_invoice_log/e_invoice_log.js index 0c181cb0b3..f9cf744010 100644 --- a/india_compliance/gst_india/doctype/e_invoice_log/e_invoice_log.js +++ b/india_compliance/gst_india/doctype/e_invoice_log/e_invoice_log.js @@ -1,8 +1,8 @@ // Copyright (c) 2022, Resilient Tech and contributors // For license information, please see license.txt -frappe.ui.form.on('e-Invoice Log', { - // refresh: function(frm) { +// frappe.ui.form.on('e-Invoice Log', { +// refresh: function(frm) { - // } -}); +// } +// }); diff --git a/india_compliance/gst_india/doctype/e_invoice_log/e_invoice_log_list.js b/india_compliance/gst_india/doctype/e_invoice_log/e_invoice_log_list.js new file mode 100644 index 0000000000..fc4cf9e480 --- /dev/null +++ b/india_compliance/gst_india/doctype/e_invoice_log/e_invoice_log_list.js @@ -0,0 +1,26 @@ +// Copyright (c) 2024, Resilient Tech and contributors +// For license information, please see license.txt + +frappe.listview_settings["e-Invoice Log"] = { + hide_name_column: true, + + button: { + show: function (doc) { + return doc.reference_name; + }, + + get_label: function () { + return __("Open Reference"); + }, + + get_description: function (doc) { + return __("Open {0}", [ + `${__(doc.reference_doctype)}: ${doc.reference_name}`, + ]); + }, + + action: function (doc) { + frappe.set_route("Form", doc.reference_doctype, doc.reference_name); + }, + }, +}; diff --git a/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log.js b/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log.js index 6752e48320..79f4086f8f 100644 --- a/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log.js +++ b/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log.js @@ -1,8 +1,8 @@ // Copyright (c) 2022, Resilient Tech and contributors // For license information, please see license.txt -frappe.ui.form.on('e-Waybill Log', { +// frappe.ui.form.on('e-Waybill Log', { // refresh: function(frm) { // } -}); +// }); diff --git a/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log.json b/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log.json index 1a2cb9b6d2..da5633c6c6 100644 --- a/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log.json +++ b/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log.json @@ -99,6 +99,7 @@ { "fieldname": "reference_doctype", "fieldtype": "Link", + "in_list_view": 1, "in_standard_filter": 1, "label": "Reference Document Type", "options": "DocType", @@ -177,7 +178,7 @@ "link_fieldname": "ewaybill" } ], - "modified": "2024-07-17 12:26:38.069883", + "modified": "2024-12-05 09:50:37.014667", "modified_by": "Administrator", "module": "GST India", "name": "e-Waybill Log", diff --git a/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log_list.js b/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log_list.js new file mode 100644 index 0000000000..34d344b1fd --- /dev/null +++ b/india_compliance/gst_india/doctype/e_waybill_log/e_waybill_log_list.js @@ -0,0 +1,26 @@ +// Copyright (c) 2024, Resilient Tech and contributors +// For license information, please see license.txt + +frappe.listview_settings["e-Waybill Log"] = { + hide_name_column: true, + + button: { + show: function (doc) { + return doc.reference_name; + }, + + get_label: function () { + return __("Open Reference"); + }, + + get_description: function (doc) { + return __("Open {0}", [ + `${__(doc.reference_doctype)}: ${doc.reference_name}`, + ]); + }, + + action: function (doc) { + frappe.set_route("Form", doc.reference_doctype, doc.reference_name); + }, + }, +}; diff --git a/india_compliance/gst_india/doctype/gst_hsn_code/gst_hsn_code.js b/india_compliance/gst_india/doctype/gst_hsn_code/gst_hsn_code.js index a563ab4c81..4f5b020598 100644 --- a/india_compliance/gst_india/doctype/gst_hsn_code/gst_hsn_code.js +++ b/india_compliance/gst_india/doctype/gst_hsn_code/gst_hsn_code.js @@ -16,7 +16,7 @@ frappe.ui.form.on('GST HSN Code', { method: 'india_compliance.gst_india.doctype.gst_hsn_code.gst_hsn_code.update_taxes_in_item_master', callback: function(r) { if(r.message){ - frappe.show_alert(__('Item taxes updated')); + frappe.show_alert(__('Items with this HSN code will be updated shortly')); } } }); diff --git a/india_compliance/gst_india/doctype/gst_hsn_code/gst_hsn_code.py b/india_compliance/gst_india/doctype/gst_hsn_code/gst_hsn_code.py index ed9ab17f09..5ff47a1482 100644 --- a/india_compliance/gst_india/doctype/gst_hsn_code/gst_hsn_code.py +++ b/india_compliance/gst_india/doctype/gst_hsn_code/gst_hsn_code.py @@ -37,6 +37,8 @@ def update_item_document(taxes, hsn_code): "item_tax_template": tax.item_tax_template, "tax_category": tax.tax_category, "valid_from": tax.valid_from, + "minimum_net_rate": tax.minimum_net_rate, + "maximum_net_rate": tax.maximum_net_rate, }, ) diff --git a/india_compliance/gst_india/doctype/gst_inward_supply/gst_inward_supply.json b/india_compliance/gst_india/doctype/gst_inward_supply/gst_inward_supply.json index 1da9c332f7..96091e8262 100644 --- a/india_compliance/gst_india/doctype/gst_inward_supply/gst_inward_supply.json +++ b/india_compliance/gst_india/doctype/gst_inward_supply/gst_inward_supply.json @@ -23,6 +23,8 @@ "supply_type", "classification", "is_reverse_charge", + "is_downloaded_from_2a", + "is_downloaded_from_2b", "section_break_16", "items", "section_break_ykls", @@ -394,11 +396,27 @@ "fieldname": "taxable_value", "fieldtype": "Float", "label": "Taxable Value" + }, + { + "default": "0", + "fieldname": "is_downloaded_from_2b", + "fieldtype": "Check", + "hidden": 1, + "label": "Downloaded from 2B", + "read_only": 1 + }, + { + "default": "0", + "fieldname": "is_downloaded_from_2a", + "fieldtype": "Check", + "hidden": 1, + "label": "Downloaded from 2A", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2024-11-16 16:23:13.402870", + "modified": "2024-12-12 17:28:52.668508", "modified_by": "Administrator", "module": "GST India", "name": "GST Inward Supply", diff --git a/india_compliance/gst_india/doctype/gst_settings/gst_settings.js b/india_compliance/gst_india/doctype/gst_settings/gst_settings.js index 378ff8f94e..c632e4fed8 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.js +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.js @@ -33,6 +33,8 @@ frappe.ui.form.on("GST Settings", { enable_e_invoice: set_auto_generate_e_waybill, auto_generate_e_invoice: set_auto_generate_e_waybill, generate_e_waybill_with_e_invoice: set_auto_generate_e_waybill, + auto_cancel_e_invoice: auto_cancel_e_invoice, + reason_for_e_invoice_cancellation: reason_for_e_invoice_cancellation, after_save(frm) { // sets latest values in frappe.boot for current user // other users will still need to refresh page @@ -115,4 +117,14 @@ function set_auto_generate_e_waybill(frm) { "auto_generate_e_waybill", frm.doc.auto_generate_e_invoice && frm.doc.generate_e_waybill_with_e_invoice ); + + frm.set_value("auto_cancel_e_waybill", frm.doc.auto_cancel_e_invoice) +} + +function auto_cancel_e_invoice(frm){ + frm.set_value("auto_cancel_e_waybill", frm.doc.auto_cancel_e_invoice) } + +function reason_for_e_invoice_cancellation(frm){ + frm.set_value("reason_for_e_waybill_cancellation", frm.doc.reason_for_e_invoice_cancellation) +} \ No newline at end of file diff --git a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json index d1edc45c7a..ec63fd885c 100644 --- a/india_compliance/gst_india/doctype/gst_settings/gst_settings.json +++ b/india_compliance/gst_india/doctype/gst_settings/gst_settings.json @@ -34,12 +34,16 @@ "column_break_10", "auto_generate_e_waybill", "e_waybill_threshold", + "auto_cancel_e_waybill", + "reason_for_e_waybill_cancellation", "e_invoice_section", "enable_e_invoice", - "auto_generate_e_invoice", "generate_e_waybill_with_e_invoice", - "fetch_e_invoice_details_from_gst_portal", + "auto_generate_e_invoice", + "auto_cancel_e_invoice", + "reason_for_e_invoice_cancellation", "column_break_17", + "fetch_e_invoice_details_from_gst_portal", "apply_e_invoice_only_for_selected_companies", "e_invoice_applicable_from", "e_invoice_applicable_companies", @@ -621,12 +625,48 @@ "fieldname": "fetch_e_invoice_details_from_gst_portal", "fieldtype": "Check", "label": "Fetch e-Invoice details from GST Portal" + }, + { + "default": "0", + "depends_on": "eval: doc.enable_e_waybill", + "description": "e-Waybill will be automatically cancelled after Sales Invoice cancellation if possible", + "fieldname": "auto_cancel_e_waybill", + "fieldtype": "Check", + "label": "Automatically Cancel e-Waybill on Invoice Cancellation", + "read_only_depends_on": "eval: doc.enable_e_invoice" + }, + { + "default": "0", + "depends_on": "eval: doc.enable_e_invoice", + "description": "e-Invoice will be automatically cancelled after Sales Invoice cancellation if possible", + "fieldname": "auto_cancel_e_invoice", + "fieldtype": "Check", + "label": "Automatically Cancel e-Invoice on Invoice Cancellation" + }, + { + "default": "Data Entry Mistake", + "depends_on": "eval: doc.enable_e_waybill", + "fieldname": "reason_for_e_waybill_cancellation", + "fieldtype": "Select", + "label": "Default Reason for e-Waybill Cancellation", + "mandatory_depends_on": "eval: doc.auto_cancel_e_waybill", + "options": "Duplicate\nOrder Cancelled\nData Entry Mistake", + "read_only_depends_on": "eval: doc.enable_e_invoice" + }, + { + "default": "Data Entry Mistake", + "depends_on": "eval: doc.enable_e_invoice", + "fieldname": "reason_for_e_invoice_cancellation", + "fieldtype": "Select", + "label": "Default Reason for e-Invoice Cancellation", + "mandatory_depends_on": "eval: doc.auto_cancel_e_invoice", + "options": "Duplicate\nOrder Cancelled\nData Entry Mistake" } ], "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2024-10-17 17:30:39.135632", + "modified": "2024-12-12 19:09:07.185191", "modified_by": "Administrator", "module": "GST India", "name": "GST Settings", diff --git a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js index 38d32a0488..a28392deb0 100644 --- a/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js +++ b/india_compliance/gst_india/doctype/gstr_1_beta/gstr_1_beta.js @@ -657,7 +657,12 @@ class GSTR1 { } filter_detailed_view = async (fieldname, value) => { - await this.filter_group.push_new_filter([DOCTYPE, fieldname, "=", value]); + await this.filter_group.add_or_remove_filter([ + DOCTYPE, + fieldname, + "=", + value.trim(), + ]); this.filter_group.apply(); }; @@ -1144,10 +1149,10 @@ class TabManager { args[2]?.indent == 0 ? `${value}` : isDescriptionCell - ? ` + ? `

${value}

` - : value; + : value; return value; } @@ -1902,9 +1907,9 @@ class FiledTab extends GSTR1_TabManager { const { include_uploaded, delete_missing } = dialog ? dialog.get_values() : { - include_uploaded: true, - delete_missing: false, - }; + include_uploaded: true, + delete_missing: false, + }; const doc = me.instance.frm.doc; @@ -2148,7 +2153,7 @@ class ReconcileTab extends FiledTab { }); } - get_creation_time_string() { } // pass + get_creation_time_string() {} // pass get_detail_view_column() { return [ @@ -2222,8 +2227,8 @@ class ErrorsTab extends TabManager { ]; } - setup_actions() { } - set_creation_time_string() { } + setup_actions() {} + set_creation_time_string() {} refresh_data(data) { data = data.error_report; @@ -2479,10 +2484,18 @@ class FileGSTR1Dialog { return ` ${description} - ${format_currency(liability.total_igst_amount)} - ${format_currency(liability.total_cgst_amount)} - ${format_currency(liability.total_sgst_amount)} - ${format_currency(liability.total_cess_amount)} + ${format_currency( + liability.total_igst_amount + )} + ${format_currency( + liability.total_cgst_amount + )} + ${format_currency( + liability.total_sgst_amount + )} + ${format_currency( + liability.total_cess_amount + )} `; } @@ -2590,8 +2603,7 @@ class GSTR1Action extends FileGSTR1Dialog { const draft_invoices = this.frm.gstr1.data.books["Document Issued"]?.filter( row => row.draft_count > 0 ); - if (!draft_invoices?.length) - return upload(); + if (!draft_invoices?.length) return upload(); frappe.confirm( __( @@ -2860,7 +2872,7 @@ function is_gstr1_api_enabled() { } function patch_set_indicator(frm) { - frm.toolbar.set_indicator = function () { }; + frm.toolbar.set_indicator = function () {}; } async function set_default_company_gstin(frm) { diff --git a/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py b/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py index 00906762d8..363e535398 100644 --- a/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py +++ b/india_compliance/gst_india/doctype/gstr_3b_report/gstr_3b_report.py @@ -295,6 +295,7 @@ def _get_tax_amount(account_type): & boe.docstatus.eq(1) & boe_taxes.gst_tax_type.eq(account_type) ) + .where(boe_taxes.parenttype == "Bill of Entry") .run() )[0][0] or 0 diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js index d4aaaaaf8e..b8b37a27f7 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.js @@ -3,6 +3,7 @@ frappe.provide("purchase_reconciliation_tool"); +const DOCTYPE = "Purchase Reconciliation Tool"; const tooltip_info = { purchase_period: "Returns purchases during this period where no match is found.", inward_supply_period: @@ -16,12 +17,13 @@ const ALERT_HTML = `
You have missing GSTR-2B downloads
- ${api_enabled - ? ` + ${ + api_enabled + ? ` Download 2B ` - : "" - } + : "" + } `; @@ -61,7 +63,7 @@ async function add_gstr2b_alert(frm) { }); } -frappe.ui.form.on("Purchase Reconciliation Tool", { +frappe.ui.form.on(DOCTYPE, { async setup(frm) { patch_set_active_tab(frm); new india_compliance.quick_info_popover(frm, tooltip_info); @@ -69,6 +71,8 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { await frappe.require("purchase_reconciliation_tool.bundle.js"); frm.trigger("company"); frm.purchase_reconciliation_tool = new PurchaseReconciliationTool(frm); + + frm.events.handle_download_failure(frm); }, onload(frm) { @@ -98,28 +102,30 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { frm.save(); }); + const action_group = __("Actions"); + // add custom buttons api_enabled ? frm.add_custom_button(__("Download 2A/2B"), () => new ImportDialog(frm)) : frm.add_custom_button( - __("Upload 2A/2B"), - () => new ImportDialog(frm, false) - ); + __("Upload 2A/2B"), + () => new ImportDialog(frm, false) + ); if (!frm.purchase_reconciliation_tool?.data?.length) return; if (frm.get_active_tab()?.df.fieldname == "invoice_tab") { frm.add_custom_button( __("Unlink"), () => unlink_documents(frm), - __("Actions") + action_group ); - frm.add_custom_button(__("dropdown-divider"), () => { }, __("Actions")); + frm.add_custom_button(__("dropdown-divider"), () => {}, action_group); } ["Accept", "Pending", "Ignore"].forEach(action => frm.add_custom_button( __(action), () => apply_action(frm, action), - __("Actions") + action_group ) ); frm.$wrapper @@ -132,10 +138,16 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { ); // move actions button next to filters - for (let button of $(".custom-actions .inner-group-button")) { - if (button.innerText?.trim() != "Actions") continue; + for (const group_div of $(".custom-actions .inner-group-button")) { + const btn_label = group_div.querySelector("button").innerText?.trim(); + if (btn_label != action_group) continue; + $(".custom-button-group .inner-group-button").remove(); - $(button).appendTo($(".custom-button-group")); + + // to hide `Actions` button group on smaller screens + $(group_div).addClass("hidden-md"); + + $(group_div).appendTo($(".custom-button-group")); } }, @@ -192,8 +204,8 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { method == "update_api_progress" ? __("Fetching data from GSTN") : __("Updating Inward Supply for Return Period {0}", [ - data.return_period, - ]); + data.return_period, + ]); frm.dashboard.show_progress( "Import GSTR Progress", @@ -220,6 +232,17 @@ frappe.ui.form.on("Purchase Reconciliation Tool", { } }); }, + + handle_download_failure(frm) { + frappe.realtime.on("gstr_2a_2b_download_failed", message => { + frm.dashboard.hide(); + frappe.msgprint({ + title: __("2A/2B Download Failed"), + message: message.error, + indicator: "red", + }); + }); + }, }); class PurchaseReconciliationTool { @@ -252,7 +275,7 @@ class PurchaseReconciliationTool { if (this.rendered_data == this.filtered_data) return; this._tabs.forEach(tab => { - this.tabs[`${tab}_tab`].refresh(this[`get_${tab}_data`]()); + this.tabs[`${tab}_tab`].datatable?.refresh(this[`get_${tab}_data`]()); }); this.rendered_data = this.filtered_data; @@ -308,7 +331,7 @@ class PurchaseReconciliationTool { setup_filter_button() { this.filter_group = new india_compliance.FilterGroup({ - doctype: "Purchase Reconciliation Tool", + doctype: DOCTYPE, parent: this.$wrapper.find(".form-tabs-list"), filter_options: { fieldname: "supplier_name", @@ -381,7 +404,7 @@ class PurchaseReconciliationTool { }, ]; - fields.forEach(field => (field.parent = "Purchase Reconciliation Tool")); + fields.forEach(field => (field.parent = DOCTYPE)); return fields; } @@ -422,7 +445,7 @@ class PurchaseReconciliationTool { render_data_tables() { this._tabs.forEach(tab => { - this.tabs[`${tab}_tab`] = new india_compliance.DataTableManager({ + this.tabs[`${tab}_tab`].datatable = new india_compliance.DataTableManager({ $wrapper: this.tab_group.get_field(`${tab}_data`).$wrapper, columns: this[`get_${tab}_columns`](), data: this[`get_${tab}_data`](), @@ -436,20 +459,20 @@ class PurchaseReconciliationTool { set_listeners() { const me = this; - this.tabs.invoice_tab.$datatable.on("click", ".btn.eye", function (e) { + this.tabs.invoice_tab.datatable.$datatable.on("click", ".btn.eye", function (e) { const row = me.mapped_invoice_data[$(this).attr("data-name")]; me.dm = new DetailViewDialog(me.frm, row); }); - this.tabs.supplier_tab.$datatable.on("click", ".btn.download", function (e) { - const row = me.tabs.supplier_tab.data.find( + this.tabs.supplier_tab.datatable.$datatable.on("click", ".btn.download", function (e) { + const row = me.tabs.supplier_tab.datatable.data.find( r => r.supplier_gstin === $(this).attr("data-name") ); me.export_data(row); }); - this.tabs.supplier_tab.$datatable.on("click", ".btn.envelope", function (e) { - const row = me.tabs.supplier_tab.data.find( + this.tabs.supplier_tab.datatable.$datatable.on("click", ".btn.envelope", function (e) { + const row = me.tabs.supplier_tab.datatable.data.find( r => r.supplier_gstin === $(this).attr("data-name") ); me.dm = new EmailDialog(me.frm, row); @@ -468,21 +491,18 @@ class PurchaseReconciliationTool { Object.keys(filter_map).forEach(tab => { Object.keys(filter_map[tab]).forEach(selector => { - this.tabs[`${tab}_tab`].$datatable.on( + this.tabs[`${tab}_tab`].datatable.$datatable.on( "click", selector, async function (e) { e.preventDefault(); - const value = $(this).text().trim(); - const field = filter_map[tab][selector]; - await me.filter_group.push_new_filter([ - "Purchase Reconciliation Tool", - field, + await me.filter_group.add_or_remove_filter([ + DOCTYPE, + filter_map[tab][selector], "=", - value, + $(this).text().trim(), ]); - me.filter_group.apply(); } ); @@ -921,8 +941,9 @@ class DetailViewDialog { ? ["GST Inward Supply"] : ["Purchase Invoice", "Bill of Entry"], - read_only_depends_on: `eval: ${this.missing_doctype == "GST Inward Supply" - }`, + read_only_depends_on: `eval: ${ + this.missing_doctype == "GST Inward Supply" + }`, onchange: () => { const doctype = this.dialog.get_value("doctype"); @@ -1638,7 +1659,7 @@ purchase_reconciliation_tool.link_documents = async function ( async function unlink_documents(frm, selected_rows) { if (frm.get_active_tab()?.df.fieldname != "invoice_tab") return; const { invoice_tab } = frm.purchase_reconciliation_tool.tabs; - if (!selected_rows) selected_rows = invoice_tab.get_checked_items(); + if (!selected_rows) selected_rows = invoice_tab.datatable.get_checked_items(); if (!selected_rows.length) return frappe.show_alert({ @@ -1692,7 +1713,7 @@ function apply_action(frm, action, selected_rows) { if (!active_tab) return; const tab = frm.purchase_reconciliation_tool.tabs[active_tab]; - if (!selected_rows) selected_rows = tab.get_checked_items(); + if (!selected_rows) selected_rows = tab.datatable.get_checked_items(); // get affected rows const { filtered_data, data } = frm.purchase_reconciliation_tool; @@ -1751,7 +1772,7 @@ function apply_action(frm, action, selected_rows) { } function after_successful_action(tab) { - if (tab) tab.clear_checked_items(); + if (tab) tab.datatable.clear_checked_items(); frappe.show_alert({ message: "Action applied successfully", indicator: "green", diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py index 3c9cf1ad9a..e9dc84c188 100644 --- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py +++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_reconciliation_tool.py @@ -267,6 +267,9 @@ def link_documents(self, purchase_invoice_name, inward_supply_name, link_doctype inward_supplies.append(inward_supply_name) self.db_set("is_modified", 1) + self.set_reconciliation_status( + link_doctype, [purchase_invoice_name], "Match Found" + ) return self.ReconciledData.get(purchases, inward_supplies) @@ -460,11 +463,20 @@ def download_gstr( if not periods: return - if return_type == ReturnType.GSTR2A: - return download_gstr_2a(company_gstin, periods, gst_categories) + try: + if return_type == ReturnType.GSTR2A: + return download_gstr_2a(company_gstin, periods, gst_categories) - if return_type == ReturnType.GSTR2B: - return download_gstr_2b(company_gstin, periods) + if return_type == ReturnType.GSTR2B: + return download_gstr_2b(company_gstin, periods) + + except Exception as e: + frappe.publish_realtime( + "gstr_2a_2b_download_failed", + {"error": str(e)}, + user=frappe.session.user, + doctype="Purchase Reconciliation Tool", + ) def get_periods_to_download(company_gstin, return_type, periods): diff --git a/india_compliance/gst_india/overrides/company.py b/india_compliance/gst_india/overrides/company.py index 554754ade2..8973d942a1 100644 --- a/india_compliance/gst_india/overrides/company.py +++ b/india_compliance/gst_india/overrides/company.py @@ -238,6 +238,7 @@ def create_default_company_account( } ) account.flags.ignore_permissions = True + account.flags.ignore_root_company_validation = True account.insert(ignore_if_duplicate=True) if default_fieldname and not frappe.db.get_value( diff --git a/india_compliance/gst_india/overrides/item.py b/india_compliance/gst_india/overrides/item.py index c69d6d3b87..c983712e7d 100644 --- a/india_compliance/gst_india/overrides/item.py +++ b/india_compliance/gst_india/overrides/item.py @@ -43,5 +43,7 @@ def set_taxes_from_hsn_code(doc): "item_tax_template": tax.item_tax_template, "tax_category": tax.tax_category, "valid_from": tax.valid_from, + "minimum_net_rate": tax.minimum_net_rate, + "maximum_net_rate": tax.maximum_net_rate, }, ) diff --git a/india_compliance/gst_india/overrides/payment_entry.py b/india_compliance/gst_india/overrides/payment_entry.py index b3dc948061..1dd7c35b77 100644 --- a/india_compliance/gst_india/overrides/payment_entry.py +++ b/india_compliance/gst_india/overrides/payment_entry.py @@ -332,6 +332,7 @@ def get_advance_payment_entries_for_regional( party_account, order_doctype, order_list=None, + default_advance_account=None, include_unallocated=True, against_all_orders=False, limit=None, @@ -347,6 +348,7 @@ def get_advance_payment_entries_for_regional( party_account=party_account, order_doctype=order_doctype, order_list=order_list, + default_advance_account=default_advance_account, include_unallocated=include_unallocated, against_all_orders=against_all_orders, limit=limit, diff --git a/india_compliance/gst_india/overrides/sales_invoice.py b/india_compliance/gst_india/overrides/sales_invoice.py index 65b53e4774..4619b30444 100644 --- a/india_compliance/gst_india/overrides/sales_invoice.py +++ b/india_compliance/gst_india/overrides/sales_invoice.py @@ -1,6 +1,7 @@ import frappe from frappe import _, bold -from frappe.utils import flt, fmt_money +from frappe.desk.form.load import run_onload +from frappe.utils import add_days, days_diff, flt, fmt_money from india_compliance.gst_india.overrides.payment_entry import get_taxes_summary from india_compliance.gst_india.overrides.transaction import ( @@ -21,10 +22,14 @@ validate_invoice_number, ) from india_compliance.gst_india.utils.e_invoice import ( + _cancel_e_invoice, get_e_invoice_info, validate_e_invoice_applicability, ) -from india_compliance.gst_india.utils.e_waybill import get_e_waybill_info +from india_compliance.gst_india.utils.e_waybill import ( + _cancel_e_waybill, + get_e_waybill_info, +) from india_compliance.gst_india.utils.transaction_data import ( validate_unique_hsn_and_uom, ) @@ -182,6 +187,7 @@ def on_submit(doc, method=None): def before_cancel(doc, method=None): + cancel_e_waybill_e_invoice(doc) if ignore_gst_validations(doc): return @@ -206,6 +212,50 @@ def before_cancel(doc, method=None): ) +def cancel_e_waybill_e_invoice(doc, method=None): + gst_settings = frappe.get_cached_doc("GST Settings") + + if not is_api_enabled(gst_settings): + return + + def auto_cancel(cancel_func, action_type): + run_onload(doc) + + if action_type == "e_invoice": + generated_on = ( + doc.get_onload().get("e_invoice_info", {}).get("acknowledged_on") + ) + reason = gst_settings.reason_for_e_invoice_cancellation + + else: + generated_on = doc.get_onload().get("e_waybill_info", {}).get("created_on") + reason = gst_settings.reason_for_e_waybill_cancellation + + if not generated_on or days_diff(add_days(generated_on, 1), generated_on) > 1: + return + + values = frappe._dict( + { + "irn": doc.irn or "", + "reason": reason, + "ewaybill": doc.ewaybill or "", + "remark": "", + } + ) + cancel_func(doc, values) + + if doc.irn and gst_settings.enable_e_invoice and gst_settings.auto_cancel_e_invoice: + auto_cancel(_cancel_e_invoice, "e_invoice") + return + + if ( + doc.ewaybill + and gst_settings.enable_e_waybill + and gst_settings.auto_cancel_e_waybill + ): + auto_cancel(_cancel_e_waybill, "e_waybill") + + def is_e_waybill_applicable(doc, gst_settings=None): if not gst_settings: gst_settings = frappe.get_cached_doc("GST Settings") diff --git a/india_compliance/gst_india/overrides/subcontracting_transaction.py b/india_compliance/gst_india/overrides/subcontracting_transaction.py index 4c9f9db280..aab00c6b72 100644 --- a/india_compliance/gst_india/overrides/subcontracting_transaction.py +++ b/india_compliance/gst_india/overrides/subcontracting_transaction.py @@ -205,20 +205,24 @@ def validate(doc, method=None): def before_save(doc, method=None): - if ignore_gst_validations(doc): + if not is_e_waybill_applicable(doc): + doc.taxes_and_charges = "" + doc.taxes = [] return - validate_doc_references(doc) + for row in doc.taxes: + if row.charge_type == "Actual": + frappe.throw( + _( + "Tax Row #{0}: Charge Type cannot be {1}. Try setting it to 'On Net Total' or 'On Item Quantity'." + ).format(row.idx, bold(row.charge_type)) + ) -def before_submit(doc, method=None): +def validate_doc_references(doc, method=None): if ignore_gst_validations(doc): return - validate_doc_references(doc) - - -def validate_doc_references(doc): is_return_material_transfer = ( doc.doctype == "Stock Entry" and doc.purpose == "Material Transfer" diff --git a/india_compliance/gst_india/overrides/test_purchase_invoice.py b/india_compliance/gst_india/overrides/test_purchase_invoice.py index 015ae0cac5..cc887c16b5 100644 --- a/india_compliance/gst_india/overrides/test_purchase_invoice.py +++ b/india_compliance/gst_india/overrides/test_purchase_invoice.py @@ -79,8 +79,9 @@ def test_validate_invoice_length(self): setattr(pinv, "__newname", "INV/2022/00001/asdfsadg") # NOQA pinv.meta.autoname = "prompt" - self.assertRaisesRegex( - frappe.exceptions.ValidationError, + pinv.save() + + self.assertEqual( + frappe.parse_json(frappe.message_log[-1]).get("message"), "Transaction Name must be 16 characters or fewer to meet GST requirements", - pinv.save, ) diff --git a/india_compliance/gst_india/overrides/test_sales_invoice.py b/india_compliance/gst_india/overrides/test_sales_invoice.py index da2c3285f8..caaadaaf96 100644 --- a/india_compliance/gst_india/overrides/test_sales_invoice.py +++ b/india_compliance/gst_india/overrides/test_sales_invoice.py @@ -16,7 +16,9 @@ def test_validate_invoice_number(self): "PI2021 - 001", ] for name in invalid_names: - doc = frappe._dict(name=name, posting_date=posting_date) + doc = frappe._dict( + name=name, posting_date=posting_date, doctype="Sales Invoice" + ) self.assertRaises(frappe.ValidationError, validate_invoice_number, doc) valid_names = [ diff --git a/india_compliance/gst_india/overrides/test_subcontracting_transaction.py b/india_compliance/gst_india/overrides/test_subcontracting_transaction.py index 2fcb536d38..ed61d4243f 100644 --- a/india_compliance/gst_india/overrides/test_subcontracting_transaction.py +++ b/india_compliance/gst_india/overrides/test_subcontracting_transaction.py @@ -1,7 +1,7 @@ import re import frappe -from frappe.tests import IntegrationTestCase, change_settings +from frappe.tests import IntegrationTestCase from erpnext.controllers.subcontracting_controller import ( get_materials_from_supplier, make_rm_stock_entry, @@ -102,25 +102,14 @@ def make_item(item_code=None, properties=None): def create_purchase_order(**args): - po_dict = { - "doctype": "Purchase Order", - "supplier": args.get("supplier") or "_Test Registered Supplier", - "is_subcontracted": 1, - "items": args.get("items"), - "supplier_warehouse": "Finished Goods - _TIRC", - "do_not_save": 1, - "do_not_submit": 1, - } - - po = create_transaction(**po_dict) - - if po.is_subcontracted: - supp_items = po.get("supplied_items") - for d in supp_items: - if not d.reserve_warehouse: - d.reserve_warehouse = "Stores - _TIRC" + args.update( + { + "doctype": "Purchase Order", + "is_subcontracted": 1, + } + ) - return po.submit() + return create_transaction(**args) def make_stock_transfer_entry(**args): @@ -145,8 +134,10 @@ def make_stock_transfer_entry(**args): ste_dict = make_rm_stock_entry(args.sco_no, items) ste_dict.update( { - "bill_from_address": "_Test Indian Registered Company-Billing", - "bill_to_address": "_Test Registered Supplier-Billing", + "bill_from_address": args.bill_from_address + or "_Test Indian Registered Company-Billing", + "bill_to_address": args.bill_to_address + or "_Test Registered Supplier-Billing", } ) @@ -156,6 +147,38 @@ def make_stock_transfer_entry(**args): return doc.submit() +def make_stock_entry(**args): + items = [ + { + "item_code": "_Test Trading Goods 1", + "qty": 1, + "s_warehouse": args.get("from_warehouse") or "Stores - _TIRC", + "t_warehouse": args.get("to_warehouse") or "Finished Goods - _TIRC", + "amount": 100, + } + ] + se = frappe.new_doc("Stock Entry") + se.update( + { + "purpose": args.get("purpose") or "Material Receipt", + "stock_entry_type": args.get("purpose") or "Material Receipt", + "company": args.get("company") or "_Test Indian Registered Company", + "items": args.get("items") or items, + } + ) + + return se + + +SERVICE_ITEM = { + "item_code": "Subcontracted Service Item 1", + "qty": 10, + "rate": 100, + "fg_item": "Subcontracted Item SA1", + "fg_item_qty": 10, +} + + class TestSubcontractingTransaction(IntegrationTestCase): @classmethod def setUpClass(cls): @@ -165,6 +188,15 @@ def setUpClass(cls): make_subcontracted_items() make_boms() + frappe.db.set_single_value( + "GST Settings", + { + "enable_api": 1, + "enable_e_waybill": 1, + "enable_e_waybill_for_sc": 1, + }, + ) + def _create_stock_entry(self, doc_args): """Generate Stock Entry to test e-Waybill functionalities""" doc_args.update({"doctype": "Stock Entry"}) @@ -176,7 +208,6 @@ def test_create_and_update_stock_entry(self): # Create a subcontracting transaction args = { "stock_entry_type": "Send to Subcontractor", - "purpose": "Send to Subcontractor", "bill_from_address": "_Test Indian Registered Company-Billing", "bill_to_address": "_Test Registered Supplier-Billing", "items": [ @@ -187,11 +218,9 @@ def test_create_and_update_stock_entry(self): "s_warehouse": "Finished Goods - _TIRC", "t_warehouse": "Goods In Transit - _TIRC", "amount": 100, - "taxable_value": 100, } ], "company": "_Test Indian Registered Company", - "base_grand_total": 100, } stock_entry = self._create_stock_entry(args) @@ -203,19 +232,88 @@ def test_create_and_update_stock_entry(self): self.assertEqual(stock_entry.select_print_heading, "Credit Note") + def test_for_unregistered_company(self): + po = create_purchase_order( + company="_Test Indian Unregistered Company", + supplier_warehouse="Finished Goods - _TIUC", + **SERVICE_ITEM, + ) + + sco = create_subcontracting_order(po_name=po.name) + self.assertEqual(sco.total_taxes, None) + + rm_items = get_rm_items(sco.supplied_items) + args = { + "sco_no": sco.name, + "rm_items": rm_items, + "bill_from_address": "_Test Indian Unregistered Company-Billing", + "bill_to_address": "_Test Unregistered Supplier-Billing", + } + se = make_stock_transfer_entry(**args) + self.assertEqual(se.total_taxes, 0.0) + + scr = make_subcontracting_receipt(sco.name) + scr.submit() + self.assertEqual(scr.total_taxes, 0.0) + + def test_stock_entry_for_material_receipt(self): + se = make_stock_entry() + se.save() + + self.assertEqual(se.total_taxes, None) + + def test_subcontracting_validations(self): + po = create_purchase_order( + **SERVICE_ITEM, supplier_warehouse="Finished Goods - _TIRC" + ) + sco = create_subcontracting_order(po_name=po.name) + + rm_items = get_rm_items(sco.supplied_items) + make_stock_transfer_entry(sco_no=sco.name, rm_items=rm_items) + + scr = make_subcontracting_receipt(sco.name) + scr.save() + + scr.billing_address = None + self.assertRaisesRegex( + frappe.ValidationError, + re.compile(r"(to ensure Company GSTIN is fetched in the transaction.$)"), + scr.save, + ) + + scr.reload() + self.assertEqual(scr.total_taxes, 252.0) + + def test_standalone_stock_entry(self): + purpose = "Send to Subcontractor" + se = make_stock_entry(purpose=purpose) + + self.assertRaisesRegex( + frappe.ValidationError, + re.compile(r"(to ensure Company GSTIN is fetched in the transaction.$)"), + se.save, + ) + + se.bill_from_address = "_Test Indian Registered Company-Billing" + + self.assertRaisesRegex( + frappe.ValidationError, + re.compile(r"(.*is a mandatory field for GST Transactions.*)"), + se.save, + ) + + se.bill_to_address = "_Test Registered Supplier-Billing" + + se.save() + def test_validation_for_doc_references(self): - service_item = [ - { - "warehouse": "Stores - _TIRC", - "item_code": "Subcontracted Service Item 1", - "qty": 10, - "rate": 100, - "fg_item": "Subcontracted Item SA1", - "fg_item_qty": 10, - } - ] + from india_compliance.gst_india.overrides.subcontracting_transaction import ( + get_stock_entry_references, + ) - po = create_purchase_order(items=service_item) + po = create_purchase_order( + **SERVICE_ITEM, supplier_warehouse="Finished Goods - _TIRC" + ) sco = create_subcontracting_order(po_name=po.name) rm_items = get_rm_items(sco.supplied_items) @@ -227,6 +325,7 @@ def test_validation_for_doc_references(self): return_se.save() scr = make_subcontracting_receipt(sco.name) + scr.save() scr.submit() self.assertRaisesRegex( @@ -236,16 +335,25 @@ def test_validation_for_doc_references(self): ) return_se.reload() + + filters = { + "supplier": return_se.supplier, + "supplied_items": [d.item_code for d in return_se.items], + "subcontracting_orders": [return_se.subcontracting_order], + } + doc_references_data = get_stock_entry_references( + filters=filters, only_linked_references=True + ) + doc_references = [row[0] for row in doc_references_data] + + self.assertTrue(se.name in doc_references) + return_se.append( "doc_references", {"link_doctype": "Stock Entry", "link_name": se.name}, ) return_se.submit() - @change_settings( - "GST Settings", - {"enable_api": 1, "enable_e_waybill": 1, "enable_e_waybill_for_sc": 1}, - ) def test_validation_when_gstin_field_empty(self): service_item = [ { @@ -259,7 +367,12 @@ def test_validation_when_gstin_field_empty(self): ] po = create_purchase_order( - items=service_item, supplier="_Test Unregistered Supplier" + items=service_item, + supplier="_Test Unregistered Supplier", + supplier_warhouse="Finished Goods - _TIUC", ) - create_subcontracting_order(po_name=po.name) + sco = create_subcontracting_order(po_name=po.name, do_not_save=True) + sco.supplier_warehouse = "Finished Goods - _TIUC" + sco.save() + sco.submit() diff --git a/india_compliance/gst_india/overrides/test_transaction.py b/india_compliance/gst_india/overrides/test_transaction.py index 167efe78f5..829ad31ffb 100644 --- a/india_compliance/gst_india/overrides/test_transaction.py +++ b/india_compliance/gst_india/overrides/test_transaction.py @@ -309,7 +309,9 @@ def test_missing_hsn_code(self): self.assertRaisesRegex( frappe.exceptions.ValidationError, - re.compile(r"^(Please enter a valid HSN/SAC code for.*)$"), + re.compile( + r"^(HSN/SAC must exist and should be 6 or 8 digits long for.*)$" + ), doc.submit, ) @@ -324,7 +326,9 @@ def test_invalid_hsn_digits(self): doc.save() self.assertRaisesRegex( frappe.exceptions.ValidationError, - re.compile(r"^(Please enter a valid HSN/SAC code for.*)$"), + re.compile( + r"^(HSN/SAC must exist and should be 6 or 8 digits long for.*)$" + ), doc.submit, ) @@ -466,6 +470,44 @@ def test_taxable_value_with_charges_after_tax(self): doc.insert() self.assertDocumentEqual({"taxable_value": 100}, doc.items[0]) + def test_credit_note_without_quantity(self): + if self.doctype != "Sales Invoice": + return + + doc = create_transaction( + **self.transaction_details, is_return=True, do_not_save=True + ) + append_item(doc) + + for item in doc.items: + item.qty = 0 + item.rate = 0 + item.price_list_rate = 0 + + # Adding charges + doc.append( + "taxes", + { + "charge_type": "Actual", + "account_head": "Freight and Forwarding Charges - _TIRC", + "description": "Freight", + "tax_amount": 20, + "cost_center": "Main - _TIRC", + }, + ) + + # Adding taxes + _append_taxes( + doc, ("CGST", "SGST"), charge_type="On Previous Row Total", row_id=1 + ) + doc.insert() + + # Ensure correct taxable_value and gst details + for item in doc.items: + self.assertDocumentEqual( + {"taxable_value": 10, "cgst_amount": 0.9, "sgst_amount": 0.9}, item + ) + def test_validate_place_of_supply(self): doc = create_transaction(**self.transaction_details, do_not_save=True) doc.place_of_supply = "96-Others" @@ -589,7 +631,7 @@ def test_purchase_from_unregistered_supplier(self): def test_invalid_charge_type_as_actual(self): doc = create_transaction(**self.transaction_details, do_not_save=True) - _append_taxes(doc, ["CGST", "SGST"], charge_type="Actual", tax_amount=9) + _append_taxes(doc, ["CGST", "SGST"], charge_type="Actual", tax_amount=9, rate=0) self.assertRaisesRegex( frappe.exceptions.ValidationError, diff --git a/india_compliance/gst_india/overrides/transaction.py b/india_compliance/gst_india/overrides/transaction.py index e42d94912b..06eeba91bf 100644 --- a/india_compliance/gst_india/overrides/transaction.py +++ b/india_compliance/gst_india/overrides/transaction.py @@ -23,6 +23,7 @@ from india_compliance.gst_india.doctype.gstin.gstin import get_and_validate_gstin_status from india_compliance.gst_india.utils import ( get_all_gst_accounts, + get_gst_account_by_item_tax_template, get_gst_account_gst_tax_type_map, get_gst_accounts_by_type, get_hsn_settings, @@ -68,6 +69,7 @@ def update_taxable_values(doc): total_charges = 0 apportioned_charges = 0 tax_witholding_amount = 0 + has_no_qty_value = False if doc.taxes: if any( @@ -99,8 +101,10 @@ def update_taxable_values(doc): # base net total may be zero if invoice has zero rated items + shipping total_value = doc.base_net_total if doc.base_net_total else doc.total_qty + # credit note without item qty and value but with charges if not total_value: - return + total_value = len(doc.items) + has_no_qty_value = True for item in doc.items: item.taxable_value = item.base_net_amount @@ -108,7 +112,12 @@ def update_taxable_values(doc): if not total_charges: continue - proportionate_value = item.base_net_amount if doc.base_net_total else item.qty + if has_no_qty_value: + proportionate_value = 1 + elif doc.base_net_total: + proportionate_value = item.base_net_amount + else: + proportionate_value = item.qty applicable_charges = flt( proportionate_value * (total_charges / total_value), @@ -497,7 +506,7 @@ def validate_for_charge_type(self): ) if row.charge_type == "On Previous Row Total": - previous_row_references.add(row.row_id) + previous_row_references.add(flt(row.row_id)) # validating charge type "On Item Quantity" and non_cess_advol_account self.validate_charge_type_for_cess_non_advol_accounts(row) @@ -545,8 +554,10 @@ def validate_missing_accounts_in_item_tax_template(self): if not row.item_tax_template: continue + template_rows = get_gst_account_by_item_tax_template(row.item_tax_template) + for account in self.used_accounts: - if account in row.item_tax_rate: + if account in template_rows: continue frappe.msgprint( @@ -576,13 +587,14 @@ def validate_items(doc): items_with_duplicate_taxes = [] for row in doc.items: + item_key = row.item_code or row.item_name # Different Item Tax Templates should not be used for the same Item Code - if row.item_code not in item_tax_templates: - item_tax_templates[row.item_code] = row.item_tax_template + if item_key not in item_tax_templates: + item_tax_templates[item_key] = row.item_tax_template continue - if row.item_tax_template != item_tax_templates[row.item_code]: - items_with_duplicate_taxes.append(bold(row.item_code)) + if row.item_tax_template != item_tax_templates[item_key]: + items_with_duplicate_taxes.append(bold(item_key)) if items_with_duplicate_taxes: frappe.throw( @@ -725,9 +737,13 @@ def _validate_hsn_codes(doc, valid_hsn_length, throw=False, message=None): frappe.throw( _( "{0}" - "Please enter a valid HSN/SAC code for the following row numbers:" - "
{1}" - ).format(message or "", frappe.bold(", ".join(rows_with_invalid_hsn))), + "HSN/SAC must exist and should be {1} digits long" + " for the following row numbers:
{2}" + ).format( + message or "", + join_list_with_custom_separators(valid_hsn_length), + frappe.bold(", ".join(rows_with_invalid_hsn)), + ), title=_("Invalid HSN/SAC"), ) @@ -914,7 +930,12 @@ def get_gst_details(party_details, doctype, company, *, update_place_of_supply=F == party_details.get(party_gstin_field) ) # Internal transfer ) - or (is_sales_transaction and is_export_without_payment_of_gst(party_details)) + or ( + is_sales_transaction + and is_export_without_payment_of_gst( + frappe._dict({**party_details, "doctype": doctype}) + ) + ) or ( not is_sales_transaction and ( diff --git a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py index 6e4d88a2cf..aa6dd5470d 100644 --- a/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py +++ b/india_compliance/gst_india/report/gstr_3b_details/gstr_3b_details.py @@ -208,6 +208,7 @@ def get_itc_from_boe(self): & (boe.company == self.company) & (boe.company_gstin == self.company_gstin) ) + .where(boe_taxes.parenttype == "Bill of Entry") .groupby(boe.name) ) diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py index 37974602a1..5763d14143 100644 --- a/india_compliance/gst_india/utils/__init__.py +++ b/india_compliance/gst_india/utils/__init__.py @@ -568,6 +568,15 @@ def get_gst_accounts_by_tax_type(company, tax_type, throw=True): ) +@frappe.request_cache +def get_gst_account_by_item_tax_template(item_tax_template): + return frappe.get_all( + "Item Tax Template Detail", + filters={"parent": item_tax_template}, + pluck="tax_type", + ) + + def get_gst_account_gst_tax_type_map(): """ - Returns gst_account by tax_type for all the companies @@ -931,23 +940,26 @@ def validate_invoice_number(doc, throw=True): if not throw: return is_valid_length and is_valid_format + if is_valid_length and is_valid_format: + return + + title = _("Invalid GST Transaction Name") + if not is_valid_length: - frappe.throw( - _( - "Transaction Name must be 16 characters or fewer to meet GST requirements" - ), - title=_("Invalid GST Transaction Name"), + message = _( + "Transaction Name must be 16 characters or fewer to meet GST requirements" ) - - if not is_valid_format: - frappe.throw( - _( - "Transaction Name should start with an alphanumeric character and can" - " only contain alphanumeric characters, dash (-) and slash (/) to meet GST requirements" - ), - title=_("Invalid GST Transaction Name"), + else: + message = _( + "Transaction Name should start with an alphanumeric character and can" + " only contain alphanumeric characters, dash (-) and slash (/) to meet GST requirements" ) + if doc.doctype == "Sales Invoice": + frappe.throw(message, title=title) + + frappe.msgprint(message, title=title) + def handle_server_errors(settings, doc, document_type, error): if not doc.doctype == "Sales Invoice": diff --git a/india_compliance/gst_india/utils/e_invoice.py b/india_compliance/gst_india/utils/e_invoice.py index f1437bc7ea..d340e2db6c 100644 --- a/india_compliance/gst_india/utils/e_invoice.py +++ b/india_compliance/gst_india/utils/e_invoice.py @@ -331,6 +331,13 @@ def log_and_process_e_invoice_generation(doc, result, sandbox_mode=False, messag def cancel_e_invoice(docname, values): doc = load_doc("Sales Invoice", docname, "cancel") values = frappe.parse_json(values) + + _cancel_e_invoice(doc, values) + + return send_updated_doc(doc) + + +def _cancel_e_invoice(doc, values): validate_if_e_invoice_can_be_cancelled(doc) if doc.get("ewaybill"): @@ -349,7 +356,6 @@ def cancel_e_invoice(docname, values): ) doc.cancel() - return send_updated_doc(doc) def log_and_process_e_invoice_cancellation(doc, values, result, message): @@ -760,11 +766,7 @@ def set_party_address_details(self): self.doc.company_address, validate_gstin=True ) - ship_to_address = ( - self.doc.port_address - if (is_foreign_doc(self.doc) and self.doc.port_address) - else self.doc.shipping_address_name - ) + ship_to_address = self.doc.shipping_address_name # Defaults self.shipping_address = None diff --git a/india_compliance/gst_india/utils/gstr_2/gstr.py b/india_compliance/gst_india/utils/gstr_2/gstr.py index 890285f761..7677e0a767 100644 --- a/india_compliance/gst_india/utils/gstr_2/gstr.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr.py @@ -47,8 +47,7 @@ def __init__(self, company, gstin, return_period, data, gen_date_2b): self.setup() def setup(self): - self.existing_transaction = {} - pass + self.existing_transaction = self.get_existing_transaction() def create_transactions(self, category, suppliers): if not suppliers: @@ -75,17 +74,14 @@ def create_transactions(self, category, suppliers): if transaction.get("unique_key") in self.existing_transaction: self.existing_transaction.pop(transaction.get("unique_key")) - self.delete_missing_transactions() - - def delete_missing_transactions(self): - """ - For GSTR2a, transactions are reflected immediately after it's pushed to GSTR-1. - At times, it may later be removed from GSTR-1. + self.handle_missing_transactions() - In such cases, we need to delete such unfilled transactions not present in the latest data. - """ + def handle_missing_transactions(self): return + def get_existing_transaction(self): + return {} + def get_all_transactions(self, category, suppliers): transactions = [] for supplier in suppliers: @@ -110,6 +106,7 @@ def get_transaction(self, category, supplier, invoice): classification=category.value, **self.get_supplier_details(supplier), **self.get_invoice_details(invoice), + **self.get_download_details(), items=self.get_transaction_items(invoice), ) @@ -134,6 +131,9 @@ def get_supplier_details(self, supplier): def get_invoice_details(self, invoice): return {} + def get_download_details(self): + return {} + def get_transaction_items(self, invoice): return [ self.get_transaction_item(frappe._dict(item)) diff --git a/india_compliance/gst_india/utils/gstr_2/gstr_2a.py b/india_compliance/gst_india/utils/gstr_2/gstr_2a.py index 7459f2da16..c96ffc58ff 100644 --- a/india_compliance/gst_india/utils/gstr_2/gstr_2a.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr_2a.py @@ -14,9 +14,9 @@ def map_date_format(date_str, source_format, target_format): class GSTR2a(GSTR): def setup(self): + super().setup() self.all_gstins = set() self.cancelled_gstins = {} - self.existing_transaction = self.get_existing_transaction() def get_existing_transaction(self): category = type(self).__name__[6:] @@ -37,7 +37,14 @@ def get_existing_transaction(self): for transaction in existing_transactions } - def delete_missing_transactions(self): + def handle_missing_transactions(self): + """ + For GSTR2a, transactions are reflected immediately after it's pushed to GSTR-1. + At times, it may later be removed from GSTR-1. + + In such cases, we need to delete such unfilled transactions not present in the latest data. + """ + if self.existing_transaction: for inward_supply_name in self.existing_transaction.values(): frappe.delete_doc("GST Inward Supply", inward_supply_name) @@ -60,6 +67,9 @@ def get_supplier_details(self, supplier): return supplier_details + def get_download_details(self): + return {"is_downloaded_from_2a": 1} + def update_gstins_list(self, supplier_details): self.all_gstins.add(supplier_details.get("supplier_gstin")) diff --git a/india_compliance/gst_india/utils/gstr_2/gstr_2b.py b/india_compliance/gst_india/utils/gstr_2/gstr_2b.py index b062cabc9d..bc8eeff44e 100644 --- a/india_compliance/gst_india/utils/gstr_2/gstr_2b.py +++ b/india_compliance/gst_india/utils/gstr_2/gstr_2b.py @@ -1,10 +1,65 @@ import frappe +from frappe.query_builder.functions import IfNull from india_compliance.gst_india.utils import parse_datetime from india_compliance.gst_india.utils.gstr_2.gstr import GSTR, get_mapped_value class GSTR2b(GSTR): + def get_existing_transaction(self): + category = type(self).__name__[6:] + + gst_is = frappe.qb.DocType("GST Inward Supply") + existing_transactions = ( + frappe.qb.from_(gst_is) + .select(gst_is.name, gst_is.supplier_gstin, gst_is.bill_no) + .where(gst_is.return_period_2b == self.return_period) + .where(gst_is.classification == category) + ).run(as_dict=True) + + return { + f"{transaction.get('supplier_gstin', '')}-{transaction.get('bill_no', '')}": transaction.get( + "name" + ) + for transaction in existing_transactions + } + + def handle_missing_transactions(self): + """ + For GSTR2b, only filed transactions are reported. They may be removed from GSTR-2b later + if marked as pending / rejected from IMS Dashboard. + + In such cases, + 1) we need to clear the return_period_2b as this could change in future. + 2) safer to clear delete them as well if no matching transactions are found (possibly rejected). + """ + if not self.existing_transaction: + return + + missing_transactions = list(self.existing_transaction.values()) + + # clear return_period_2b + inward_supply = frappe.qb.DocType("GST Inward Supply") + ( + frappe.qb.update(inward_supply) + .set(inward_supply.return_period_2b, "") + .set(inward_supply.is_downloaded_from_2b, 0) + .where(inward_supply.name.isin(missing_transactions)) + .run() + ) + + # delete unmatched transactions + unmatched_transactions = ( + frappe.qb.from_(inward_supply) + .select(inward_supply.name) + .where(inward_supply.name.isin(missing_transactions)) + .where(IfNull(inward_supply.link_name, "") == "") + .run(pluck=True) + ) + + for transaction_name in unmatched_transactions: + frappe.delete_doc("GST Inward Supply", transaction_name) + def get_transaction(self, category, supplier, invoice): transaction = super().get_transaction(category, supplier, invoice) transaction.return_period_2b = self.return_period @@ -19,6 +74,9 @@ def get_supplier_details(self, supplier): "sup_return_period": supplier.supprd, } + def get_download_details(self): + return {"is_downloaded_from_2b": 1} + def get_transaction_item(self, item): return { "item_number": item.num, diff --git a/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py b/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py index 4126583b3c..09c261d83f 100644 --- a/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py +++ b/india_compliance/gst_india/utils/gstr_2/test_gstr_2a.py @@ -115,6 +115,7 @@ def test_gstr2a_b2b(self): "gstr_3b_filled": 1, "gstr_1_filing_date": date(2019, 11, 18), "registration_cancel_date": date(2019, 8, 27), + "is_downloaded_from_2a": 1, }, doc, ) @@ -162,6 +163,7 @@ def test_gstr2a_b2ba(self): "gstr_3b_filled": 1, "gstr_1_filing_date": date(2020, 5, 12), "registration_cancel_date": date(2019, 8, 27), + "is_downloaded_from_2a": 1, }, doc, ) @@ -202,6 +204,7 @@ def test_gstr2a_cdn(self): "897ADG56RTY78956HYUG90BNHHIJK453GFTD99845672FDHHHSHGFH4567FG56TR" ), "irn_gen_date": date(2019, 12, 24), + "is_downloaded_from_2a": 1, }, doc, ) @@ -239,6 +242,7 @@ def test_gstr2a_cdna(self): "gstr_3b_filled": 1, "gstr_1_filing_date": date(2019, 11, 18), "registration_cancel_date": date(2019, 8, 27), + "is_downloaded_from_2a": 1, }, doc, ) @@ -261,6 +265,7 @@ def test_gstr2a_isd(self): "cgst": 20, "sgst": 20, "cess": 20, + "is_downloaded_from_2a": 1, }, doc, ) @@ -283,6 +288,7 @@ def test_gstr2a_impg(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2a": 1, }, doc, ) @@ -305,6 +311,7 @@ def test_gstr2a_impgsez(self): "cgst": 0, "sgst": 0, "cess": 0.5, + "is_downloaded_from_2a": 1, }, doc, ) diff --git a/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v3_0.py b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v3_0.py index 01538c8932..22b537fdcc 100644 --- a/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v3_0.py +++ b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v3_0.py @@ -67,6 +67,7 @@ def test_gstr2b_b2b(self): "cess": 0, } ], + "is_downloaded_from_2b": 1, }, doc, ) @@ -107,6 +108,7 @@ def test_gstr2b_b2ba(self): "cess": 0, } ], + "is_downloaded_from_2b": 1, }, doc, ) @@ -147,6 +149,7 @@ def test_gstr2b_cdnr(self): "cess": 0, } ], + "is_downloaded_from_2b": 1, }, doc, ) @@ -185,6 +188,7 @@ def test_gstr2b_cdnra(self): "cess": 0, } ], + "is_downloaded_from_2b": 1, }, doc, ) @@ -208,6 +212,7 @@ def test_gstr2b_isd(self): "cgst": 200, "sgst": 200, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -234,6 +239,7 @@ def test_gstr2b_isda(self): "cgst": 200, "sgst": 200, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -253,6 +259,7 @@ def test_gstr2b_impg(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2b": 1, }, doc, ) @@ -274,6 +281,7 @@ def test_gstr2b_impgsez(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2b": 1, }, doc, ) diff --git a/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v4_0.py b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v4_0.py index fead03e510..a32e7db70f 100644 --- a/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v4_0.py +++ b/india_compliance/gst_india/utils/gstr_2/test_gstr_2b_v4_0.py @@ -61,6 +61,7 @@ def test_gstr2b_b2b(self): "cgst": 0, "sgst": 0, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -95,6 +96,7 @@ def test_gstr2b_b2ba(self): "cgst": 0, "sgst": 0, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -129,6 +131,7 @@ def test_gstr2b_cdnr(self): "cgst": 0, "sgst": 0, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -161,6 +164,7 @@ def test_gstr2b_cdnra(self): "cgst": 0, "sgst": 0, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -184,6 +188,7 @@ def test_gstr2b_isd(self): "cgst": 200, "sgst": 200, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -210,6 +215,7 @@ def test_gstr2b_isda(self): "cgst": 200, "sgst": 200, "cess": 0, + "is_downloaded_from_2b": 1, }, doc, ) @@ -229,6 +235,7 @@ def test_gstr2b_impg(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2b": 1, }, doc, ) @@ -250,6 +257,7 @@ def test_gstr2b_impgsez(self): "taxable_value": 123.02, "igst": 123.02, "cess": 0.5, + "is_downloaded_from_2b": 1, }, doc, ) diff --git a/india_compliance/gst_india/utils/test_e_waybill.py b/india_compliance/gst_india/utils/test_e_waybill.py index 53bffa414f..e5ce3f7e79 100644 --- a/india_compliance/gst_india/utils/test_e_waybill.py +++ b/india_compliance/gst_india/utils/test_e_waybill.py @@ -15,7 +15,7 @@ from erpnext.controllers.sales_and_purchase_return import make_return_doc from india_compliance.gst_india.api_classes.base import BASE_URL -from india_compliance.gst_india.utils import load_doc +from india_compliance.gst_india.utils import load_doc, parse_datetime from india_compliance.gst_india.utils.e_invoice import ( retry_e_invoice_e_waybill_generation, ) @@ -1048,6 +1048,18 @@ def test_e_waybill_for_stock_entry(self): frappe.get_doc("e-Waybill Log", {"reference_name": stock_entry.name}), ) + stock_entry = load_doc("Stock Entry", stock_entry.name, "submit") + + e_waybill_info = stock_entry.get("__onload").e_waybill_info + + self.assertEqual( + e_waybill_info.valid_upto, + parse_datetime( + se_data.get("response_data").get("result").get("validUpto"), + day_first=True, + ), + ) + @change_settings("GST Settings", {"enable_e_waybill_for_sc": 1}) @responses.activate def test_e_waybill_for_stock_entry_same_gstin(self): @@ -1063,6 +1075,18 @@ def test_e_waybill_for_stock_entry_same_gstin(self): frappe.get_doc("e-Waybill Log", {"reference_name": stock_entry.name}), ) + stock_entry = load_doc("Stock Entry", stock_entry.name, "submit") + + e_waybill_info = stock_entry.get("__onload").e_waybill_info + + self.assertEqual( + e_waybill_info.valid_upto, + parse_datetime( + se_data.get("response_data").get("result").get("validUpto"), + day_first=True, + ), + ) + def update_dates_for_test_data(test_data): """Update dates in test data""" diff --git a/india_compliance/gst_india/utils/tests.py b/india_compliance/gst_india/utils/tests.py index ceb3bd8ae4..c773514397 100644 --- a/india_compliance/gst_india/utils/tests.py +++ b/india_compliance/gst_india/utils/tests.py @@ -108,6 +108,8 @@ def append_item(transaction, data=None, company_abbr="_TIRC"): "warehouse": f"Stores - {company_abbr}", "expense_account": f"Cost of Goods Sold - {company_abbr}", "taxable_value": data.taxable_value or 0, + "fg_item": data.fg_item, + "fg_item_qty": data.fg_item_qty, }, ) diff --git a/india_compliance/hooks.py b/india_compliance/hooks.py index 6adae4196b..c03d87911a 100644 --- a/india_compliance/hooks.py +++ b/india_compliance/hooks.py @@ -232,17 +232,22 @@ "Stock Entry": { "onload": "india_compliance.gst_india.overrides.subcontracting_transaction.onload", "validate": "india_compliance.gst_india.overrides.subcontracting_transaction.validate", - "before_submit": "india_compliance.gst_india.overrides.subcontracting_transaction.before_submit", + "before_save": "india_compliance.gst_india.overrides.subcontracting_transaction.before_save", + "before_submit": "india_compliance.gst_india.overrides.subcontracting_transaction.validate_doc_references", "after_mapping": "india_compliance.gst_india.overrides.subcontracting_transaction.after_mapping_stock_entry", }, "Subcontracting Order": { "validate": "india_compliance.gst_india.overrides.subcontracting_transaction.validate", + "before_save": "india_compliance.gst_india.overrides.subcontracting_transaction.before_save", "after_mapping": "india_compliance.gst_india.overrides.subcontracting_transaction.after_mapping_subcontracting_order", }, "Subcontracting Receipt": { "onload": "india_compliance.gst_india.overrides.subcontracting_transaction.onload", "validate": "india_compliance.gst_india.overrides.subcontracting_transaction.validate", - "before_save": "india_compliance.gst_india.overrides.subcontracting_transaction.before_save", + "before_save": [ + "india_compliance.gst_india.overrides.subcontracting_transaction.before_save", + "india_compliance.gst_india.overrides.subcontracting_transaction.validate_doc_references", + ], "before_mapping": "india_compliance.gst_india.overrides.subcontracting_transaction.before_mapping_subcontracting_receipt", }, "Supplier": { diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt index 42d31d02a7..f40bf933b2 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() #59 +execute:from india_compliance.gst_india.setup import create_custom_fields; create_custom_fields() #61 execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #10 execute:from india_compliance.income_tax_india.setup import create_custom_fields; create_custom_fields() #2 india_compliance.patches.post_install.remove_old_fields #2 @@ -55,6 +55,7 @@ india_compliance.patches.v14.update_item_gst_details_and_gst_trearment_in_bill_o india_compliance.patches.v14.update_default_auto_reconciliation_settings india_compliance.patches.v14.update_default_gstr1_settings india_compliance.patches.v14.stop_unnecessary_scheduled_jobs +india_compliance.patches.v14.set_reconciliation_status_for_manual_match india_compliance.patches.v14.add_match_found_in_purchase_reconciliation_status india_compliance.patches.v14.unset_inward_supply_link_for_cancelled_purchase india_compliance.patches.v14.delete_not_generated_gstr_import_log @@ -66,3 +67,4 @@ india_compliance.patches.v15.update_action_for_gst_inward_supply india_compliance.patches.v15.set_default_for_new_gst_category_notification india_compliance.patches.v15.make_e_invoice_log_extensible india_compliance.patches.v15.migrate_boe_taxes_to_ic_taxes +india_compliance.patches.v15.set_download_document_for_2a_2b diff --git a/india_compliance/patches/check_version_compatibility.py b/india_compliance/patches/check_version_compatibility.py index 2067317b31..fb12de4091 100644 --- a/india_compliance/patches/check_version_compatibility.py +++ b/india_compliance/patches/check_version_compatibility.py @@ -18,7 +18,7 @@ { "app_name": "ERPNext", "current_version": version.parse(erpnext.__version__), - "required_versions": {"version-14": "14.70.7", "version-15": "15.27.7"}, + "required_versions": {"version-14": "14.70.7", "version-15": "15.45.5"}, }, ] diff --git a/india_compliance/patches/post_install/improve_item_tax_template.py b/india_compliance/patches/post_install/improve_item_tax_template.py index 436c94a0cb..7df878f3b1 100644 --- a/india_compliance/patches/post_install/improve_item_tax_template.py +++ b/india_compliance/patches/post_install/improve_item_tax_template.py @@ -125,6 +125,7 @@ def create_or_update_item_tax_templates(companies): elif doc.gst_rate == 0: doc.gst_treatment = "Nil-Rated" + doc.flags.ignore_validate = True # eg: account_type validation doc.save() # create new templates for nil rated, exempted, non gst @@ -261,6 +262,7 @@ def remove_old_item_variant_settings(): if field.field_name in ("is_nil_exempt", "is_non_gst"): item_variant.fields.remove(field) + item_variant.flags.ignore_validate = True item_variant.save() diff --git a/india_compliance/patches/v14/set_reconciliation_status_for_manual_match.py b/india_compliance/patches/v14/set_reconciliation_status_for_manual_match.py new file mode 100644 index 0000000000..ae68c032f5 --- /dev/null +++ b/india_compliance/patches/v14/set_reconciliation_status_for_manual_match.py @@ -0,0 +1,26 @@ +import frappe + + +def execute(): + """ + Match status was not being updated when manually matched. This patch will update the reconciliation status. + """ + inward_supply = frappe.qb.DocType("GST Inward Supply") + docs = ( + frappe.qb.from_(inward_supply) + .select(inward_supply.link_doctype, inward_supply.link_name) + .where(inward_supply.link_doctype.isin(("Purchase Invoice", "Bill of Entry"))) + .where(inward_supply.action == "No Action") # status updated on action + .where(inward_supply.match_status == "Manual Match") + .run(as_dict=True) + ) + + docs_to_update = {} + + for doc in docs: + docs_to_update.setdefault(doc.link_doctype, []).append(doc.link_name) + + for doctype, doc_names in docs_to_update.items(): + frappe.db.set_value( + doctype, {"name": ("in", doc_names)}, "reconciliation_status", "Match Found" + ) diff --git a/india_compliance/patches/v15/migrate_boe_taxes_to_ic_taxes.py b/india_compliance/patches/v15/migrate_boe_taxes_to_ic_taxes.py index ad0cbe2ea5..36f36f9161 100644 --- a/india_compliance/patches/v15/migrate_boe_taxes_to_ic_taxes.py +++ b/india_compliance/patches/v15/migrate_boe_taxes_to_ic_taxes.py @@ -1,4 +1,6 @@ import frappe +from frappe.model.document import bulk_insert +from frappe.model.naming import _generate_random_string def execute(): @@ -8,16 +10,33 @@ def execute(): boe_taxes = frappe.qb.DocType("Bill of Entry Taxes") boe_taxes_docs = frappe.qb.from_(boe_taxes).select("*").run(as_dict=True) + ic_taxes_names = set( + frappe.get_all("India Compliance Taxes and Charges", pluck="name") + ) + ic_taxes = [] + for doc in boe_taxes_docs: ic_taxes_doc = frappe.get_doc( { **doc, "doctype": "India Compliance Taxes and Charges", - "name": None, + "name": set_name(doc.name, ic_taxes_names), "base_total": doc.total, } ) - ic_taxes_doc.insert(ignore_if_duplicate=True) + + ic_taxes.append(ic_taxes_doc) + + bulk_insert("India Compliance Taxes and Charges", ic_taxes) # Drop the old table frappe.db.delete("Bill of Entry Taxes") + + +def set_name(name, names): + new_name = name + while new_name in names: + new_name = _generate_random_string(10) + + names.add(new_name) + return new_name diff --git a/india_compliance/patches/v15/set_download_document_for_2a_2b.py b/india_compliance/patches/v15/set_download_document_for_2a_2b.py new file mode 100644 index 0000000000..b5eba2e0f2 --- /dev/null +++ b/india_compliance/patches/v15/set_download_document_for_2a_2b.py @@ -0,0 +1,18 @@ +import frappe + + +def execute(): + if frappe.db.has_column("GST Inward Supply", "is_downloaded_from_2a"): + # set "is_downloaded_from_2a" to "1" for all GST Inward Supply + frappe.db.set_value( + "GST Inward Supply", {"name": ["is", "set"]}, "is_downloaded_from_2a", 1 + ) + + if frappe.db.has_column("GST Inward Supply", "is_downloaded_from_2b"): + # set "is_downloaded_from_2b" to "1" where 2B return period is set + frappe.db.set_value( + "GST Inward Supply", + {"return_period_2b": ["is", "set"]}, + "is_downloaded_from_2b", + 1, + ) diff --git a/india_compliance/public/js/components/filter_group.js b/india_compliance/public/js/components/filter_group.js index 363a613452..3ea3b45bc9 100644 --- a/india_compliance/public/js/components/filter_group.js +++ b/india_compliance/public/js/components/filter_group.js @@ -33,7 +33,9 @@ FILTER_GROUP_BUTTON = $( ${__("Filter")} -