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")}
-