diff --git a/india_compliance/gst_india/api_classes/base.py b/india_compliance/gst_india/api_classes/base.py
index 9e2b00301a..cc333e5813 100644
--- a/india_compliance/gst_india/api_classes/base.py
+++ b/india_compliance/gst_india/api_classes/base.py
@@ -226,7 +226,6 @@ def is_ignored_error(self, response_json):
pass
def handle_http_code(self, status_code, response_json):
-
# GSP connectivity issues
if status_code == 401 or (
status_code == 403
@@ -265,22 +264,23 @@ def mask_sensitive_info(self, log):
output = log.output
data = log.data
request_body = data and data.get("body")
+ placeholder = "*****"
for key in self.SENSITIVE_INFO:
if key in request_headers:
- request_headers[key] = "*****"
+ request_headers[key] = placeholder
if output and key in output:
- output[key] = "*****"
+ output[key] = placeholder
if not data:
continue
if key in data:
- data[key] = "*****"
+ data[key] = placeholder
if request_body and key in request_body:
- request_body[key] = "*****"
+ request_body[key] = placeholder
def get_public_ip():
diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py
index 7291c79f36..13600ac5f1 100644
--- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py
+++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/__init__.py
@@ -349,6 +349,7 @@ def __init__(self, **kwargs):
def get_all(self, additional_fields=None, names=None, only_names=False):
query = self.get_query(additional_fields)
+ match_found = ("Reconciled", "Match Found")
if only_names and not names:
return
@@ -360,7 +361,7 @@ def get_all(self, additional_fields=None, names=None, only_names=False):
query = query.where(
(
(self.PI.posting_date[self.from_date : self.to_date])
- & (IfNull(self.PI.reconciliation_status, "") != "Reconciled")
+ & (IfNull(self.PI.reconciliation_status, "").notin(match_found))
)
| (self.PI.name.isin(names))
)
@@ -368,7 +369,7 @@ def get_all(self, additional_fields=None, names=None, only_names=False):
else:
query = query.where(
(self.PI.posting_date[self.from_date : self.to_date])
- & (IfNull(self.PI.reconciliation_status, "") != "Reconciled")
+ & (IfNull(self.PI.reconciliation_status, "").notin(match_found))
)
return query.run(as_dict=True)
@@ -502,6 +503,7 @@ def __init__(self, **kwargs):
def get_all(self, additional_fields=None, names=None, only_names=False):
query = self.get_query(additional_fields)
+ match_found = ("Reconciled", "Match Found")
if only_names and not names:
return
@@ -513,7 +515,7 @@ def get_all(self, additional_fields=None, names=None, only_names=False):
query = query.where(
(
(self.BOE.posting_date[self.from_date : self.to_date])
- & (IfNull(self.BOE.reconciliation_status, "") != "Reconciled")
+ & (IfNull(self.BOE.reconciliation_status, "").notin(match_found))
)
| (self.BOE.name.isin(names))
)
@@ -521,7 +523,7 @@ def get_all(self, additional_fields=None, names=None, only_names=False):
else:
query = query.where(
(self.BOE.posting_date[self.from_date : self.to_date])
- & (IfNull(self.BOE.reconciliation_status, "") != "Reconciled")
+ & (IfNull(self.BOE.reconciliation_status, "").notin(match_found))
)
return query.run(as_dict=True)
@@ -745,6 +747,8 @@ def reconcile(self, category, amended_category):
"""
Reconcile purchases and inward supplies for given category.
"""
+ self.category = category
+
# GSTIN Level matching
purchases = self.get_unmatched_purchase_or_bill_of_entry(category)
inward_supplies = self.get_unmatched_inward_supply(category, amended_category)
@@ -789,12 +793,13 @@ def reconcile_for_rule(self, purchases, inward_supplies, match_status, rules):
for inward_supply_name, inward_supply in (
inward_supplies[supplier_gstin].copy().items()
):
- if match_status == "Residual Match":
- if (
- abs((purchase.bill_date - inward_supply.bill_date).days)
- > 10
- ):
- continue
+ if (
+ match_status == "Residual Match"
+ and self.category != "CDNR"
+ and abs((purchase.bill_date - inward_supply.bill_date).days)
+ > 10
+ ):
+ continue
if not self.is_doc_matching(purchase, inward_supply, rules):
continue
diff --git a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_detail_comparision.html b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_detail_comparision.html
index 6735501a0d..3bef8ae602 100644
--- a/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_detail_comparision.html
+++ b/india_compliance/gst_india/doctype/purchase_reconciliation_tool/purchase_detail_comparision.html
@@ -4,7 +4,7 @@
|
2A / 2B |
- Purchase |
+ {{ purchase.doctype }} |
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 06ba93e615..591ae0f694 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
@@ -16,12 +16,13 @@ const ALERT_HTML = `
You have missing GSTR-2B downloads
- ${api_enabled
- ? `
+ ${
+ api_enabled
+ ? `
Download 2B
`
- : ""
- }
+ : ""
+ }
`;
@@ -96,30 +97,31 @@ 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")
- )
+ ["Accept", "Pending", "Ignore"].forEach(action =>
+ frm.add_custom_button(
+ __(action),
+ () => apply_action(frm, action),
+ action_group
+ )
);
frm.$wrapper
.find("[data-label='dropdown-divider']")
@@ -131,10 +133,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"));
}
},
@@ -191,8 +199,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",
@@ -377,6 +385,12 @@ class PurchaseReconciliationTool {
fieldname: "is_reverse_charge",
fieldtype: "Check",
},
+ {
+ label: "DocType",
+ fieldname: "purchase_doctype",
+ fieldtype: "Select",
+ options: ["Purchase Invoice", "Bill of Entry"],
+ },
];
fields.forEach(field => (field.parent = "Purchase Reconciliation Tool"));
@@ -453,47 +467,39 @@ class PurchaseReconciliationTool {
me.dm = new EmailDialog(me.frm, row);
});
- this.tabs.summary_tab.$datatable.on(
- "click",
- ".match-status",
- async function (e) {
- e.preventDefault();
-
- const match_status = $(this).text();
- await me.filter_group.push_new_filter([
- "Purchase Reconciliation Tool",
- "match_status",
- "=",
- match_status,
- ]);
- me.filter_group.apply();
- }
- );
-
- this.tabs.supplier_tab.$datatable.on(
- "click",
- ".supplier-gstin",
- add_supplier_gstin_filter
- );
-
- this.tabs.invoice_tab.$datatable.on(
- "click",
- ".supplier-gstin",
- add_supplier_gstin_filter
- );
-
- async function add_supplier_gstin_filter(e) {
- e.preventDefault();
+ const filter_map = {
+ // TAB: { SELECTOR: FIELDNAME }
+ summary: { ".match-status": "match_status" },
+ supplier: { ".supplier-gstin": "supplier_gstin" },
+ invoice: {
+ ".match-status": "match_status",
+ ".action-performed": "action",
+ ".supplier-gstin": "supplier_gstin",
+ },
+ };
- const supplier_gstin = $(this).text().trim();
- await me.filter_group.push_new_filter([
- "Purchase Reconciliation Tool",
- "supplier_gstin",
- "=",
- supplier_gstin,
- ]);
- me.filter_group.apply();
- }
+ Object.keys(filter_map).forEach(tab => {
+ Object.keys(filter_map[tab]).forEach(selector => {
+ this.tabs[`${tab}_tab`].$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,
+ "=",
+ value,
+ ]);
+
+ me.filter_group.apply();
+ }
+ );
+ });
+ });
}
export_data(selected_row) {
@@ -743,12 +749,15 @@ class PurchaseReconciliationTool {
label: "Match Status",
fieldname: "match_status",
width: 120,
+ _value: (...args) => {
+ return `${args[0]}`;
+ },
},
{
label: "GST Inward
Supply",
fieldname: "inward_supply_name",
fieldtype: "Link",
- doctype: "GST Inward Supply",
+ options: "GST Inward Supply",
align: "center",
width: 120,
},
@@ -787,6 +796,9 @@ class PurchaseReconciliationTool {
{
label: "Action",
fieldname: "action",
+ _value: (...args) => {
+ return `${args[0]}`;
+ },
},
];
}
@@ -921,8 +933,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");
@@ -1243,8 +1256,7 @@ class ImportDialog {
download_gstr_by_period(only_missing) {
if (only_missing && this.has_no_pending_download) {
frappe.msgprint({
- message:
- "There are no pending downloads for the selected period.",
+ message: "There are no pending downloads for the selected period.",
title: "No Pending Downloads",
indicator: "orange",
});
@@ -1830,6 +1842,7 @@ async function create_new_purchase_invoice(row, company, company_gstin) {
bill_no: doc.bill_no,
bill_date: doc.bill_date,
is_reverse_charge: ["Yes", 1].includes(doc.is_reverse_charge) ? 1 : 0,
+ is_return: ["CDNR", "CDNRA"].includes(doc.classification) ? 1 : 0,
};
_set_value({
diff --git a/india_compliance/gst_india/overrides/test_transaction.py b/india_compliance/gst_india/overrides/test_transaction.py
index 0722ae6aef..77702e6aa1 100644
--- a/india_compliance/gst_india/overrides/test_transaction.py
+++ b/india_compliance/gst_india/overrides/test_transaction.py
@@ -1155,9 +1155,11 @@ def test_so_and_po_after_item_update(self):
class TestPlaceOfSupply(FrappeTestCase):
def test_pos_sales_invoice(self):
+ # Sales Invoice with Shipping Address
doc_args = {
"doctype": "Sales Invoice",
"customer": "_Test Registered Composition Customer",
+ "shipping_address_name": "_Test Indian Registered Company-Billing",
}
settings = ["Accounts Settings", None, "determine_address_tax_category_from"]
@@ -1171,3 +1173,26 @@ def test_pos_sales_invoice(self):
frappe.db.set_value(*settings, "Billing Address")
doc = create_transaction(**doc_args)
self.assertEqual(doc.place_of_supply, "29-Karnataka")
+
+ frappe.db.set_value(*settings, "Shipping Address")
+
+ # Sales Invoice with only Billing Address
+ doc_args = {
+ "doctype": "Sales Invoice",
+ "customer": "_Test Registered Composition Customer",
+ }
+
+ settings = ["Accounts Settings", None, "determine_address_tax_category_from"]
+
+ # (from Billing Address)
+ doc = create_transaction(**doc_args)
+ self.assertEqual(doc.place_of_supply, "29-Karnataka") # Billing Address
+
+ # Sales Invoice for Unregistered Customer
+ doc_args = {
+ "doctype": "Sales Invoice",
+ "customer": "_Test Unregistered Customer",
+ }
+
+ doc = create_transaction(**doc_args)
+ self.assertEqual(doc.place_of_supply, "24-Gujarat") # Company GSTIN
diff --git a/india_compliance/gst_india/setup/property_setters.py b/india_compliance/gst_india/setup/property_setters.py
index af9b7bef58..b0be7a4f2d 100644
--- a/india_compliance/gst_india/setup/property_setters.py
+++ b/india_compliance/gst_india/setup/property_setters.py
@@ -81,17 +81,23 @@ def get_property_setters(*, include_defaults=False):
"property": "read_only",
"value": "1",
},
+ {
+ "doctype": "Accounts Settings",
+ "fieldname": "add_taxes_from_item_tax_template",
+ "property": "description",
+ "value": "Overridden by India Compliance",
+ },
{
"doctype": "Accounts Settings",
"fieldname": "tax_settings_section",
"property": "label",
- "value": "Tax Settings (Overridden by India Compliance)",
+ "value": "Tax Settings",
},
{
"doctype": "Accounts Settings",
"fieldname": "tax_settings_section",
"property": "collapsible",
- "value": "1",
+ "value": "0",
},
{
"doctype": "Purchase Reconciliation Tool",
diff --git a/india_compliance/gst_india/utils/__init__.py b/india_compliance/gst_india/utils/__init__.py
index 206eb10ba9..f7e2f366ed 100644
--- a/india_compliance/gst_india/utils/__init__.py
+++ b/india_compliance/gst_india/utils/__init__.py
@@ -386,35 +386,43 @@ def get_place_of_supply(party_details, doctype):
:param party_details: A frappe._dict or document containing fields related to party
"""
- pos_basis = frappe.get_cached_value(
- "Accounts Settings", "Accounts Settings", "determine_address_tax_category_from"
- )
-
- if pos_basis == "Shipping Address" and doctype in SALES_DOCTYPES:
- # POS Basis Shipping Address is only applicable for Sales
- pos_gstin = party_details.company_gstin
-
# fallback to company GSTIN for sales or supplier GSTIN for purchases
# (in retail scenarios, customer / company GSTIN may not be set)
-
- elif doctype in SALES_DOCTYPES or doctype == "Payment Entry":
+ if doctype in SALES_DOCTYPES or doctype == "Payment Entry":
# for exports, Place of Supply is set using GST category in absence of GSTIN
if party_details.gst_category == "Overseas":
return get_overseas_place_of_supply(party_details)
+ # customer address based on POS Basis
+ customer_address = party_details.customer_address
+ pos_basis = frappe.get_cached_value(
+ "Accounts Settings",
+ "Accounts Settings",
+ "determine_address_tax_category_from",
+ )
+
+ shipping_gstin = None
if (
- party_details.gst_category == "Unregistered"
- and party_details.customer_address
+ doctype != "Payment Entry"
+ and pos_basis == "Shipping Address"
+ and party_details.shipping_address_name
):
+ customer_address = party_details.shipping_address_name
+ shipping_gstin = frappe.db.get_value("Address", customer_address, "gstin")
+
+ customer_gstin = shipping_gstin or party_details.billing_address_gstin
+ # for unregistered
+ if not customer_gstin and customer_address:
gst_state_number, gst_state = frappe.db.get_value(
"Address",
- party_details.customer_address,
+ customer_address,
("gst_state_number", "gst_state"),
)
if gst_state_number and gst_state:
return f"{gst_state_number}-{gst_state}"
- pos_gstin = party_details.billing_address_gstin or party_details.company_gstin
+ # for registered
+ pos_gstin = customer_gstin or party_details.company_gstin
elif doctype == "Stock Entry":
pos_gstin = party_details.bill_to_gstin or party_details.bill_from_gstin
diff --git a/india_compliance/patches.txt b/india_compliance/patches.txt
index dab87a295e..3dae9b4039 100644
--- a/india_compliance/patches.txt
+++ b/india_compliance/patches.txt
@@ -4,7 +4,7 @@ execute:import frappe; frappe.delete_doc_if_exists("DocType", "GSTIN")
[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() #58
-execute:from india_compliance.gst_india.setup import create_property_setters; create_property_setters() #9
+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
india_compliance.patches.post_install.set_gst_tax_type
diff --git a/india_compliance/public/js/quick_entry.js b/india_compliance/public/js/quick_entry.js
index ba9207193e..ab54f36294 100644
--- a/india_compliance/public/js/quick_entry.js
+++ b/india_compliance/public/js/quick_entry.js
@@ -4,6 +4,10 @@ class GSTQuickEntryForm extends frappe.ui.form.QuickEntryForm {
this.skip_redirect_on_error = true;
this.api_enabled =
india_compliance.is_api_enabled() && gst_settings.autofill_party_info;
+ this.gstin_to_party_type_map = {
+ F: "Partnership",
+ C: "Company",
+ };
}
async setup() {
@@ -92,6 +96,14 @@ class GSTQuickEntryForm extends frappe.ui.form.QuickEntryForm {
ignore_validation: true,
onchange: () => {
const d = this.dialog;
+
+ if (["Customer", "Supplier"].includes(this.doctype)) {
+ d.set_value(
+ `${this.doctype.toLowerCase()}_type`,
+ this.gstin_to_party_type_map[d.doc._gstin[5]] || "Individual"
+ );
+ }
+
if (this.api_enabled && !gst_settings.sandbox_mode)
return autofill_fields(d);
@@ -308,6 +320,7 @@ class AddressQuickEntryForm extends GSTQuickEntryForm {
"Customer",
"Supplier",
"Company",
+ "Lead"
].includes(doc.doctype)
)
return;