Skip to content

Commit 2d0765a

Browse files
authored
Merge pull request #3821 from ljain112/feat-duplicate-pan-gstin
2 parents a0ed1c8 + 07bd6af commit 2d0765a

File tree

4 files changed

+136
-1
lines changed

4 files changed

+136
-1
lines changed

india_compliance/gst_india/client_scripts/party.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ function validate_gstin(doctype) {
6262
frm.doc.gstin = gstin;
6363
frm.refresh_field("gstin");
6464

65+
india_compliance.check_duplicate_gstin(gstin, frm.doctype, frm.docname);
66+
6567
if (!frm.fields_dict.pan) return;
6668

6769
// extract PAN from GSTIN
@@ -89,6 +91,8 @@ function validate_pan(doctype) {
8991

9092
frm.doc.pan = pan;
9193
frm.refresh_field("pan");
94+
95+
india_compliance.check_duplicate_pan(pan, frm.doctype, frm.docname);
9296
set_party_type(frm);
9397
},
9498
});

india_compliance/gst_india/utils/__init__.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
E_INVOICE_MASTER_CODES_URL,
3434
GST_ACCOUNT_FIELDS,
3535
GST_INVOICE_NUMBER_FORMAT,
36+
GST_PARTY_TYPES,
3637
GSTIN_FORMATS,
3738
PAN_NUMBER,
3839
PINCODE_FORMAT,
@@ -1131,3 +1132,108 @@ def has_permission_of_page(page_name, throw=False):
11311132
)
11321133

11331134
return True
1135+
1136+
1137+
@frappe.whitelist()
1138+
def check_duplicate_party(field, value, party_type, party=None):
1139+
"""
1140+
Check duplicates based on PAN/GSTIN for the given party type.
1141+
"""
1142+
if not value:
1143+
return
1144+
1145+
if party_type not in GST_PARTY_TYPES:
1146+
return
1147+
1148+
frappe.has_permission(party_type, doc=party, throw=True)
1149+
1150+
value = value.upper().strip()
1151+
1152+
# Check for duplicates
1153+
if field == "pan":
1154+
existing_parties = _get_duplicate_pan_party(value, party_type, party)
1155+
elif field == "gstin":
1156+
existing_parties = _get_duplicate_gstin_party(value, party_type, party)
1157+
else:
1158+
return
1159+
1160+
if not existing_parties:
1161+
return
1162+
1163+
# Show message
1164+
duplicate_links = []
1165+
for row in existing_parties:
1166+
party_link = get_link_to_form(party_type, row.name)
1167+
if row.via_address:
1168+
address_link = get_link_to_form("Address", row.address)
1169+
link_msg = _("{0} (via Address {1})").format(party_link, address_link)
1170+
1171+
else:
1172+
link_msg = party_link
1173+
1174+
duplicate_links.append(f"<li>{link_msg}</li>")
1175+
1176+
msg = _("{0} {1} is already registered with the following {2}(s):").format(
1177+
field.capitalize(), frappe.bold(value), party_type
1178+
)
1179+
msg += f"<br><br><ul>{''.join(duplicate_links)}</ul>"
1180+
1181+
frappe.msgprint(msg=msg, indicator="orange")
1182+
1183+
1184+
def _get_duplicate_pan_party(pan, party_type, party=None):
1185+
filters = {"pan": ("=", pan)}
1186+
if party:
1187+
filters["name"] = ("!=", party)
1188+
1189+
return frappe.get_all(party_type, filters=filters)
1190+
1191+
1192+
def _get_duplicate_gstin_party(gstin, party_type, party=None):
1193+
party_table = frappe.qb.DocType(party_type)
1194+
address = frappe.qb.DocType("Address")
1195+
dynamic_link = frappe.qb.DocType("Dynamic Link")
1196+
1197+
party_query = (
1198+
frappe.qb.from_(party_table)
1199+
.select(
1200+
party_table.name,
1201+
frappe.qb.terms.ValueWrapper(None).as_("address_name"),
1202+
frappe.qb.terms.ValueWrapper(0).as_("via_address"),
1203+
)
1204+
.where(party_table.gstin == gstin)
1205+
)
1206+
1207+
if party:
1208+
party_query = party_query.where(party_table.name != party)
1209+
1210+
address_query = (
1211+
frappe.qb.from_(address)
1212+
.join(dynamic_link)
1213+
.on(dynamic_link.parent == address.name)
1214+
.select(
1215+
dynamic_link.link_name.as_("name"),
1216+
address.name.as_("address_name"),
1217+
frappe.qb.terms.ValueWrapper(1).as_("via_address"),
1218+
)
1219+
.where(dynamic_link.link_doctype == party_type)
1220+
.where(address.gstin == gstin)
1221+
)
1222+
1223+
if party:
1224+
address_query = address_query.where(dynamic_link.link_name != party)
1225+
1226+
results = (party_query + address_query).orderby("via_address").run(as_dict=True)
1227+
1228+
duplicates_dict = {}
1229+
for row in results:
1230+
if row.name in duplicates_dict:
1231+
continue
1232+
1233+
duplicates_dict[row.name] = {
1234+
"name": row.name,
1235+
"via_address": bool(row.via_address),
1236+
"address": row.address_name,
1237+
}
1238+
1239+
return list(duplicates_dict.values())

india_compliance/public/js/quick_entry.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ class GSTQuickEntryForm extends frappe.ui.form.QuickEntryForm {
9797
onchange: () => {
9898
const d = this.dialog;
9999

100+
india_compliance.check_duplicate_gstin(d.doc._gstin, this.doctype);
101+
100102
if (["Customer", "Supplier"].includes(this.doctype)) {
101103
d.set_value(
102104
`${this.doctype.toLowerCase()}_type`,
@@ -110,7 +112,10 @@ class GSTQuickEntryForm extends frappe.ui.form.QuickEntryForm {
110112

111113
d.set_value(
112114
"gst_category",
113-
india_compliance.guess_gst_category(d.doc._gstin, d.doc.country)
115+
india_compliance.guess_gst_category(
116+
d.doc._gstin,
117+
d.doc.country,
118+
),
114119
);
115120
},
116121
},

india_compliance/public/js/utils.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ Object.assign(india_compliance, {
7070
return `${month}${year}`;
7171
},
7272

73+
check_duplicate_gstin(gstin, party_type, party = null) {
74+
if (!gstin || gstin.length !== 15) return;
75+
this.check_duplicate_party("gstin", gstin, party_type, party);
76+
},
77+
78+
check_duplicate_pan(pan, party_type, party = null) {
79+
if (!pan || pan.length !== 10) return;
80+
this.check_duplicate_party("pan", pan, party_type, party);
81+
},
82+
83+
check_duplicate_party(field, value, party_type, party = null) {
84+
if (!party_type) return;
85+
if (!frappe.boot.gst_party_types.includes(party_type)) return;
86+
87+
frappe.call({
88+
method: "india_compliance.gst_india.utils.check_duplicate_party",
89+
args: { field, value, party_type, party },
90+
});
91+
},
92+
7393
get_gstin_query(party, party_type = "Company", exclude_isd = false) {
7494
if (!party) {
7595
frappe.show_alert({

0 commit comments

Comments
 (0)