Compare commits
274 Commits
v15.21.0
...
enconnex_e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65771b431b | ||
|
|
25e4af5b03 | ||
|
|
ae04395caf | ||
|
|
11e4760530 | ||
|
|
171519a4b8 | ||
|
|
984d6b2d39 | ||
|
|
4f6b760cfd | ||
|
|
ca8fe28138 | ||
|
|
5b9e0b5b57 | ||
|
|
c7349145c5 | ||
|
|
3875f23d9a | ||
|
|
b5ea536530 | ||
|
|
ac61a86021 | ||
|
|
a5b7d5ecf0 | ||
|
|
fda58abfaf | ||
|
|
86e700b24f | ||
|
|
80b57e7c41 | ||
|
|
900adeefda | ||
|
|
36c9a8ab08 | ||
|
|
8288b5a302 | ||
|
|
d27e663b2a | ||
|
|
a82ecf8c07 | ||
|
|
427f9b6367 | ||
|
|
e42f423ed8 | ||
|
|
e4274c1cc5 | ||
|
|
1e7133baf1 | ||
|
|
2c56ae78a7 | ||
|
|
666af17cfa | ||
|
|
88da5f9073 | ||
|
|
1dd3396b60 | ||
|
|
153323ee41 | ||
|
|
09ef7b62a1 | ||
|
|
e2e2a305da | ||
|
|
c648027473 | ||
|
|
14096fdfc8 | ||
|
|
4bc4e96c2a | ||
|
|
b8d5db6689 | ||
|
|
4b9aed2c0e | ||
|
|
7a3dbe1b72 | ||
|
|
cf23f83d6c | ||
|
|
f0816acfc2 | ||
|
|
d73f1a8170 | ||
|
|
9faaa8ffd8 | ||
|
|
afb60c3678 | ||
|
|
542c959257 | ||
|
|
70d5d2cb2b | ||
|
|
c55bbc2db9 | ||
|
|
f9c13bf32b | ||
|
|
03107ffedd | ||
|
|
68c0427aef | ||
|
|
e6d9d47cb4 | ||
|
|
afbe93de3c | ||
|
|
62895a4ca5 | ||
|
|
868b816c5a | ||
|
|
66773cabef | ||
|
|
a44db68b8e | ||
|
|
6b198241e1 | ||
|
|
a9f3c3e50a | ||
|
|
ba563ca232 | ||
|
|
103ce8bbb7 | ||
|
|
93c67ffd92 | ||
|
|
491987b8d3 | ||
|
|
552330f744 | ||
|
|
8e01c8aefb | ||
|
|
ff57fbe2c1 | ||
|
|
fb0936be50 | ||
|
|
ed369c105f | ||
|
|
192002e156 | ||
|
|
52ac8484ff | ||
|
|
bcb7848c84 | ||
|
|
19ca4d000b | ||
|
|
288082abfd | ||
|
|
2b1809bd0e | ||
|
|
5fab55a564 | ||
|
|
799f0daa1e | ||
|
|
2f4b91172d | ||
|
|
adb4225fa7 | ||
|
|
fdcc926597 | ||
|
|
04fa2e8d29 | ||
|
|
0c6d69e27c | ||
|
|
12c957b2b2 | ||
|
|
11cef55649 | ||
|
|
1585986fa4 | ||
|
|
10c069588f | ||
|
|
ca966601a2 | ||
|
|
655a57513f | ||
|
|
a4fabefee7 | ||
|
|
5bb15b08d9 | ||
|
|
f045eb3135 | ||
|
|
62890adef4 | ||
|
|
f000b3b362 | ||
|
|
1a8df8f112 | ||
|
|
760e6cebd4 | ||
|
|
7835ec6a6c | ||
|
|
5c7333bd3a | ||
|
|
a821647283 | ||
|
|
f6ffdcef98 | ||
|
|
71976de79c | ||
|
|
3f6f20b0d2 | ||
|
|
d9a6a6fe6a | ||
|
|
1cfb69aaae | ||
|
|
50cd7a1650 | ||
|
|
f4a4efb510 | ||
|
|
7337669e49 | ||
|
|
fc34ded7a8 | ||
|
|
342299fe3b | ||
|
|
5307f8e794 | ||
|
|
540c119a27 | ||
|
|
21479fc5ea | ||
|
|
2c12124550 | ||
|
|
f68e85519c | ||
|
|
0868bf5d45 | ||
|
|
8bc69d3fe0 | ||
|
|
cd3d6ade04 | ||
|
|
9070ec3237 | ||
|
|
cfcb1a15f8 | ||
|
|
d0ee3fc0cd | ||
|
|
776d89c4de | ||
|
|
a02cad5ec4 | ||
|
|
837aae7bcb | ||
|
|
cfcc9eac39 | ||
|
|
4b86f0440e | ||
|
|
f709d57475 | ||
|
|
377fe373e2 | ||
|
|
39ba31e68f | ||
|
|
6d3e843f07 | ||
|
|
a679d43722 | ||
|
|
2466c3214a | ||
|
|
646ead65d8 | ||
|
|
7b282d253f | ||
|
|
37320074b2 | ||
|
|
a5353c7d2d | ||
|
|
e85050c190 | ||
|
|
c3b22c6e5c | ||
|
|
73cef56df7 | ||
|
|
a0f6381d6e | ||
|
|
b7b3944cef | ||
|
|
9d4bf040e9 | ||
|
|
afef92a88b | ||
|
|
a9f4508cd1 | ||
|
|
dac19cbca5 | ||
|
|
573d9094bb | ||
|
|
0413e34f0d | ||
|
|
7d2c5c24c6 | ||
|
|
107e2cdaba | ||
|
|
a2bf727794 | ||
|
|
ed93d41727 | ||
|
|
7b458be170 | ||
|
|
9d806df589 | ||
|
|
dd2f6aade1 | ||
|
|
0f30c52759 | ||
|
|
610eb508bf | ||
|
|
047bf3833e | ||
|
|
e4554f690c | ||
|
|
57d79a8149 | ||
|
|
65fb0729d2 | ||
|
|
692c617cb2 | ||
|
|
614c04e357 | ||
|
|
07e86e0c3d | ||
|
|
da9418b378 | ||
|
|
8e641d353c | ||
|
|
e00f52c455 | ||
|
|
c4bdbe1046 | ||
|
|
4d6c53f7b2 | ||
|
|
94aad17fd5 | ||
|
|
08d562a5bb | ||
|
|
509e40a584 | ||
|
|
cb80aaf707 | ||
|
|
0ce888ec08 | ||
|
|
4569c9b5e3 | ||
|
|
e895ddc952 | ||
|
|
3a02fd9bda | ||
|
|
fb4ad9042c | ||
|
|
d2b44e90d6 | ||
|
|
094328b8c9 | ||
|
|
317a0495e2 | ||
|
|
e3f9c5246e | ||
|
|
c56d775541 | ||
|
|
75037e4493 | ||
|
|
465fc5163f | ||
|
|
9c88c37102 | ||
|
|
f3bf9aa70f | ||
|
|
9765809264 | ||
|
|
0803102a92 | ||
|
|
34650b7122 | ||
|
|
28ec74d35e | ||
|
|
fc45f9dc17 | ||
|
|
8ade3b25ab | ||
|
|
dbb378f759 | ||
|
|
62cd0c6592 | ||
|
|
97dafc06f9 | ||
|
|
dc98db611b | ||
|
|
bf5a791d0e | ||
|
|
b8b66a1450 | ||
|
|
e1b7d465c4 | ||
|
|
db2d221976 | ||
|
|
383a3df807 | ||
|
|
bc3b5e334b | ||
|
|
b855a32d84 | ||
|
|
8fe07e4567 | ||
|
|
da0c064c70 | ||
|
|
f3d74b7638 | ||
|
|
529727224c | ||
|
|
9546e3ea32 | ||
|
|
6f035d2169 | ||
|
|
8d612d3f78 | ||
|
|
c1acb7b77a | ||
|
|
e0ef4abbcf | ||
|
|
bd16e0885b | ||
|
|
35f985e514 | ||
|
|
b32de3ab13 | ||
|
|
4c420ca490 | ||
|
|
469c23b562 | ||
|
|
3fe127d483 | ||
|
|
78b30ccc4f | ||
|
|
10fdd38e32 | ||
|
|
dc9438c3a4 | ||
|
|
6771d97c42 | ||
|
|
ec17111e27 | ||
|
|
8679ce475a | ||
|
|
fd3952ba9a | ||
|
|
5138f00176 | ||
|
|
6a24d2df8c | ||
|
|
96bd2f72d0 | ||
|
|
fb33f7d3c3 | ||
|
|
5e0817f57a | ||
|
|
f6d23df826 | ||
|
|
499273e64a | ||
|
|
14c3715fef | ||
|
|
d43d7a0a58 | ||
|
|
67e68670bb | ||
|
|
9dcab60d61 | ||
|
|
30a7da51d3 | ||
|
|
df859fdc23 | ||
|
|
48527c48d2 | ||
|
|
df09c76148 | ||
|
|
0ce26ce90d | ||
|
|
a9f65654a1 | ||
|
|
57caf99a7e | ||
|
|
d729f7f50e | ||
|
|
6fce734476 | ||
|
|
354da7ad4c | ||
|
|
aaf445af98 | ||
|
|
6eb4fe6659 | ||
|
|
3739dc3f78 | ||
|
|
dc3c03e47e | ||
|
|
2fbc8c8f30 | ||
|
|
06a21da29e | ||
|
|
b488872cbd | ||
|
|
88fa9866b1 | ||
|
|
62109c3c1a | ||
|
|
1bbdc504bf | ||
|
|
e43a50357b | ||
|
|
17cd44ecd7 | ||
|
|
54ebb06d8a | ||
|
|
ddf9d9ae0e | ||
|
|
5488ccfa8c | ||
|
|
0b304b3561 | ||
|
|
4971061054 | ||
|
|
a7167f775d | ||
|
|
1ccba26173 | ||
|
|
ffbf30809d | ||
|
|
ad02cfe98e | ||
|
|
392a2856d3 | ||
|
|
56b0136696 | ||
|
|
da82a380e1 | ||
|
|
c0e3bcf865 | ||
|
|
398c0f0b7f | ||
|
|
6b9f7e35f4 | ||
|
|
f9a6a90900 | ||
|
|
9ebe8d5895 | ||
|
|
24c5fc862d | ||
|
|
0c92e7f9a1 | ||
|
|
c788ee28c2 |
@@ -109,7 +109,7 @@ def get_region(company=None):
|
||||
'''
|
||||
if company or frappe.flags.company:
|
||||
return frappe.get_cached_value('Company',
|
||||
company or frappe.flags.company, 'country')
|
||||
company or frappe.flags.company, 'country')
|
||||
elif frappe.flags.country:
|
||||
return frappe.flags.country
|
||||
else:
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"frozen_accounts_modifier",
|
||||
"determine_address_tax_category_from",
|
||||
"over_billing_allowance",
|
||||
"role_allowed_to_over_bill",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
@@ -226,6 +227,13 @@
|
||||
"fieldname": "delete_linked_ledger_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Delete Accounting and Stock Ledger Entries on deletion of Transaction"
|
||||
},
|
||||
{
|
||||
"description": "Users with this role are allowed to over bill above the allowance perecentage",
|
||||
"fieldname": "role_allowed_to_over_bill",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Over Bill ",
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -233,7 +241,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-05 13:04:00.118892",
|
||||
"modified": "2021-03-11 18:52:05.601996",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -1,196 +1,82 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2018-01-02 15:48:58.768352",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2018-01-02 15:48:58.768352",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"cgst_account",
|
||||
"sgst_account",
|
||||
"igst_account",
|
||||
"cess_account",
|
||||
"is_reverse_charge_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 1,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "cgst_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "CGST Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "cgst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "CGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sgst_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "SGST Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "sgst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "SGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "igst_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "IGST Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "igst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "IGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "cess_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "CESS Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"columns": 2,
|
||||
"fieldname": "cess_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "CESS Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "is_reverse_charge_account",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Reverse Charge Account"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-01-02 15:52:22.335988",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-09 12:30:25.889993",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -283,7 +283,9 @@ frappe.ui.form.on('Payment Entry', {
|
||||
let party_types = Object.keys(frappe.boot.party_account_types);
|
||||
if(frm.doc.party_type && !party_types.includes(frm.doc.party_type)){
|
||||
frm.set_value("party_type", "");
|
||||
frappe.throw(__("Party can only be one of {0}", [party_types.join(", ")]));
|
||||
frappe.throw(
|
||||
__("Party can only be one of {0}", [party_types.join(", ")])
|
||||
);
|
||||
}
|
||||
|
||||
frm.set_query("party", function() {
|
||||
@@ -637,13 +639,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
let to_field = fields[key][1];
|
||||
|
||||
if (filters[from_field] && !filters[to_field]) {
|
||||
frappe.throw(__("Error: {0} is mandatory field",
|
||||
[to_field.replace(/_/g, " ")]
|
||||
));
|
||||
frappe.throw(
|
||||
__("Error: {0} is mandatory field", [to_field.replace(/_/g, " ")])
|
||||
);
|
||||
} else if (filters[from_field] && filters[from_field] > filters[to_field]) {
|
||||
frappe.throw(__("{0}: {1} must be less than {2}",
|
||||
[key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")]
|
||||
));
|
||||
frappe.throw(
|
||||
__("{0}: {1} must be less than {2}", [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")])
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -692,6 +694,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
c.total_amount = d.invoice_amount;
|
||||
c.outstanding_amount = d.outstanding_amount;
|
||||
c.bill_no = d.bill_no;
|
||||
c.payment_term = d.payment_term;
|
||||
c.allocated_amount = d.allocated_amount;
|
||||
|
||||
if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) {
|
||||
if(flt(d.outstanding_amount) > 0)
|
||||
@@ -774,12 +778,15 @@ frappe.ui.form.on('Payment Entry', {
|
||||
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
||||
if(paid_amount > total_negative_outstanding) {
|
||||
if(total_negative_outstanding == 0) {
|
||||
frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice",
|
||||
[frm.doc.payment_type,
|
||||
(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type]));
|
||||
frappe.msgprint(
|
||||
__("Cannot {0} {1} {2} without any negative outstanding invoice", [frm.doc.payment_type,
|
||||
(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type])
|
||||
);
|
||||
return false
|
||||
} else {
|
||||
frappe.msgprint(__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding]));
|
||||
frappe.msgprint(
|
||||
__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding])
|
||||
);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -791,10 +798,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
|
||||
$.each(frm.doc.references || [], function(i, row) {
|
||||
row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
||||
if(frappe.flags.allocate_payment_amount != 0){
|
||||
if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
|
||||
if(row.outstanding_amount >= allocated_positive_outstanding) {
|
||||
if (frappe.flags.allocate_payment_amount == 0) {
|
||||
//If allocate payment amount checkbox is unchecked, set zero to allocate amount
|
||||
row.allocated_amount = 0;
|
||||
|
||||
} else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) {
|
||||
if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
|
||||
if (row.outstanding_amount >= allocated_positive_outstanding) {
|
||||
row.allocated_amount = allocated_positive_outstanding;
|
||||
} else {
|
||||
row.allocated_amount = row.outstanding_amount;
|
||||
@@ -802,9 +812,11 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
allocated_positive_outstanding -= flt(row.allocated_amount);
|
||||
} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
|
||||
if(Math.abs(row.outstanding_amount) >= allocated_negative_outstanding)
|
||||
if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) {
|
||||
row.allocated_amount = -1*allocated_negative_outstanding;
|
||||
else row.allocated_amount = row.outstanding_amount;
|
||||
} else {
|
||||
row.allocated_amount = row.outstanding_amount;
|
||||
};
|
||||
|
||||
allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
|
||||
}
|
||||
|
||||
@@ -333,33 +333,50 @@ class PaymentEntry(AccountsController):
|
||||
invoice_payment_amount_map = {}
|
||||
invoice_paid_amount_map = {}
|
||||
|
||||
for reference in self.get('references'):
|
||||
if reference.payment_term and reference.reference_name:
|
||||
key = (reference.payment_term, reference.reference_name)
|
||||
for ref in self.get('references'):
|
||||
if ref.payment_term and ref.reference_name:
|
||||
key = (ref.payment_term, ref.reference_name)
|
||||
invoice_payment_amount_map.setdefault(key, 0.0)
|
||||
invoice_payment_amount_map[key] += reference.allocated_amount
|
||||
invoice_payment_amount_map[key] += ref.allocated_amount
|
||||
|
||||
if not invoice_paid_amount_map.get(key):
|
||||
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
|
||||
fields=['paid_amount', 'payment_amount', 'payment_term'])
|
||||
payment_schedule = frappe.get_all(
|
||||
'Payment Schedule',
|
||||
filters={'parent': ref.reference_name},
|
||||
fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding']
|
||||
)
|
||||
for term in payment_schedule:
|
||||
invoice_key = (term.payment_term, reference.reference_name)
|
||||
invoice_key = (term.payment_term, ref.reference_name)
|
||||
invoice_paid_amount_map.setdefault(invoice_key, {})
|
||||
invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
|
||||
invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
|
||||
invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
|
||||
|
||||
for key, allocated_amount in iteritems(invoice_payment_amount_map):
|
||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
||||
discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt'))
|
||||
|
||||
for key, amount in iteritems(invoice_payment_amount_map):
|
||||
if cancel:
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabPayment Schedule`
|
||||
SET
|
||||
paid_amount = `paid_amount` - %s,
|
||||
discounted_amount = `discounted_amount` - %s,
|
||||
outstanding = `outstanding` + %s
|
||||
WHERE parent = %s and payment_term = %s""",
|
||||
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
|
||||
else:
|
||||
outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
|
||||
|
||||
if amount > outstanding:
|
||||
if allocated_amount > outstanding:
|
||||
frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
|
||||
|
||||
if amount and outstanding:
|
||||
frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
|
||||
WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
|
||||
if allocated_amount and outstanding:
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabPayment Schedule`
|
||||
SET
|
||||
paid_amount = `paid_amount` + %s,
|
||||
discounted_amount = `discounted_amount` + %s,
|
||||
outstanding = `outstanding` - %s
|
||||
WHERE parent = %s and payment_term = %s""",
|
||||
(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 2:
|
||||
@@ -704,6 +721,8 @@ def get_outstanding_reference_documents(args):
|
||||
outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
|
||||
args.get("party_account"), filters=args, condition=condition)
|
||||
|
||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
||||
|
||||
for d in outstanding_invoices:
|
||||
d["exchange_rate"] = 1
|
||||
if party_account_currency != company_currency:
|
||||
@@ -731,6 +750,46 @@ def get_outstanding_reference_documents(args):
|
||||
return data
|
||||
|
||||
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices):
|
||||
invoice_ref_based_on_payment_terms = {}
|
||||
for idx, d in enumerate(outstanding_invoices):
|
||||
if d.voucher_type in ['Sales Invoice', 'Purchase Invoice']:
|
||||
payment_term_template = frappe.db.get_value(d.voucher_type, d.voucher_no, 'payment_terms_template')
|
||||
if payment_term_template:
|
||||
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||
'Payment Terms Template', payment_term_template, 'allocate_payment_based_on_payment_terms')
|
||||
if allocate_payment_based_on_payment_terms:
|
||||
payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': d.voucher_no}, fields=["*"])
|
||||
|
||||
for payment_term in payment_schedule:
|
||||
if payment_term.outstanding > 0.1:
|
||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
||||
invoice_ref_based_on_payment_terms[idx].append(frappe._dict({
|
||||
'due_date': d.due_date,
|
||||
'currency': d.currency,
|
||||
'voucher_no': d.voucher_no,
|
||||
'voucher_type': d.voucher_type,
|
||||
'posting_date': d.posting_date,
|
||||
'invoice_amount': flt(d.invoice_amount),
|
||||
'outstanding_amount': flt(d.outstanding_amount),
|
||||
'payment_amount': payment_term.payment_amount,
|
||||
'payment_term': payment_term.payment_term,
|
||||
'allocated_amount': payment_term.outstanding
|
||||
}))
|
||||
|
||||
if invoice_ref_based_on_payment_terms:
|
||||
for idx, ref in invoice_ref_based_on_payment_terms.items():
|
||||
voucher_no = outstanding_invoices[idx]['voucher_no']
|
||||
voucher_type = outstanding_invoices[idx]['voucher_type']
|
||||
|
||||
frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format(
|
||||
voucher_type, voucher_no, len(ref)), alert=True)
|
||||
|
||||
outstanding_invoices.pop(idx - 1)
|
||||
outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
|
||||
|
||||
return outstanding_invoices
|
||||
|
||||
def get_orders_to_be_billed(posting_date, party_type, party,
|
||||
company, party_account_currency, company_currency, cost_center=None, filters=None):
|
||||
if party_type == "Customer":
|
||||
@@ -1083,6 +1142,8 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
paid_amount, received_amount = set_paid_amount_and_received_amount(
|
||||
dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
|
||||
|
||||
paid_amount, received_amount, discount_amount = apply_early_payment_discount(paid_amount, received_amount, doc)
|
||||
|
||||
pe = frappe.new_doc("Payment Entry")
|
||||
pe.payment_type = payment_type
|
||||
pe.company = doc.company
|
||||
@@ -1152,11 +1213,20 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
|
||||
pe.setup_party_account_field()
|
||||
pe.set_missing_values()
|
||||
|
||||
if party_account and bank:
|
||||
if dt == "Employee Advance":
|
||||
reference_doc = doc
|
||||
pe.set_exchange_rate(ref_doc=reference_doc)
|
||||
pe.set_amounts()
|
||||
if discount_amount:
|
||||
pe.set_gain_or_loss(account_details={
|
||||
'account': frappe.get_cached_value('Company', pe.company, "default_discount_account"),
|
||||
'cost_center': pe.cost_center or frappe.get_cached_value('Company', pe.company, "cost_center"),
|
||||
'amount': discount_amount * (-1 if payment_type == "Pay" else 1)
|
||||
})
|
||||
pe.set_difference_amount()
|
||||
|
||||
return pe
|
||||
|
||||
def get_bank_cash_account(doc, bank_account):
|
||||
@@ -1272,6 +1342,33 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
|
||||
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
||||
return paid_amount, received_amount
|
||||
|
||||
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
||||
total_discount = 0
|
||||
if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule:
|
||||
for term in doc.payment_schedule:
|
||||
if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
|
||||
if term.discount_type == 'Percentage':
|
||||
discount_amount = flt(doc.get('grand_total')) * (term.discount / 100)
|
||||
else:
|
||||
discount_amount = term.discount
|
||||
|
||||
discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1)
|
||||
|
||||
if doc.doctype == 'Sales Invoice':
|
||||
paid_amount -= discount_amount
|
||||
received_amount -= discount_amount_in_foreign_currency
|
||||
else:
|
||||
received_amount -= discount_amount
|
||||
paid_amount -= discount_amount_in_foreign_currency
|
||||
|
||||
total_discount += discount_amount
|
||||
|
||||
if total_discount:
|
||||
money = frappe.utils.fmt_money(total_discount, currency=doc.get('currency'))
|
||||
frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
|
||||
|
||||
return paid_amount, received_amount, total_discount
|
||||
|
||||
def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
|
||||
references = []
|
||||
for payment_term in payment_schedule:
|
||||
|
||||
@@ -193,6 +193,34 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
|
||||
self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
|
||||
|
||||
def test_payment_entry_against_payment_terms_with_discount(self):
|
||||
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
|
||||
create_payment_terms_template_with_discount()
|
||||
si.payment_terms_template = 'Test Discount Template'
|
||||
|
||||
frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC')
|
||||
|
||||
si.append('taxes', {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"description": "Service Tax",
|
||||
"rate": 18
|
||||
})
|
||||
si.save()
|
||||
|
||||
si.submit()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
|
||||
pe.submit()
|
||||
si.load_from_db()
|
||||
|
||||
self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount')
|
||||
self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
|
||||
self.assertEqual(si.payment_schedule[0].paid_amount, 212.40)
|
||||
self.assertEqual(si.payment_schedule[0].outstanding, 0)
|
||||
self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
|
||||
|
||||
|
||||
def test_payment_against_purchase_invoice_to_check_status(self):
|
||||
pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
|
||||
@@ -591,6 +619,26 @@ def create_payment_terms_template():
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
def create_payment_terms_template_with_discount():
|
||||
|
||||
create_payment_term('30 Credit Days with 10% Discount')
|
||||
|
||||
if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'):
|
||||
payment_term_template = frappe.get_doc({
|
||||
'doctype': 'Payment Terms Template',
|
||||
'template_name': 'Test Discount Template',
|
||||
'allocate_payment_based_on_payment_terms': 1,
|
||||
'terms': [{
|
||||
'doctype': 'Payment Terms Template Detail',
|
||||
'payment_term': '30 Credit Days with 10% Discount',
|
||||
'invoice_portion': 100,
|
||||
'credit_days_based_on': 'Day(s) after invoice date',
|
||||
'credit_days': 2,
|
||||
'discount': 10,
|
||||
'discount_validity_based_on': 'Day(s) after invoice date',
|
||||
'discount_validity': 1
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
def create_payment_term(name):
|
||||
if not frappe.db.exists('Payment Term', name):
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Total Amount",
|
||||
"label": "Grand Total",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -92,9 +92,10 @@
|
||||
"options": "Payment Term"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-13 12:07:19.362539",
|
||||
"modified": "2021-02-10 11:25:47.144392",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
|
||||
@@ -6,12 +6,25 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"section_break_15",
|
||||
"description",
|
||||
"section_break_4",
|
||||
"due_date",
|
||||
"invoice_portion",
|
||||
"payment_amount",
|
||||
"mode_of_payment",
|
||||
"paid_amount"
|
||||
"column_break_5",
|
||||
"invoice_portion",
|
||||
"section_break_6",
|
||||
"discount_type",
|
||||
"discount_date",
|
||||
"column_break_9",
|
||||
"discount",
|
||||
"section_break_9",
|
||||
"payment_amount",
|
||||
"outstanding",
|
||||
"paid_amount",
|
||||
"discounted_amount",
|
||||
"column_break_3",
|
||||
"base_payment_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -25,6 +38,7 @@
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
@@ -62,14 +76,90 @@
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"depends_on": "paid_amount",
|
||||
"fieldname": "paid_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Paid Amount"
|
||||
"label": "Paid Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "discounted_amount",
|
||||
"fieldname": "discounted_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Discounted Amount",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_amount",
|
||||
"fieldname": "outstanding",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Outstanding",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Discount Date",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fetch_from": "payment_term.discount_type",
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_term.discount",
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_15",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_payment_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Payment Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-13 17:58:24.729526",
|
||||
"modified": "2021-04-28 05:41:35.084233",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Schedule",
|
||||
|
||||
@@ -1,2 +1,22 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
frappe.ui.form.on('Payment Term', {
|
||||
onload(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
discount(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
discount_type(frm) {
|
||||
frm.trigger('set_dynamic_description');
|
||||
},
|
||||
set_dynamic_description(frm) {
|
||||
if (frm.doc.discount) {
|
||||
let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
|
||||
if (frm.doc.discount_type == 'Amount') {
|
||||
description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]);
|
||||
}
|
||||
frm.set_df_property("discount", "description", description);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,386 +1,166 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:payment_term_name",
|
||||
"beta": 0,
|
||||
"creation": "2017-08-10 15:24:54.876365",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:payment_term_name",
|
||||
"creation": "2017-08-10 15:24:54.876365",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term_name",
|
||||
"invoice_portion",
|
||||
"mode_of_payment",
|
||||
"column_break_3",
|
||||
"due_date_based_on",
|
||||
"credit_days",
|
||||
"credit_months",
|
||||
"section_break_8",
|
||||
"discount_type",
|
||||
"discount",
|
||||
"column_break_11",
|
||||
"discount_validity_based_on",
|
||||
"discount_validity",
|
||||
"section_break_6",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "payment_term_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payment Term Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "payment_term_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Payment Term Name",
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"description": "Provide the invoice portion in percent",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Invoice Portion",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"label": "Invoice Portion (%)"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Mode of Payment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Mode of Payment",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Due Date Based On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Due Date Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"description": "Give number of days according to prior selection",
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Days",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"bold": 1,
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Days"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Months",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Months"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 1,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"bold": 1,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount Settings"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"default": "Day(s) after invoice date",
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_validity_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Validity Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fieldname": "discount_validity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Discount Validity",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2020-10-14 10:47:32.830478",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Term",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-02-15 20:30:56.256403",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Term",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -3,11 +3,6 @@
|
||||
|
||||
frappe.ui.form.on('Payment Terms Template', {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch("payment_term", "description", "description");
|
||||
frm.add_fetch("payment_term", "invoice_portion", "invoice_portion");
|
||||
frm.add_fetch("payment_term", "due_date_based_on", "due_date_based_on");
|
||||
frm.add_fetch("payment_term", "credit_days", "credit_days");
|
||||
frm.add_fetch("payment_term", "credit_months", "credit_months");
|
||||
frm.add_fetch("payment_term", "mode_of_payment", "mode_of_payment");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@ from frappe import _
|
||||
class PaymentTermsTemplate(Document):
|
||||
def validate(self):
|
||||
self.validate_invoice_portion()
|
||||
self.validate_credit_days()
|
||||
self.check_duplicate_terms()
|
||||
|
||||
def validate_invoice_portion(self):
|
||||
@@ -24,11 +23,6 @@ class PaymentTermsTemplate(Document):
|
||||
if flt(total_portion, 2) != 100.00:
|
||||
frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red')
|
||||
|
||||
def validate_credit_days(self):
|
||||
for term in self.terms:
|
||||
if cint(term.credit_days) < 0:
|
||||
frappe.msgprint(_('Credit Days cannot be a negative number'), raise_exception=1, indicator='red')
|
||||
|
||||
def check_duplicate_terms(self):
|
||||
terms = []
|
||||
for term in self.terms:
|
||||
|
||||
@@ -1,278 +1,164 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2017-08-10 15:34:09.409562",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2017-08-10 15:34:09.409562",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"section_break_13",
|
||||
"description",
|
||||
"section_break_4",
|
||||
"invoice_portion",
|
||||
"mode_of_payment",
|
||||
"column_break_3",
|
||||
"due_date_based_on",
|
||||
"credit_days",
|
||||
"credit_months",
|
||||
"section_break_8",
|
||||
"discount_type",
|
||||
"discount",
|
||||
"column_break_11",
|
||||
"discount_validity_based_on",
|
||||
"discount_validity"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payment Term",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Payment Term",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Description",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.description",
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Invoice Portion",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.invoice_portion",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "invoice_portion",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Invoice Portion (%)",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Due Date Based On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.due_date_based_on",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "due_date_based_on",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Due Date Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Days",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"default": "0",
|
||||
"depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
|
||||
"fetch_from": "payment_term.credit_days",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "credit_days",
|
||||
"fieldtype": "Int",
|
||||
"in_list_view": 1,
|
||||
"label": "Credit Days",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Credit Months",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
|
||||
"fetch_from": "payment_term.credit_months",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "credit_months",
|
||||
"fieldtype": "Int",
|
||||
"label": "Credit Months",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Mode of Payment",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Mode of Payment",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fetch_from": "payment_term.mode_of_payment",
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Link",
|
||||
"label": "Mode of Payment",
|
||||
"options": "Mode of Payment"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount Settings"
|
||||
},
|
||||
{
|
||||
"default": "Percentage",
|
||||
"fetch_from": "payment_term.discount_type",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Type",
|
||||
"options": "Percentage\nAmount"
|
||||
},
|
||||
{
|
||||
"fetch_from": "payment_term.discount",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Day(s) after invoice date",
|
||||
"depends_on": "discount",
|
||||
"fetch_from": "payment_term.discount_validity_based_on",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_validity_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Discount Validity Based On",
|
||||
"options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_13",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"depends_on": "discount",
|
||||
"fetch_from": "payment_term.discount_validity",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "discount_validity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Discount Validity",
|
||||
"mandatory_depends_on": "discount"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-08-21 16:15:55.143025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Terms Template Detail",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-24 11:56:12.410807",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Terms Template Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -62,11 +62,11 @@ class POSProfile(Document):
|
||||
|
||||
if len(default_mode) > 1:
|
||||
frappe.throw(_("You can only select one mode of payment as default"))
|
||||
|
||||
|
||||
invalid_modes = []
|
||||
for d in self.payments:
|
||||
account = frappe.db.get_value(
|
||||
"Mode of Payment Account",
|
||||
"Mode of Payment Account",
|
||||
{"parent": d.mode_of_payment, "company": self.company},
|
||||
"default_account"
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"apply_on",
|
||||
"price_or_product_discount",
|
||||
"warehouse",
|
||||
"condition",
|
||||
"column_break_7",
|
||||
"items",
|
||||
"item_groups",
|
||||
|
||||
@@ -523,6 +523,28 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.events.add_custom_buttons(frm);
|
||||
},
|
||||
|
||||
add_custom_buttons: function(frm) {
|
||||
if (frm.doc.per_received < 100) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frm.events.make_purchase_receipt(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frappe.route_options = {
|
||||
'purchase_invoice': frm.doc.name
|
||||
}
|
||||
|
||||
frappe.set_route("List", "Purchase Receipt", "List")
|
||||
}, __('View'));
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(frm.doc.__onload && frm.is_new()) {
|
||||
if(frm.doc.supplier) {
|
||||
@@ -548,5 +570,13 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
update_stock: function(frm) {
|
||||
hide_fields(frm.doc);
|
||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
|
||||
},
|
||||
|
||||
make_purchase_receipt: function(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
|
||||
frm: frm,
|
||||
freeze_message: __("Creating Purchase Receipt ...")
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -164,7 +164,8 @@
|
||||
"to_date",
|
||||
"column_break_114",
|
||||
"auto_repeat",
|
||||
"update_auto_repeat_reference"
|
||||
"update_auto_repeat_reference",
|
||||
"per_received"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -1372,13 +1373,22 @@
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"fieldname": "per_received",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 1,
|
||||
"label": "Per Received",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-09 21:56:28.748582",
|
||||
"modified": "2021-03-28 04:13:59.829389",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -102,6 +102,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.set_status()
|
||||
self.validate_purchase_receipt_if_update_stock()
|
||||
validate_inter_company_party(self.doctype, self.supplier, self.company, self.inter_company_invoice_reference)
|
||||
self.set_inter_company_account()
|
||||
|
||||
def validate_release_date(self):
|
||||
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
|
||||
@@ -376,6 +377,33 @@ class PurchaseInvoice(BuyingController):
|
||||
where name=`tabPurchase Invoice Item`.parent and update_stock=1 and is_return=1)"""
|
||||
})
|
||||
|
||||
def set_inter_company_account(self):
|
||||
"""
|
||||
Set intercompany account for inter warehouse transactions
|
||||
This account will be used in case billing company and internal customer's
|
||||
representation company is same
|
||||
"""
|
||||
|
||||
if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
|
||||
unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
|
||||
|
||||
if not unrealized_profit_loss_account:
|
||||
msg = _("Please select inter-company account or add default inter-company account for company {0}").format(
|
||||
frappe.bold(self.company))
|
||||
frappe.throw(msg)
|
||||
|
||||
self.unrealized_profit_loss_account = unrealized_profit_loss_account
|
||||
|
||||
def is_internal_transfer(self):
|
||||
"""
|
||||
It will an internal transfer if its an internal supplier and representation
|
||||
company is same as billing company
|
||||
"""
|
||||
if self.is_internal_supplier and (self.represents_company == self.company):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def validate_purchase_receipt_if_update_stock(self):
|
||||
if self.update_stock:
|
||||
for item in self.get("items"):
|
||||
@@ -1207,3 +1235,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
|
||||
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
|
||||
target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
|
||||
flt(obj.rate) * flt(source_parent.conversion_rate)
|
||||
|
||||
doc = get_mapped_doc("Purchase Invoice", source_name, {
|
||||
"Purchase Invoice": {
|
||||
"doctype": "Purchase Receipt",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
},
|
||||
"Purchase Invoice Item": {
|
||||
"doctype": "Purchase Receipt Item",
|
||||
"field_map": {
|
||||
"name": "purchase_invoice_item",
|
||||
"parent": "purchase_invoice",
|
||||
"bom": "bom",
|
||||
"purchase_order": "purchase_order",
|
||||
"po_detail": "purchase_order_item",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item"
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges"
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -397,7 +397,7 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
pi.update({
|
||||
"payment_schedule": get_payment_terms("_Test Payment Term Template",
|
||||
pi.posting_date, pi.grand_total)
|
||||
pi.posting_date, pi.grand_total, pi.base_grand_total)
|
||||
})
|
||||
|
||||
pi.save()
|
||||
|
||||
@@ -601,6 +601,7 @@
|
||||
"oldfieldname": "purchase_order",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@@ -804,7 +805,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-30 21:43:21.488258",
|
||||
"modified": "2021-03-30 09:02:39.256602",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -585,6 +585,16 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("adjustment_against", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
customer: frm.doc.customer,
|
||||
docstatus: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
@@ -695,6 +705,7 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
refresh_field(['timesheets'])
|
||||
}
|
||||
})
|
||||
frm.refresh();
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
@@ -810,6 +821,69 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.project) {
|
||||
frm.add_custom_button(__('Fetch Timesheet'), function() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __('Fetch Timesheet'),
|
||||
fields: [
|
||||
{
|
||||
"label" : "From",
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
},
|
||||
{
|
||||
fieldtype: 'Column Break',
|
||||
fieldname: 'col_break_1',
|
||||
},
|
||||
{
|
||||
"label" : "To",
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
}
|
||||
],
|
||||
primary_action: function() {
|
||||
let data = d.get_values();
|
||||
frappe.call({
|
||||
method: "erpnext.projects.doctype.timesheet.timesheet.get_projectwise_timesheet_data",
|
||||
args: {
|
||||
from_time: data.from_time,
|
||||
to_time: data.to_time,
|
||||
project: frm.doc.project
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
if(r.message.length > 0) {
|
||||
frm.clear_table('timesheets')
|
||||
r.message.forEach((d) => {
|
||||
frm.add_child('timesheets',{
|
||||
'time_sheet': d.parent,
|
||||
'billing_hours': d.billing_hours,
|
||||
'billing_amount': d.billing_amt,
|
||||
'timesheet_detail': d.name
|
||||
});
|
||||
});
|
||||
frm.refresh_field('timesheets')
|
||||
}
|
||||
else {
|
||||
frappe.msgprint(__('No Timesheet Found.'))
|
||||
}
|
||||
d.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
primary_action_label: __('Get Timesheets')
|
||||
});
|
||||
d.show();
|
||||
})
|
||||
}
|
||||
|
||||
if (frm.doc.is_debit_note) {
|
||||
frm.set_df_property('return_against', 'label', 'Adjustment Against');
|
||||
}
|
||||
|
||||
if (frappe.boot.active_domains.includes("Healthcare")) {
|
||||
frm.set_df_property("patient", "hidden", 0);
|
||||
frm.set_df_property("patient_name", "hidden", 0);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
"pos_profile",
|
||||
"offline_pos_name",
|
||||
"is_return",
|
||||
"is_debit_note",
|
||||
"update_billed_amount_in_sales_order",
|
||||
"column_break1",
|
||||
"company",
|
||||
"company_tax_id",
|
||||
@@ -405,15 +407,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "return_against",
|
||||
"fieldname": "returns",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Returns"
|
||||
},
|
||||
{
|
||||
"depends_on": "return_against",
|
||||
"depends_on": "eval:doc.return_against || doc.is_debit_note",
|
||||
"fieldname": "return_against",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
@@ -422,7 +416,7 @@
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:doc.is_return",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@@ -1982,6 +1976,12 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Set Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_debit_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Debit Note"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -1995,7 +1995,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-01-12 12:16:15.192520",
|
||||
"modified": "2021-04-22 22:36:32.916354",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -23,6 +23,7 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import \
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||
|
||||
from erpnext.healthcare.utils import manage_invoice_submit_cancel
|
||||
|
||||
@@ -76,6 +77,8 @@ class SalesInvoice(SellingController):
|
||||
if not self.is_pos:
|
||||
self.so_dn_required()
|
||||
|
||||
self.set_tax_withholding()
|
||||
|
||||
self.validate_proj_cust()
|
||||
self.validate_pos_return()
|
||||
self.validate_with_previous_doc()
|
||||
@@ -91,6 +94,7 @@ class SalesInvoice(SellingController):
|
||||
self.set_income_account_for_fixed_assets()
|
||||
self.validate_item_cost_centers()
|
||||
validate_inter_company_party(self.doctype, self.customer, self.company, self.inter_company_invoice_reference)
|
||||
self.set_inter_company_account()
|
||||
|
||||
if cint(self.is_pos):
|
||||
self.validate_pos()
|
||||
@@ -153,6 +157,32 @@ class SalesInvoice(SellingController):
|
||||
if cost_center_company != self.company:
|
||||
frappe.throw(_("Row #{0}: Cost Center {1} does not belong to company {2}").format(frappe.bold(item.idx), frappe.bold(item.cost_center), frappe.bold(self.company)))
|
||||
|
||||
def set_tax_withholding(self):
|
||||
tax_withholding_details = get_party_tax_withholding_details(self)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
accounts = []
|
||||
tax_withholding_account = tax_withholding_details.get("account_head")
|
||||
|
||||
for d in self.taxes:
|
||||
if d.account_head == tax_withholding_account:
|
||||
d.update(tax_withholding_details)
|
||||
accounts.append(d.account_head)
|
||||
|
||||
if not accounts or tax_withholding_account not in accounts:
|
||||
self.append("taxes", tax_withholding_details)
|
||||
|
||||
to_remove = [d for d in self.taxes
|
||||
if not d.tax_amount and d.charge_type == "Actual" and d.account_head == tax_withholding_account]
|
||||
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
# calculate totals again after applying TDS
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
def before_save(self):
|
||||
set_account_for_mode_of_payment(self)
|
||||
|
||||
@@ -614,6 +644,33 @@ class SalesInvoice(SellingController):
|
||||
if not res:
|
||||
throw(_("Customer {0} does not belong to project {1}").format(self.customer,self.project))
|
||||
|
||||
def set_inter_company_account(self):
|
||||
"""
|
||||
Set intercompany account for inter warehouse transactions
|
||||
This account will be used in case billing company and internal customer's
|
||||
representation company is same
|
||||
"""
|
||||
|
||||
if self.is_internal_transfer() and not self.unrealized_profit_loss_account:
|
||||
unrealized_profit_loss_account = frappe.db.get_value('Company', self.company, 'unrealized_profit_loss_account')
|
||||
|
||||
if not unrealized_profit_loss_account:
|
||||
msg = _("Please select Unrealized Profit / Loss account or add default Unrealized Profit / Loss account account for company {0}").format(
|
||||
frappe.bold(self.company))
|
||||
frappe.throw(msg)
|
||||
|
||||
self.unrealized_profit_loss_account = unrealized_profit_loss_account
|
||||
|
||||
def is_internal_transfer(self):
|
||||
"""
|
||||
It will an internal transfer if its an internal customer and representation
|
||||
company is same as billing company
|
||||
"""
|
||||
if self.is_internal_customer and (self.represents_company == self.company):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def validate_pos(self):
|
||||
if self.is_return:
|
||||
invoice_total = self.rounded_total or self.grand_total
|
||||
|
||||
@@ -1870,7 +1870,17 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
|
||||
def test_einvoice_submission_without_irn(self):
|
||||
# init
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
|
||||
einvoice_settings = frappe.get_doc('E Invoice Settings')
|
||||
einvoice_settings.enable = 1
|
||||
einvoice_settings.applicable_from = nowdate()
|
||||
einvoice_settings.append('credentials', {
|
||||
'company': '_Test Company',
|
||||
'gstin': '27AAECE4835E1ZR',
|
||||
'username': 'test',
|
||||
'password': 'test'
|
||||
})
|
||||
einvoice_settings.save()
|
||||
|
||||
country = frappe.flags.country
|
||||
frappe.flags.country = 'India'
|
||||
|
||||
@@ -1881,7 +1891,8 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.submit()
|
||||
|
||||
# reset
|
||||
frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
|
||||
einvoice_settings.enable = 0
|
||||
einvoice_settings.save()
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
@@ -2271,4 +2282,4 @@ def add_taxes(doc):
|
||||
"cost_center": "Main - TCP1",
|
||||
"description": "Excise Duty",
|
||||
"rate": 12
|
||||
})
|
||||
})
|
||||
|
||||
@@ -12,37 +12,62 @@ from erpnext.accounts.utils import get_fiscal_year
|
||||
class TaxWithholdingCategory(Document):
|
||||
pass
|
||||
|
||||
def get_party_tax_withholding_details(ref_doc, tax_withholding_category=None):
|
||||
def get_party_details(inv):
|
||||
party_type, party = '', ''
|
||||
|
||||
if inv.doctype == 'Sales Invoice':
|
||||
party_type = 'Customer'
|
||||
party = inv.customer
|
||||
else:
|
||||
party_type = 'Supplier'
|
||||
party = inv.supplier
|
||||
|
||||
return party_type, party
|
||||
|
||||
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
pan_no = ''
|
||||
suppliers = []
|
||||
parties = []
|
||||
party_type, party = get_party_details(inv)
|
||||
|
||||
if not tax_withholding_category:
|
||||
tax_withholding_category, pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, ['tax_withholding_category', 'pan'])
|
||||
tax_withholding_category, pan_no = frappe.db.get_value(party_type, party, ['tax_withholding_category', 'pan'])
|
||||
|
||||
if not tax_withholding_category:
|
||||
return
|
||||
|
||||
# if tax_withholding_category passed as an argument but not pan_no
|
||||
if not pan_no:
|
||||
pan_no = frappe.db.get_value('Supplier', ref_doc.supplier, 'pan')
|
||||
pan_no = frappe.db.get_value(party_type, party, 'pan')
|
||||
|
||||
# Get others suppliers with the same PAN No
|
||||
if pan_no:
|
||||
suppliers = [d.name for d in frappe.get_all('Supplier', fields=['name'], filters={'pan': pan_no})]
|
||||
parties = frappe.get_all(party_type, filters={ 'pan': pan_no }, pluck='name')
|
||||
|
||||
if not suppliers:
|
||||
suppliers.append(ref_doc.supplier)
|
||||
if not parties:
|
||||
parties.append(party)
|
||||
|
||||
fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company)
|
||||
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
|
||||
|
||||
fy = get_fiscal_year(ref_doc.posting_date, company=ref_doc.company)
|
||||
tax_details = get_tax_withholding_details(tax_withholding_category, fy[0], ref_doc.company)
|
||||
if not tax_details:
|
||||
frappe.throw(_('Please set associated account in Tax Withholding Category {0} against Company {1}')
|
||||
.format(tax_withholding_category, ref_doc.company))
|
||||
.format(tax_withholding_category, inv.company))
|
||||
|
||||
tds_amount = get_tds_amount(suppliers, ref_doc.net_total, ref_doc.company,
|
||||
tax_details, fy, ref_doc.posting_date, pan_no)
|
||||
if party_type == 'Customer' and not tax_details.cumulative_threshold:
|
||||
# TCS is only chargeable on sum of invoiced value
|
||||
frappe.throw(_('Tax Withholding Category {} against Company {} for Customer {} should have Cumulative Threshold value.')
|
||||
.format(tax_withholding_category, inv.company, party))
|
||||
|
||||
tax_row = get_tax_row(tax_details, tds_amount)
|
||||
tax_amount, tax_deducted = get_tax_amount(
|
||||
party_type, parties,
|
||||
inv, tax_details,
|
||||
fiscal_year, pan_no
|
||||
)
|
||||
|
||||
if party_type == 'Supplier':
|
||||
tax_row = get_tax_row_for_tds(tax_details, tax_amount)
|
||||
else:
|
||||
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
|
||||
|
||||
return tax_row
|
||||
|
||||
@@ -69,147 +94,254 @@ def get_tax_withholding_rates(tax_withholding, fiscal_year):
|
||||
|
||||
frappe.throw(_("No Tax Withholding data found for the current Fiscal Year."))
|
||||
|
||||
def get_tax_row(tax_details, tds_amount):
|
||||
|
||||
return {
|
||||
def get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted):
|
||||
row = {
|
||||
"category": "Total",
|
||||
"add_deduct_tax": "Deduct",
|
||||
"charge_type": "Actual",
|
||||
"account_head": tax_details.account_head,
|
||||
"tax_amount": tax_amount,
|
||||
"description": tax_details.description,
|
||||
"tax_amount": tds_amount
|
||||
"account_head": tax_details.account_head
|
||||
}
|
||||
|
||||
def get_tds_amount(suppliers, net_total, company, tax_details, fiscal_year_details, posting_date, pan_no=None):
|
||||
fiscal_year, year_start_date, year_end_date = fiscal_year_details
|
||||
tds_amount = 0
|
||||
tds_deducted = 0
|
||||
if tax_deducted:
|
||||
# TCS already deducted on previous invoices
|
||||
# So, TCS will be calculated by 'Previous Row Total'
|
||||
|
||||
def _get_tds(amount, rate):
|
||||
if amount <= 0:
|
||||
return 0
|
||||
|
||||
return amount * rate / 100
|
||||
|
||||
ldc_name = frappe.db.get_value('Lower Deduction Certificate',
|
||||
{
|
||||
'pan_no': pan_no,
|
||||
'fiscal_year': fiscal_year
|
||||
}, 'name')
|
||||
ldc = ''
|
||||
|
||||
if ldc_name:
|
||||
ldc = frappe.get_doc('Lower Deduction Certificate', ldc_name)
|
||||
|
||||
entries = frappe.db.sql("""
|
||||
select voucher_no, credit
|
||||
from `tabGL Entry`
|
||||
where company = %s and
|
||||
party in %s and fiscal_year=%s and credit > 0
|
||||
and is_opening = 'No'
|
||||
""", (company, tuple(suppliers), fiscal_year), as_dict=1)
|
||||
|
||||
vouchers = [d.voucher_no for d in entries]
|
||||
advance_vouchers = get_advance_vouchers(suppliers, fiscal_year=fiscal_year, company=company)
|
||||
|
||||
tds_vouchers = vouchers + advance_vouchers
|
||||
|
||||
if tds_vouchers:
|
||||
tds_deducted = frappe.db.sql("""
|
||||
SELECT sum(credit) FROM `tabGL Entry`
|
||||
WHERE
|
||||
account=%s and fiscal_year=%s and credit > 0
|
||||
and voucher_no in ({0})""". format(','.join(['%s'] * len(tds_vouchers))),
|
||||
((tax_details.account_head, fiscal_year) + tuple(tds_vouchers)))
|
||||
|
||||
tds_deducted = tds_deducted[0][0] if tds_deducted and tds_deducted[0][0] else 0
|
||||
|
||||
if tds_deducted:
|
||||
if ldc:
|
||||
limit_consumed = frappe.db.get_value('Purchase Invoice',
|
||||
{
|
||||
'supplier': ('in', suppliers),
|
||||
'apply_tds': 1,
|
||||
'docstatus': 1
|
||||
}, 'sum(net_total)')
|
||||
|
||||
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, limit_consumed, net_total,
|
||||
ldc.certificate_limit):
|
||||
|
||||
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
taxes_excluding_tcs = [d for d in inv.taxes if d.account_head != tax_details.account_head]
|
||||
if taxes_excluding_tcs:
|
||||
# chargeable amount is the total amount after other charges are applied
|
||||
row.update({
|
||||
"charge_type": "On Previous Row Total",
|
||||
"row_id": len(taxes_excluding_tcs),
|
||||
"rate": tax_details.rate
|
||||
})
|
||||
else:
|
||||
tds_amount = _get_tds(net_total, tax_details.rate)
|
||||
else:
|
||||
supplier_credit_amount = frappe.get_all('Purchase Invoice',
|
||||
fields = ['sum(net_total)'],
|
||||
filters = {'name': ('in', vouchers), 'docstatus': 1, "apply_tds": 1}, as_list=1)
|
||||
# if only TCS is to be charged, then net total is chargeable amount
|
||||
row.update({
|
||||
"charge_type": "On Net Total",
|
||||
"rate": tax_details.rate
|
||||
})
|
||||
|
||||
supplier_credit_amount = (supplier_credit_amount[0][0]
|
||||
if supplier_credit_amount and supplier_credit_amount[0][0] else 0)
|
||||
return row
|
||||
|
||||
jv_supplier_credit_amt = frappe.get_all('Journal Entry Account',
|
||||
fields = ['sum(credit_in_account_currency)'],
|
||||
filters = {
|
||||
'parent': ('in', vouchers), 'docstatus': 1,
|
||||
'party': ('in', suppliers),
|
||||
'reference_type': ('not in', ['Purchase Invoice'])
|
||||
}, as_list=1)
|
||||
def get_tax_row_for_tds(tax_details, tax_amount):
|
||||
return {
|
||||
"category": "Total",
|
||||
"charge_type": "Actual",
|
||||
"tax_amount": tax_amount,
|
||||
"add_deduct_tax": "Deduct",
|
||||
"description": tax_details.description,
|
||||
"account_head": tax_details.account_head
|
||||
}
|
||||
|
||||
supplier_credit_amount += (jv_supplier_credit_amt[0][0]
|
||||
if jv_supplier_credit_amt and jv_supplier_credit_amt[0][0] else 0)
|
||||
def get_lower_deduction_certificate(fiscal_year, pan_no):
|
||||
ldc_name = frappe.db.get_value('Lower Deduction Certificate', { 'pan_no': pan_no, 'fiscal_year': fiscal_year }, 'name')
|
||||
if ldc_name:
|
||||
return frappe.get_doc('Lower Deduction Certificate', ldc_name)
|
||||
|
||||
supplier_credit_amount += net_total
|
||||
def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, pan_no=None):
|
||||
fiscal_year = fiscal_year_details[0]
|
||||
|
||||
debit_note_amount = get_debit_note_amount(suppliers, year_start_date, year_end_date)
|
||||
supplier_credit_amount -= debit_note_amount
|
||||
vouchers = get_invoice_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
advance_vouchers = get_advance_vouchers(parties, fiscal_year, inv.company, party_type=party_type)
|
||||
taxable_vouchers = vouchers + advance_vouchers
|
||||
|
||||
if ((tax_details.get('threshold', 0) and supplier_credit_amount >= tax_details.threshold)
|
||||
or (tax_details.get('cumulative_threshold', 0) and supplier_credit_amount >= tax_details.cumulative_threshold)):
|
||||
tax_deducted = 0
|
||||
if taxable_vouchers:
|
||||
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
|
||||
|
||||
if ldc and is_valid_certificate(ldc.valid_from, ldc.valid_upto, posting_date, tds_deducted, net_total,
|
||||
ldc.certificate_limit):
|
||||
tds_amount = get_ltds_amount(supplier_credit_amount, 0, ldc.certificate_limit, ldc.rate,
|
||||
tax_details)
|
||||
tax_amount = 0
|
||||
posting_date = inv.posting_date
|
||||
if party_type == 'Supplier':
|
||||
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
|
||||
if tax_deducted:
|
||||
net_total = inv.net_total
|
||||
if ldc:
|
||||
tax_amount = get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total)
|
||||
else:
|
||||
tds_amount = _get_tds(supplier_credit_amount, tax_details.rate)
|
||||
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
|
||||
else:
|
||||
tax_amount = get_tds_amount(
|
||||
ldc, parties, inv, tax_details,
|
||||
fiscal_year_details, tax_deducted, vouchers
|
||||
)
|
||||
|
||||
elif party_type == 'Customer':
|
||||
if tax_deducted:
|
||||
# if already TCS is charged, then amount will be calculated based on 'Previous Row Total'
|
||||
tax_amount = 0
|
||||
else:
|
||||
# if no TCS has been charged in FY,
|
||||
# then chargeable value is "prev invoices + advances" value which cross the threshold
|
||||
tax_amount = get_tcs_amount(
|
||||
parties, inv, tax_details,
|
||||
fiscal_year_details, vouchers, advance_vouchers
|
||||
)
|
||||
|
||||
return tax_amount, tax_deducted
|
||||
|
||||
def get_invoice_vouchers(parties, fiscal_year, company, party_type='Supplier'):
|
||||
dr_or_cr = 'credit' if party_type == 'Supplier' else 'debit'
|
||||
|
||||
filters = {
|
||||
dr_or_cr: ['>', 0],
|
||||
'company': company,
|
||||
'party_type': party_type,
|
||||
'party': ['in', parties],
|
||||
'fiscal_year': fiscal_year,
|
||||
'is_opening': 'No',
|
||||
'is_cancelled': 0
|
||||
}
|
||||
|
||||
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck="voucher_no") or [""]
|
||||
|
||||
def get_advance_vouchers(parties, fiscal_year=None, company=None, from_date=None, to_date=None, party_type='Supplier'):
|
||||
# for advance vouchers, debit and credit is reversed
|
||||
dr_or_cr = 'debit' if party_type == 'Supplier' else 'credit'
|
||||
|
||||
filters = {
|
||||
dr_or_cr: ['>', 0],
|
||||
'is_opening': 'No',
|
||||
'is_cancelled': 0,
|
||||
'party_type': party_type,
|
||||
'party': ['in', parties],
|
||||
'against_voucher': ['is', 'not set']
|
||||
}
|
||||
|
||||
if fiscal_year:
|
||||
filters['fiscal_year'] = fiscal_year
|
||||
if company:
|
||||
filters['company'] = company
|
||||
if from_date and to_date:
|
||||
filters['posting_date'] = ['between', (from_date, to_date)]
|
||||
|
||||
return frappe.get_all('GL Entry', filters=filters, distinct=1, pluck='voucher_no') or [""]
|
||||
|
||||
def get_deducted_tax(taxable_vouchers, fiscal_year, tax_details):
|
||||
# check if TDS / TCS account is already charged on taxable vouchers
|
||||
filters = {
|
||||
'is_cancelled': 0,
|
||||
'credit': ['>', 0],
|
||||
'fiscal_year': fiscal_year,
|
||||
'account': tax_details.account_head,
|
||||
'voucher_no': ['in', taxable_vouchers],
|
||||
}
|
||||
field = "sum(credit)"
|
||||
|
||||
return frappe.db.get_value('GL Entry', filters, field) or 0.0
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_deducted, vouchers):
|
||||
tds_amount = 0
|
||||
|
||||
supp_credit_amt = frappe.db.get_value('Purchase Invoice', {
|
||||
'name': ('in', vouchers), 'docstatus': 1, 'apply_tds': 1
|
||||
}, 'sum(net_total)') or 0.0
|
||||
|
||||
supp_jv_credit_amt = frappe.db.get_value('Journal Entry Account', {
|
||||
'parent': ('in', vouchers), 'docstatus': 1,
|
||||
'party': ('in', parties), 'reference_type': ('!=', 'Purchase Invoice')
|
||||
}, 'sum(credit_in_account_currency)') or 0.0
|
||||
|
||||
supp_credit_amt += supp_jv_credit_amt
|
||||
supp_credit_amt += inv.net_total
|
||||
|
||||
debit_note_amount = get_debit_note_amount(parties, fiscal_year_details, inv.company)
|
||||
supp_credit_amt -= debit_note_amount
|
||||
|
||||
threshold = tax_details.get('threshold', 0)
|
||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||
|
||||
if ((threshold and supp_credit_amt >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if ldc and is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto,
|
||||
inv.posting_date, tax_deducted,
|
||||
inv.net_total, ldc.certificate_limit
|
||||
):
|
||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
else:
|
||||
tds_amount = supp_credit_amt * tax_details.rate / 100 if supp_credit_amt > 0 else 0
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_advance_vouchers(suppliers, fiscal_year=None, company=None, from_date=None, to_date=None):
|
||||
condition = "fiscal_year=%s" % fiscal_year
|
||||
def get_tcs_amount(parties, inv, tax_details, fiscal_year_details, vouchers, adv_vouchers):
|
||||
tcs_amount = 0
|
||||
fiscal_year, _, _ = fiscal_year_details
|
||||
|
||||
# sum of debit entries made from sales invoices
|
||||
invoiced_amt = frappe.db.get_value('GL Entry', {
|
||||
'is_cancelled': 0,
|
||||
'party': ['in', parties],
|
||||
'company': inv.company,
|
||||
'voucher_no': ['in', vouchers],
|
||||
}, 'sum(debit)') or 0.0
|
||||
|
||||
# sum of credit entries made from PE / JV with unset 'against voucher'
|
||||
advance_amt = frappe.db.get_value('GL Entry', {
|
||||
'is_cancelled': 0,
|
||||
'party': ['in', parties],
|
||||
'company': inv.company,
|
||||
'voucher_no': ['in', adv_vouchers],
|
||||
}, 'sum(credit)') or 0.0
|
||||
|
||||
# sum of credit entries made from sales invoice
|
||||
credit_note_amt = frappe.db.get_value('GL Entry', {
|
||||
'is_cancelled': 0,
|
||||
'credit': ['>', 0],
|
||||
'party': ['in', parties],
|
||||
'fiscal_year': fiscal_year,
|
||||
'company': inv.company,
|
||||
'voucher_type': 'Sales Invoice',
|
||||
}, 'sum(credit)') or 0.0
|
||||
|
||||
cumulative_threshold = tax_details.get('cumulative_threshold', 0)
|
||||
|
||||
current_invoice_total = get_invoice_total_without_tcs(inv, tax_details)
|
||||
total_invoiced_amt = current_invoice_total + invoiced_amt + advance_amt - credit_note_amt
|
||||
|
||||
if ((cumulative_threshold and total_invoiced_amt >= cumulative_threshold)):
|
||||
chargeable_amt = total_invoiced_amt - cumulative_threshold
|
||||
tcs_amount = chargeable_amt * tax_details.rate / 100 if chargeable_amt > 0 else 0
|
||||
|
||||
return tcs_amount
|
||||
|
||||
def get_invoice_total_without_tcs(inv, tax_details):
|
||||
tcs_tax_row = [d for d in inv.taxes if d.account_head == tax_details.account_head]
|
||||
tcs_tax_row_amount = tcs_tax_row[0].base_tax_amount if tcs_tax_row else 0
|
||||
|
||||
return inv.grand_total - tcs_tax_row_amount
|
||||
|
||||
def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, posting_date, net_total):
|
||||
tds_amount = 0
|
||||
limit_consumed = frappe.db.get_value('Purchase Invoice', {
|
||||
'supplier': ('in', parties),
|
||||
'apply_tds': 1,
|
||||
'docstatus': 1
|
||||
}, 'sum(net_total)')
|
||||
|
||||
if is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto,
|
||||
posting_date, limit_consumed,
|
||||
net_total, ldc.certificate_limit
|
||||
):
|
||||
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
|
||||
_, year_start_date, year_end_date = fiscal_year_details
|
||||
|
||||
filters = {
|
||||
'supplier': ['in', suppliers],
|
||||
'is_return': 1,
|
||||
'docstatus': 1,
|
||||
'posting_date': ['between', (year_start_date, year_end_date)]
|
||||
}
|
||||
fields = ['abs(sum(net_total)) as net_total']
|
||||
|
||||
if company:
|
||||
condition += "and company =%s" % (company)
|
||||
if from_date and to_date:
|
||||
condition += "and posting_date between %s and %s" % (from_date, to_date)
|
||||
filters['company'] = company
|
||||
|
||||
## Appending the same supplier again if length of suppliers list is 1
|
||||
## since tuple of single element list contains None, For example ('Test Supplier 1', )
|
||||
## and the below query fails
|
||||
if len(suppliers) == 1:
|
||||
suppliers.append(suppliers[0])
|
||||
|
||||
return frappe.db.sql_list("""
|
||||
select distinct voucher_no
|
||||
from `tabGL Entry`
|
||||
where party in %s and %s and debit > 0
|
||||
and is_opening = 'No'
|
||||
""", (tuple(suppliers), condition)) or []
|
||||
|
||||
def get_debit_note_amount(suppliers, year_start_date, year_end_date, company=None):
|
||||
condition = "and 1=1"
|
||||
if company:
|
||||
condition = " and company=%s " % company
|
||||
|
||||
if len(suppliers) == 1:
|
||||
suppliers.append(suppliers[0])
|
||||
|
||||
return flt(frappe.db.sql("""
|
||||
select abs(sum(net_total))
|
||||
from `tabPurchase Invoice`
|
||||
where supplier in %s and is_return=1 and docstatus=1
|
||||
and posting_date between %s and %s %s
|
||||
""", (tuple(suppliers), year_start_date, year_end_date, condition)))
|
||||
return frappe.get_all('Purchase Invoice', filters, fields)[0].get('net_total') or 0.0
|
||||
|
||||
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
|
||||
if current_amount < (certificate_limit - deducted_amount):
|
||||
@@ -227,4 +359,4 @@ def is_valid_certificate(valid_from, valid_upto, posting_date, deducted_amount,
|
||||
certificate_limit > deducted_amount):
|
||||
valid = True
|
||||
|
||||
return valid
|
||||
return valid
|
||||
|
||||
@@ -9,7 +9,7 @@ from frappe.utils import today
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
|
||||
test_dependencies = ["Supplier Group"]
|
||||
test_dependencies = ["Supplier Group", "Customer Group"]
|
||||
|
||||
class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
@classmethod
|
||||
@@ -18,6 +18,9 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
create_records()
|
||||
create_tax_with_holding_category()
|
||||
|
||||
def tearDown(self):
|
||||
cancel_invoices()
|
||||
|
||||
def test_cumulative_threshold_tds(self):
|
||||
frappe.db.set_value("Supplier", "Test TDS Supplier", "tax_withholding_category", "Cumulative Threshold TDS")
|
||||
invoices = []
|
||||
@@ -128,9 +131,59 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def test_cumulative_threshold_tcs(self):
|
||||
frappe.db.set_value("Customer", "Test TCS Customer", "tax_withholding_category", "Cumulative Threshold TCS")
|
||||
invoices = []
|
||||
|
||||
# create invoices for lower than single threshold tax rate
|
||||
for _ in range(2):
|
||||
si = create_sales_invoice(customer = "Test TCS Customer")
|
||||
si.submit()
|
||||
invoices.append(si)
|
||||
|
||||
# create another invoice whose total when added to previously created invoice,
|
||||
# surpasses cumulative threshhold
|
||||
si = create_sales_invoice(customer = "Test TCS Customer", rate=12000)
|
||||
si.submit()
|
||||
|
||||
# assert tax collection on total invoice amount created until now
|
||||
tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
|
||||
self.assertEqual(tcs_charged, 200)
|
||||
self.assertEqual(si.grand_total, 12200)
|
||||
invoices.append(si)
|
||||
|
||||
# TCS is already collected once, so going forward system will collect TCS on every invoice
|
||||
si = create_sales_invoice(customer = "Test TCS Customer", rate=5000)
|
||||
si.submit()
|
||||
|
||||
tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
|
||||
self.assertEqual(tcs_charged, 500)
|
||||
invoices.append(si)
|
||||
|
||||
#delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
d.cancel()
|
||||
|
||||
def cancel_invoices():
|
||||
purchase_invoices = frappe.get_all("Purchase Invoice", {
|
||||
'supplier': ['in', ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']],
|
||||
'docstatus': 1
|
||||
}, pluck="name")
|
||||
|
||||
sales_invoices = frappe.get_all("Sales Invoice", {
|
||||
'customer': 'Test TCS Customer',
|
||||
'docstatus': 1
|
||||
}, pluck="name")
|
||||
|
||||
for d in purchase_invoices:
|
||||
frappe.get_doc('Purchase Invoice', d).cancel()
|
||||
|
||||
for d in sales_invoices:
|
||||
frappe.get_doc('Sales Invoice', d).cancel()
|
||||
|
||||
def create_purchase_invoice(**args):
|
||||
# return sales invoice doc object
|
||||
item = frappe.get_doc('Item', {'item_name': 'TDS Item'})
|
||||
item = frappe.db.get_value('Item', {'item_name': 'TDS Item'}, "name")
|
||||
|
||||
args = frappe._dict(args)
|
||||
pi = frappe.get_doc({
|
||||
@@ -145,7 +198,7 @@ def create_purchase_invoice(**args):
|
||||
"taxes": [],
|
||||
"items": [{
|
||||
'doctype': 'Purchase Invoice Item',
|
||||
'item_code': item.name,
|
||||
'item_code': item,
|
||||
'qty': args.qty or 1,
|
||||
'rate': args.rate or 10000,
|
||||
'cost_center': 'Main - _TC',
|
||||
@@ -156,6 +209,33 @@ def create_purchase_invoice(**args):
|
||||
pi.save()
|
||||
return pi
|
||||
|
||||
def create_sales_invoice(**args):
|
||||
# return sales invoice doc object
|
||||
item = frappe.db.get_value('Item', {'item_name': 'TCS Item'}, "name")
|
||||
|
||||
args = frappe._dict(args)
|
||||
si = frappe.get_doc({
|
||||
"doctype": "Sales Invoice",
|
||||
"posting_date": today(),
|
||||
"customer": args.customer,
|
||||
"company": '_Test Company',
|
||||
"taxes_and_charges": "",
|
||||
"currency": "INR",
|
||||
"debit_to": "Debtors - _TC",
|
||||
"taxes": [],
|
||||
"items": [{
|
||||
'doctype': 'Sales Invoice Item',
|
||||
'item_code': item,
|
||||
'qty': args.qty or 1,
|
||||
'rate': args.rate or 10000,
|
||||
'cost_center': 'Main - _TC',
|
||||
'expense_account': 'Cost of Goods Sold - _TC'
|
||||
}]
|
||||
})
|
||||
|
||||
si.save()
|
||||
return si
|
||||
|
||||
def create_records():
|
||||
# create a new suppliers
|
||||
for name in ['Test TDS Supplier', 'Test TDS Supplier1', 'Test TDS Supplier2']:
|
||||
@@ -168,7 +248,17 @@ def create_records():
|
||||
"doctype": "Supplier",
|
||||
}).insert()
|
||||
|
||||
# create an item
|
||||
for name in ['Test TCS Customer']:
|
||||
if frappe.db.exists('Customer', name):
|
||||
continue
|
||||
|
||||
frappe.get_doc({
|
||||
"customer_group": "_Test Customer Group",
|
||||
"customer_name": name,
|
||||
"doctype": "Customer"
|
||||
}).insert()
|
||||
|
||||
# create item
|
||||
if not frappe.db.exists('Item', "TDS Item"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
@@ -178,7 +268,16 @@ def create_records():
|
||||
"is_stock_item": 0,
|
||||
}).insert()
|
||||
|
||||
# create an account
|
||||
if not frappe.db.exists('Item', "TCS Item"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Item",
|
||||
"item_code": "TCS Item",
|
||||
"item_name": "TCS Item",
|
||||
"item_group": "All Item Groups",
|
||||
"is_stock_item": 1
|
||||
}).insert()
|
||||
|
||||
# create tds account
|
||||
if not frappe.db.exists("Account", "TDS - _TC"):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Account',
|
||||
@@ -189,6 +288,17 @@ def create_records():
|
||||
'root_type': 'Asset'
|
||||
}).insert()
|
||||
|
||||
# create tcs account
|
||||
if not frappe.db.exists("Account", "TCS - _TC"):
|
||||
frappe.get_doc({
|
||||
'doctype': 'Account',
|
||||
'company': '_Test Company',
|
||||
'account_name': 'TCS',
|
||||
'parent_account': 'Duties and Taxes - _TC',
|
||||
'report_type': 'Balance Sheet',
|
||||
'root_type': 'Liability'
|
||||
}).insert()
|
||||
|
||||
def create_tax_with_holding_category():
|
||||
fiscal_year = get_fiscal_year(today(), company="_Test Company")[0]
|
||||
|
||||
@@ -210,6 +320,23 @@ def create_tax_with_holding_category():
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
if not frappe.db.exists("Tax Withholding Category", "Cumulative Threshold TCS"):
|
||||
frappe.get_doc({
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": "Cumulative Threshold TCS",
|
||||
"category_name": "10% TCS",
|
||||
"rates": [{
|
||||
'fiscal_year': fiscal_year,
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 0,
|
||||
'cumulative_threshold': 30000.00
|
||||
}],
|
||||
"accounts": [{
|
||||
'company': '_Test Company',
|
||||
'account': 'TCS - _TC'
|
||||
}]
|
||||
}).insert()
|
||||
|
||||
# Single thresold
|
||||
if not frappe.db.exists("Tax Withholding Category", "Single Threshold TDS"):
|
||||
frappe.get_doc({
|
||||
|
||||
@@ -364,7 +364,7 @@ class ReceivablePayableReport(object):
|
||||
payment_terms_details = frappe.db.sql("""
|
||||
select
|
||||
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
||||
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount
|
||||
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
from `tab{0}` si, `tabPayment Schedule` ps
|
||||
where
|
||||
si.name = ps.parent and
|
||||
@@ -395,13 +395,13 @@ class ReceivablePayableReport(object):
|
||||
"invoiced": invoiced,
|
||||
"invoice_grand_total": row.invoiced,
|
||||
"payment_term": d.description,
|
||||
"paid": d.paid_amount,
|
||||
"paid": d.paid_amount + d.discounted_amount,
|
||||
"credit_note": 0.0,
|
||||
"outstanding": invoiced - d.paid_amount
|
||||
"outstanding": invoiced - d.paid_amount - d.discounted_amount
|
||||
}))
|
||||
|
||||
if d.paid_amount:
|
||||
row['paid'] -= d.paid_amount
|
||||
row['paid'] -= d.paid_amount + d.discounted_amount
|
||||
|
||||
def allocate_closing_to_term(self, row, term, key):
|
||||
if row[key]:
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.query_reports['Billed Items To Be Received'] = {
|
||||
'filters': [
|
||||
{
|
||||
'label': __('Company'),
|
||||
'fieldname': 'company',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Company',
|
||||
'reqd': 1,
|
||||
'default': frappe.defaults.get_default('Company')
|
||||
},
|
||||
{
|
||||
'label': __('As on Date'),
|
||||
'fieldname': 'posting_date',
|
||||
'fieldtype': 'Date',
|
||||
'reqd': 1,
|
||||
'default': get_today()
|
||||
},
|
||||
{
|
||||
'label': __('Purchase Invoice'),
|
||||
'fieldname': 'purchase_invoice',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Purchase Invoice'
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-03-30 09:35:38.683028",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-03-31 08:48:30.944429",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Billed Items To Be Received",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"query": "",
|
||||
"ref_doctype": "Purchase Invoice",
|
||||
"report_name": "Billed Items To Be Received",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Purchase User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
},
|
||||
{
|
||||
"role": "Stock User"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
def execute(filters=None):
|
||||
data = get_data(filters) or []
|
||||
columns = get_columns()
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_data(report_filters):
|
||||
filters = get_report_filters(report_filters)
|
||||
fields = get_report_fields()
|
||||
|
||||
return frappe.get_all('Purchase Invoice',
|
||||
fields= fields, filters=filters)
|
||||
|
||||
def get_report_filters(report_filters):
|
||||
filters = [['Purchase Invoice','company','=',report_filters.get('company')],
|
||||
['Purchase Invoice','posting_date','<=',report_filters.get('posting_date')], ['Purchase Invoice','docstatus','=',1],
|
||||
['Purchase Invoice','per_received','<',100], ['Purchase Invoice','update_stock','=',0]]
|
||||
|
||||
if report_filters.get('purchase_invoice'):
|
||||
filters.append(['Purchase Invoice','per_received','in',[report_filters.get('purchase_invoice')]])
|
||||
|
||||
return filters
|
||||
|
||||
def get_report_fields():
|
||||
fields = []
|
||||
for p_field in ['name', 'supplier', 'company', 'posting_date', 'currency']:
|
||||
fields.append('`tabPurchase Invoice`.`{}`'.format(p_field))
|
||||
|
||||
for c_field in ['item_code', 'item_name', 'uom', 'qty', 'received_qty', 'rate', 'amount']:
|
||||
fields.append('`tabPurchase Invoice Item`.`{}`'.format(c_field))
|
||||
|
||||
return fields
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
'label': _('Purchase Invoice'),
|
||||
'fieldname': 'name',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Purchase Invoice',
|
||||
'width': 170
|
||||
},
|
||||
{
|
||||
'label': _('Supplier'),
|
||||
'fieldname': 'supplier',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Supplier',
|
||||
'width': 120
|
||||
},
|
||||
{
|
||||
'label': _('Posting Date'),
|
||||
'fieldname': 'posting_date',
|
||||
'fieldtype': 'Date',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Item Code'),
|
||||
'fieldname': 'item_code',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'Item',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Item Name'),
|
||||
'fieldname': 'item_name',
|
||||
'fieldtype': 'Data',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('UOM'),
|
||||
'fieldname': 'uom',
|
||||
'fieldtype': 'Link',
|
||||
'options': 'UOM',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Invoiced Qty'),
|
||||
'fieldname': 'qty',
|
||||
'fieldtype': 'Float',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Received Qty'),
|
||||
'fieldname': 'received_qty',
|
||||
'fieldtype': 'Float',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Rate'),
|
||||
'fieldname': 'rate',
|
||||
'fieldtype': 'Currency',
|
||||
'width': 100
|
||||
},
|
||||
{
|
||||
'label': _('Amount'),
|
||||
'fieldname': 'amount',
|
||||
'fieldtype': 'Currency',
|
||||
'width': 100
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
/* eslint-disable */
|
||||
|
||||
frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
frappe.query_reports["Dimension-wise Accounts Balance Report"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"default": frappe.defaults.get_user_default("Company"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"reqd": 1,
|
||||
"on_change": function(query_report) {
|
||||
var fiscal_year = query_report.get_values().fiscal_year;
|
||||
if (!fiscal_year) {
|
||||
return;
|
||||
}
|
||||
frappe.model.with_doc("Fiscal Year", fiscal_year, function(r) {
|
||||
var fy = frappe.model.get_doc("Fiscal Year", fiscal_year);
|
||||
frappe.query_report.set_filter_value({
|
||||
from_date: fy.year_start_date,
|
||||
to_date: fy.year_end_date
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"label": __("Finance Book"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Finance Book",
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension",
|
||||
"label": __("Select Dimension"),
|
||||
"fieldtype": "Select",
|
||||
"options": get_accounting_dimension_options(),
|
||||
"reqd": 1,
|
||||
},
|
||||
],
|
||||
"formatter": erpnext.financial_statements.formatter,
|
||||
"tree": true,
|
||||
"name_field": "account",
|
||||
"parent_field": "parent_account",
|
||||
"initial_depth": 3
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
function get_accounting_dimension_options() {
|
||||
let options =["", "Cost Center", "Project"];
|
||||
frappe.db.get_list('Accounting Dimension',
|
||||
{fields:['document_type']}).then((res) => {
|
||||
res.forEach((dimension) => {
|
||||
options.push(dimension.document_type);
|
||||
});
|
||||
});
|
||||
return options
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2021-04-09 16:48:59.548018",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2021-04-09 16:48:59.548018",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Dimension-wise Accounts Balance Report",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "Dimension-wise Accounts Balance Report",
|
||||
"report_type": "Script Report",
|
||||
"roles": []
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import (flt, cstr)
|
||||
|
||||
from erpnext.accounts.report.financial_statements import filter_accounts, filter_out_zero_value_rows
|
||||
from erpnext.accounts.report.trial_balance.trial_balance import validate_filters
|
||||
|
||||
from six import itervalues
|
||||
|
||||
def execute(filters=None):
|
||||
validate_filters(filters)
|
||||
dimension_items_list = get_dimension_items_list(filters.dimension, filters.company)
|
||||
|
||||
if not dimension_items_list:
|
||||
return [], []
|
||||
|
||||
dimension_items_list = [''.join(d) for d in dimension_items_list]
|
||||
columns = get_columns(dimension_items_list)
|
||||
data = get_data(filters, dimension_items_list)
|
||||
|
||||
return columns, data
|
||||
|
||||
def get_data(filters, dimension_items_list):
|
||||
company_currency = erpnext.get_company_currency(filters.company)
|
||||
acc = frappe.db.sql("""
|
||||
select
|
||||
name, account_number, parent_account, lft, rgt, root_type,
|
||||
report_type, account_name, include_in_gross, account_type, is_group
|
||||
from
|
||||
`tabAccount`
|
||||
where
|
||||
company=%s
|
||||
order by lft""", (filters.company), as_dict=True)
|
||||
|
||||
if not acc:
|
||||
return None
|
||||
|
||||
accounts, accounts_by_name, parent_children_map = filter_accounts(acc)
|
||||
|
||||
min_lft, max_rgt = frappe.db.sql("""select min(lft), max(rgt) from `tabAccount`
|
||||
where company=%s""", (filters.company))[0]
|
||||
|
||||
account = frappe.db.sql_list("""select name from `tabAccount`
|
||||
where lft >= %s and rgt <= %s and company = %s""", (min_lft, max_rgt, filters.company))
|
||||
|
||||
gl_entries_by_account = {}
|
||||
set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account)
|
||||
format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list)
|
||||
accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list)
|
||||
out = prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list)
|
||||
out = filter_out_zero_value_rows(out, parent_children_map)
|
||||
|
||||
return out
|
||||
|
||||
def set_gl_entries_by_account(dimension_items_list, filters, account, gl_entries_by_account):
|
||||
for item in dimension_items_list:
|
||||
condition = get_condition(filters.from_date, item, filters.dimension)
|
||||
if account:
|
||||
condition += " and account in ({})"\
|
||||
.format(", ".join([frappe.db.escape(d) for d in account]))
|
||||
|
||||
gl_filters = {
|
||||
"company": filters.get("company"),
|
||||
"from_date": filters.get("from_date"),
|
||||
"to_date": filters.get("to_date"),
|
||||
"finance_book": cstr(filters.get("finance_book"))
|
||||
}
|
||||
|
||||
gl_filters['item'] = ''.join(item)
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
gl_filters["company_fb"] = frappe.db.get_value("Company",
|
||||
filters.company, 'default_finance_book')
|
||||
|
||||
for key, value in filters.items():
|
||||
if value:
|
||||
gl_filters.update({
|
||||
key: value
|
||||
})
|
||||
|
||||
gl_entries = frappe.db.sql("""
|
||||
select
|
||||
posting_date, account, debit, credit, is_opening, fiscal_year,
|
||||
debit_in_account_currency, credit_in_account_currency, account_currency
|
||||
from
|
||||
`tabGL Entry`
|
||||
where
|
||||
company=%(company)s
|
||||
{condition}
|
||||
and posting_date <= %(to_date)s
|
||||
and is_cancelled = 0
|
||||
order by account, posting_date""".format(
|
||||
condition=condition),
|
||||
gl_filters, as_dict=True) #nosec
|
||||
|
||||
for entry in gl_entries:
|
||||
entry['dimension_item'] = ''.join(item)
|
||||
gl_entries_by_account.setdefault(entry.account, []).append(entry)
|
||||
|
||||
def format_gl_entries(gl_entries_by_account, accounts_by_name, dimension_items_list):
|
||||
|
||||
for entries in itervalues(gl_entries_by_account):
|
||||
for entry in entries:
|
||||
d = accounts_by_name.get(entry.account)
|
||||
if not d:
|
||||
frappe.msgprint(
|
||||
_("Could not retrieve information for {0}.").format(entry.account), title="Error",
|
||||
raise_exception=1
|
||||
)
|
||||
for item in dimension_items_list:
|
||||
if item == entry.dimension_item:
|
||||
d[frappe.scrub(item)] = d.get(frappe.scrub(item), 0.0) + flt(entry.debit) - flt(entry.credit)
|
||||
|
||||
def prepare_data(accounts, filters, parent_children_map, company_currency, dimension_items_list):
|
||||
data = []
|
||||
|
||||
for d in accounts:
|
||||
has_value = False
|
||||
row = {
|
||||
"account": d.name,
|
||||
"parent_account": d.parent_account,
|
||||
"indent": d.indent,
|
||||
"from_date": filters.from_date,
|
||||
"to_date": filters.to_date,
|
||||
"currency": company_currency,
|
||||
"account_name": ('{} - {}'.format(d.account_number, d.account_name)
|
||||
if d.account_number else d.account_name)
|
||||
}
|
||||
|
||||
for item in dimension_items_list:
|
||||
row[frappe.scrub(item)] = flt(d.get(frappe.scrub(item), 0.0), 3)
|
||||
|
||||
if abs(row[frappe.scrub(item)]) >= 0.005:
|
||||
# ignore zero values
|
||||
has_value = True
|
||||
|
||||
row["has_value"] = has_value
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
def accumulate_values_into_parents(accounts, accounts_by_name, dimension_items_list):
|
||||
"""accumulate children's values in parent accounts"""
|
||||
for d in reversed(accounts):
|
||||
if d.parent_account:
|
||||
for item in dimension_items_list:
|
||||
accounts_by_name[d.parent_account][frappe.scrub(item)] = \
|
||||
accounts_by_name[d.parent_account].get(frappe.scrub(item), 0.0) + d.get(frappe.scrub(item), 0.0)
|
||||
|
||||
def get_condition(from_date, item, dimension):
|
||||
conditions = []
|
||||
|
||||
if from_date:
|
||||
conditions.append("posting_date >= %(from_date)s")
|
||||
if dimension:
|
||||
if dimension not in ['Cost Center', 'Project']:
|
||||
if dimension in ['Customer', 'Supplier']:
|
||||
dimension = 'Party'
|
||||
else:
|
||||
dimension = 'Voucher No'
|
||||
txt = "{0} = %(item)s".format(frappe.scrub(dimension))
|
||||
conditions.append(txt)
|
||||
|
||||
return " and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
def get_dimension_items_list(dimension, company):
|
||||
meta = frappe.get_meta(dimension, cached=False)
|
||||
fieldnames = [d.fieldname for d in meta.get("fields")]
|
||||
filters = {}
|
||||
if 'company' in fieldnames:
|
||||
filters['company'] = company
|
||||
return frappe.get_all(dimension, filters, as_list=True)
|
||||
|
||||
def get_columns(dimension_items_list, accumulated_values=1, company=None):
|
||||
columns = [{
|
||||
"fieldname": "account",
|
||||
"label": _("Account"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 300
|
||||
}]
|
||||
if company:
|
||||
columns.append({
|
||||
"fieldname": "currency",
|
||||
"label": _("Currency"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Currency",
|
||||
"hidden": 1
|
||||
})
|
||||
for item in dimension_items_list:
|
||||
columns.append({
|
||||
"fieldname": frappe.scrub(item),
|
||||
"label": item,
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 150
|
||||
})
|
||||
|
||||
return columns
|
||||
@@ -318,6 +318,7 @@ def get_items(filters, additional_query_columns):
|
||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||
`tabPurchase Invoice`.unrealized_profit_loss_account,
|
||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||
`tabPurchase Invoice`.unrealized_profit_loss_account,
|
||||
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
|
||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
"po_required",
|
||||
"pr_required",
|
||||
"maintain_same_rate",
|
||||
"maintain_same_rate_action",
|
||||
"role_to_override_stop_action",
|
||||
"allow_multiple_items",
|
||||
"subcontract",
|
||||
"backflush_raw_materials_of_subcontract_based_on",
|
||||
@@ -89,6 +91,23 @@
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Stop",
|
||||
"depends_on": "maintain_same_rate",
|
||||
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
|
||||
"fieldname": "maintain_same_rate_action",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action If Same Rate is Not Maintained",
|
||||
"mandatory_depends_on": "maintain_same_rate",
|
||||
"options": "Stop\nWarn"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.maintain_same_rate_action == 'Stop'",
|
||||
"fieldname": "role_to_override_stop_action",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Override Stop Action",
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -96,7 +115,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-13 12:00:23.276329",
|
||||
"modified": "2021-04-04 20:01:44.087066",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -697,7 +697,9 @@ class AccountsController(TransactionBase):
|
||||
total_billed_amt = abs(total_billed_amt)
|
||||
max_allowed_amt = abs(max_allowed_amt)
|
||||
|
||||
if total_billed_amt - max_allowed_amt > 0.01:
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
|
||||
if total_billed_amt - max_allowed_amt > 0.01 and role_allowed_to_over_bill not in frappe.get_roles():
|
||||
frappe.throw(_("Cannot overbill for Item {0} in row {1} more than {2}. To allow over-billing, please set allowance in Accounts Settings")
|
||||
.format(item.item_code, item.idx, max_allowed_amt))
|
||||
|
||||
@@ -882,29 +884,35 @@ class AccountsController(TransactionBase):
|
||||
date = self.get("due_date")
|
||||
due_date = date or posting_date
|
||||
|
||||
if party_account_currency == self.company_currency:
|
||||
grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
else:
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
|
||||
if self.get("total_advance"):
|
||||
grand_total -= self.get("total_advance")
|
||||
if party_account_currency == self.company_currency:
|
||||
base_grand_total -= self.get("total_advance")
|
||||
grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
|
||||
else:
|
||||
grand_total -= self.get("total_advance")
|
||||
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||
|
||||
if not self.get("payment_schedule"):
|
||||
if self.get("payment_terms_template"):
|
||||
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total)
|
||||
data = get_payment_terms(self.payment_terms_template, posting_date, grand_total, base_grand_total)
|
||||
for item in data:
|
||||
self.append("payment_schedule", item)
|
||||
else:
|
||||
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total)
|
||||
data = dict(due_date=due_date, invoice_portion=100, payment_amount=grand_total, base_payment_amount=base_grand_total)
|
||||
self.append("payment_schedule", data)
|
||||
else:
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount'))
|
||||
d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.base_payment_amount = flt(base_grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
|
||||
d.outstanding = d.payment_amount
|
||||
|
||||
def set_due_date(self):
|
||||
due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
|
||||
@@ -940,22 +948,28 @@ class AccountsController(TransactionBase):
|
||||
|
||||
if self.get("payment_schedule"):
|
||||
total = 0
|
||||
base_total = 0
|
||||
for d in self.get("payment_schedule"):
|
||||
total += flt(d.payment_amount)
|
||||
base_total += flt(d.base_payment_amount)
|
||||
|
||||
if party_account_currency == self.company_currency:
|
||||
total = flt(total, self.precision("base_grand_total"))
|
||||
grand_total = flt(self.get("base_rounded_total") or self.base_grand_total, self.precision('base_grand_total'))
|
||||
else:
|
||||
total = flt(total, self.precision("grand_total"))
|
||||
grand_total = flt(self.get("rounded_total") or self.grand_total, self.precision('grand_total'))
|
||||
|
||||
if self.get("total_advance"):
|
||||
grand_total -= self.get("total_advance")
|
||||
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||
grand_total = grand_total - flt(self.write_off_amount)
|
||||
if total != flt(grand_total, self.precision("grand_total")):
|
||||
|
||||
if self.get("total_advance"):
|
||||
if party_account_currency == self.company_currency:
|
||||
base_grand_total -= self.get("total_advance")
|
||||
grand_total = flt(base_grand_total / self.get("conversion_rate"), self.precision("grand_total"))
|
||||
else:
|
||||
grand_total -= self.get("total_advance")
|
||||
base_grand_total = flt(grand_total * self.get("conversion_rate"), self.precision("base_grand_total"))
|
||||
print(grand_total, base_grand_total)
|
||||
if total != flt(grand_total, self.precision("grand_total")) or \
|
||||
base_total != flt(base_grand_total, self.precision("base_grand_total")):
|
||||
frappe.throw(_("Total Payment Amount in Payment Schedule must be equal to Grand / Rounded Total"))
|
||||
|
||||
def is_rounded_total_disabled(self):
|
||||
@@ -1195,7 +1209,7 @@ def update_invoice_status():
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_date=None):
|
||||
def get_payment_terms(terms_template, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
|
||||
if not terms_template:
|
||||
return
|
||||
|
||||
@@ -1203,14 +1217,14 @@ def get_payment_terms(terms_template, posting_date=None, grand_total=None, bill_
|
||||
|
||||
schedule = []
|
||||
for d in terms_doc.get("terms"):
|
||||
term_details = get_payment_term_details(d, posting_date, grand_total, bill_date)
|
||||
term_details = get_payment_term_details(d, posting_date, grand_total, base_grand_total, bill_date)
|
||||
schedule.append(term_details)
|
||||
|
||||
return schedule
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_term_details(term, posting_date=None, grand_total=None, bill_date=None):
|
||||
def get_payment_term_details(term, posting_date=None, grand_total=None, base_grand_total=None, bill_date=None):
|
||||
term_details = frappe._dict()
|
||||
if isinstance(term, text_type):
|
||||
term = frappe.get_doc("Payment Term", term)
|
||||
@@ -1219,18 +1233,24 @@ def get_payment_term_details(term, posting_date=None, grand_total=None, bill_dat
|
||||
term_details.description = term.description
|
||||
term_details.invoice_portion = term.invoice_portion
|
||||
term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
|
||||
term_details.base_payment_amount = flt(term.invoice_portion) * flt(base_grand_total) / 100
|
||||
term_details.discount_type = term.discount_type
|
||||
term_details.discount = term.discount
|
||||
term_details.outstanding = term_details.payment_amount
|
||||
term_details.mode_of_payment = term.mode_of_payment
|
||||
|
||||
if bill_date:
|
||||
term_details.due_date = get_due_date(term, bill_date)
|
||||
term_details.discount_date = get_discount_date(term, bill_date)
|
||||
elif posting_date:
|
||||
term_details.due_date = get_due_date(term, posting_date)
|
||||
term_details.discount_date = get_discount_date(term, posting_date)
|
||||
|
||||
if getdate(term_details.due_date) < getdate(posting_date):
|
||||
term_details.due_date = posting_date
|
||||
term_details.mode_of_payment = term.mode_of_payment
|
||||
|
||||
return term_details
|
||||
|
||||
|
||||
def get_due_date(term, posting_date=None, bill_date=None):
|
||||
due_date = None
|
||||
date = bill_date or posting_date
|
||||
@@ -1242,6 +1262,16 @@ def get_due_date(term, posting_date=None, bill_date=None):
|
||||
due_date = add_months(get_last_day(date), term.credit_months)
|
||||
return due_date
|
||||
|
||||
def get_discount_date(term, posting_date=None, bill_date=None):
|
||||
discount_validity = None
|
||||
date = bill_date or posting_date
|
||||
if term.discount_validity_based_on == "Day(s) after invoice date":
|
||||
discount_validity = add_days(date, term.discount_validity)
|
||||
elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
|
||||
discount_validity = add_days(get_last_day(date), term.discount_validity)
|
||||
elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
|
||||
discount_validity = add_months(get_last_day(date), term.discount_validity)
|
||||
return discount_validity
|
||||
|
||||
def get_supplier_block_status(party_name):
|
||||
"""
|
||||
@@ -1395,8 +1425,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
)
|
||||
|
||||
def get_new_child_item(item_row):
|
||||
new_child_function = set_sales_order_defaults if parent_doctype == "Sales Order" else set_purchase_order_defaults
|
||||
return new_child_function(parent_doctype, parent_doctype_name, child_docname, item_row)
|
||||
child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
|
||||
return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
|
||||
|
||||
def validate_quantity(child_item, d):
|
||||
if parent_doctype == "Sales Order" and flt(d.get("qty")) < flt(child_item.delivered_qty):
|
||||
|
||||
@@ -201,10 +201,14 @@ class StatusUpdater(Document):
|
||||
get_allowance_for(item['item_code'], self.item_allowance,
|
||||
self.global_qty_allowance, self.global_amount_allowance, qty_or_amount)
|
||||
|
||||
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
|
||||
item[args['target_ref_field']]) * 100
|
||||
role_allowed_to_over_deliver_receive = frappe.db.get_single_value('Stock Settings', 'role_allowed_to_over_deliver_receive')
|
||||
role_allowed_to_over_bill = frappe.db.get_single_value('Accounts Settings', 'role_allowed_to_over_bill')
|
||||
role = role_allowed_to_over_deliver_receive if qty_or_amount == 'qty' else role_allowed_to_over_bill
|
||||
|
||||
if overflow_percent - allowance > 0.01:
|
||||
overflow_percent = ((item[args['target_field']] - item[args['target_ref_field']]) /
|
||||
item[args['target_ref_field']]) * 100
|
||||
|
||||
if overflow_percent - allowance > 0.01 and role not in frappe.get_roles():
|
||||
item['max_allowed'] = flt(item[args['target_ref_field']] * (100+allowance)/100)
|
||||
item['reduce_by'] = item[args['target_field']] - item['max_allowed']
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ from erpnext.accounts.doctype.journal_entry.journal_entry import get_exchange_ra
|
||||
class calculate_taxes_and_totals(object):
|
||||
def __init__(self, doc):
|
||||
self.doc = doc
|
||||
frappe.flags.round_off_applicable_accounts = []
|
||||
get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
|
||||
self.calculate()
|
||||
|
||||
def calculate(self):
|
||||
@@ -340,10 +342,18 @@ class calculate_taxes_and_totals(object):
|
||||
elif tax.charge_type == "On Item Quantity":
|
||||
current_tax_amount = tax_rate * item.qty
|
||||
|
||||
current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
|
||||
self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
|
||||
|
||||
return current_tax_amount
|
||||
|
||||
def get_final_current_tax_amount(self, tax, current_tax_amount):
|
||||
# Some countries need individual tax components to be rounded
|
||||
# Handeled via regional doctypess
|
||||
if tax.account_head in frappe.flags.round_off_applicable_accounts:
|
||||
current_tax_amount = round(current_tax_amount, 0)
|
||||
return current_tax_amount
|
||||
|
||||
def set_item_wise_tax(self, item, tax, tax_rate, current_tax_amount):
|
||||
# store tax breakup for each item
|
||||
key = item.item_code or item.item_name
|
||||
@@ -701,6 +711,15 @@ def get_itemised_tax_breakup_html(doc):
|
||||
)
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_round_off_applicable_accounts(company, account_list):
|
||||
account_list = get_regional_round_off_accounts(company, account_list)
|
||||
|
||||
return account_list
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_regional_round_off_accounts(company, account_list):
|
||||
pass
|
||||
|
||||
@erpnext.allow_regional
|
||||
def update_itemised_tax_data(doc):
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Integration Item', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
@@ -0,0 +1,119 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-03-13 12:24:28.511716",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"integration_item_name",
|
||||
"erpnext_item_code"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "integration_item_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Integration Item Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "erpnext_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "ERPNext Item Code ",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-13 18:11:18.798940",
|
||||
"modified_by": "Administrator",
|
||||
"module": "ERPNext Integrations",
|
||||
"name": "Integration Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Item Manager",
|
||||
"select": 1,
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Stock User",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Sales User",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Purchase User",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Maintenance User",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"select": 1,
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "integration_item_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class IntegrationItem(Document):
|
||||
pass
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
class TestIntegrationItem(unittest.TestCase):
|
||||
pass
|
||||
@@ -116,7 +116,7 @@ def create_item(shopify_item, warehouse, has_variant=0, attributes=None,variant_
|
||||
]
|
||||
}
|
||||
|
||||
if not is_item_exists(item_dict, attributes, variant_of=variant_of):
|
||||
if not item_exists(item_dict, attributes, variant_of=variant_of):
|
||||
item_details = get_item_details(shopify_item)
|
||||
name = ''
|
||||
|
||||
@@ -255,11 +255,16 @@ def get_item_details(shopify_item):
|
||||
["name", "stock_uom", "item_name"], as_dict=1)
|
||||
return item_details
|
||||
|
||||
def is_item_exists(shopify_item, attributes=None, variant_of=None):
|
||||
if variant_of:
|
||||
name = variant_of
|
||||
else:
|
||||
name = frappe.db.get_value("Item", {"item_name": shopify_item.get("item_name")})
|
||||
def item_exists(shopify_item, attributes=None, variant_of=None):
|
||||
name = ''
|
||||
name = frappe.db.get_value('Integration Item', {'integration_item_name': shopify_item.get("item_name")},
|
||||
'erpnext_item_code')
|
||||
|
||||
if not name:
|
||||
if variant_of:
|
||||
name = variant_of
|
||||
else:
|
||||
name = frappe.db.get_value("Item", {"item_name": shopify_item.get("item_name")})
|
||||
|
||||
if name:
|
||||
item = frappe.get_doc("Item", name)
|
||||
|
||||
@@ -6,10 +6,12 @@ import frappe
|
||||
|
||||
import unittest, os, json
|
||||
from frappe.utils import cstr
|
||||
from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
|
||||
from erpnext.erpnext_integrations.connectors.shopify_connection import create_order, validate_customer, validate_item
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
|
||||
from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
|
||||
from frappe.core.doctype.data_import.data_import import import_doc
|
||||
from erpnext.stock.doctype.item.test_item import make_item as make_erpnext_item
|
||||
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
|
||||
|
||||
|
||||
class ShopifySettings(unittest.TestCase):
|
||||
@@ -96,3 +98,239 @@ class ShopifySettings(unittest.TestCase):
|
||||
where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
|
||||
|
||||
self.assertEqual(delivery_note_count, len(shopify_order.get("order").get("fulfillments")))
|
||||
|
||||
def test_product_sync_using_integration_item(self):
|
||||
# Create erpnext item
|
||||
item = make_erpnext_item("_Test Shopify Item")
|
||||
|
||||
if not frappe.db.get_value("Customer", "_Test Shopify Customer"):
|
||||
customer = get_customer_dict("_Test Shopify Customer")
|
||||
customer['shopify_customer_id'] = "5049858588862"
|
||||
frappe.get_doc(customer).insert(ignore_permissions=True)
|
||||
|
||||
# Add Integration Item
|
||||
if not frappe.db.get_value("Integration Item", {"integration_item_name": "Protein Bar"}):
|
||||
frappe.get_doc({
|
||||
"doctype":"Integration Item",
|
||||
"integration_item_name": "Protein Bar",
|
||||
"erpnext_item_code": "_Test Shopify Item"
|
||||
}).insert()
|
||||
|
||||
item = {
|
||||
"id": 6565573427390,
|
||||
"title": "Protein Bar",
|
||||
"body_html": "",
|
||||
"vendor": "Maple Stores Mumbai",
|
||||
"product_type": "",
|
||||
"created_at": "2021-03-12T11:42:04+05:30",
|
||||
"handle": "macbook-air-copper",
|
||||
"updated_at": "2021-03-12T13:40:18+05:30",
|
||||
"published_at": "2021-03-12T11:42:05+05:30",
|
||||
"template_suffix": "",
|
||||
"status": "active",
|
||||
"published_scope": "web",
|
||||
"tags": "",
|
||||
"variants": [
|
||||
{
|
||||
"id": 39351752425662,
|
||||
"product_id": 6565573427390,
|
||||
"title": "Default Title",
|
||||
"price": "140000.00",
|
||||
"sku": "",
|
||||
"position": 1,
|
||||
"inventory_policy": "deny",
|
||||
"fulfillment_service": "manual",
|
||||
"inventory_management": "shopify",
|
||||
"option1": "Default Title",
|
||||
"created_at": "2021-03-12T11:42:04+05:30",
|
||||
"updated_at": "2021-03-12T13:38:35+05:30",
|
||||
"barcode": "",
|
||||
"grams": 0,
|
||||
"weight": 0.0,
|
||||
"weight_unit": "kg",
|
||||
"inventory_item_id": 41445821972670,
|
||||
"inventory_quantity": 14,
|
||||
"old_inventory_quantity": 14,
|
||||
}
|
||||
],
|
||||
"options": [
|
||||
{
|
||||
"id": 8439626989758,
|
||||
"product_id": 6565573427390,
|
||||
"name": "Title",
|
||||
"position": 1,
|
||||
"values": [
|
||||
"Default Title"
|
||||
]
|
||||
}
|
||||
],
|
||||
"images": [],
|
||||
}
|
||||
|
||||
shopify_order_json = {
|
||||
"id": 3669049704638,
|
||||
"created_at": "2021-03-12T13:38:34+05:30",
|
||||
"updated_at": "2021-03-12T13:38:35+05:30",
|
||||
"number": 8,
|
||||
"note": "",
|
||||
"token": "88d2c4051b4b13e268ebb6ed409db82a",
|
||||
"gateway": "manual",
|
||||
"total_price": "165200.00",
|
||||
"subtotal_price": "140000.00",
|
||||
"total_weight": 0,
|
||||
"total_tax": "25200.00",
|
||||
"currency": "INR",
|
||||
"financial_status": "pending",
|
||||
"total_discounts": "0.00",
|
||||
"total_line_items_price": "140000.00",
|
||||
"name": "#1008",
|
||||
"total_price_usd": "2272.11",
|
||||
"user_id": 71496466622,
|
||||
"location_id": 61178446014,
|
||||
"processed_at": "2021-03-12T13:38:34+05:30",
|
||||
"customer_locale": "en",
|
||||
"app_id": 1354745,
|
||||
"order_number": 1008,
|
||||
"discount_applications": [],
|
||||
"discount_codes": [],
|
||||
"note_attributes": [],
|
||||
"payment_gateway_names": [
|
||||
"manual"
|
||||
],
|
||||
"processing_method": "manual",
|
||||
"source_name": "shopify_draft_order",
|
||||
"tax_lines": [],
|
||||
"tags": "",
|
||||
"contact_email": "frappe@maplestores.com",
|
||||
"presentment_currency": "INR",
|
||||
"total_line_items_price_set": {
|
||||
"shop_money": {
|
||||
"amount": "140000.00",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
"presentment_money": {
|
||||
"amount": "140000.00",
|
||||
"currency_code": "INR"
|
||||
}
|
||||
},
|
||||
"total_discounts_set": {
|
||||
"shop_money": {
|
||||
"amount": "0.00",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
"presentment_money": {
|
||||
"amount": "0.00",
|
||||
"currency_code": "INR"
|
||||
}
|
||||
},
|
||||
"total_shipping_price_set": {
|
||||
"shop_money": {
|
||||
"amount": "0.00",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
"presentment_money": {
|
||||
"amount": "0.00",
|
||||
"currency_code": "INR"
|
||||
}
|
||||
},
|
||||
"subtotal_price_set": {
|
||||
"shop_money": {
|
||||
"amount": "140000.00",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
"presentment_money": {
|
||||
"amount": "140000.00",
|
||||
"currency_code": "INR"
|
||||
}
|
||||
},
|
||||
"total_price_set": {
|
||||
"shop_money": {
|
||||
"amount": "165200.00",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
"presentment_money": {
|
||||
"amount": "165200.00",
|
||||
"currency_code": "INR"
|
||||
}
|
||||
},
|
||||
"total_tax_set": {
|
||||
"shop_money": {
|
||||
"amount": "25200.00",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
"presentment_money": {
|
||||
"amount": "25200.00",
|
||||
"currency_code": "INR"
|
||||
}
|
||||
},
|
||||
"line_items": [
|
||||
{
|
||||
"id": 9645225836734,
|
||||
"variant_id": 39351752425662,
|
||||
"title": "Protein Bar",
|
||||
"quantity": 1,
|
||||
"sku": "",
|
||||
"vendor": "Maple Stores Mumbai",
|
||||
"fulfillment_service": "manual",
|
||||
"product_id": 6565573427390,
|
||||
"name": "Protein Bar",
|
||||
"variant_inventory_management": "shopify",
|
||||
"properties": [],
|
||||
"product_exists": 1,
|
||||
"fulfillable_quantity": 1,
|
||||
"grams": 0,
|
||||
"price": "140000.00",
|
||||
"total_discount": "0.00",
|
||||
"price_set": {
|
||||
"shop_money": {
|
||||
"amount": "140000.00",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
"presentment_money": {
|
||||
"amount": "140000.00",
|
||||
"currency_code": "INR"
|
||||
}
|
||||
},
|
||||
"total_discount_set": {
|
||||
"shop_money": {
|
||||
"amount": "0.00",
|
||||
"currency_code": "INR"
|
||||
},
|
||||
"presentment_money": {
|
||||
"amount": "0.00",
|
||||
"currency_code": "INR"
|
||||
}
|
||||
},
|
||||
"discount_allocations": [],
|
||||
"duties": [],
|
||||
"tax_lines": []
|
||||
}
|
||||
],
|
||||
"fulfillments": [],
|
||||
"refunds": [],
|
||||
"total_tip_received": "0.0",
|
||||
"shipping_lines": [],
|
||||
"customer": {
|
||||
"id": 5049858588862,
|
||||
"email": "frappe@maplestores.com",
|
||||
"created_at": "2021-03-10T19:57:40+05:30",
|
||||
"updated_at": "2021-03-12T13:38:35+05:30",
|
||||
"first_name": "_Test Shopify Customer",
|
||||
"last_name": "",
|
||||
"orders_count": 5,
|
||||
"state": "disabled",
|
||||
"total_spent": "731600.00",
|
||||
"last_order_id": 3669049704638,
|
||||
"note": "",
|
||||
"tags": "",
|
||||
"last_order_name": "#1008",
|
||||
"currency": "INR",
|
||||
"accepts_marketing_updated_at": "2021-03-10T19:57:40+05:30",
|
||||
}
|
||||
}
|
||||
|
||||
# Create Order
|
||||
make_item("_Test Warehouse - _TC", item)
|
||||
create_order(shopify_order_json, self.shopify_settings, False, company="_Test Company")
|
||||
sales_order_doc = frappe.get_doc("Sales Order", {"shopify_order_id": "3669049704638"})
|
||||
self.assertEqual(sales_order_doc.items[0].item_code, "_Test Shopify Item")
|
||||
@@ -37,9 +37,9 @@ def get_webhook_address(connector_name, method, exclude_uri=False):
|
||||
try:
|
||||
url = frappe.request.url
|
||||
except RuntimeError:
|
||||
url = "http://localhost:8000"
|
||||
url = "https://localhost:8000"
|
||||
|
||||
server_url = '{uri.scheme}://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint)
|
||||
server_url = 'https://{uri.netloc}/api/method/{endpoint}'.format(uri=urlparse(url), endpoint=endpoint)
|
||||
|
||||
return server_url
|
||||
|
||||
|
||||
@@ -255,14 +255,17 @@ doc_events = {
|
||||
"erpnext.regional.italy.utils.sales_invoice_on_cancel",
|
||||
"erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
|
||||
],
|
||||
"on_trash": "erpnext.regional.check_deletion_permission"
|
||||
"on_trash": "erpnext.regional.check_deletion_permission",
|
||||
"validate": ["erpnext.regional.india.utils.update_taxable_values"]
|
||||
},
|
||||
"Purchase Invoice": {
|
||||
"validate": [
|
||||
"erpnext.regional.india.utils.update_grand_total_for_rcm",
|
||||
"erpnext.regional.india.utils.validate_reverse_charge_transaction",
|
||||
"erpnext.regional.india.utils.update_itc_availed_fields",
|
||||
"erpnext.regional.united_arab_emirates.utils.update_grand_total_for_rcm",
|
||||
"erpnext.regional.united_arab_emirates.utils.validate_returns"
|
||||
]
|
||||
"erpnext.regional.united_arab_emirates.utils.validate_returns",
|
||||
"erpnext.regional.india.utils.update_taxable_values"
|
||||
]
|
||||
},
|
||||
"Payment Entry": {
|
||||
"on_submit": ["erpnext.regional.create_transaction_log", "erpnext.accounts.doctype.payment_request.payment_request.update_payment_req_status", "erpnext.accounts.doctype.dunning.dunning.resolve_dunning"],
|
||||
@@ -402,9 +405,9 @@ regional_overrides = {
|
||||
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header',
|
||||
'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data',
|
||||
'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
|
||||
'erpnext.controllers.taxes_and_totals.get_regional_round_off_accounts': 'erpnext.regional.india.utils.get_regional_round_off_accounts',
|
||||
'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
|
||||
'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
|
||||
'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
|
||||
'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
|
||||
},
|
||||
'United Arab Emirates': {
|
||||
|
||||
@@ -753,5 +753,9 @@ erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
|
||||
erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
|
||||
erpnext.patches.v12_0.add_state_code_for_ladakh
|
||||
erpnext.patches.v13_0.update_vehicle_no_reqd_condition
|
||||
erpnext.patches.v12_0.create_itc_reversal_custom_fields #1
|
||||
erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
|
||||
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
|
||||
erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
|
||||
erpnext.patches.v12_0.create_taxable_value_field
|
||||
erpnext.patches.v12_0.add_company_link_to_einvoice_settings
|
||||
erpnext.patches.v13_0.update_payment_terms_outstanding
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company or not frappe.db.count('E Invoice User'):
|
||||
return
|
||||
|
||||
frappe.reload_doc("regional", "doctype", "e_invoice_user")
|
||||
for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']):
|
||||
company_name = frappe.db.sql("""
|
||||
select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
|
||||
where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
|
||||
""", (creds.get('gstin')))
|
||||
if company_name and len(company_name) > 0:
|
||||
frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])
|
||||
115
erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
Normal file
115
erpnext/patches/v12_0/create_itc_reversal_custom_fields.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from erpnext.regional.india.utils import get_gst_accounts
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'}, fields=['name'])
|
||||
if not company:
|
||||
return
|
||||
|
||||
frappe.reload_doc("regional", "doctype", "gst_settings")
|
||||
frappe.reload_doc("accounts", "doctype", "gst_account")
|
||||
|
||||
journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
|
||||
make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
|
||||
|
||||
custom_fields = {
|
||||
'Journal Entry': [
|
||||
dict(fieldname='reversal_type', label='Reversal Type',
|
||||
fieldtype='Select', insert_after='voucher_type', print_hide=1,
|
||||
options="As per rules 42 & 43 of CGST Rules\nOthers",
|
||||
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
|
||||
dict(fieldname='company_address', label='Company Address',
|
||||
fieldtype='Link', options='Address', insert_after='reversal_type',
|
||||
print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
|
||||
dict(fieldname='company_gstin', label='Company GSTIN',
|
||||
fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1,
|
||||
fetch_from='company_address.gstin',
|
||||
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'")
|
||||
],
|
||||
'Purchase Invoice': [
|
||||
dict(fieldname='eligibility_for_itc', label='Eligibility For ITC',
|
||||
fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1,
|
||||
options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC',
|
||||
default="All Other ITC")
|
||||
],
|
||||
'Purchase Invoice Item': [
|
||||
dict(fieldname='taxable_value', label='Taxable Value',
|
||||
fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
|
||||
print_hide=1)
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
|
||||
# Patch ITC Availed fields from Data to Currency
|
||||
# Patch Availed ITC for current fiscal_year
|
||||
|
||||
gst_accounts = get_gst_accounts(only_non_reverse_charge=1)
|
||||
|
||||
frappe.db.sql("""
|
||||
UPDATE `tabCustom Field` SET fieldtype='Currency', options='Company:company:default_currency'
|
||||
WHERE dt = 'Purchase Invoice' and fieldname in ('itc_integrated_tax', 'itc_state_tax', 'itc_central_tax',
|
||||
'itc_cess_amount')
|
||||
""")
|
||||
|
||||
frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_integrated_tax = '0'
|
||||
WHERE trim(coalesce(itc_integrated_tax, '')) = '' """)
|
||||
|
||||
frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_state_tax = '0'
|
||||
WHERE trim(coalesce(itc_state_tax, '')) = '' """)
|
||||
|
||||
frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_central_tax = '0'
|
||||
WHERE trim(coalesce(itc_central_tax, '')) = '' """)
|
||||
|
||||
frappe.db.sql("""UPDATE `tabPurchase Invoice` set itc_cess_amount = '0'
|
||||
WHERE trim(coalesce(itc_cess_amount, '')) = '' """)
|
||||
|
||||
# Get purchase invoices
|
||||
invoices = frappe.get_all('Purchase Invoice',
|
||||
{'posting_date': ('>=', '2021-04-01'), 'eligibility_for_itc': ('!=', 'Ineligible')},
|
||||
['name'])
|
||||
|
||||
amount_map = {}
|
||||
|
||||
if invoices:
|
||||
invoice_list = set([d.name for d in invoices])
|
||||
|
||||
# Get GST applied
|
||||
amounts = frappe.db.sql("""
|
||||
SELECT parent, account_head, sum(base_tax_amount_after_discount_amount) as amount
|
||||
FROM `tabPurchase Taxes and Charges`
|
||||
where parent in %s
|
||||
GROUP BY parent, account_head
|
||||
""", (invoice_list), as_dict=1)
|
||||
|
||||
for d in amounts:
|
||||
amount_map.setdefault(d.parent,
|
||||
{
|
||||
'itc_integrated_tax': 0,
|
||||
'itc_state_tax': 0,
|
||||
'itc_central_tax': 0,
|
||||
'itc_cess_amount': 0
|
||||
})
|
||||
|
||||
if d.account_head in gst_accounts.get('igst_account'):
|
||||
amount_map[d.parent]['itc_integrated_tax'] += d.amount
|
||||
if d.account_head in gst_accounts.get('cgst_account'):
|
||||
amount_map[d.parent]['itc_central_tax'] += d.amount
|
||||
if d.account_head in gst_accounts.get('sgst_account'):
|
||||
amount_map[d.parent]['itc_state_tax'] += d.amount
|
||||
if d.account_head in gst_accounts.get('cess_account'):
|
||||
amount_map[d.parent]['itc_cess_amount'] += d.amount
|
||||
|
||||
for invoice, values in amount_map.items():
|
||||
frappe.db.set_value('Purchase Invoice', invoice, {
|
||||
'itc_integrated_tax': values.get('itc_integrated_tax'),
|
||||
'itc_central_tax': values.get('itc_central_tax'),
|
||||
'itc_state_tax': values['itc_state_tax'],
|
||||
'itc_cess_amount': values['itc_cess_amount'],
|
||||
})
|
||||
18
erpnext/patches/v12_0/create_taxable_value_field.py
Normal file
18
erpnext/patches/v12_0/create_taxable_value_field.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
|
||||
|
||||
def execute():
|
||||
company = frappe.get_all('Company', filters = {'country': 'India'})
|
||||
if not company:
|
||||
return
|
||||
|
||||
custom_fields = {
|
||||
'Sales Invoice Item': [
|
||||
dict(fieldname='taxable_value', label='Taxable Value',
|
||||
fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
|
||||
print_hide=1)
|
||||
]
|
||||
}
|
||||
|
||||
create_custom_fields(custom_fields, update=True)
|
||||
15
erpnext/patches/v13_0/update_payment_terms_outstanding.py
Normal file
15
erpnext/patches/v13_0/update_payment_terms_outstanding.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
|
||||
def execute():
|
||||
frappe.reload_doc("accounts", "doctype", "Payment Schedule")
|
||||
if frappe.db.count('Payment Schedule'):
|
||||
frappe.db.sql('''
|
||||
UPDATE
|
||||
`tabPayment Schedule` ps
|
||||
SET
|
||||
ps.outstanding = (ps.payment_amount - ps.paid_amount)
|
||||
''')
|
||||
@@ -78,7 +78,7 @@ class Task(NestedSet):
|
||||
def validate_dependencies_for_template_task(self):
|
||||
if self.is_template:
|
||||
self.validate_depends_on_tasks()
|
||||
|
||||
|
||||
def validate_depends_on_tasks(self):
|
||||
if self.depends_on:
|
||||
for task in self.depends_on:
|
||||
@@ -209,18 +209,25 @@ def check_if_child_exists(name):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_project(doctype, txt, searchfield, start, page_len, filters):
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
return frappe.db.sql(""" select name from `tabProject`
|
||||
where %(key)s like %(txt)s
|
||||
%(mcond)s
|
||||
order by name
|
||||
limit %(start)s, %(page_len)s""" % {
|
||||
'key': searchfield,
|
||||
'txt': frappe.db.escape('%' + txt + '%'),
|
||||
'mcond':get_match_cond(doctype),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
meta = frappe.get_meta(doctype)
|
||||
searchfields = meta.get_search_fields()
|
||||
search_columns = ", " + ", ".join(searchfields) if searchfields else ''
|
||||
search_cond = " or " + " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
return frappe.db.sql(""" select name {search_columns} from `tabProject`
|
||||
where %(key)s like %(txt)s
|
||||
%(mcond)s
|
||||
{search_condition}
|
||||
order by name
|
||||
limit %(start)s, %(page_len)s""".format(search_columns = search_columns,
|
||||
search_condition=search_cond), {
|
||||
'key': searchfield,
|
||||
'txt': '%' + txt + '%',
|
||||
'mcond':get_match_cond(doctype),
|
||||
'start': start,
|
||||
'page_len': page_len
|
||||
})
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -204,14 +204,16 @@ class Timesheet(Document):
|
||||
ts_detail.billing_rate = 0.0
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_projectwise_timesheet_data(project, parent=None):
|
||||
cond = ''
|
||||
def get_projectwise_timesheet_data(project, parent=None, from_time=None, to_time=None):
|
||||
condition = ''
|
||||
if parent:
|
||||
cond = "and parent = %(parent)s"
|
||||
condition = "AND parent = %(parent)s"
|
||||
if from_time and to_time:
|
||||
condition += "AND from_time BETWEEN %(from_time)s AND %(to_time)s"
|
||||
|
||||
return frappe.db.sql("""select name, parent, billing_hours, billing_amount as billing_amt
|
||||
from `tabTimesheet Detail` where parenttype = 'Timesheet' and docstatus=1 and project = %(project)s {0} and billable = 1
|
||||
and sales_invoice is null""".format(cond), {'project': project, 'parent': parent}, as_dict=1)
|
||||
and sales_invoice is null""".format(condition), {'project': project, 'parent': parent, 'from_time': from_time, 'to_time': to_time}, as_dict=1)
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
|
||||
@@ -198,6 +198,10 @@ erpnext.buying.BuyingController = erpnext.TransactionController.extend({
|
||||
this._super(doc, cdt, cdn);
|
||||
},
|
||||
|
||||
batch_no: function(doc, cdt, cdn) {
|
||||
this._super(doc, cdt, cdn);
|
||||
},
|
||||
|
||||
received_qty: function(doc, cdt, cdn) {
|
||||
this.calculate_accepted_qty(doc, cdt, cdn)
|
||||
},
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
setup: function() {},
|
||||
setup: function() {
|
||||
this.fetch_round_off_accounts();
|
||||
},
|
||||
|
||||
apply_pricing_rule_on_item: function(item){
|
||||
let effective_item_rate = item.price_list_rate;
|
||||
@@ -151,6 +153,22 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
});
|
||||
},
|
||||
|
||||
fetch_round_off_accounts: function() {
|
||||
let me = this;
|
||||
frappe.flags.round_off_applicable_accounts = [];
|
||||
|
||||
return frappe.call({
|
||||
"method": "erpnext.controllers.taxes_and_totals.get_round_off_applicable_accounts",
|
||||
"args": {
|
||||
"company": me.frm.doc.company,
|
||||
"account_list": frappe.flags.round_off_applicable_accounts
|
||||
},
|
||||
callback: function(r) {
|
||||
frappe.flags.round_off_applicable_accounts.push(...r.message);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
determine_exclusive_rate: function() {
|
||||
var me = this;
|
||||
|
||||
@@ -371,11 +389,21 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
} else if (tax.charge_type == "On Item Quantity") {
|
||||
current_tax_amount = tax_rate * item.qty;
|
||||
}
|
||||
|
||||
current_tax_amount = this.get_final_tax_amount(tax, current_tax_amount);
|
||||
this.set_item_wise_tax(item, tax, tax_rate, current_tax_amount);
|
||||
|
||||
return current_tax_amount;
|
||||
},
|
||||
|
||||
get_final_tax_amount: function(tax, current_tax_amount) {
|
||||
if (frappe.flags.round_off_applicable_accounts.includes(tax.account_head)) {
|
||||
current_tax_amount = Math.round(current_tax_amount);
|
||||
}
|
||||
|
||||
return current_tax_amount;
|
||||
},
|
||||
|
||||
set_item_wise_tax: function(item, tax, tax_rate, current_tax_amount) {
|
||||
// store tax breakup for each item
|
||||
let tax_detail = tax.item_wise_tax_detail;
|
||||
|
||||
@@ -596,13 +596,23 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
() => me.toggle_conversion_factor(item),
|
||||
() => {
|
||||
if (show_batch_dialog)
|
||||
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
||||
.then((r) => {
|
||||
if (r.message &&
|
||||
(r.message.has_batch_no || r.message.has_serial_no)) {
|
||||
frappe.flags.hide_serial_batch_dialog = false;
|
||||
}
|
||||
});
|
||||
return frappe.db.get_value("Item", item.item_code, ["has_batch_no", "has_serial_no"])
|
||||
.then((r) => {
|
||||
if (r.message &&
|
||||
(r.message.has_batch_no || r.message.has_serial_no)) {
|
||||
frappe.flags.hide_serial_batch_dialog = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
() => {
|
||||
// check if batch serial selector is disabled or not
|
||||
if (show_batch_dialog && !frappe.flags.hide_serial_batch_dialog)
|
||||
return frappe.db.get_single_value('Stock Settings', 'disable_serial_no_and_batch_selector')
|
||||
.then((value) => {
|
||||
if(value) {
|
||||
frappe.flags.hide_serial_batch_dialog = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
() => {
|
||||
// check if batch serial selector is disabled or not
|
||||
@@ -639,6 +649,10 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
let key = item.name;
|
||||
me.apply_rule_on_other_items({key: item});
|
||||
}
|
||||
},
|
||||
() => {
|
||||
var company_currency = me.get_company_currency();
|
||||
me.update_item_grid_labels(company_currency);
|
||||
}
|
||||
]);
|
||||
}
|
||||
@@ -1136,7 +1150,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
if(frappe.flags.dont_fetch_price_list_rate) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if (!dont_fetch_price_list_rate &&
|
||||
frappe.meta.has_field(doc.doctype, "price_list_currency")) {
|
||||
this.apply_price_list(item, true);
|
||||
@@ -1278,11 +1292,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
change_grid_labels: function(company_currency) {
|
||||
var me = this;
|
||||
|
||||
this.frm.set_currency_labels(["base_rate", "base_net_rate", "base_price_list_rate", "base_amount", "base_net_amount"],
|
||||
company_currency, "items");
|
||||
this.update_item_grid_labels(company_currency);
|
||||
|
||||
this.frm.set_currency_labels(["rate", "net_rate", "price_list_rate", "amount", "net_amount", "stock_uom_rate"],
|
||||
this.frm.doc.currency, "items");
|
||||
this.toggle_item_grid_columns(company_currency);
|
||||
|
||||
if(this.frm.fields_dict["operations"]) {
|
||||
this.frm.set_currency_labels(["operating_cost", "hour_rate"], this.frm.doc.currency, "operations");
|
||||
@@ -1317,6 +1329,39 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
this.frm.doc.party_account_currency, "advances");
|
||||
}
|
||||
|
||||
this.update_payment_schedule_grid_labels(company_currency);
|
||||
},
|
||||
|
||||
update_item_grid_labels: function(company_currency) {
|
||||
this.frm.set_currency_labels([
|
||||
"base_rate", "base_net_rate", "base_price_list_rate",
|
||||
"base_amount", "base_net_amount", "base_rate_with_margin"
|
||||
], company_currency, "items");
|
||||
|
||||
this.frm.set_currency_labels([
|
||||
"rate", "net_rate", "price_list_rate", "amount",
|
||||
"net_amount", "stock_uom_rate", "rate_with_margin"
|
||||
], this.frm.doc.currency, "items");
|
||||
},
|
||||
|
||||
update_payment_schedule_grid_labels: function(company_currency) {
|
||||
const me = this;
|
||||
if (this.frm.fields_dict["payment_schedule"]) {
|
||||
this.frm.set_currency_labels(["base_payment_amount", "base_outstanding", "base_paid_amount"],
|
||||
company_currency, "payment_schedule");
|
||||
this.frm.set_currency_labels(["payment_amount", "outstanding", "paid_amount"],
|
||||
this.frm.doc.currency, "payment_schedule");
|
||||
|
||||
var schedule_grid = this.frm.fields_dict["payment_schedule"].grid;
|
||||
$.each(["base_payment_amount", "base_outstanding", "base_paid_amount"], function(i, fname) {
|
||||
if (frappe.meta.get_docfield(schedule_grid.doctype, fname))
|
||||
schedule_grid.set_column_disp(fname, me.frm.doc.currency != company_currency);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
toggle_item_grid_columns: function(company_currency) {
|
||||
const me = this;
|
||||
// toggle columns
|
||||
var item_grid = this.frm.fields_dict["items"].grid;
|
||||
$.each(["base_rate", "base_price_list_rate", "base_amount"], function(i, fname) {
|
||||
@@ -1336,9 +1381,6 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
if(frappe.meta.get_docfield(item_grid.doctype, fname))
|
||||
item_grid.set_column_disp(fname, (show && (me.frm.doc.currency != company_currency)));
|
||||
});
|
||||
|
||||
// set labels
|
||||
var $wrapper = $(this.frm.wrapper);
|
||||
},
|
||||
|
||||
recalculate: function() {
|
||||
@@ -1952,11 +1994,14 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
terms_template: doc.payment_terms_template,
|
||||
posting_date: posting_date,
|
||||
grand_total: doc.rounded_total || doc.grand_total,
|
||||
base_grand_total: doc.base_rounded_total || doc.base_grand_total,
|
||||
bill_date: doc.bill_date
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message && !r.exc) {
|
||||
me.frm.set_value("payment_schedule", r.message);
|
||||
const company_currency = me.get_company_currency();
|
||||
this.update_payment_schedule_grid_labels(company_currency);
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1964,6 +2009,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
},
|
||||
|
||||
payment_term: function(doc, cdt, cdn) {
|
||||
const me = this;
|
||||
var row = locals[cdt][cdn];
|
||||
if(row.payment_term) {
|
||||
frappe.call({
|
||||
@@ -1972,12 +2018,15 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
term: row.payment_term,
|
||||
bill_date: this.frm.doc.bill_date,
|
||||
posting_date: this.frm.doc.posting_date || this.frm.doc.transaction_date,
|
||||
grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total
|
||||
grand_total: this.frm.doc.rounded_total || this.frm.doc.grand_total,
|
||||
base_grand_total: this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message && !r.exc) {
|
||||
for (var d in r.message) {
|
||||
frappe.model.set_value(cdt, cdn, d, r.message[d]);
|
||||
const company_currency = me.get_company_currency();
|
||||
me.update_payment_schedule_grid_labels(company_currency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,4 +100,4 @@ erpnext.accounts.dimensions = {
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
{{
|
||||
"SlNo": "{item.sr_no}",
|
||||
"PrdDesc": "{item.description}",
|
||||
"IsServc": "{item.is_service_item}",
|
||||
"HsnCd": "{item.gst_hsn_code}",
|
||||
"Barcde": "{item.barcode}",
|
||||
"Unit": "{item.uom}",
|
||||
"Qty": "{item.qty}",
|
||||
"FreeQty": "{item.free_qty}",
|
||||
"UnitPrice": "{item.base_rate}",
|
||||
"TotAmt": "{item.base_amount}",
|
||||
"Discount": "{item.discount_amount}",
|
||||
"AssAmt": "{item.base_amount}",
|
||||
"PrdSlNo": "{item.serial_no}",
|
||||
"GstRt": "{item.tax_rate}",
|
||||
"IgstAmt": "{item.igst_amount}",
|
||||
"CgstAmt": "{item.cgst_amount}",
|
||||
"SgstAmt": "{item.sgst_amount}",
|
||||
"CesRt": "{item.cess_rate}",
|
||||
"CesAmt": "{item.cess_amount}",
|
||||
"TotItemVal": "{item.total_value}",
|
||||
"BchDtls": {{
|
||||
"Nm": "{item.batch_no}",
|
||||
"ExpDt": "{item.batch_expiry_date}"
|
||||
}}
|
||||
}}
|
||||
109
erpnext/regional/doctype/e_invoice_settings/e_inv_schema.json
Normal file
109
erpnext/regional/doctype/e_invoice_settings/e_inv_schema.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{{
|
||||
"Version": "1.01",
|
||||
"TranDtls": {{
|
||||
"TaxSch": "{trans_details.tax_scheme}",
|
||||
"SupTyp": "{trans_details.supply_type}",
|
||||
"RegRev": "{trans_details.reverse_charge}",
|
||||
"EcmGstin": "{trans_details.ecom_gstin}",
|
||||
"IgstOnIntra": "{trans_details.igst_on_intra}"
|
||||
}},
|
||||
"DocDtls": {{
|
||||
"Typ": "{doc_details.invoice_type}",
|
||||
"No": "{doc_details.invoice_name}",
|
||||
"Dt": "{doc_details.invoice_date}"
|
||||
}},
|
||||
"SellerDtls": {{
|
||||
"Gstin": "{seller_details.gstin}",
|
||||
"LglNm": "{seller_details.legal_name}",
|
||||
"TrdNm": "{seller_details.trade_name}",
|
||||
"Loc": "{seller_details.location}",
|
||||
"Pin": "{seller_details.pincode}",
|
||||
"Stcd": "{seller_details.state_code}",
|
||||
"Addr1": "{seller_details.address_line1}",
|
||||
"Addr2": "{seller_details.address_line2}",
|
||||
"Ph": "{seller_details.phone}",
|
||||
"Em": "{seller_details.email}"
|
||||
}},
|
||||
"BuyerDtls": {{
|
||||
"Gstin": "{buyer_details.gstin}",
|
||||
"LglNm": "{buyer_details.legal_name}",
|
||||
"TrdNm": "{buyer_details.trade_name}",
|
||||
"Addr1": "{buyer_details.address_line1}",
|
||||
"Addr2": "{buyer_details.address_line2}",
|
||||
"Loc": "{buyer_details.location}",
|
||||
"Pin": "{buyer_details.pincode}",
|
||||
"Stcd": "{buyer_details.state_code}",
|
||||
"Ph": "{buyer_details.phone}",
|
||||
"Em": "{buyer_details.email}",
|
||||
"Pos": "{buyer_details.place_of_supply}"
|
||||
}},
|
||||
"DispDtls": {{
|
||||
"Nm": "{dispatch_details.company_name}",
|
||||
"Addr1": "{dispatch_details.address_line1}",
|
||||
"Addr2": "{dispatch_details.address_line2}",
|
||||
"Loc": "{dispatch_details.location}",
|
||||
"Pin": "{dispatch_details.pincode}",
|
||||
"Stcd": "{dispatch_details.state_code}"
|
||||
}},
|
||||
"ShipDtls": {{
|
||||
"Gstin": "{shipping_details.gstin}",
|
||||
"LglNm": "{shipping_details.legal_name}",
|
||||
"TrdNm": "{shipping_details.trader_name}",
|
||||
"Addr1": "{shipping_details.address_line1}",
|
||||
"Addr2": "{shipping_details.address_line2}",
|
||||
"Loc": "{shipping_details.location}",
|
||||
"Pin": "{shipping_details.pincode}",
|
||||
"Stcd": "{shipping_details.state_code}"
|
||||
}},
|
||||
"ItemList": [
|
||||
{item_list}
|
||||
],
|
||||
"ValDtls": {{
|
||||
"AssVal": "{value_details.base_net_total}",
|
||||
"CgstVal": "{value_details.total_cgst_amt}",
|
||||
"SgstVal": "{value_details.total_sgst_amt}",
|
||||
"IgstVal": "{value_details.total_igst_amt}",
|
||||
"CesVal": "{value_details.total_cess_amt}",
|
||||
"Discount": "{value_details.invoice_discount_amt}",
|
||||
"RndOffAmt": "{value_details.round_off}",
|
||||
"TotInvVal": "{value_details.base_grand_total}",
|
||||
"TotInvValFc": "{value_details.grand_total}"
|
||||
}},
|
||||
"PayDtls": {{
|
||||
"Nm": "{payment_details.payee_name}",
|
||||
"AccDet": "{payment_details.account_no}",
|
||||
"Mode": "{payment_details.mode_of_payment}",
|
||||
"FinInsBr": "{payment_details.ifsc_code}",
|
||||
"PayTerm": "{payment_details.terms}",
|
||||
"PaidAmt": "{payment_details.paid_amount}",
|
||||
"PaymtDue": "{payment_details.outstanding_amount}"
|
||||
}},
|
||||
"RefDtls": {{
|
||||
"DocPerdDtls": {{
|
||||
"InvStDt": "{period_details.start_date}",
|
||||
"InvEndDt": "{period_details.end_date}"
|
||||
}},
|
||||
"PrecDocDtls": {{
|
||||
"InvNo": "{prev_doc_details.invoice_name}",
|
||||
"InvDt": "{prev_doc_details.invoice_date}"
|
||||
}}
|
||||
}},
|
||||
"ExpDtls": {{
|
||||
"ShipBNo": "{export_details.bill_no}",
|
||||
"ShipBDt": "{export_details.bill_date}",
|
||||
"Port": "{export_details.port}",
|
||||
"ForCur": "{export_details.foreign_curr_code}",
|
||||
"CntCode": "{export_details.country_code}",
|
||||
"ExpDuty": "{export_details.export_duty}"
|
||||
}},
|
||||
"EwbDtls": {{
|
||||
"TransId": "{eway_bill_details.gstin}",
|
||||
"TransName": "{eway_bill_details.name}",
|
||||
"TransMode": "{eway_bill_details.mode_of_transport}",
|
||||
"Distance": "{eway_bill_details.distance}",
|
||||
"TransDocNo": "{eway_bill_details.document_name}",
|
||||
"TransDocDt": "{eway_bill_details.document_date}",
|
||||
"VehNo": "{eway_bill_details.vehicle_no}",
|
||||
"VehType": "{eway_bill_details.vehicle_type}"
|
||||
}}
|
||||
}}
|
||||
@@ -0,0 +1,828 @@
|
||||
{
|
||||
"Version": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 6
|
||||
},
|
||||
"Irn": {
|
||||
"type": "string",
|
||||
"minLength": 64,
|
||||
"maxLength": 64
|
||||
},
|
||||
"TranDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"TaxSch": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 10,
|
||||
"enum": ["GST"]
|
||||
},
|
||||
"SupTyp": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 10,
|
||||
"enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"]
|
||||
},
|
||||
"RegRev": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"enum": ["Y", "N"]
|
||||
},
|
||||
"EcmGstin": {
|
||||
"type": "string",
|
||||
"minLength": 15,
|
||||
"maxLength": 15,
|
||||
"pattern": "([0-9]{2}[0-9A-Z]{13})"
|
||||
},
|
||||
"IgstOnIntra": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"enum": ["Y", "N"]
|
||||
}
|
||||
},
|
||||
"required": ["TaxSch", "SupTyp"]
|
||||
},
|
||||
"DocDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Typ": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 3,
|
||||
"enum": ["INV", "CRN", "DBN"]
|
||||
},
|
||||
"No": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 16,
|
||||
"pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$"
|
||||
},
|
||||
"Dt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
}
|
||||
},
|
||||
"required": ["Typ", "No", "Dt"]
|
||||
},
|
||||
"SellerDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Gstin": {
|
||||
"type": "string",
|
||||
"minLength": 15,
|
||||
"maxLength": 15,
|
||||
"pattern": "([0-9]{2}[0-9A-Z]{13})"
|
||||
},
|
||||
"LglNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"TrdNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr1": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr2": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Loc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 50
|
||||
},
|
||||
"Pin": {
|
||||
"type": "number",
|
||||
"minimum": 100000,
|
||||
"maximum": 999999
|
||||
},
|
||||
"Stcd": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
"Ph": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"maxLength": 12
|
||||
},
|
||||
"Em": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"maxLength": 100
|
||||
}
|
||||
},
|
||||
"required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||
},
|
||||
"BuyerDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Gstin": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 15,
|
||||
"pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$"
|
||||
},
|
||||
"LglNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"TrdNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Pos": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
"Addr1": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr2": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Loc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Pin": {
|
||||
"type": "number",
|
||||
"minimum": 100000,
|
||||
"maximum": 999999
|
||||
},
|
||||
"Stcd": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
"Ph": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"maxLength": 12
|
||||
},
|
||||
"Em": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"maxLength": 100
|
||||
}
|
||||
},
|
||||
"required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"]
|
||||
},
|
||||
"DispDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Nm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr1": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr2": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Loc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Pin": {
|
||||
"type": "number",
|
||||
"minimum": 100000,
|
||||
"maximum": 999999
|
||||
},
|
||||
"Stcd": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
}
|
||||
},
|
||||
"required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||
},
|
||||
"ShipDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Gstin": {
|
||||
"type": "string",
|
||||
"maxLength": 15,
|
||||
"minLength": 3,
|
||||
"pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$"
|
||||
},
|
||||
"LglNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"TrdNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr1": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr2": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Loc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Pin": {
|
||||
"type": "number",
|
||||
"minimum": 100000,
|
||||
"maximum": 999999
|
||||
},
|
||||
"Stcd": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
}
|
||||
},
|
||||
"required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||
},
|
||||
"ItemList": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"SlNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 6
|
||||
},
|
||||
"PrdDesc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 300
|
||||
},
|
||||
"IsServc": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"enum": ["Y", "N"]
|
||||
},
|
||||
"HsnCd": {
|
||||
"type": "string",
|
||||
"minLength": 4,
|
||||
"maxLength": 8
|
||||
},
|
||||
"Barcde": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 30
|
||||
},
|
||||
"Qty": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 9999999999.999
|
||||
},
|
||||
"FreeQty": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 9999999999.999
|
||||
},
|
||||
"Unit": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 8
|
||||
},
|
||||
"UnitPrice": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.999
|
||||
},
|
||||
"TotAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"Discount": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"PreTaxVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"AssAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"GstRt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999.999
|
||||
},
|
||||
"IgstAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"CgstAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"SgstAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"CesRt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999.999
|
||||
},
|
||||
"CesAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"CesNonAdvlAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"StateCesRt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999.999
|
||||
},
|
||||
"StateCesAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"StateCesNonAdvlAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"OthChrg": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"TotItemVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"OrdLineRef": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
"OrgCntry": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 2
|
||||
},
|
||||
"PrdSlNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20
|
||||
},
|
||||
"BchDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Nm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 20
|
||||
},
|
||||
"ExpDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"WrDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
}
|
||||
},
|
||||
"required": ["Nm"]
|
||||
},
|
||||
"AttribDtls": {
|
||||
"type": "Array",
|
||||
"Attribute": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Nm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Val": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"SlNo",
|
||||
"IsServc",
|
||||
"HsnCd",
|
||||
"UnitPrice",
|
||||
"TotAmt",
|
||||
"AssAmt",
|
||||
"GstRt",
|
||||
"TotItemVal"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ValDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"AssVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"CgstVal": {
|
||||
"type": "number",
|
||||
"maximum": 99999999999999.99,
|
||||
"minimum": 0
|
||||
},
|
||||
"SgstVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"IgstVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"CesVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"StCesVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"Discount": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"OthChrg": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"RndOffAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99.99
|
||||
},
|
||||
"TotInvVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"TotInvValFc": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
}
|
||||
},
|
||||
"required": ["AssVal", "TotInvVal"]
|
||||
},
|
||||
"PayDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Nm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"AccDet": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
"Mode": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
"FinInsBr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 11
|
||||
},
|
||||
"PayTerm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"PayInstr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"CrTrn": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"DirDr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"CrDay": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 9999
|
||||
},
|
||||
"PaidAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"PaymtDue": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
}
|
||||
}
|
||||
},
|
||||
"RefDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"InvRm": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 3,
|
||||
"pattern": "^[0-9A-Za-z/-]{3,100}$"
|
||||
},
|
||||
"DocPerdDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"InvStDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"InvEndDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
}
|
||||
},
|
||||
"required": ["InvStDt", "InvEndDt"]
|
||||
},
|
||||
"PrecDocDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"InvNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 16,
|
||||
"pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$"
|
||||
},
|
||||
"InvDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"OthRefNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20
|
||||
}
|
||||
},
|
||||
"required": ["InvNo", "InvDt"]
|
||||
},
|
||||
"ContrDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"RecAdvRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"RecAdvDt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"TendRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"ContrRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"ExtRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"ProjRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"PORefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 16,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,16}$"
|
||||
},
|
||||
"PORefDt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["InvStDt", "InvEndDt"]
|
||||
},
|
||||
"AddlDocDtls": {
|
||||
"type": "Array",
|
||||
"AddlDocument": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Url": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Docs": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 1000
|
||||
},
|
||||
"Info": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ExpDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ShipBNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20
|
||||
},
|
||||
"ShipBDt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"Port": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 10,
|
||||
"pattern": "^[0-9A-Za-z]{2,10}$"
|
||||
},
|
||||
"RefClm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
},
|
||||
"ForCur": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 16
|
||||
},
|
||||
"CntCode": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 2
|
||||
},
|
||||
"ExpDuty": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
}
|
||||
}
|
||||
},
|
||||
"EwbDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"TransId": {
|
||||
"type": "string",
|
||||
"minLength": 15,
|
||||
"maxLength": 15
|
||||
},
|
||||
"TransName": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"TransMode": {
|
||||
"type": "string",
|
||||
"maxLength": 1,
|
||||
"minLength": 1,
|
||||
"enum": ["1", 2, 3, 4]
|
||||
},
|
||||
"Distance": {
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 9999
|
||||
},
|
||||
"TransDocNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"pattern": "^([0-9A-Z/-]){1,15}$"
|
||||
},
|
||||
"TransDocDt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"VehNo": {
|
||||
"type": "string",
|
||||
"minLength": 4,
|
||||
"maxLength": 20
|
||||
},
|
||||
"VehType": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"enum": ["O", "R"]
|
||||
}
|
||||
},
|
||||
"required": ["Distance"]
|
||||
},
|
||||
"required": [
|
||||
"Version",
|
||||
"TranDtls",
|
||||
"DocDtls",
|
||||
"SellerDtls",
|
||||
"BuyerDtls",
|
||||
"ItemList",
|
||||
"ValDtls"
|
||||
]
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"gstin",
|
||||
"username",
|
||||
"password"
|
||||
@@ -30,12 +31,20 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Password",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-22 15:10:53.466205",
|
||||
"modified": "2021-03-22 12:16:56.365616",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "E Invoice User",
|
||||
|
||||
@@ -1,222 +1,86 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2017-06-27 15:09:01.318003",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2017-06-27 15:09:01.318003",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"gst_summary",
|
||||
"column_break_2",
|
||||
"round_off_gst_values",
|
||||
"gstin_email_sent_on",
|
||||
"section_break_4",
|
||||
"gst_accounts",
|
||||
"b2c_limit"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "gst_summary",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "GST Summary",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "gst_summary",
|
||||
"fieldtype": "HTML",
|
||||
"label": "GST Summary",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "gstin_email_sent_on",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "GSTIN Email Sent On",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "gstin_email_sent_on",
|
||||
"fieldtype": "Date",
|
||||
"label": "GSTIN Email Sent On",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_4",
|
||||
"fieldtype": "Section Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "gst_accounts",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "GST Accounts",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "GST Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "gst_accounts",
|
||||
"fieldtype": "Table",
|
||||
"label": "GST Accounts",
|
||||
"options": "GST Account",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "250000",
|
||||
"description": "Set Invoice Value for B2C. B2CL and B2CS calculated based on this invoice value.",
|
||||
"fieldname": "b2c_limit",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "B2C Limit",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"default": "250000",
|
||||
"description": "Set Invoice Value for B2C. B2CL and B2CS calculated based on this invoice value.",
|
||||
"fieldname": "b2c_limit",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "B2C Limit",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enabling this option will round off individual GST components in all the Invoices",
|
||||
"fieldname": "round_off_gst_values",
|
||||
"fieldtype": "Check",
|
||||
"label": "Round Off GST Values",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-02-14 08:14:15.375181",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "GST Settings",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-28 17:19:47.969260",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "GST Settings",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -172,7 +172,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>(A) {{__("ITC Available (whether in full op part)")}}</b></td>
|
||||
<td><b>(A) {{__("ITC Available (whether in full or part)")}}</b></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
|
||||
@@ -3,148 +3,21 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import json
|
||||
import frappe
|
||||
from six import iteritems
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
import json
|
||||
from six import iteritems
|
||||
from frappe.utils import flt, getdate
|
||||
from frappe.utils import flt, getdate, cstr
|
||||
from erpnext.regional.india import state_numbers
|
||||
|
||||
class GSTR3BReport(Document):
|
||||
def before_save(self):
|
||||
|
||||
def validate(self):
|
||||
self.get_data()
|
||||
|
||||
def get_data(self):
|
||||
|
||||
self.report_dict = {
|
||||
"gstin": "",
|
||||
"ret_period": "",
|
||||
"inward_sup": {
|
||||
"isup_details": [
|
||||
{
|
||||
"ty": "GST",
|
||||
"intra": 0,
|
||||
"inter": 0
|
||||
},
|
||||
{
|
||||
"ty": "NONGST",
|
||||
"inter": 0,
|
||||
"intra": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"sup_details": {
|
||||
"osup_zero": {
|
||||
"csamt": 0,
|
||||
"txval": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
"osup_nil_exmp": {
|
||||
"txval": 0
|
||||
},
|
||||
"osup_det": {
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"txval": 0,
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
"isup_rev": {
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"txval": 0,
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
"osup_nongst": {
|
||||
"txval": 0,
|
||||
}
|
||||
},
|
||||
"inter_sup": {
|
||||
"unreg_details": [],
|
||||
"comp_details": [],
|
||||
"uin_details": []
|
||||
},
|
||||
"itc_elg": {
|
||||
"itc_avl": [
|
||||
{
|
||||
"csamt": 0,
|
||||
"samt": 0,
|
||||
"ty": "IMPG",
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
{
|
||||
"csamt": 0,
|
||||
"samt": 0,
|
||||
"ty": "IMPS",
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
{
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"ty": "ISRC",
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
{
|
||||
"ty": "ISD",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
},
|
||||
{
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"ty": "OTH",
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
}
|
||||
],
|
||||
"itc_rev": [
|
||||
{
|
||||
"ty": "RUL",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
},
|
||||
{
|
||||
"ty": "OTH",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
}
|
||||
],
|
||||
"itc_net": {
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
"itc_inelg": [
|
||||
{
|
||||
"ty": "RUL",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
},
|
||||
{
|
||||
"ty": "OTH",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
self.report_dict = json.loads(get_json('gstr_3b_report_template'))
|
||||
|
||||
self.gst_details = self.get_company_gst_details()
|
||||
self.report_dict["gstin"] = self.gst_details.get("gstin")
|
||||
@@ -152,23 +25,19 @@ class GSTR3BReport(Document):
|
||||
self.month_no = get_period(self.month)
|
||||
self.account_heads = self.get_account_heads()
|
||||
|
||||
outward_supply_tax_amounts = self.get_tax_amounts("Sales Invoice")
|
||||
inward_supply_tax_amounts = self.get_tax_amounts("Purchase Invoice", reverse_charge="Y")
|
||||
self.get_outward_supply_details("Sales Invoice")
|
||||
self.set_outward_taxable_supplies()
|
||||
|
||||
self.get_outward_supply_details("Purchase Invoice", reverse_charge=True)
|
||||
self.set_supplies_liable_to_reverse_charge()
|
||||
|
||||
itc_details = self.get_itc_details()
|
||||
|
||||
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_det", ["Registered Regular"])
|
||||
self.prepare_data("Sales Invoice", outward_supply_tax_amounts, "sup_details", "osup_zero", ["SEZ", "Deemed Export", "Overseas"])
|
||||
self.prepare_data("Purchase Invoice", inward_supply_tax_amounts, "sup_details", "isup_rev", ["Unregistered", "Overseas"], reverse_charge="Y")
|
||||
self.report_dict["sup_details"]["osup_nil_exmp"]["txval"] = flt(self.get_nil_rated_supply_value(), 2)
|
||||
self.set_itc_details(itc_details)
|
||||
|
||||
inter_state_supplies = self.get_inter_state_supplies(self.gst_details.get("gst_state_number"))
|
||||
self.get_itc_reversal_entries()
|
||||
inward_nil_exempt = self.get_inward_nil_exempt(self.gst_details.get("gst_state"))
|
||||
self.set_inter_state_supply(inter_state_supplies)
|
||||
self.set_inward_nil_exempt(inward_nil_exempt)
|
||||
|
||||
self.missing_field_invoices = self.get_missing_field_invoices()
|
||||
|
||||
self.json_output = frappe.as_json(self.report_dict)
|
||||
|
||||
def set_inward_nil_exempt(self, inward_nil_exempt):
|
||||
@@ -179,182 +48,95 @@ class GSTR3BReport(Document):
|
||||
self.report_dict["inward_sup"]["isup_details"][1]["intra"] = flt(inward_nil_exempt.get("non_gst").get("intra"), 2)
|
||||
|
||||
def set_itc_details(self, itc_details):
|
||||
|
||||
itc_type_map = {
|
||||
itc_eligible_type_map = {
|
||||
'IMPG': 'Import Of Capital Goods',
|
||||
'IMPS': 'Import Of Service',
|
||||
'ISRC': 'ITC on Reverse Charge',
|
||||
'ISD': 'Input Service Distributor',
|
||||
'OTH': 'All Other ITC'
|
||||
}
|
||||
|
||||
itc_ineligible_map = {
|
||||
'RUL': 'Ineligible As Per Section 17(5)',
|
||||
'OTH': 'Ineligible Others'
|
||||
}
|
||||
|
||||
net_itc = self.report_dict["itc_elg"]["itc_net"]
|
||||
|
||||
for d in self.report_dict["itc_elg"]["itc_avl"]:
|
||||
|
||||
itc_type = itc_type_map.get(d["ty"])
|
||||
|
||||
if d["ty"] == 'ISRC':
|
||||
reverse_charge = ["Y"]
|
||||
itc_type = 'All Other ITC'
|
||||
gst_category = ['Unregistered', 'Overseas']
|
||||
else:
|
||||
gst_category = ['Unregistered', 'Overseas', 'Registered Regular']
|
||||
reverse_charge = ["N", "Y"]
|
||||
|
||||
for account_head in self.account_heads:
|
||||
for category in gst_category:
|
||||
for charge_type in reverse_charge:
|
||||
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
|
||||
d[key[0]] += flt(itc_details.get((category, itc_type, charge_type, account_head.get(key[1])), {}).get("amount"), 2)
|
||||
|
||||
itc_type = itc_eligible_type_map.get(d["ty"])
|
||||
for key in ['iamt', 'camt', 'samt', 'csamt']:
|
||||
d[key] = flt(itc_details.get(itc_type, {}).get(key))
|
||||
net_itc[key] += flt(d[key], 2)
|
||||
|
||||
for account_head in self.account_heads:
|
||||
itc_inelg = self.report_dict["itc_elg"]["itc_inelg"][1]
|
||||
for key in [['iamt', 'igst_account'], ['camt', 'cgst_account'], ['samt', 'sgst_account'], ['csamt', 'cess_account']]:
|
||||
itc_inelg[key[0]] = flt(itc_details.get(("Ineligible", "N", account_head.get(key[1])), {}).get("amount"), 2)
|
||||
for d in self.report_dict["itc_elg"]["itc_inelg"]:
|
||||
itc_type = itc_ineligible_map.get(d["ty"])
|
||||
for key in ['iamt', 'camt', 'samt', 'csamt']:
|
||||
d[key] = flt(itc_details.get(itc_type, {}).get(key))
|
||||
|
||||
def prepare_data(self, doctype, tax_details, supply_type, supply_category, gst_category_list, reverse_charge="N"):
|
||||
def get_itc_reversal_entries(self):
|
||||
reversal_entries = frappe.db.sql("""
|
||||
SELECT ja.account, j.reversal_type, sum(credit_in_account_currency) as amount
|
||||
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
|
||||
where j.docstatus = 1
|
||||
and j.is_opening = 'No'
|
||||
and ja.parent = j.name
|
||||
and j.voucher_type = 'Reversal Of ITC'
|
||||
and month(j.posting_date) = %s and year(j.posting_date) = %s
|
||||
and j.company = %s and j.company_gstin = %s
|
||||
GROUP BY ja.account, j.reversal_type""", (self.month_no, self.year, self.company,
|
||||
self.gst_details.get("gstin")), as_dict=1)
|
||||
|
||||
account_map = {
|
||||
'sgst_account': 'samt',
|
||||
'cess_account': 'csamt',
|
||||
'cgst_account': 'camt',
|
||||
'igst_account': 'iamt'
|
||||
}
|
||||
net_itc = self.report_dict["itc_elg"]["itc_net"]
|
||||
|
||||
txval = 0
|
||||
total_taxable_value = self.get_total_taxable_value(doctype, reverse_charge)
|
||||
for entry in reversal_entries:
|
||||
if entry.reversal_type == 'As per rules 42 & 43 of CGST Rules':
|
||||
index = 0
|
||||
else:
|
||||
index = 1
|
||||
|
||||
for gst_category in gst_category_list:
|
||||
txval += total_taxable_value.get(gst_category,0)
|
||||
for account_head in self.account_heads:
|
||||
for account_type, account_name in iteritems(account_head):
|
||||
if account_map.get(account_type) in self.report_dict.get(supply_type).get(supply_category):
|
||||
self.report_dict[supply_type][supply_category][account_map.get(account_type)] += \
|
||||
flt(tax_details.get((account_name, gst_category), {}).get("amount"), 2)
|
||||
|
||||
self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2)
|
||||
|
||||
def set_inter_state_supply(self, inter_state_supply):
|
||||
|
||||
osup_det = self.report_dict["sup_details"]["osup_det"]
|
||||
|
||||
for key, value in iteritems(inter_state_supply):
|
||||
if key[0] == "Unregistered":
|
||||
self.report_dict["inter_sup"]["unreg_details"].append(value)
|
||||
|
||||
if key[0] == "Registered Composition":
|
||||
self.report_dict["inter_sup"]["comp_details"].append(value)
|
||||
|
||||
if key[0] == "UIN Holders":
|
||||
self.report_dict["inter_sup"]["uin_details"].append(value)
|
||||
|
||||
def get_total_taxable_value(self, doctype, reverse_charge):
|
||||
|
||||
return frappe._dict(frappe.db.sql("""
|
||||
select gst_category, sum(net_total) as total
|
||||
from `tab{doctype}`
|
||||
where docstatus = 1 and month(posting_date) = %s
|
||||
and year(posting_date) = %s and reverse_charge = %s
|
||||
and company = %s and company_gstin = %s
|
||||
group by gst_category
|
||||
""" #nosec
|
||||
.format(doctype = doctype), (self.month_no, self.year, reverse_charge, self.company, self.gst_details.get("gstin"))))
|
||||
for key in ['camt', 'samt', 'iamt', 'csamt']:
|
||||
if entry.account in self.account_heads.get(key):
|
||||
self.report_dict["itc_elg"]["itc_rev"][index][key] += flt(entry.amount)
|
||||
net_itc[key] -= flt(entry.amount)
|
||||
|
||||
def get_itc_details(self):
|
||||
itc_amount = frappe.db.sql("""
|
||||
select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount,
|
||||
t.account_head, s.eligibility_for_itc, s.reverse_charge
|
||||
from `tabPurchase Invoice` s , `tabPurchase Taxes and Charges` t
|
||||
where s.docstatus = 1 and t.parent = s.name
|
||||
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
|
||||
and s.company_gstin = %s
|
||||
group by t.account_head, s.gst_category, s.eligibility_for_itc
|
||||
""",
|
||||
(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
itc_amounts = frappe.db.sql("""
|
||||
SELECT eligibility_for_itc, sum(itc_integrated_tax) as itc_integrated_tax,
|
||||
sum(itc_central_tax) as itc_central_tax,
|
||||
sum(itc_state_tax) as itc_state_tax,
|
||||
sum(itc_cess_amount) as itc_cess_amount
|
||||
FROM `tabPurchase Invoice`
|
||||
WHERE docstatus = 1
|
||||
and is_opening = 'No'
|
||||
and month(posting_date) = %s and year(posting_date) = %s and company = %s
|
||||
and company_gstin = %s
|
||||
GROUP BY eligibility_for_itc
|
||||
""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
|
||||
itc_details = {}
|
||||
|
||||
for d in itc_amount:
|
||||
itc_details.setdefault((d.gst_category, d.eligibility_for_itc, d.reverse_charge, d.account_head),{
|
||||
"amount": d.tax_amount
|
||||
for d in itc_amounts:
|
||||
itc_details.setdefault(d.eligibility_for_itc, {
|
||||
'iamt': d.itc_integrated_tax,
|
||||
'camt': d.itc_central_tax,
|
||||
'samt': d.itc_state_tax,
|
||||
'csamt': d.itc_cess_amount
|
||||
})
|
||||
|
||||
return itc_details
|
||||
|
||||
def get_nil_rated_supply_value(self):
|
||||
|
||||
return frappe.db.sql("""
|
||||
select sum(i.base_amount) as total from
|
||||
`tabSales Invoice Item` i, `tabSales Invoice` s
|
||||
where s.docstatus = 1 and i.parent = s.name and i.is_nil_exempt = 1
|
||||
and month(s.posting_date) = %s and year(s.posting_date) = %s
|
||||
and s.company = %s and s.company_gstin = %s""",
|
||||
(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)[0].total
|
||||
|
||||
def get_inter_state_supplies(self, state_number):
|
||||
inter_state_supply_tax = frappe.db.sql(""" select t.account_head, t.tax_amount_after_discount_amount as tax_amount,
|
||||
s.name, s.net_total, s.place_of_supply, s.gst_category from `tabSales Invoice` s, `tabSales Taxes and Charges` t
|
||||
where t.parent = s.name and s.docstatus = 1 and month(s.posting_date) = %s and year(s.posting_date) = %s
|
||||
and s.company = %s and s.company_gstin = %s and s.gst_category in ('Unregistered', 'Registered Composition', 'UIN Holders')
|
||||
""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
|
||||
inter_state_supply_tax_mapping = {}
|
||||
inter_state_supply_details = {}
|
||||
|
||||
for d in inter_state_supply_tax:
|
||||
inter_state_supply_tax_mapping.setdefault(d.name, {
|
||||
'place_of_supply': d.place_of_supply,
|
||||
'taxable_value': d.net_total,
|
||||
'gst_category': d.gst_category,
|
||||
'camt': 0.0,
|
||||
'samt': 0.0,
|
||||
'iamt': 0.0,
|
||||
'csamt': 0.0
|
||||
})
|
||||
|
||||
if d.account_head in [a.cgst_account for a in self.account_heads]:
|
||||
inter_state_supply_tax_mapping[d.name]['camt'] += d.tax_amount
|
||||
|
||||
if d.account_head in [a.sgst_account for a in self.account_heads]:
|
||||
inter_state_supply_tax_mapping[d.name]['samt'] += d.tax_amount
|
||||
|
||||
if d.account_head in [a.igst_account for a in self.account_heads]:
|
||||
inter_state_supply_tax_mapping[d.name]['iamt'] += d.tax_amount
|
||||
|
||||
if d.account_head in [a.cess_account for a in self.account_heads]:
|
||||
inter_state_supply_tax_mapping[d.name]['csamt'] += d.tax_amount
|
||||
|
||||
for key, value in iteritems(inter_state_supply_tax_mapping):
|
||||
if value.get('place_of_supply'):
|
||||
osup_det = self.report_dict["sup_details"]["osup_det"]
|
||||
osup_det["txval"] = flt(osup_det["txval"] + value['taxable_value'], 2)
|
||||
osup_det["iamt"] = flt(osup_det["iamt"] + value['iamt'], 2)
|
||||
osup_det["camt"] = flt(osup_det["camt"] + value['camt'], 2)
|
||||
osup_det["samt"] = flt(osup_det["samt"] + value['samt'], 2)
|
||||
osup_det["csamt"] = flt(osup_det["csamt"] + value['csamt'], 2)
|
||||
|
||||
if state_number != value.get('place_of_supply').split("-")[0]:
|
||||
inter_state_supply_details.setdefault((value.get('gst_category'), value.get('place_of_supply')), {
|
||||
"txval": 0.0,
|
||||
"pos": value.get('place_of_supply').split("-")[0],
|
||||
"iamt": 0.0
|
||||
})
|
||||
|
||||
inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['txval'] += value['taxable_value']
|
||||
inter_state_supply_details[(value.get('gst_category'), value.get('place_of_supply'))]['iamt'] += value['iamt']
|
||||
|
||||
return inter_state_supply_details
|
||||
|
||||
def get_inward_nil_exempt(self, state):
|
||||
inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount,
|
||||
i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
|
||||
where p.docstatus = 1 and p.name = i.parent
|
||||
and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and
|
||||
month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s
|
||||
group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
inward_nil_exempt = frappe.db.sql("""
|
||||
SELECT p.place_of_supply, sum(i.base_amount) as base_amount, i.is_nil_exempt, i.is_non_gst
|
||||
FROM `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
|
||||
WHERE p.docstatus = 1 and p.name = i.parent
|
||||
and p.is_opening = 'No'
|
||||
and p.gst_category != 'Registered Composition'
|
||||
and (i.is_nil_exempt = 1 or i.is_non_gst = 1 or p.gst_category = 'Registered Composition') and
|
||||
month(p.posting_date) = %s and year(p.posting_date) = %s
|
||||
and p.company = %s and p.company_gstin = %s
|
||||
GROUP BY p.place_of_supply, i.is_nil_exempt, i.is_non_gst""",
|
||||
(self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
|
||||
inward_nil_exempt_details = {
|
||||
"gst": {
|
||||
@@ -380,37 +162,190 @@ class GSTR3BReport(Document):
|
||||
|
||||
return inward_nil_exempt_details
|
||||
|
||||
def get_tax_amounts(self, doctype, reverse_charge="N"):
|
||||
def get_outward_supply_details(self, doctype, reverse_charge=None):
|
||||
self.get_outward_tax_invoices(doctype, reverse_charge=reverse_charge)
|
||||
self.get_outward_items(doctype)
|
||||
self.get_outward_tax_details(doctype)
|
||||
|
||||
def get_outward_tax_invoices(self, doctype, reverse_charge=None):
|
||||
self.invoices = []
|
||||
self.invoice_detail_map = {}
|
||||
condition = ''
|
||||
|
||||
if reverse_charge:
|
||||
condition += "AND reverse_charge = 'Y'"
|
||||
|
||||
invoice_details = frappe.db.sql("""
|
||||
SELECT
|
||||
name, gst_category, export_type, place_of_supply
|
||||
FROM
|
||||
`tab{doctype}`
|
||||
WHERE
|
||||
docstatus = 1
|
||||
AND month(posting_date) = %s
|
||||
AND year(posting_date) = %s
|
||||
AND company = %s
|
||||
AND company_gstin = %s
|
||||
AND is_opening = 'No'
|
||||
{reverse_charge}
|
||||
ORDER BY name
|
||||
""".format(doctype=doctype, reverse_charge=condition), (self.month_no, self.year,
|
||||
self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
|
||||
for d in invoice_details:
|
||||
self.invoice_detail_map.setdefault(d.name, d)
|
||||
self.invoices.append(d.name)
|
||||
|
||||
def get_outward_items(self, doctype):
|
||||
self.invoice_items = frappe._dict()
|
||||
self.is_nil_exempt = []
|
||||
self.is_non_gst = []
|
||||
|
||||
item_details = frappe.db.sql("""
|
||||
SELECT
|
||||
item_code, parent, taxable_value, base_net_amount, item_tax_rate,
|
||||
is_nil_exempt, is_non_gst
|
||||
FROM
|
||||
`tab%s Item`
|
||||
WHERE parent in (%s)
|
||||
""" % (doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
||||
|
||||
for d in item_details:
|
||||
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
|
||||
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in item_details
|
||||
if i.item_code == d.item_code and i.parent == d.parent))
|
||||
|
||||
if d.is_nil_exempt and d.item_code not in self.is_nil_exempt:
|
||||
self.is_nil_exempt.append(d.item_code)
|
||||
|
||||
if d.is_non_gst and d.item_code not in self.is_non_gst:
|
||||
self.is_non_gst.append(d.item_code)
|
||||
|
||||
def get_outward_tax_details(self, doctype):
|
||||
if doctype == "Sales Invoice":
|
||||
tax_template = 'Sales Taxes and Charges'
|
||||
elif doctype == "Purchase Invoice":
|
||||
tax_template = 'Purchase Taxes and Charges'
|
||||
|
||||
tax_amounts = frappe.db.sql("""
|
||||
select s.gst_category, sum(t.base_tax_amount_after_discount_amount) as tax_amount, t.account_head
|
||||
from `tab{doctype}` s , `tab{template}` t
|
||||
where s.docstatus = 1 and t.parent = s.name and s.reverse_charge = %s
|
||||
and month(s.posting_date) = %s and year(s.posting_date) = %s and s.company = %s
|
||||
and s.company_gstin = %s
|
||||
group by t.account_head, s.gst_category
|
||||
""" #nosec
|
||||
.format(doctype=doctype, template=tax_template),
|
||||
(reverse_charge, self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
|
||||
self.items_based_on_tax_rate = {}
|
||||
self.invoice_cess = frappe._dict()
|
||||
self.cgst_sgst_invoices = []
|
||||
|
||||
tax_details = {}
|
||||
tax_details = frappe.db.sql("""
|
||||
SELECT
|
||||
parent, account_head, item_wise_tax_detail, base_tax_amount_after_discount_amount
|
||||
FROM `tab%s`
|
||||
WHERE
|
||||
parenttype = %s and docstatus = 1
|
||||
and parent in (%s)
|
||||
ORDER BY account_head
|
||||
""" % (tax_template, '%s', ', '.join(['%s']*len(self.invoices))),
|
||||
tuple([doctype] + list(self.invoices)))
|
||||
|
||||
for d in tax_amounts:
|
||||
tax_details.setdefault(
|
||||
(d.account_head,d.gst_category),{
|
||||
"amount": d.get("tax_amount"),
|
||||
}
|
||||
)
|
||||
for parent, account, item_wise_tax_detail, tax_amount in tax_details:
|
||||
if account in self.account_heads.get('csamt'):
|
||||
self.invoice_cess.setdefault(parent, tax_amount)
|
||||
else:
|
||||
if item_wise_tax_detail:
|
||||
try:
|
||||
item_wise_tax_detail = json.loads(item_wise_tax_detail)
|
||||
cgst_or_sgst = False
|
||||
if account in self.account_heads.get('camt') \
|
||||
or account in self.account_heads.get('samt'):
|
||||
cgst_or_sgst = True
|
||||
|
||||
return tax_details
|
||||
for item_code, tax_amounts in item_wise_tax_detail.items():
|
||||
if not (cgst_or_sgst or account in self.account_heads.get('iamt') or
|
||||
(item_code in self.is_non_gst + self.is_nil_exempt)):
|
||||
continue
|
||||
|
||||
tax_rate = tax_amounts[0]
|
||||
if tax_rate:
|
||||
if cgst_or_sgst:
|
||||
tax_rate *= 2
|
||||
if parent not in self.cgst_sgst_invoices:
|
||||
self.cgst_sgst_invoices.append(parent)
|
||||
|
||||
rate_based_dict = self.items_based_on_tax_rate\
|
||||
.setdefault(parent, {}).setdefault(tax_rate, [])
|
||||
if item_code not in rate_based_dict:
|
||||
rate_based_dict.append(item_code)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
|
||||
# Build itemised tax for export invoices, nil and exempted where tax table is blank
|
||||
for invoice, items in iteritems(self.invoice_items):
|
||||
if invoice not in self.items_based_on_tax_rate and (self.invoice_detail_map.get(invoice, {}).get('export_type')\
|
||||
== "Without Payment of Tax"):
|
||||
self.items_based_on_tax_rate.setdefault(invoice, {}).setdefault(0, items.keys())
|
||||
|
||||
def set_outward_taxable_supplies(self):
|
||||
inter_state_supply_details = {}
|
||||
|
||||
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||
for rate, items in items_based_on_rate.items():
|
||||
for item_code, taxable_value in self.invoice_items.get(inv).items():
|
||||
if item_code in items:
|
||||
if item_code in self.is_nil_exempt:
|
||||
self.report_dict['sup_details']['osup_nil_exmp']['txval'] += taxable_value
|
||||
elif item_code in self.is_non_gst:
|
||||
self.report_dict['sup_details']['osup_nongst']['txval'] += taxable_value
|
||||
elif rate == 0:
|
||||
self.report_dict['sup_details']['osup_zero']['txval'] += taxable_value
|
||||
#self.report_dict['sup_details']['osup_zero'][key] += tax_amount
|
||||
else:
|
||||
if inv in self.cgst_sgst_invoices:
|
||||
tax_rate = rate/2
|
||||
self.report_dict['sup_details']['osup_det']['camt'] += (taxable_value * tax_rate /100)
|
||||
self.report_dict['sup_details']['osup_det']['samt'] += (taxable_value * tax_rate /100)
|
||||
self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
|
||||
else:
|
||||
self.report_dict['sup_details']['osup_det']['iamt'] += (taxable_value * rate /100)
|
||||
self.report_dict['sup_details']['osup_det']['txval'] += taxable_value
|
||||
|
||||
gst_category = self.invoice_detail_map.get(inv, {}).get('gst_category')
|
||||
place_of_supply = self.invoice_detail_map.get(inv, {}).get('place_of_supply', '00-Other Territory')
|
||||
|
||||
if gst_category in ['Unregistered', 'Registered Composition', 'UIN Holders'] and \
|
||||
self.gst_details.get("gst_state") != place_of_supply:
|
||||
inter_state_supply_details.set_default((gst_category, place_of_supply), {
|
||||
"txval": 0.0,
|
||||
"pos": value.get('place_of_supply').split("-")[0],
|
||||
"iamt": 0.0
|
||||
})
|
||||
inter_state_supply_details[(gst_category, place_of_supply)]['taxval'] += taxable_value
|
||||
inter_state_supply_details[(gst_category, place_of_supply)]['iamt'] += (taxable_value * rate /100)
|
||||
|
||||
self.set_inter_state_supply(inter_state_supply_details)
|
||||
|
||||
def set_supplies_liable_to_reverse_charge(self):
|
||||
for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
|
||||
for rate, items in items_based_on_rate.items():
|
||||
for item_code, taxable_value in self.invoice_items.get(inv).items():
|
||||
if item_code in items:
|
||||
if inv in self.cgst_sgst_invoices:
|
||||
tax_rate = rate/2
|
||||
self.report_dict['sup_details']['isup_rev']['camt'] += (taxable_value * tax_rate /100)
|
||||
self.report_dict['sup_details']['isup_rev']['samt'] += (taxable_value * tax_rate /100)
|
||||
self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value
|
||||
else:
|
||||
self.report_dict['sup_details']['isup_rev']['iamt'] += (taxable_value * rate /100)
|
||||
self.report_dict['sup_details']['isup_rev']['txval'] += taxable_value
|
||||
|
||||
def set_inter_state_supply(self, inter_state_supply):
|
||||
for key, value in iteritems(inter_state_supply):
|
||||
if key[0] == "Unregistered":
|
||||
self.report_dict["inter_sup"]["unreg_details"].append(value)
|
||||
|
||||
if key[0] == "Registered Composition":
|
||||
self.report_dict["inter_sup"]["comp_details"].append(value)
|
||||
|
||||
if key[0] == "UIN Holders":
|
||||
self.report_dict["inter_sup"]["uin_details"].append(value)
|
||||
|
||||
def get_company_gst_details(self):
|
||||
|
||||
gst_details = frappe.get_all("Address",
|
||||
fields=["gstin", "gst_state", "gst_state_number"],
|
||||
filters={
|
||||
@@ -423,20 +358,28 @@ class GSTR3BReport(Document):
|
||||
frappe.throw(_("Please enter GSTIN and state for the Company Address {0}").format(self.company_address))
|
||||
|
||||
def get_account_heads(self):
|
||||
account_map = {
|
||||
'sgst_account': 'samt',
|
||||
'cess_account': 'csamt',
|
||||
'cgst_account': 'camt',
|
||||
'igst_account': 'iamt'
|
||||
}
|
||||
|
||||
account_heads = frappe.get_all("GST Account",
|
||||
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"],
|
||||
filters={
|
||||
"company":self.company
|
||||
})
|
||||
account_heads = {}
|
||||
gst_settings_accounts = frappe.get_all("GST Account",
|
||||
filters={'company': self.company, 'is_reverse_charge_account': 0},
|
||||
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
|
||||
|
||||
if account_heads:
|
||||
return account_heads
|
||||
else:
|
||||
frappe.throw(_("Please set account heads in GST Settings for Compnay {0}").format(self.company))
|
||||
if not gst_settings_accounts:
|
||||
frappe.throw(_("Please set GST Accounts in GST Settings"))
|
||||
|
||||
for d in gst_settings_accounts:
|
||||
for acc, val in d.items():
|
||||
account_heads.setdefault(account_map.get(acc), []).append(val)
|
||||
|
||||
return account_heads
|
||||
|
||||
def get_missing_field_invoices(self):
|
||||
|
||||
missing_field_invoices = []
|
||||
|
||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
@@ -448,26 +391,32 @@ class GSTR3BReport(Document):
|
||||
party_type = 'Supplier'
|
||||
party = 'supplier'
|
||||
|
||||
docnames = frappe.db.sql("""
|
||||
select t1.name from `tab{doctype}` t1, `tab{party_type}` t2
|
||||
where t1.docstatus = 1 and month(t1.posting_date) = %s and year(t1.posting_date) = %s
|
||||
docnames = frappe.db.sql(
|
||||
"""
|
||||
SELECT t1.name FROM `tab{doctype}` t1, `tab{party_type}` t2
|
||||
WHERE t1.docstatus = 1 and t1.is_opening = 'No'
|
||||
and month(t1.posting_date) = %s and year(t1.posting_date) = %s
|
||||
and t1.company = %s and t1.place_of_supply IS NULL and t1.{party} = t2.name and
|
||||
t2.gst_category != 'Overseas'
|
||||
""".format(doctype = doctype, party_type = party_type, party=party), (self.month_no, self.year, self.company), as_dict=1) #nosec
|
||||
""".format(doctype = doctype, party_type = party_type,
|
||||
party=party) ,(self.month_no, self.year, self.company), as_dict=1) #nosec
|
||||
|
||||
for d in docnames:
|
||||
missing_field_invoices.append(d.name)
|
||||
|
||||
return ",".join(missing_field_invoices)
|
||||
|
||||
def get_state_code(state):
|
||||
def get_json(template):
|
||||
file_path = os.path.join(os.path.dirname(__file__), '{template}.json'.format(template=template))
|
||||
with open(file_path, 'r') as f:
|
||||
return cstr(f.read())
|
||||
|
||||
def get_state_code(state):
|
||||
state_code = state_numbers.get(state)
|
||||
|
||||
return state_code
|
||||
|
||||
def get_period(month, year=None):
|
||||
|
||||
month_no = {
|
||||
"January": 1,
|
||||
"February": 2,
|
||||
@@ -491,13 +440,11 @@ def get_period(month, year=None):
|
||||
|
||||
@frappe.whitelist()
|
||||
def view_report(name):
|
||||
|
||||
json_data = frappe.get_value("GSTR 3B Report", name, 'json_output')
|
||||
return json.loads(json_data)
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_json(name):
|
||||
|
||||
json_data = frappe.get_value("GSTR 3B Report", name, 'json_output')
|
||||
file_name = "GST3B.json"
|
||||
frappe.local.response.filename = file_name
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"gstin": "",
|
||||
"ret_period": "",
|
||||
"inward_sup": {
|
||||
"isup_details": [
|
||||
{
|
||||
"ty": "GST",
|
||||
"intra": 0,
|
||||
"inter": 0
|
||||
},
|
||||
{
|
||||
"ty": "NONGST",
|
||||
"inter": 0,
|
||||
"intra": 0
|
||||
}
|
||||
]
|
||||
},
|
||||
"sup_details": {
|
||||
"osup_zero": {
|
||||
"csamt": 0,
|
||||
"txval": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
"osup_nil_exmp": {
|
||||
"txval": 0
|
||||
},
|
||||
"osup_det": {
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"txval": 0,
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
"isup_rev": {
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"txval": 0,
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
"osup_nongst": {
|
||||
"txval": 0
|
||||
}
|
||||
},
|
||||
"inter_sup": {
|
||||
"unreg_details": [],
|
||||
"comp_details": [],
|
||||
"uin_details": []
|
||||
},
|
||||
"itc_elg": {
|
||||
"itc_avl": [
|
||||
{
|
||||
"csamt": 0,
|
||||
"samt": 0,
|
||||
"ty": "IMPG",
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
{
|
||||
"csamt": 0,
|
||||
"samt": 0,
|
||||
"ty": "IMPS",
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
{
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"ty": "ISRC",
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
{
|
||||
"ty": "ISD",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
},
|
||||
{
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"ty": "OTH",
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
}
|
||||
],
|
||||
"itc_rev": [
|
||||
{
|
||||
"ty": "RUL",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
},
|
||||
{
|
||||
"ty": "OTH",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
}
|
||||
],
|
||||
"itc_net": {
|
||||
"samt": 0,
|
||||
"csamt": 0,
|
||||
"camt": 0,
|
||||
"iamt": 0
|
||||
},
|
||||
"itc_inelg": [
|
||||
{
|
||||
"ty": "RUL",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
},
|
||||
{
|
||||
"ty": "OTH",
|
||||
"iamt": 0,
|
||||
"camt": 0,
|
||||
"samt": 0,
|
||||
"csamt": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,20 @@ import json
|
||||
test_dependencies = ["Territory", "Customer Group", "Supplier Group", "Item"]
|
||||
|
||||
class TestGSTR3BReport(unittest.TestCase):
|
||||
def test_gstr_3b_report(self):
|
||||
def setUp(self):
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'")
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'")
|
||||
frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'")
|
||||
|
||||
make_company()
|
||||
make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000})
|
||||
set_account_heads()
|
||||
make_customers()
|
||||
make_suppliers()
|
||||
|
||||
def test_gstr_3b_report(self):
|
||||
month_number_mapping = {
|
||||
1: "January",
|
||||
2: "February",
|
||||
@@ -31,17 +43,6 @@ class TestGSTR3BReport(unittest.TestCase):
|
||||
12: "December"
|
||||
}
|
||||
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company GST'")
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company GST'")
|
||||
frappe.db.sql("delete from `tabGSTR 3B Report` where company='_Test Company GST'")
|
||||
|
||||
make_company()
|
||||
make_item("Milk", properties = {"is_nil_exempt": 1, "standard_rate": 0.000000})
|
||||
set_account_heads()
|
||||
make_customers()
|
||||
make_suppliers()
|
||||
make_sales_invoice()
|
||||
create_purchase_invoices()
|
||||
|
||||
@@ -67,6 +68,42 @@ class TestGSTR3BReport(unittest.TestCase):
|
||||
self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50)
|
||||
self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50)
|
||||
|
||||
def test_gst_rounding(self):
|
||||
gst_settings = frappe.get_doc('GST Settings')
|
||||
gst_settings.round_off_gst_values = 1
|
||||
gst_settings.save()
|
||||
|
||||
current_country = frappe.flags.country
|
||||
frappe.flags.country = 'India'
|
||||
|
||||
si = create_sales_invoice(company="_Test Company GST",
|
||||
customer = '_Test GST Customer',
|
||||
currency = 'INR',
|
||||
warehouse = 'Finished Goods - _GST',
|
||||
debit_to = 'Debtors - _GST',
|
||||
income_account = 'Sales - _GST',
|
||||
expense_account = 'Cost of Goods Sold - _GST',
|
||||
cost_center = 'Main - _GST',
|
||||
rate=216,
|
||||
do_not_save=1
|
||||
)
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "IGST - _GST",
|
||||
"cost_center": "Main - _GST",
|
||||
"description": "IGST @ 18.0",
|
||||
"rate": 18
|
||||
})
|
||||
|
||||
si.save()
|
||||
# Check for 39 instead of 38.88
|
||||
self.assertEqual(si.taxes[0].base_tax_amount_after_discount_amount, 39)
|
||||
|
||||
frappe.flags.country = current_country
|
||||
gst_settings.round_off_gst_values = 1
|
||||
gst_settings.save()
|
||||
|
||||
def make_sales_invoice():
|
||||
si = create_sales_invoice(company="_Test Company GST",
|
||||
customer = '_Test GST Customer',
|
||||
@@ -145,7 +182,6 @@ def make_sales_invoice():
|
||||
si3.submit()
|
||||
|
||||
def create_purchase_invoices():
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
company="_Test Company GST",
|
||||
supplier = '_Test Registered Supplier',
|
||||
@@ -193,7 +229,6 @@ def create_purchase_invoices():
|
||||
pi1.submit()
|
||||
|
||||
def make_suppliers():
|
||||
|
||||
if not frappe.db.exists("Supplier", "_Test Registered Supplier"):
|
||||
frappe.get_doc({
|
||||
"supplier_group": "_Test Supplier Group",
|
||||
@@ -257,7 +292,6 @@ def make_suppliers():
|
||||
address.save()
|
||||
|
||||
def make_customers():
|
||||
|
||||
if not frappe.db.exists("Customer", "_Test GST Customer"):
|
||||
frappe.get_doc({
|
||||
"customer_group": "_Test Customer Group",
|
||||
@@ -354,9 +388,9 @@ def make_customers():
|
||||
address.save()
|
||||
|
||||
def make_company():
|
||||
|
||||
if frappe.db.exists("Company", "_Test Company GST"):
|
||||
return
|
||||
|
||||
company = frappe.new_doc("Company")
|
||||
company.company_name = "_Test Company GST"
|
||||
company.abbr = "_GST"
|
||||
@@ -388,7 +422,6 @@ def make_company():
|
||||
address.save()
|
||||
|
||||
def set_account_heads():
|
||||
|
||||
gst_settings = frappe.get_doc("GST Settings")
|
||||
|
||||
gst_account = frappe.get_all(
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
erpnext.setup_einvoice_actions = (doctype) => {
|
||||
frappe.ui.form.on(doctype, {
|
||||
async refresh(frm) {
|
||||
const { message } = await frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable");
|
||||
const einvoicing_enabled = cint(message.enable);
|
||||
const supply_type = frm.doc.gst_category;
|
||||
const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type);
|
||||
const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin;
|
||||
const res = await frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility',
|
||||
args: { doc: frm.doc }
|
||||
});
|
||||
const invoice_eligible = res.message;
|
||||
|
||||
if (!einvoicing_enabled || !valid_supply_type || company_transaction) return;
|
||||
if (!invoice_eligible) return;
|
||||
|
||||
const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
|
||||
|
||||
@@ -110,45 +110,25 @@ erpnext.setup_einvoice_actions = (doctype) => {
|
||||
}
|
||||
|
||||
if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
|
||||
const fields = [
|
||||
{
|
||||
"label": "Reason",
|
||||
"fieldname": "reason",
|
||||
"fieldtype": "Select",
|
||||
"reqd": 1,
|
||||
"default": "1-Duplicate",
|
||||
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
|
||||
},
|
||||
{
|
||||
"label": "Remark",
|
||||
"fieldname": "remark",
|
||||
"fieldtype": "Data",
|
||||
"reqd": 1
|
||||
}
|
||||
];
|
||||
const action = () => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Cancel E-Way Bill'),
|
||||
fields: fields,
|
||||
let message = __('Cancellation of e-way bill is currently not supported. ');
|
||||
message += '<br><br>';
|
||||
message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
|
||||
|
||||
frappe.msgprint({
|
||||
title: __('Update E-Way Bill Cancelled Status?'),
|
||||
message: message,
|
||||
indicator: 'orange',
|
||||
primary_action: function() {
|
||||
const data = d.get_values();
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
|
||||
args: {
|
||||
doctype,
|
||||
docname: name,
|
||||
eway_bill: ewaybill,
|
||||
reason: data.reason.split('-')[0],
|
||||
remark: data.remark
|
||||
},
|
||||
args: { doctype, docname: name },
|
||||
freeze: true,
|
||||
callback: () => frm.reload_doc() || d.hide(),
|
||||
error: () => d.hide()
|
||||
callback: () => frm.reload_doc()
|
||||
});
|
||||
},
|
||||
primary_action_label: __('Submit')
|
||||
primary_action_label: __('Yes')
|
||||
});
|
||||
d.show();
|
||||
};
|
||||
add_custom_button(__("Cancel E-Way Bill"), action);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import jwt
|
||||
import sys
|
||||
import json
|
||||
@@ -16,16 +17,38 @@ from frappe import _, bold
|
||||
from pyqrcode import create as qrcreate
|
||||
from frappe.integrations.utils import make_post_request, make_get_request
|
||||
from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
|
||||
from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form
|
||||
from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, getdate, get_link_to_form
|
||||
|
||||
def validate_einvoice_fields(doc):
|
||||
einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
|
||||
invalid_doctype = doc.doctype != 'Sales Invoice'
|
||||
@frappe.whitelist()
|
||||
def validate_eligibility(doc):
|
||||
if isinstance(doc, six.string_types):
|
||||
doc = json.loads(doc)
|
||||
|
||||
invalid_doctype = doc.get('doctype') != 'Sales Invoice'
|
||||
if invalid_doctype:
|
||||
return False
|
||||
|
||||
einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable'))
|
||||
if not einvoicing_enabled:
|
||||
return False
|
||||
|
||||
if getdate(doc.get('posting_date')) < getdate('2021-04-01'):
|
||||
return False
|
||||
|
||||
invalid_company = not frappe.db.get_value('E Invoice User', { 'company': doc.get('company') })
|
||||
invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
|
||||
company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
|
||||
no_taxes_applied = not doc.get('taxes')
|
||||
|
||||
if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied:
|
||||
|
||||
if invalid_company or invalid_supply_type or company_transaction or no_taxes_applied:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def validate_einvoice_fields(doc):
|
||||
invoice_eligible = validate_eligibility(doc)
|
||||
|
||||
if not invoice_eligible:
|
||||
return
|
||||
|
||||
if doc.docstatus == 0 and doc._action == 'save':
|
||||
@@ -86,35 +109,39 @@ def get_doc_details(invoice):
|
||||
invoice_date=invoice_date
|
||||
))
|
||||
|
||||
def get_party_details(address_name):
|
||||
d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
|
||||
|
||||
if (not d.gstin
|
||||
or not d.city
|
||||
or not d.pincode
|
||||
or not d.address_title
|
||||
or not d.address_line1
|
||||
or not d.gst_state_number):
|
||||
def validate_address_fields(address, is_shipping_address):
|
||||
if ((not address.gstin and not is_shipping_address)
|
||||
or not address.city
|
||||
or not address.pincode
|
||||
or not address.address_title
|
||||
or not address.address_line1
|
||||
or not address.gst_state_number):
|
||||
|
||||
frappe.throw(
|
||||
msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format(
|
||||
get_link_to_form('Address', address_name)
|
||||
),
|
||||
msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name),
|
||||
title=_('Missing Address Fields')
|
||||
)
|
||||
|
||||
if d.gst_state_number == 97:
|
||||
# according to einvoice standard
|
||||
pincode = 999999
|
||||
def get_party_details(address_name, is_shipping_address=False):
|
||||
addr = frappe.get_doc('Address', address_name)
|
||||
|
||||
return frappe._dict(dict(
|
||||
gstin=d.gstin, legal_name=d.address_title,
|
||||
location=d.city, pincode=d.pincode,
|
||||
state_code=d.gst_state_number,
|
||||
address_line1=d.address_line1,
|
||||
address_line2=d.address_line2
|
||||
validate_address_fields(addr, is_shipping_address)
|
||||
|
||||
if addr.gst_state_number == 97:
|
||||
# according to einvoice standard
|
||||
addr.pincode = 999999
|
||||
|
||||
party_address_details = frappe._dict(dict(
|
||||
legal_name=sanitize_for_json(addr.address_title),
|
||||
location=sanitize_for_json(addr.city),
|
||||
pincode=addr.pincode, gstin=addr.gstin,
|
||||
state_code=addr.gst_state_number,
|
||||
address_line1=sanitize_for_json(addr.address_line1),
|
||||
address_line2=sanitize_for_json(addr.address_line2)
|
||||
))
|
||||
|
||||
return party_address_details
|
||||
|
||||
def get_gstin_details(gstin):
|
||||
if not hasattr(frappe.local, 'gstin_cache'):
|
||||
frappe.local.gstin_cache = {}
|
||||
@@ -163,10 +190,15 @@ def get_item_list(invoice):
|
||||
item.description = json.dumps(d.item_name)[1:-1]
|
||||
|
||||
item.qty = abs(item.qty)
|
||||
item.discount_amount = 0
|
||||
item.unit_rate = abs(item.base_net_amount / item.qty)
|
||||
item.gross_amount = abs(item.base_net_amount)
|
||||
item.taxable_value = abs(item.base_net_amount)
|
||||
|
||||
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
|
||||
item.discount_amount = abs(item.base_amount - item.base_net_amount)
|
||||
else:
|
||||
item.discount_amount = 0
|
||||
|
||||
item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty)
|
||||
item.gross_amount = abs(item.taxable_value) + item.discount_amount
|
||||
item.taxable_value = abs(item.taxable_value)
|
||||
|
||||
item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
|
||||
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||
@@ -196,12 +228,14 @@ def update_item_taxes(invoice, item):
|
||||
item[attr] = 0
|
||||
|
||||
for t in invoice.taxes:
|
||||
# this contains item wise tax rate & tax amount (incl. discount)
|
||||
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
|
||||
if t.account_head in gst_accounts_list:
|
||||
is_applicable = t.tax_amount and t.account_head in gst_accounts_list
|
||||
if is_applicable:
|
||||
# this contains item wise tax rate & tax amount (incl. discount)
|
||||
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name)
|
||||
|
||||
item_tax_rate = item_tax_detail[0]
|
||||
# item tax amount excluding discount amount
|
||||
item_tax_amount = (item_tax_rate / 100) * item.base_net_amount
|
||||
item_tax_amount = (item_tax_rate / 100) * item.taxable_value
|
||||
|
||||
if t.account_head in gst_accounts.cess_account:
|
||||
item_tax_amount_after_discount = item_tax_detail[1]
|
||||
@@ -222,10 +256,14 @@ def get_invoice_value_details(invoice):
|
||||
invoice_value_details = frappe._dict(dict())
|
||||
|
||||
if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
|
||||
invoice_value_details.base_total = abs(invoice.base_total)
|
||||
# Discount already applied on net total which means on items
|
||||
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
|
||||
invoice_value_details.invoice_discount_amt = 0
|
||||
elif invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount:
|
||||
invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
|
||||
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
|
||||
else:
|
||||
invoice_value_details.base_total = abs(invoice.base_net_total)
|
||||
invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
|
||||
# since tax already considers discount amount
|
||||
invoice_value_details.invoice_discount_amt = 0
|
||||
|
||||
@@ -246,7 +284,11 @@ def update_invoice_taxes(invoice, invoice_value_details):
|
||||
invoice_value_details.total_igst_amt = 0
|
||||
invoice_value_details.total_cess_amt = 0
|
||||
invoice_value_details.total_other_charges = 0
|
||||
considered_rows = []
|
||||
|
||||
for t in invoice.taxes:
|
||||
tax_amount = t.base_tax_amount if (invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount) \
|
||||
else t.base_tax_amount_after_discount_amount
|
||||
if t.account_head in gst_accounts_list:
|
||||
if t.account_head in gst_accounts.cess_account:
|
||||
# using after discount amt since item also uses after discount amt for cess calc
|
||||
@@ -254,12 +296,26 @@ def update_invoice_taxes(invoice, invoice_value_details):
|
||||
|
||||
for tax_type in ['igst', 'cgst', 'sgst']:
|
||||
if t.account_head in gst_accounts[f'{tax_type}_account']:
|
||||
invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount_after_discount_amount)
|
||||
invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount)
|
||||
|
||||
update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
|
||||
else:
|
||||
invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
|
||||
invoice_value_details.total_other_charges += abs(tax_amount)
|
||||
|
||||
return invoice_value_details
|
||||
|
||||
def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows):
|
||||
prev_row_id = cint(tax_row.row_id) - 1
|
||||
if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows:
|
||||
if tax_row.charge_type == 'On Previous Row Amount':
|
||||
amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount
|
||||
invoice_value_details.total_other_charges -= abs(amount)
|
||||
considered_rows.append(prev_row_id)
|
||||
if tax_row.charge_type == 'On Previous Row Total':
|
||||
amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total
|
||||
invoice_value_details.total_other_charges -= abs(amount)
|
||||
considered_rows.append(prev_row_id)
|
||||
|
||||
def get_payment_details(invoice):
|
||||
payee_name = invoice.company
|
||||
mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
|
||||
@@ -272,6 +328,10 @@ def get_payment_details(invoice):
|
||||
))
|
||||
|
||||
def get_return_doc_reference(invoice):
|
||||
if not invoice.return_against:
|
||||
frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.')
|
||||
.format(frappe.bold('Return Against')), title=_('Missing Field'))
|
||||
|
||||
invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
|
||||
return frappe._dict(dict(
|
||||
invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
|
||||
@@ -279,7 +339,11 @@ def get_return_doc_reference(invoice):
|
||||
|
||||
def get_eway_bill_details(invoice):
|
||||
if invoice.is_return:
|
||||
frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed'))
|
||||
frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
|
||||
title=_('Invalid Fields'))
|
||||
|
||||
if not invoice.distance:
|
||||
frappe.throw(_('Distance is mandatory for generating e-way bill for an e-invoice.'), title=_('Missing Field'))
|
||||
|
||||
mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
|
||||
vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
|
||||
@@ -297,9 +361,15 @@ def get_eway_bill_details(invoice):
|
||||
|
||||
def validate_mandatory_fields(invoice):
|
||||
if not invoice.company_address:
|
||||
frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields'))
|
||||
frappe.throw(
|
||||
_('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'),
|
||||
title=_('Missing Fields')
|
||||
)
|
||||
if not invoice.customer_address:
|
||||
frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields'))
|
||||
frappe.throw(
|
||||
_('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'),
|
||||
title=_('Missing Fields')
|
||||
)
|
||||
if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
|
||||
frappe.throw(
|
||||
_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
|
||||
@@ -311,6 +381,39 @@ def validate_mandatory_fields(invoice):
|
||||
title=_('Missing Fields')
|
||||
)
|
||||
|
||||
def validate_totals(einvoice):
|
||||
item_list = einvoice['ItemList']
|
||||
value_details = einvoice['ValDtls']
|
||||
|
||||
total_item_ass_value = 0
|
||||
total_item_cgst_value = 0
|
||||
total_item_sgst_value = 0
|
||||
total_item_igst_value = 0
|
||||
total_item_value = 0
|
||||
for item in item_list:
|
||||
total_item_ass_value += flt(item['AssAmt'])
|
||||
total_item_cgst_value += flt(item['CgstAmt'])
|
||||
total_item_sgst_value += flt(item['SgstAmt'])
|
||||
total_item_igst_value += flt(item['IgstAmt'])
|
||||
total_item_value += flt(item['TotItemVal'])
|
||||
|
||||
if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1:
|
||||
frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx))
|
||||
|
||||
if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
|
||||
frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
|
||||
|
||||
if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - flt(value_details['OthChrg']) - total_item_value) > 1:
|
||||
frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
|
||||
|
||||
calculated_invoice_value = \
|
||||
flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
|
||||
+ flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
|
||||
+ flt(value_details['OthChrg']) - flt(value_details['Discount'])
|
||||
|
||||
if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
|
||||
frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
|
||||
|
||||
def make_einvoice(invoice):
|
||||
validate_mandatory_fields(invoice)
|
||||
|
||||
@@ -326,16 +429,23 @@ def make_einvoice(invoice):
|
||||
buyer_details = get_overseas_address_details(invoice.customer_address)
|
||||
else:
|
||||
buyer_details = get_party_details(invoice.customer_address)
|
||||
place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin
|
||||
place_of_supply = place_of_supply[:2]
|
||||
place_of_supply = get_place_of_supply(invoice, invoice.doctype)
|
||||
if place_of_supply:
|
||||
place_of_supply = place_of_supply.split('-')[0]
|
||||
else:
|
||||
place_of_supply = invoice.billing_address_gstin[:2]
|
||||
|
||||
buyer_details.update(dict(place_of_supply=place_of_supply))
|
||||
|
||||
seller_details.update(dict(legal_name=invoice.company))
|
||||
buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer))
|
||||
|
||||
shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
|
||||
if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
|
||||
if invoice.gst_category == 'Overseas':
|
||||
shipping_details = get_overseas_address_details(invoice.shipping_address_name)
|
||||
else:
|
||||
shipping_details = get_party_details(invoice.shipping_address_name)
|
||||
shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True)
|
||||
|
||||
if invoice.is_pos and invoice.base_paid_amount:
|
||||
payment_details = get_payment_details(invoice)
|
||||
@@ -343,7 +453,7 @@ def make_einvoice(invoice):
|
||||
if invoice.is_return and invoice.return_against:
|
||||
prev_doc_details = get_return_doc_reference(invoice)
|
||||
|
||||
if invoice.transporter:
|
||||
if invoice.transporter and flt(invoice.distance) and not invoice.is_return:
|
||||
eway_bill_details = get_eway_bill_details(invoice)
|
||||
|
||||
# not yet implemented
|
||||
@@ -356,71 +466,86 @@ def make_einvoice(invoice):
|
||||
period_details=period_details, prev_doc_details=prev_doc_details,
|
||||
export_details=export_details, eway_bill_details=eway_bill_details
|
||||
)
|
||||
einvoice = json.loads(einvoice)
|
||||
|
||||
validations = json.loads(read_json('einv_validation'))
|
||||
errors = validate_einvoice(validations, einvoice)
|
||||
if errors:
|
||||
message = "\n".join([
|
||||
"E Invoice: ", json.dumps(einvoice, indent=4),
|
||||
"-" * 50,
|
||||
"Errors: ", json.dumps(errors, indent=4)
|
||||
])
|
||||
frappe.log_error(title="E Invoice Validation Failed", message=message)
|
||||
frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1)
|
||||
try:
|
||||
einvoice = safe_json_load(einvoice)
|
||||
einvoice = santize_einvoice_fields(einvoice)
|
||||
except Exception:
|
||||
show_link_to_error_log(invoice, einvoice)
|
||||
|
||||
validate_totals(einvoice)
|
||||
|
||||
return einvoice
|
||||
|
||||
def validate_einvoice(validations, einvoice, errors=[]):
|
||||
for fieldname, field_validation in validations.items():
|
||||
value = einvoice.get(fieldname, None)
|
||||
if not value or value == "None":
|
||||
# remove keys with empty values
|
||||
einvoice.pop(fieldname, None)
|
||||
continue
|
||||
def show_link_to_error_log(invoice, einvoice):
|
||||
err_log = log_error(einvoice)
|
||||
link_to_error_log = get_link_to_form('Error Log', err_log.name, 'Error Log')
|
||||
frappe.throw(
|
||||
_('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
|
||||
invoice.name, link_to_error_log),
|
||||
title=_('E Invoice Creation Failed')
|
||||
)
|
||||
|
||||
value_type = field_validation.get("type").lower()
|
||||
if value_type in ['object', 'array']:
|
||||
child_validations = field_validation.get('properties')
|
||||
def log_error(data=None):
|
||||
if isinstance(data, six.string_types):
|
||||
data = json.loads(data)
|
||||
|
||||
if isinstance(value, list):
|
||||
for d in value:
|
||||
validate_einvoice(child_validations, d, errors)
|
||||
if not d:
|
||||
# remove empty dicts
|
||||
einvoice.pop(fieldname, None)
|
||||
seperator = "--" * 50
|
||||
err_tb = traceback.format_exc()
|
||||
err_msg = str(sys.exc_info()[1])
|
||||
data = json.dumps(data, indent=4)
|
||||
|
||||
message = "\n".join([
|
||||
"Error", err_msg, seperator,
|
||||
"Data:", data, seperator,
|
||||
"Exception:", err_tb
|
||||
])
|
||||
frappe.log_error(title=_('E Invoice Request Failed'), message=message)
|
||||
|
||||
def santize_einvoice_fields(einvoice):
|
||||
int_fields = ["Pin","Distance","CrDay"]
|
||||
float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",]
|
||||
copy = einvoice.copy()
|
||||
for key, value in copy.items():
|
||||
if isinstance(value, list):
|
||||
for idx, d in enumerate(value):
|
||||
santized_dict = santize_einvoice_fields(d)
|
||||
if santized_dict:
|
||||
einvoice[key][idx] = santized_dict
|
||||
else:
|
||||
einvoice[key].pop(idx)
|
||||
|
||||
if not einvoice[key]:
|
||||
einvoice.pop(key, None)
|
||||
|
||||
elif isinstance(value, dict):
|
||||
santized_dict = santize_einvoice_fields(value)
|
||||
if santized_dict:
|
||||
einvoice[key] = santized_dict
|
||||
else:
|
||||
validate_einvoice(child_validations, value, errors)
|
||||
if not value:
|
||||
# remove empty dicts
|
||||
einvoice.pop(fieldname, None)
|
||||
continue
|
||||
einvoice.pop(key, None)
|
||||
|
||||
# convert to int or str
|
||||
if value_type == 'string':
|
||||
einvoice[fieldname] = str(value)
|
||||
elif value_type == 'number':
|
||||
is_integer = '.' not in str(field_validation.get('maximum'))
|
||||
precision = 3 if '.999' in str(field_validation.get('maximum')) else 2
|
||||
einvoice[fieldname] = flt(value, precision) if not is_integer else cint(value)
|
||||
value = einvoice[fieldname]
|
||||
elif not value or value == "None":
|
||||
einvoice.pop(key, None)
|
||||
|
||||
max_length = field_validation.get('maxLength')
|
||||
minimum = flt(field_validation.get('minimum'))
|
||||
maximum = flt(field_validation.get('maximum'))
|
||||
pattern_str = field_validation.get('pattern')
|
||||
pattern = re.compile(pattern_str or '')
|
||||
elif key in float_fields:
|
||||
einvoice[key] = flt(value, 2)
|
||||
|
||||
label = field_validation.get('description') or fieldname
|
||||
elif key in int_fields:
|
||||
einvoice[key] = cint(value)
|
||||
|
||||
if value_type == 'string' and len(value) > max_length:
|
||||
errors.append(_('{} should not exceed {} characters').format(label, max_length))
|
||||
if value_type == 'number' and (value > maximum or value < minimum):
|
||||
errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum))
|
||||
if pattern_str and not pattern.match(value):
|
||||
errors.append(field_validation.get('validationMsg'))
|
||||
return einvoice
|
||||
|
||||
return errors
|
||||
def safe_json_load(json_string):
|
||||
JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
|
||||
try:
|
||||
return json.loads(json_string)
|
||||
except JSONDecodeError as e:
|
||||
# print a snippet of 40 characters around the location where error occured
|
||||
pos = e.pos
|
||||
start, end = max(0, pos-20), min(len(json_string)-1, pos+20)
|
||||
snippet = json_string[start:end]
|
||||
frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
|
||||
|
||||
class RequestFailed(Exception): pass
|
||||
|
||||
@@ -446,15 +571,17 @@ class GSPConnector():
|
||||
def get_credentials(self):
|
||||
if self.invoice:
|
||||
gstin = self.get_seller_gstin()
|
||||
if not self.e_invoice_settings.enable:
|
||||
frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
|
||||
credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
|
||||
credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin]
|
||||
if credentials_for_gstin:
|
||||
credentials = credentials_for_gstin[0]
|
||||
else:
|
||||
frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings'))
|
||||
else:
|
||||
credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
|
||||
return credentials
|
||||
|
||||
def get_seller_gstin(self):
|
||||
gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
|
||||
gstin = frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
|
||||
if not gstin:
|
||||
frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
|
||||
return gstin
|
||||
@@ -502,7 +629,7 @@ class GSPConnector():
|
||||
self.e_invoice_settings.reload()
|
||||
|
||||
except Exception:
|
||||
self.log_error(res)
|
||||
log_error(res)
|
||||
self.raise_error(True)
|
||||
|
||||
def get_headers(self):
|
||||
@@ -524,14 +651,14 @@ class GSPConnector():
|
||||
if res.get('success'):
|
||||
return res.get('result')
|
||||
else:
|
||||
self.log_error(res)
|
||||
log_error(res)
|
||||
raise RequestFailed
|
||||
|
||||
except RequestFailed:
|
||||
self.raise_error()
|
||||
|
||||
except Exception:
|
||||
self.log_error()
|
||||
log_error()
|
||||
self.raise_error(True)
|
||||
@staticmethod
|
||||
def get_gstin_details(gstin):
|
||||
@@ -576,7 +703,7 @@ class GSPConnector():
|
||||
self.raise_error(errors=errors)
|
||||
|
||||
except Exception:
|
||||
self.log_error(data)
|
||||
log_error(data)
|
||||
self.raise_error(True)
|
||||
|
||||
def get_irn_details(self, irn):
|
||||
@@ -595,7 +722,7 @@ class GSPConnector():
|
||||
self.raise_error(errors=errors)
|
||||
|
||||
except Exception:
|
||||
self.log_error()
|
||||
log_error()
|
||||
self.raise_error(True)
|
||||
|
||||
def cancel_irn(self, irn, reason, remark):
|
||||
@@ -608,7 +735,7 @@ class GSPConnector():
|
||||
|
||||
try:
|
||||
res = self.make_request('post', self.cancel_irn_url, headers, data)
|
||||
if res.get('success'):
|
||||
if res.get('success') or '9999' in res.get('message'):
|
||||
self.invoice.irn_cancelled = 1
|
||||
self.invoice.flags.updater_reference = {
|
||||
'doctype': self.invoice.doctype,
|
||||
@@ -625,7 +752,7 @@ class GSPConnector():
|
||||
self.raise_error(errors=errors)
|
||||
|
||||
except Exception:
|
||||
self.log_error(data)
|
||||
log_error(data)
|
||||
self.raise_error(True)
|
||||
def generate_eway_bill(self, **kwargs):
|
||||
args = frappe._dict(kwargs)
|
||||
@@ -665,7 +792,7 @@ class GSPConnector():
|
||||
self.raise_error(errors=errors)
|
||||
|
||||
except Exception:
|
||||
self.log_error(data)
|
||||
log_error(data)
|
||||
self.raise_error(True)
|
||||
|
||||
def cancel_eway_bill(self, eway_bill, reason, remark):
|
||||
@@ -697,7 +824,7 @@ class GSPConnector():
|
||||
self.raise_error(errors=errors)
|
||||
|
||||
except Exception:
|
||||
self.log_error(data)
|
||||
log_error(data)
|
||||
self.raise_error(True)
|
||||
|
||||
def sanitize_error_message(self, message):
|
||||
@@ -723,22 +850,6 @@ class GSPConnector():
|
||||
|
||||
return errors
|
||||
|
||||
def log_error(self, data={}):
|
||||
if not isinstance(data, dict):
|
||||
data = json.loads(data)
|
||||
|
||||
seperator = "--" * 50
|
||||
err_tb = traceback.format_exc()
|
||||
err_msg = str(sys.exc_info()[1])
|
||||
data = json.dumps(data, indent=4)
|
||||
|
||||
message = "\n".join([
|
||||
"Error", err_msg, seperator,
|
||||
"Data:", data, seperator,
|
||||
"Exception:", err_tb
|
||||
])
|
||||
frappe.log_error(title=_('E Invoice Request Failed'), message=message)
|
||||
|
||||
def raise_error(self, raise_exception=False, errors=[]):
|
||||
title = _('E Invoice Request Failed')
|
||||
if errors:
|
||||
@@ -758,6 +869,8 @@ class GSPConnector():
|
||||
|
||||
self.invoice.irn = res.get('Irn')
|
||||
self.invoice.ewaybill = res.get('EwbNo')
|
||||
self.invoice.ack_no = res.get('AckNo')
|
||||
self.invoice.ack_date = res.get('AckDt')
|
||||
self.invoice.signed_einvoice = dec_signed_invoice
|
||||
self.invoice.signed_qr_code = res.get('SignedQRCode')
|
||||
|
||||
@@ -795,6 +908,11 @@ class GSPConnector():
|
||||
self.invoice.flags.ignore_validate = True
|
||||
self.invoice.save()
|
||||
|
||||
def sanitize_for_json(string):
|
||||
"""Escape JSON specific characters from a string."""
|
||||
# json.dumps adds double-quotes to the string. Indexing to remove them.
|
||||
return json.dumps(string)[1:-1]
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_einvoice(doctype, docname):
|
||||
invoice = frappe.get_doc(doctype, docname)
|
||||
@@ -817,5 +935,9 @@ def generate_eway_bill(doctype, docname, **kwargs):
|
||||
|
||||
@frappe.whitelist()
|
||||
def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
|
||||
gsp_connector = GSPConnector(doctype, docname)
|
||||
gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
|
||||
# TODO: uncomment when eway_bill api from Adequare is enabled
|
||||
# gsp_connector = GSPConnector(doctype, docname)
|
||||
# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
|
||||
|
||||
# update cancelled status only, to be able to cancel irn next
|
||||
frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)
|
||||
|
||||
578
erpnext/regional/india/e_invoice_utils.py
Normal file
578
erpnext/regional/india/e_invoice_utils.py
Normal file
@@ -0,0 +1,578 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import re
|
||||
import jwt
|
||||
import json
|
||||
import base64
|
||||
import frappe
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import PKCS1_v1_5, AES
|
||||
from Crypto.Util.Padding import pad, unpad
|
||||
from frappe.model.document import Document
|
||||
from frappe import _, get_module_path, scrub
|
||||
from erpnext.regional.india.utils import get_gst_accounts
|
||||
from frappe.integrations.utils import make_post_request, make_get_request
|
||||
from frappe.utils.data import get_datetime, cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime
|
||||
|
||||
def validate_einvoice_fields(doc):
|
||||
e_invoice_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable")
|
||||
if not doc.doctype in ['Sales Invoice', 'Purchase Invoice'] or not e_invoice_enabled: return
|
||||
|
||||
if doc.docstatus == 0 and doc._action == 'save' and doc.irn:
|
||||
frappe.throw(_("You cannot edit the invoice after generating IRN"), title=_("Edit Not Allowed"))
|
||||
elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
|
||||
frappe.throw(_("You must generate IRN before submitting the document."), title=_("Missing IRN"))
|
||||
elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
|
||||
frappe.throw(_("You must cancel IRN before cancelling the document."), title=_("Cancel Not Allowed"))
|
||||
|
||||
def get_einv_credentials(for_token=False):
|
||||
creds = frappe.get_doc("E Invoice Settings")
|
||||
if not for_token and (not creds.token_expiry or time_diff_in_seconds(now_datetime(), creds.token_expiry) > 5.0):
|
||||
fetch_token()
|
||||
creds.load_from_db()
|
||||
|
||||
return creds
|
||||
|
||||
def rsa_encrypt(msg, key):
|
||||
if not (isinstance(msg, bytes) or isinstance(msg, bytearray)):
|
||||
msg = str.encode(msg)
|
||||
|
||||
rsa_pub_key = RSA.import_key(key)
|
||||
cipher = PKCS1_v1_5.new(rsa_pub_key)
|
||||
enc_msg = cipher.encrypt(msg)
|
||||
b64_enc_msg = base64.b64encode(enc_msg)
|
||||
return b64_enc_msg.decode()
|
||||
|
||||
def aes_decrypt(enc_msg, key):
|
||||
encode_as_b64 = True
|
||||
if not (isinstance(key, bytes) or isinstance(key, bytearray)):
|
||||
key = base64.b64decode(key)
|
||||
encode_as_b64 = False
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
b64_enc_msg = base64.b64decode(enc_msg)
|
||||
msg_bytes = cipher.decrypt(b64_enc_msg)
|
||||
msg_bytes = unpad(msg_bytes, AES.block_size) # due to ECB/PKCS5Padding
|
||||
if encode_as_b64:
|
||||
msg_bytes = base64.b64encode(msg_bytes)
|
||||
return msg_bytes.decode()
|
||||
|
||||
def aes_encrypt(msg, key):
|
||||
if not (isinstance(key, bytes) or isinstance(key, bytearray)):
|
||||
key = base64.b64decode(key)
|
||||
|
||||
cipher = AES.new(key, AES.MODE_ECB)
|
||||
bytes_msg = str.encode(msg)
|
||||
padded_bytes_msg = pad(bytes_msg, AES.block_size)
|
||||
enc_msg = cipher.encrypt(padded_bytes_msg)
|
||||
b64_enc_msg = base64.b64encode(enc_msg)
|
||||
return b64_enc_msg.decode()
|
||||
|
||||
def jwt_decrypt(token):
|
||||
return jwt.decode(token, verify=False)
|
||||
|
||||
def get_header(creds):
|
||||
headers = { 'content-type': 'application/json' }
|
||||
headers.update(dict(client_id=creds.client_id, client_secret=creds.client_secret, user_name=creds.username))
|
||||
headers.update(dict(Gstin=creds.gstin, AuthToken=creds.auth_token))
|
||||
return headers
|
||||
|
||||
@frappe.whitelist()
|
||||
def fetch_token():
|
||||
einv_creds = get_einv_credentials(for_token=True)
|
||||
|
||||
endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/auth'
|
||||
headers = { 'content-type': 'application/json' }
|
||||
headers.update(dict(client_id=einv_creds.client_id, client_secret=einv_creds.client_secret))
|
||||
payload = dict(UserName=einv_creds.username, ForceRefreshAccessToken=bool(einv_creds.auto_refresh_token))
|
||||
|
||||
appkey = bytearray(os.urandom(32))
|
||||
enc_appkey = rsa_encrypt(appkey, einv_creds.public_key)
|
||||
|
||||
password = einv_creds.get_password(fieldname='password')
|
||||
enc_password = rsa_encrypt(password, einv_creds.public_key)
|
||||
|
||||
payload.update(dict(Password=enc_password, AppKey=enc_appkey))
|
||||
|
||||
res = make_post_request(endpoint, headers=headers, data=json.dumps({ 'data': payload }))
|
||||
handle_err_response(res)
|
||||
|
||||
auth_token, token_expiry, sek = extract_token_and_sek(res, appkey)
|
||||
|
||||
einv_creds.auth_token = auth_token
|
||||
einv_creds.token_expiry = get_datetime(token_expiry)
|
||||
einv_creds.sek = sek
|
||||
einv_creds.save()
|
||||
|
||||
def extract_token_and_sek(response, appkey):
|
||||
data = response.get('Data')
|
||||
auth_token = data.get('AuthToken')
|
||||
token_expiry = data.get('TokenExpiry')
|
||||
enc_sek = data.get('Sek')
|
||||
sek = aes_decrypt(enc_sek, appkey)
|
||||
return auth_token, token_expiry, sek
|
||||
|
||||
def attach_signed_json(invoice, data):
|
||||
f = frappe.get_doc({
|
||||
"doctype": "File",
|
||||
"file_name": invoice.name + "e_invoice.json",
|
||||
"attached_to_doctype": invoice.doctype,
|
||||
"attached_to_name": invoice.name,
|
||||
"content": json.dumps(data),
|
||||
"is_private": True
|
||||
}).insert()
|
||||
|
||||
def get_gstin_details(gstin):
|
||||
einv_creds = get_einv_credentials()
|
||||
|
||||
endpoint = 'https://einv-apisandbox.nic.in/eivital/v1.03/Master/gstin/{gstin}'.format(gstin=gstin)
|
||||
headers = get_header(einv_creds)
|
||||
|
||||
res = make_get_request(endpoint, headers=headers)
|
||||
handle_err_response(res)
|
||||
|
||||
enc_json = res.get('Data')
|
||||
json_str = aes_decrypt(enc_json, einv_creds.sek)
|
||||
data = json.loads(json_str)
|
||||
|
||||
return data
|
||||
|
||||
@frappe.whitelist()
|
||||
def generate_irn(doctype, name):
|
||||
endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice'
|
||||
einv_creds = get_einv_credentials()
|
||||
headers = get_header(einv_creds)
|
||||
|
||||
invoice = frappe.get_doc(doctype, name)
|
||||
e_invoice = make_e_invoice(invoice)
|
||||
|
||||
enc_e_invoice_json = aes_encrypt(e_invoice, einv_creds.sek)
|
||||
payload = dict(Data=enc_e_invoice_json)
|
||||
|
||||
res = make_post_request(endpoint, headers=headers, data=json.dumps(payload))
|
||||
res = handle_err_response(res)
|
||||
|
||||
enc_json = res.get('Data')
|
||||
json_str = aes_decrypt(enc_json, einv_creds.sek)
|
||||
|
||||
data = json.loads(json_str)
|
||||
handle_irn_response(data)
|
||||
|
||||
attach_signed_json(invoice, data['DecryptedSignedInvoice'])
|
||||
|
||||
return data
|
||||
|
||||
def get_irn_details(irn):
|
||||
einv_creds = get_einv_credentials()
|
||||
|
||||
endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/irn/{irn}'.format(irn=irn)
|
||||
headers = get_header(einv_creds)
|
||||
|
||||
res = make_get_request(endpoint, headers=headers)
|
||||
handle_err_response(res)
|
||||
|
||||
# enc_json = res.get('Data')
|
||||
# json_str = aes_decrypt(enc_json, einv_creds.sek)
|
||||
|
||||
# data = json.loads(json_str)
|
||||
# handle_irn_response(data)
|
||||
|
||||
return res
|
||||
|
||||
@frappe.whitelist()
|
||||
def cancel_irn(irn, reason, remark=''):
|
||||
einv_creds = get_einv_credentials()
|
||||
|
||||
endpoint = 'https://einv-apisandbox.nic.in/eicore/v1.03/Invoice/Cancel'
|
||||
headers = get_header(einv_creds)
|
||||
|
||||
cancel_e_inv = json.dumps(dict(Irn=irn, CnlRsn=reason, CnlRem=remark))
|
||||
enc_json = aes_encrypt(cancel_e_inv, einv_creds.sek)
|
||||
payload = dict(Data=enc_json)
|
||||
|
||||
res = make_post_request(endpoint, headers=headers, data=json.dumps(payload))
|
||||
handle_err_response(res)
|
||||
|
||||
return res
|
||||
|
||||
@frappe.whitelist()
|
||||
def cancel_eway_bill(eway_bill, reason, remark=''):
|
||||
einv_creds = get_einv_credentials()
|
||||
endpoint = 'https://einv-apisandbox.nic.in/ewaybillapi/v1.03/ewayapi'
|
||||
headers = get_header(einv_creds)
|
||||
|
||||
cancel_eway_bill_json = json.dumps(dict(ewbNo=eway_bill, cancelRsnCode=reason, cancelRmrk=remark))
|
||||
enc_json = aes_encrypt(cancel_eway_bill_json, einv_creds.sek)
|
||||
payload = dict(action="CANEWB", Data=enc_json)
|
||||
|
||||
res = make_post_request(endpoint, headers=headers, data=json.dumps(payload))
|
||||
handle_err_response(res)
|
||||
|
||||
return res
|
||||
|
||||
def handle_irn_response(data):
|
||||
enc_signed_invoice = data['SignedInvoice']
|
||||
enc_signed_qr_code = data['SignedQRCode']
|
||||
signed_invoice = jwt_decrypt(enc_signed_invoice)['data']
|
||||
signed_qr_code = jwt_decrypt(enc_signed_qr_code)['data']
|
||||
data['DecryptedSignedInvoice'] = json.loads(signed_invoice)
|
||||
data['DecryptedSignedQRCode'] = json.loads(signed_qr_code)
|
||||
|
||||
def handle_err_response(response):
|
||||
if response.get('Status') == 0:
|
||||
err_details = response.get('ErrorDetails')
|
||||
print(response)
|
||||
err_msg = ""
|
||||
for d in err_details:
|
||||
err_code = d.get('ErrorCode')
|
||||
if err_code == '2150':
|
||||
irn = [d['Desc']['Irn'] for d in response.get('InfoDtls') if d['InfCd'] == 'DUPIRN']
|
||||
response = get_irn_details(irn[0])
|
||||
return response
|
||||
|
||||
err_msg += d.get('ErrorMessage')
|
||||
err_msg += "<br>"
|
||||
frappe.throw(_(err_msg), title=_('API Request Failed'))
|
||||
|
||||
return response
|
||||
|
||||
def read_json(name):
|
||||
file_path = os.path.join(os.path.dirname(__file__), "{name}.json".format(name=name))
|
||||
with open(file_path, 'r') as f:
|
||||
return cstr(f.read())
|
||||
|
||||
def get_trans_details(invoice):
|
||||
supply_type = ''
|
||||
if invoice.gst_category == 'Registered Regular': supply_type = 'B2B'
|
||||
elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP'
|
||||
elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
|
||||
elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
|
||||
|
||||
if not supply_type:
|
||||
return _('Invalid invoice transaction category.')
|
||||
|
||||
return frappe._dict(dict(
|
||||
tax_scheme='GST', supply_type=supply_type, reverse_charge=invoice.reverse_charge
|
||||
))
|
||||
|
||||
def get_doc_details(invoice):
|
||||
if invoice.doctype == 'Purchase Invoice' and invoice.is_return:
|
||||
invoice_type = 'DBN'
|
||||
else:
|
||||
invoice_type = 'CRN' if invoice.is_return else 'INV'
|
||||
|
||||
invoice_name = invoice.name
|
||||
invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy')
|
||||
|
||||
return frappe._dict(dict(
|
||||
invoice_type=invoice_type, invoice_name=invoice_name, invoice_date=invoice_date
|
||||
))
|
||||
|
||||
def get_party_gstin_details(party_address):
|
||||
address = frappe.get_all("Address", filters={"name": party_address}, fields=["*"])[0]
|
||||
|
||||
gstin = address.get('gstin')
|
||||
gstin_details = get_gstin_details(gstin)
|
||||
# legal_name = address.get('address_title')
|
||||
legal_name = gstin_details.get('LegalName')
|
||||
trade_name = gstin_details.get('TradeName')
|
||||
# location = address.get('city')
|
||||
location = gstin_details.get('Loc')
|
||||
state_code = address.get('gst_state_number')
|
||||
pincode = cint(address.get('pincode'))
|
||||
address_line1 = address.get('address_line1')
|
||||
address_line2 = address.get('address_line2')
|
||||
email_id = address.get('email_id')
|
||||
phone = address.get('phone')
|
||||
if state_code == 97:
|
||||
pincode = 999999
|
||||
|
||||
return frappe._dict(dict(
|
||||
gstin=gstin, legal_name=legal_name, location=location,
|
||||
pincode=pincode, state_code=state_code, address_line1=address_line1,
|
||||
address_line2=address_line2, email=email_id, phone=phone
|
||||
))
|
||||
|
||||
def get_overseas_address_details(party_address):
|
||||
address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
|
||||
"Address", party_address, ["address_title", "address_line1", "address_line2", "city", "phone", "email_id"]
|
||||
)
|
||||
|
||||
return frappe._dict(dict(
|
||||
gstin='URP', legal_name=address_title, address_line1=address_line1,
|
||||
address_line2=address_line2, email=email_id, phone=phone,
|
||||
pincode=999999, state_code=96, place_of_supply=96, location=city
|
||||
))
|
||||
|
||||
def get_item_list(invoice):
|
||||
item_list = []
|
||||
gst_accounts = get_gst_accounts(invoice.company)
|
||||
gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
|
||||
|
||||
for d in invoice.items:
|
||||
item_schema = read_json("einv_item_template")
|
||||
item = frappe._dict(dict())
|
||||
item.update(d.as_dict())
|
||||
item.sr_no = d.idx
|
||||
item.description = d.item_name
|
||||
item.is_service_item = "N" if frappe.db.get_value("Item", d.item_code, "is_stock_item") else "Y"
|
||||
item.batch_expiry_date = frappe.db.get_value("Batch", d.batch_no, "expiry_date") if d.batch_no else None
|
||||
item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
|
||||
item.unit_rate = item.base_price_list_rate if item.discount_amount else item.base_rate
|
||||
item.total_amount = item.unit_rate * item.qty
|
||||
item.discount_amount = item.discount_amount * item.qty
|
||||
item.tax_rate = 0
|
||||
item.igst_amount = 0
|
||||
item.cgst_amount = 0
|
||||
item.sgst_amount = 0
|
||||
item.cess_rate = 0
|
||||
item.cess_amount = 0
|
||||
for t in invoice.taxes:
|
||||
if t.account_head in gst_accounts_list:
|
||||
item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
|
||||
if t.account_head in gst_accounts.cess_account:
|
||||
item.cess_rate += item_tax_detail[0]
|
||||
item.cess_amount += item_tax_detail[1]
|
||||
elif t.account_head in gst_accounts.igst_account:
|
||||
item.tax_rate += item_tax_detail[0]
|
||||
item.igst_amount += item_tax_detail[1]
|
||||
elif t.account_head in gst_accounts.sgst_account:
|
||||
item.tax_rate += item_tax_detail[0]
|
||||
item.sgst_amount += item_tax_detail[1]
|
||||
elif t.account_head in gst_accounts.cgst_account:
|
||||
item.tax_rate += item_tax_detail[0]
|
||||
item.cgst_amount += item_tax_detail[1]
|
||||
|
||||
item.total_value = item.base_amount + item.igst_amount + item.sgst_amount + item.cgst_amount + item.cess_amount
|
||||
e_inv_item = item_schema.format(item=item)
|
||||
item_list.append(e_inv_item)
|
||||
|
||||
return ", ".join(item_list)
|
||||
|
||||
def get_value_details(invoice):
|
||||
gst_accounts = get_gst_accounts(invoice.company)
|
||||
gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
|
||||
|
||||
value_details = frappe._dict(dict())
|
||||
value_details.base_net_total = invoice.base_net_total
|
||||
value_details.invoice_discount_amt = invoice.discount_amount
|
||||
value_details.round_off = invoice.base_rounding_adjustment
|
||||
value_details.base_grand_total = invoice.base_rounded_total
|
||||
value_details.grand_total = invoice.rounded_total
|
||||
value_details.total_cgst_amt = 0
|
||||
value_details.total_sgst_amt = 0
|
||||
value_details.total_igst_amt = 0
|
||||
value_details.total_cess_amt = 0
|
||||
for t in invoice.taxes:
|
||||
if t.account_head in gst_accounts_list:
|
||||
if t.account_head in gst_accounts.cess_account:
|
||||
value_details.total_cess_amt += t.base_tax_amount
|
||||
elif t.account_head in gst_accounts.igst_account:
|
||||
value_details.total_igst_amt += t.base_tax_amount
|
||||
elif t.account_head in gst_accounts.sgst_account:
|
||||
value_details.total_sgst_amt += t.base_tax_amount
|
||||
elif t.account_head in gst_accounts.cgst_account:
|
||||
value_details.total_cgst_amt += t.base_tax_amount
|
||||
|
||||
return value_details
|
||||
|
||||
def get_payment_details(invoice):
|
||||
payee_name = invoice.company
|
||||
mode_of_payment = ", ".join([d.mode_of_payment for d in invoice.payments])
|
||||
paid_amount = invoice.base_paid_amount
|
||||
outstanding_amount = invoice.outstanding_amount
|
||||
|
||||
return frappe._dict(dict(
|
||||
payee_name=payee_name, mode_of_payment=mode_of_payment,
|
||||
paid_amount=paid_amount, outstanding_amount=outstanding_amount
|
||||
))
|
||||
|
||||
def get_return_doc_reference(invoice):
|
||||
invoice_date = frappe.db.get_value("Sales Invoice", invoice.return_against, "posting_date")
|
||||
return frappe._dict(dict(
|
||||
invoice_name=invoice.return_against, invoice_date=invoice_date
|
||||
))
|
||||
|
||||
def get_eway_bill_details(invoice):
|
||||
if not invoice.distance:
|
||||
frappe.throw(_("Distance is mandatory for E-Way Bill generation"), title=_("Missing Values"))
|
||||
|
||||
mode_of_transport = { 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
|
||||
vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
|
||||
|
||||
return frappe._dict(dict(
|
||||
gstin=invoice.gst_transporter_id,
|
||||
name=invoice.transporter_name,
|
||||
mode_of_transport=mode_of_transport[invoice.mode_of_transport],
|
||||
distance=invoice.distance,
|
||||
document_name=invoice.lr_no,
|
||||
document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'),
|
||||
vehicle_no=invoice.vehicle_no,
|
||||
vehicle_type=vehicle_type[invoice.gst_vehicle_type]
|
||||
))
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_e_invoice(doctype, name):
|
||||
invoice = frappe.get_doc(doctype, name)
|
||||
schema = read_json("einv_template")
|
||||
validations = json.loads(read_json("einv_validation"))
|
||||
|
||||
trans_details = get_trans_details(invoice)
|
||||
doc_details = get_doc_details(invoice)
|
||||
seller_details = get_party_gstin_details(invoice.company_address)
|
||||
|
||||
if invoice.gst_category == 'Overseas':
|
||||
buyer_details = get_overseas_address_details(invoice.customer_address)
|
||||
else:
|
||||
buyer_details = get_party_gstin_details(invoice.customer_address)
|
||||
place_of_supply = invoice.place_of_supply.split('-')[0]
|
||||
buyer_details.update(dict(place_of_supply=place_of_supply))
|
||||
|
||||
item_list = get_item_list(invoice)
|
||||
value_details = get_value_details(invoice)
|
||||
|
||||
shipping_details = frappe._dict({})
|
||||
if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
|
||||
shipping_details = get_party_gstin_details(invoice.shipping_address_name)
|
||||
|
||||
payment_details = frappe._dict({})
|
||||
if invoice.is_pos and invoice.base_paid_amount:
|
||||
payment_details = get_payment_details(invoice)
|
||||
|
||||
prev_doc_details = frappe._dict({})
|
||||
if invoice.is_return and invoice.return_against:
|
||||
prev_doc_details = get_return_doc_reference(invoice)
|
||||
|
||||
dispatch_details = frappe._dict({})
|
||||
period_details = frappe._dict({})
|
||||
export_details = frappe._dict({})
|
||||
eway_bill_details = frappe._dict({})
|
||||
if invoice.transporter:
|
||||
eway_bill_details = get_eway_bill_details(invoice)
|
||||
|
||||
e_invoice = schema.format(
|
||||
trans_details=trans_details, doc_details=doc_details, dispatch_details=dispatch_details,
|
||||
seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details,
|
||||
item_list=item_list, value_details=value_details, payment_details=payment_details,
|
||||
period_details=period_details, prev_doc_details=prev_doc_details,
|
||||
export_details=export_details, eway_bill_details=eway_bill_details
|
||||
)
|
||||
e_invoice = json.loads(e_invoice)
|
||||
|
||||
error_msgs = validate_einvoice(validations, e_invoice, [])
|
||||
if error_msgs:
|
||||
if len(error_msgs) > 1:
|
||||
li = ["<li>"+ d +"</li>" for d in error_msgs]
|
||||
frappe.throw(_("""<ul style="padding-left: 20px">{}</ul>""").format("".join(li)), title=_("E Invoice Validation Failed"))
|
||||
else:
|
||||
frappe.throw(_("{}").format(error_msgs[0]), title=_("E Invoice Validation Failed"))
|
||||
|
||||
return {'einvoice': json.dumps([e_invoice])}
|
||||
|
||||
def validate_einvoice(validations, e_invoice, error_msgs=[]):
|
||||
type_map = {
|
||||
"string": cstr,
|
||||
"number": cint,
|
||||
"object": dict,
|
||||
"array": list
|
||||
}
|
||||
|
||||
for field, value in validations.items():
|
||||
if isinstance(value, list): value = value[0]
|
||||
|
||||
invoice_value = e_invoice.get(field)
|
||||
if not invoice_value:
|
||||
continue
|
||||
|
||||
should_be_of_type = type_map[value.get('type').lower()]
|
||||
if should_be_of_type == dict:
|
||||
properties = value.get('properties')
|
||||
|
||||
if isinstance(invoice_value, list):
|
||||
for d in invoice_value:
|
||||
validate_einvoice(properties, d, error_msgs)
|
||||
else:
|
||||
validate_einvoice(properties, invoice_value, error_msgs)
|
||||
# remove keys with empty dicts
|
||||
if not invoice_value:
|
||||
e_invoice.pop(field, None)
|
||||
continue
|
||||
|
||||
if invoice_value == "None":
|
||||
# remove keys with empty values
|
||||
e_invoice.pop(field, None)
|
||||
continue
|
||||
|
||||
# convert to int or str
|
||||
e_invoice[field] = should_be_of_type(invoice_value)
|
||||
|
||||
should_be_of_len = value.get('maxLength')
|
||||
should_be_greater_than = flt(value.get('minimum'))
|
||||
should_be_less_than = flt(value.get('maximum'))
|
||||
pattern_str = value.get('pattern')
|
||||
pattern = re.compile(pattern_str or "")
|
||||
|
||||
field_label = value.get("label") or field
|
||||
|
||||
if value.get('type').lower() == 'string' and len(invoice_value) > should_be_of_len:
|
||||
error_msgs.append("{} should not exceed {} characters".format(field_label, should_be_of_len))
|
||||
if value.get('type').lower() == 'number' and not (flt(invoice_value) <= should_be_less_than):
|
||||
error_msgs.append("{} should be less than {}".format(field_label, should_be_less_than))
|
||||
if pattern_str and not pattern.match(invoice_value):
|
||||
error_msgs.append(value.get('validationMsg'))
|
||||
|
||||
return error_msgs
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_einvoice():
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
einvoice = data['einvoice']
|
||||
name = data['name']
|
||||
|
||||
frappe.response['filename'] = "E-Invoice-" + name + ".json"
|
||||
frappe.response['filecontent'] = einvoice
|
||||
frappe.response['content_type'] = 'application/json'
|
||||
frappe.response['type'] = 'download'
|
||||
|
||||
@frappe.whitelist()
|
||||
def upload_einvoice():
|
||||
signed_einvoice = json.loads(frappe.local.uploaded_file)
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
doctype = data['doctype']
|
||||
name = data['docname']
|
||||
|
||||
enc_signed_invoice = signed_einvoice.get('SignedInvoice')
|
||||
decrypted_signed_invoice = jwt_decrypt(enc_signed_invoice)['data']
|
||||
|
||||
frappe.db.set_value(doctype, name, 'irn', signed_einvoice.get('Irn'))
|
||||
frappe.db.set_value(doctype, name, 'ewaybill', signed_einvoice.get('EwbNo'))
|
||||
frappe.db.set_value(doctype, name, 'signed_qr_code', signed_einvoice.get('SignedQRCode'))
|
||||
frappe.db.set_value(doctype, name, 'signed_einvoice', decrypted_signed_invoice)
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_cancel_einvoice():
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
name = data['name']
|
||||
irn = data['irn']
|
||||
reason = data['reason']
|
||||
remark = data['remark']
|
||||
|
||||
cancel_einvoice = json.dumps([dict(Irn=irn, CnlRsn=reason, CnlRem=remark)])
|
||||
|
||||
frappe.response['filename'] = "Cancel E-Invoice " + name + ".json"
|
||||
frappe.response['filecontent'] = cancel_einvoice
|
||||
frappe.response['content_type'] = 'application/json'
|
||||
frappe.response['type'] = 'download'
|
||||
|
||||
@frappe.whitelist()
|
||||
def upload_cancel_ack():
|
||||
cancel_ack = json.loads(frappe.local.uploaded_file)
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
doctype = data['doctype']
|
||||
name = data['docname']
|
||||
|
||||
frappe.db.set_value(doctype, name, "irn_cancelled", 1)
|
||||
26
erpnext/regional/india/einv_item_template.json
Normal file
26
erpnext/regional/india/einv_item_template.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{{
|
||||
"SlNo": "{item.sr_no}",
|
||||
"PrdDesc": "{item.description}",
|
||||
"IsServc": "{item.is_service_item}",
|
||||
"HsnCd": "{item.gst_hsn_code}",
|
||||
"Barcde": "{item.barcode}",
|
||||
"Unit": "{item.uom}",
|
||||
"Qty": "{item.qty}",
|
||||
"FreeQty": "{item.free_qty}",
|
||||
"UnitPrice": "{item.unit_rate}",
|
||||
"TotAmt": "{item.total_amount}",
|
||||
"Discount": "{item.discount_amount}",
|
||||
"AssAmt": "{item.base_amount}",
|
||||
"PrdSlNo": "{item.serial_no}",
|
||||
"GstRt": "{item.tax_rate}",
|
||||
"IgstAmt": "{item.igst_amount}",
|
||||
"CgstAmt": "{item.cgst_amount}",
|
||||
"SgstAmt": "{item.sgst_amount}",
|
||||
"CesRt": "{item.cess_rate}",
|
||||
"CesAmt": "{item.cess_amount}",
|
||||
"TotItemVal": "{item.total_value}",
|
||||
"BchDtls": {{
|
||||
"Nm": "{item.batch_no}",
|
||||
"ExpDt": "{item.batch_expiry_date}"
|
||||
}}
|
||||
}}
|
||||
109
erpnext/regional/india/einv_template.json
Normal file
109
erpnext/regional/india/einv_template.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{{
|
||||
"Version": "1.01",
|
||||
"TranDtls": {{
|
||||
"TaxSch": "{trans_details.tax_scheme}",
|
||||
"SupTyp": "{trans_details.supply_type}",
|
||||
"RegRev": "{trans_details.reverse_charge}",
|
||||
"EcmGstin": "{trans_details.ecom_gstin}",
|
||||
"IgstOnIntra": "{trans_details.igst_on_intra}"
|
||||
}},
|
||||
"DocDtls": {{
|
||||
"Typ": "{doc_details.invoice_type}",
|
||||
"No": "{doc_details.invoice_name}",
|
||||
"Dt": "{doc_details.invoice_date}"
|
||||
}},
|
||||
"SellerDtls": {{
|
||||
"Gstin": "{seller_details.gstin}",
|
||||
"LglNm": "{seller_details.legal_name}",
|
||||
"TrdNm": "{seller_details.trade_name}",
|
||||
"Loc": "{seller_details.location}",
|
||||
"Pin": "{seller_details.pincode}",
|
||||
"Stcd": "{seller_details.state_code}",
|
||||
"Addr1": "{seller_details.address_line1}",
|
||||
"Addr2": "{seller_details.address_line2}",
|
||||
"Ph": "{seller_details.phone}",
|
||||
"Em": "{seller_details.email}"
|
||||
}},
|
||||
"BuyerDtls": {{
|
||||
"Gstin": "{buyer_details.gstin}",
|
||||
"LglNm": "{buyer_details.legal_name}",
|
||||
"TrdNm": "{buyer_details.trade_name}",
|
||||
"Addr1": "{buyer_details.address_line1}",
|
||||
"Addr2": "{buyer_details.address_line2}",
|
||||
"Loc": "{buyer_details.location}",
|
||||
"Pin": "{buyer_details.pincode}",
|
||||
"Stcd": "{buyer_details.state_code}",
|
||||
"Ph": "{buyer_details.phone}",
|
||||
"Em": "{buyer_details.email}",
|
||||
"Pos": "{buyer_details.place_of_supply}"
|
||||
}},
|
||||
"DispDtls": {{
|
||||
"Nm": "{dispatch_details.company_name}",
|
||||
"Addr1": "{dispatch_details.address_line1}",
|
||||
"Addr2": "{dispatch_details.address_line2}",
|
||||
"Loc": "{dispatch_details.location}",
|
||||
"Pin": "{dispatch_details.pincode}",
|
||||
"Stcd": "{dispatch_details.state_code}"
|
||||
}},
|
||||
"ShipDtls": {{
|
||||
"Gstin": "{shipping_details.gstin}",
|
||||
"LglNm": "{shipping_details.legal_name}",
|
||||
"TrdNm": "{shipping_details.trader_name}",
|
||||
"Addr1": "{shipping_details.address_line1}",
|
||||
"Addr2": "{shipping_details.address_line2}",
|
||||
"Loc": "{shipping_details.location}",
|
||||
"Pin": "{shipping_details.pincode}",
|
||||
"Stcd": "{shipping_details.state_code}"
|
||||
}},
|
||||
"ItemList": [
|
||||
{item_list}
|
||||
],
|
||||
"ValDtls": {{
|
||||
"AssVal": "{value_details.base_net_total}",
|
||||
"CgstVal": "{value_details.total_cgst_amt}",
|
||||
"SgstVal": "{value_details.total_sgst_amt}",
|
||||
"IgstVal": "{value_details.total_igst_amt}",
|
||||
"CesVal": "{value_details.total_cess_amt}",
|
||||
"Discount": "{value_details.invoice_discount_amt}",
|
||||
"RndOffAmt": "{value_details.round_off}",
|
||||
"TotInvVal": "{value_details.base_grand_total}",
|
||||
"TotInvValFc": "{value_details.grand_total}"
|
||||
}},
|
||||
"PayDtls": {{
|
||||
"Nm": "{payment_details.payee_name}",
|
||||
"AccDet": "{payment_details.account_no}",
|
||||
"Mode": "{payment_details.mode_of_payment}",
|
||||
"FinInsBr": "{payment_details.ifsc_code}",
|
||||
"PayTerm": "{payment_details.terms}",
|
||||
"PaidAmt": "{payment_details.paid_amount}",
|
||||
"PaymtDue": "{payment_details.outstanding_amount}"
|
||||
}},
|
||||
"RefDtls": {{
|
||||
"DocPerdDtls": {{
|
||||
"InvStDt": "{period_details.start_date}",
|
||||
"InvEndDt": "{period_details.end_date}"
|
||||
}},
|
||||
"PrecDocDtls": {{
|
||||
"InvNo": "{prev_doc_details.invoice_name}",
|
||||
"InvDt": "{prev_doc_details.invoice_date}"
|
||||
}}
|
||||
}},
|
||||
"ExpDtls": {{
|
||||
"ShipBNo": "{export_details.bill_no}",
|
||||
"ShipBDt": "{export_details.bill_date}",
|
||||
"Port": "{export_details.port}",
|
||||
"ForCur": "{export_details.foreign_curr_code}",
|
||||
"CntCode": "{export_details.country_code}",
|
||||
"ExpDuty": "{export_details.export_duty}"
|
||||
}},
|
||||
"EwbDtls": {{
|
||||
"TransId": "{eway_bill_details.gstin}",
|
||||
"TransName": "{eway_bill_details.name}",
|
||||
"TransMode": "{eway_bill_details.mode_of_transport}",
|
||||
"Distance": "{eway_bill_details.distance}",
|
||||
"TransDocNo": "{eway_bill_details.document_name}",
|
||||
"TransDocDt": "{eway_bill_details.document_date}",
|
||||
"VehNo": "{eway_bill_details.vehicle_no}",
|
||||
"VehType": "{eway_bill_details.vehicle_type}"
|
||||
}}
|
||||
}}
|
||||
836
erpnext/regional/india/einv_validation.json
Normal file
836
erpnext/regional/india/einv_validation.json
Normal file
@@ -0,0 +1,836 @@
|
||||
{
|
||||
"Version": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 6
|
||||
},
|
||||
"Irn": {
|
||||
"type": "string",
|
||||
"minLength": 64,
|
||||
"maxLength": 64
|
||||
},
|
||||
"TranDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"TaxSch": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 10,
|
||||
"enum": ["GST"]
|
||||
},
|
||||
"SupTyp": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 10,
|
||||
"enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"]
|
||||
},
|
||||
"RegRev": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"enum": ["Y", "N"]
|
||||
},
|
||||
"EcmGstin": {
|
||||
"type": "string",
|
||||
"minLength": 15,
|
||||
"maxLength": 15,
|
||||
"pattern": "([0-9]{2}[0-9A-Z]{13})"
|
||||
},
|
||||
"IgstOnIntra": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"enum": ["Y", "N"]
|
||||
}
|
||||
},
|
||||
"required": ["TaxSch", "SupTyp"]
|
||||
},
|
||||
"DocDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Typ": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 3,
|
||||
"enum": ["INV", "CRN", "DBN"]
|
||||
},
|
||||
"No": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 16,
|
||||
"pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$",
|
||||
"label": "Document Name",
|
||||
"validationMsg": "Document name should not be starting with 0, / and -"
|
||||
},
|
||||
"Dt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||
"validationMsg": "Document Date is invalid"
|
||||
}
|
||||
},
|
||||
"required": ["Typ", "No", "Dt"]
|
||||
},
|
||||
"SellerDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Gstin": {
|
||||
"type": "string",
|
||||
"minLength": 15,
|
||||
"maxLength": 15,
|
||||
"pattern": "([0-9]{2}[0-9A-Z]{13})",
|
||||
"validationMsg": "Seller GSTIN is invalid"
|
||||
},
|
||||
"LglNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"TrdNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr1": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr2": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Loc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 50
|
||||
},
|
||||
"Pin": {
|
||||
"type": "number",
|
||||
"minimum": 100000,
|
||||
"maximum": 999999
|
||||
},
|
||||
"Stcd": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
"Ph": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"maxLength": 12
|
||||
},
|
||||
"Em": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"maxLength": 100
|
||||
}
|
||||
},
|
||||
"required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||
},
|
||||
"BuyerDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Gstin": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 15,
|
||||
"pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
|
||||
"validationMsg": "Buyer GSTIN is invalid"
|
||||
},
|
||||
"LglNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"TrdNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Pos": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
"Addr1": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr2": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Loc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Pin": {
|
||||
"type": "number",
|
||||
"minimum": 100000,
|
||||
"maximum": 999999
|
||||
},
|
||||
"Stcd": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
},
|
||||
"Ph": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"maxLength": 12
|
||||
},
|
||||
"Em": {
|
||||
"type": "string",
|
||||
"minLength": 6,
|
||||
"maxLength": 100
|
||||
}
|
||||
},
|
||||
"required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"]
|
||||
},
|
||||
"DispDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Nm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr1": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr2": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Loc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Pin": {
|
||||
"type": "number",
|
||||
"minimum": 100000,
|
||||
"maximum": 999999
|
||||
},
|
||||
"Stcd": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
}
|
||||
},
|
||||
"required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||
},
|
||||
"ShipDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Gstin": {
|
||||
"type": "string",
|
||||
"maxLength": 15,
|
||||
"minLength": 3,
|
||||
"pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
|
||||
"validationMsg": "Shipping Address GSTIN is invalid"
|
||||
},
|
||||
"LglNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"TrdNm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr1": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Addr2": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Loc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Pin": {
|
||||
"type": "number",
|
||||
"minimum": 100000,
|
||||
"maximum": 999999
|
||||
},
|
||||
"Stcd": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 2
|
||||
}
|
||||
},
|
||||
"required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"]
|
||||
},
|
||||
"ItemList": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"SlNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 6
|
||||
},
|
||||
"PrdDesc": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 300
|
||||
},
|
||||
"IsServc": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"enum": ["Y", "N"]
|
||||
},
|
||||
"HsnCd": {
|
||||
"type": "string",
|
||||
"minLength": 4,
|
||||
"maxLength": 8
|
||||
},
|
||||
"Barcde": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 30
|
||||
},
|
||||
"Qty": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 9999999999.999
|
||||
},
|
||||
"FreeQty": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 9999999999.999
|
||||
},
|
||||
"Unit": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 8
|
||||
},
|
||||
"UnitPrice": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.999
|
||||
},
|
||||
"TotAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"Discount": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"PreTaxVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"AssAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"GstRt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999.999
|
||||
},
|
||||
"IgstAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"CgstAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"SgstAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"CesRt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999.999
|
||||
},
|
||||
"CesAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"CesNonAdvlAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"StateCesRt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999.999
|
||||
},
|
||||
"StateCesAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"StateCesNonAdvlAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"OthChrg": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"TotItemVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
},
|
||||
"OrdLineRef": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 50
|
||||
},
|
||||
"OrgCntry": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 2
|
||||
},
|
||||
"PrdSlNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20
|
||||
},
|
||||
"BchDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Nm": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 20
|
||||
},
|
||||
"ExpDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||
"validationMsg": "Expiry Date is invalid"
|
||||
},
|
||||
"WrDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
|
||||
"validationMsg": "Warranty Date is invalid"
|
||||
}
|
||||
},
|
||||
"required": ["Nm"]
|
||||
},
|
||||
"AttribDtls": {
|
||||
"type": "Array",
|
||||
"Attribute": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Nm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Val": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"SlNo",
|
||||
"IsServc",
|
||||
"HsnCd",
|
||||
"UnitPrice",
|
||||
"TotAmt",
|
||||
"AssAmt",
|
||||
"GstRt",
|
||||
"TotItemVal"
|
||||
]
|
||||
}
|
||||
],
|
||||
"ValDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"AssVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"CgstVal": {
|
||||
"type": "number",
|
||||
"maximum": 99999999999999.99,
|
||||
"minimum": 0
|
||||
},
|
||||
"SgstVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"IgstVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"CesVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"StCesVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"Discount": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"OthChrg": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"RndOffAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99.99
|
||||
},
|
||||
"TotInvVal": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"TotInvValFc": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
}
|
||||
},
|
||||
"required": ["AssVal", "TotInvVal"]
|
||||
},
|
||||
"PayDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Nm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"AccDet": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
"Mode": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 18
|
||||
},
|
||||
"FinInsBr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 11
|
||||
},
|
||||
"PayTerm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"PayInstr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"CrTrn": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"DirDr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"CrDay": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 9999
|
||||
},
|
||||
"PaidAmt": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
},
|
||||
"PaymtDue": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 99999999999999.99
|
||||
}
|
||||
}
|
||||
},
|
||||
"RefDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"InvRm": {
|
||||
"type": "string",
|
||||
"maxLength": 100,
|
||||
"minLength": 3,
|
||||
"pattern": "^[0-9A-Za-z/-]{3,100}$"
|
||||
},
|
||||
"DocPerdDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"InvStDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"InvEndDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
}
|
||||
},
|
||||
"required": ["InvStDt", "InvEndDt"]
|
||||
},
|
||||
"PrecDocDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"InvNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 16,
|
||||
"pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$"
|
||||
},
|
||||
"InvDt": {
|
||||
"type": "string",
|
||||
"maxLength": 10,
|
||||
"minLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"OthRefNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20
|
||||
}
|
||||
},
|
||||
"required": ["InvNo", "InvDt"]
|
||||
},
|
||||
"ContrDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"RecAdvRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"RecAdvDt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"TendRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"ContrRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"ExtRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"ProjRefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,20}$"
|
||||
},
|
||||
"PORefr": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 16,
|
||||
"pattern": "^([0-9A-Za-z/-]){1,16}$"
|
||||
},
|
||||
"PORefDt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["InvStDt", "InvEndDt"]
|
||||
},
|
||||
"AddlDocDtls": {
|
||||
"type": "Array",
|
||||
"AddlDocument": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Url": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"Docs": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 1000
|
||||
},
|
||||
"Info": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 1000
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"ExpDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ShipBNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 20
|
||||
},
|
||||
"ShipBDt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"Port": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 10,
|
||||
"pattern": "^[0-9A-Za-z]{2,10}$"
|
||||
},
|
||||
"RefClm": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1
|
||||
},
|
||||
"ForCur": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 16
|
||||
},
|
||||
"CntCode": {
|
||||
"type": "string",
|
||||
"minLength": 2,
|
||||
"maxLength": 2
|
||||
},
|
||||
"ExpDuty": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 999999999999.99
|
||||
}
|
||||
}
|
||||
},
|
||||
"EwbDtls": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"TransId": {
|
||||
"type": "string",
|
||||
"minLength": 15,
|
||||
"maxLength": 15
|
||||
},
|
||||
"TransName": {
|
||||
"type": "string",
|
||||
"minLength": 3,
|
||||
"maxLength": 100
|
||||
},
|
||||
"TransMode": {
|
||||
"type": "string",
|
||||
"maxLength": 1,
|
||||
"minLength": 1,
|
||||
"enum": ["1", 2, 3, 4]
|
||||
},
|
||||
"Distance": {
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 9999
|
||||
},
|
||||
"TransDocNo": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 15,
|
||||
"pattern": "^([0-9A-Z/-]){1,15}$"
|
||||
},
|
||||
"TransDocDt": {
|
||||
"type": "string",
|
||||
"minLength": 10,
|
||||
"maxLength": 10,
|
||||
"pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]"
|
||||
},
|
||||
"VehNo": {
|
||||
"type": "string",
|
||||
"minLength": 4,
|
||||
"maxLength": 20
|
||||
},
|
||||
"VehType": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 1,
|
||||
"enum": ["O", "R"]
|
||||
}
|
||||
},
|
||||
"required": ["Distance"]
|
||||
},
|
||||
"required": [
|
||||
"Version",
|
||||
"TranDtls",
|
||||
"DocDtls",
|
||||
"SellerDtls",
|
||||
"BuyerDtls",
|
||||
"ItemList",
|
||||
"ValDtls"
|
||||
]
|
||||
}
|
||||
180
erpnext/regional/india/einvoice.js
Normal file
180
erpnext/regional/india/einvoice.js
Normal file
@@ -0,0 +1,180 @@
|
||||
erpnext.setup_einvoice_actions = (doctype) => {
|
||||
frappe.ui.form.on(doctype, {
|
||||
refresh(frm) {
|
||||
const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable");
|
||||
const supply_type = frm.doc.gst_category;
|
||||
if (!einvoicing_enabled
|
||||
|| !['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type)) {
|
||||
return;
|
||||
}
|
||||
// if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) {
|
||||
// frm.add_custom_button(
|
||||
// "Generate IRN",
|
||||
// () => {
|
||||
// frappe.call({
|
||||
// method: 'erpnext.regional.india.e_invoice_utils.generate_irn',
|
||||
// args: { doctype: frm.doc.doctype, name: frm.doc.name },
|
||||
// freeze: true,
|
||||
// callback: (res) => {
|
||||
// console.log(res.message);
|
||||
// frm.set_value('irn', res.message['Irn']);
|
||||
// frm.set_value('signed_einvoice', JSON.stringify(res.message['DecryptedSignedInvoice']));
|
||||
// frm.set_value('signed_qr_code', JSON.stringify(res.message['DecryptedSignedQRCode']));
|
||||
|
||||
// if (res.message['EwbNo']) frm.set_value('ewaybill', res.message['EwbNo']);
|
||||
// frm.save();
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
|
||||
// if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) {
|
||||
// frm.add_custom_button(
|
||||
// "Cancel IRN",
|
||||
// () => {
|
||||
// const d = new frappe.ui.Dialog({
|
||||
// title: __('Cancel IRN'),
|
||||
// fields: [
|
||||
// { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate",
|
||||
// "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] },
|
||||
// { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 }
|
||||
// ],
|
||||
// primary_action: function() {
|
||||
// const data = d.get_values();
|
||||
// frappe.call({
|
||||
// method: 'erpnext.regional.india.e_invoice_utils.cancel_irn',
|
||||
// args: { irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark },
|
||||
// freeze: true,
|
||||
// callback: () => {
|
||||
// frm.set_value('irn_cancelled', 1);
|
||||
// frm.save("Update");
|
||||
// d.hide()
|
||||
// },
|
||||
// error: () => d.hide()
|
||||
// })
|
||||
// },
|
||||
// primary_action_label: __('Submit')
|
||||
// });
|
||||
// d.show();
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
|
||||
// if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled && !frm.doc.eway_bill_cancelled) {
|
||||
// frm.add_custom_button(
|
||||
// "Cancel E-Way Bill",
|
||||
// () => {
|
||||
// const d = new frappe.ui.Dialog({
|
||||
// title: __('Cancel E-Way Bill'),
|
||||
// fields: [
|
||||
// { "label" : "Reason", "fieldname": "reason", "fieldtype": "Select", "reqd": 1, "default": "1-Duplicate",
|
||||
// "options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"] },
|
||||
// { "label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1 }
|
||||
// ],
|
||||
// primary_action: function() {
|
||||
// const data = d.get_values();
|
||||
// frappe.call({
|
||||
// method: 'erpnext.regional.india.e_invoice_utils.cancel_eway_bill',
|
||||
// args: { eway_bill: frm.doc.ewaybill, reason: data.reason.split('-')[0], remark: data.remark },
|
||||
// freeze: true,
|
||||
// callback: () => {
|
||||
// frm.set_value('eway_bill_cancelled', 1);
|
||||
// frm.save("Update");
|
||||
// d.hide()
|
||||
// },
|
||||
// error: () => d.hide()
|
||||
// })
|
||||
// },
|
||||
// primary_action_label: __('Submit')
|
||||
// });
|
||||
// d.show();
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
|
||||
if (frm.doc.docstatus == 0 && !frm.doc.irn && !frm.doc.__unsaved) {
|
||||
frm.add_custom_button(
|
||||
"Download E-Invoice",
|
||||
() => {
|
||||
frappe.call({
|
||||
method: 'erpnext.regional.india.e_invoice_utils.make_e_invoice',
|
||||
args: { doctype: frm.doc.doctype, name: frm.doc.name },
|
||||
freeze: true,
|
||||
callback: (res) => {
|
||||
if (!res.exc) {
|
||||
const args = {
|
||||
cmd: 'erpnext.regional.india.e_invoice_utils.download_einvoice',
|
||||
einvoice: res.message.einvoice,
|
||||
name: frm.doc.name
|
||||
};
|
||||
open_url_post(frappe.request.url, args);
|
||||
}
|
||||
}
|
||||
})
|
||||
}, "E-Invoicing");
|
||||
frm.add_custom_button(
|
||||
"Upload Signed E-Invoice",
|
||||
() => {
|
||||
new frappe.ui.FileUploader({
|
||||
method: 'erpnext.regional.india.e_invoice_utils.upload_einvoice',
|
||||
allow_multiple: 0,
|
||||
doctype: frm.doc.doctype,
|
||||
docname: frm.doc.name,
|
||||
on_success: (attachment, r) => {
|
||||
if (!r.exc) {
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, "E-Invoicing");
|
||||
}
|
||||
if (frm.doc.docstatus == 1 && frm.doc.irn && !frm.doc.irn_cancelled) {
|
||||
frm.add_custom_button(
|
||||
"Cancel IRN",
|
||||
() => {
|
||||
const d = new frappe.ui.Dialog({
|
||||
title: __('Cancel IRN'),
|
||||
fields: [
|
||||
{
|
||||
"label" : "Reason", "fieldname": "reason",
|
||||
"fieldtype": "Select", "reqd": 1, "default": "1-Duplicate",
|
||||
"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
|
||||
},
|
||||
{
|
||||
"label": "Remark", "fieldname": "remark", "fieldtype": "Data", "reqd": 1
|
||||
}
|
||||
],
|
||||
primary_action: function() {
|
||||
const data = d.get_values();
|
||||
const args = {
|
||||
cmd: 'erpnext.regional.india.e_invoice_utils.download_cancel_einvoice',
|
||||
irn: frm.doc.irn, reason: data.reason.split('-')[0], remark: data.remark, name: frm.doc.name
|
||||
};
|
||||
open_url_post(frappe.request.url, args);
|
||||
d.hide();
|
||||
},
|
||||
primary_action_label: __('Download JSON')
|
||||
});
|
||||
d.show();
|
||||
}, "E-Invoicing");
|
||||
|
||||
frm.add_custom_button(
|
||||
"Upload Cancel JSON",
|
||||
() => {
|
||||
new frappe.ui.FileUploader({
|
||||
method: 'erpnext.regional.india.e_invoice_utils.upload_cancel_ack',
|
||||
allow_multiple: 0,
|
||||
doctype: frm.doc.doctype,
|
||||
docname: frm.doc.name,
|
||||
on_success: (attachment, r) => {
|
||||
if (!r.exc) {
|
||||
frm.reload_doc();
|
||||
}
|
||||
}
|
||||
});
|
||||
}, "E-Invoicing");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -105,8 +105,17 @@ def add_print_formats():
|
||||
frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
|
||||
frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
|
||||
|
||||
frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
|
||||
name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """)
|
||||
frappe.db.set_value("Print Format", "GST POS Invoice", "disabled", 0)
|
||||
frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
|
||||
frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
|
||||
|
||||
def make_property_setters():
|
||||
# GST rules do not allow for an invoice no. bigger than 16 characters
|
||||
journal_entry_types = frappe.get_meta("Journal Entry").get_options("voucher_type").split("\n") + ['Reversal Of ITC']
|
||||
|
||||
make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
|
||||
make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
|
||||
make_property_setter('Journal Entry', 'voucher_type', 'options', '\n'.join(journal_entry_types), '')
|
||||
|
||||
def make_custom_fields(update=True):
|
||||
hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
|
||||
@@ -118,6 +127,9 @@ def make_custom_fields(update=True):
|
||||
is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST',
|
||||
fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt',
|
||||
print_hide=1)
|
||||
taxable_value = dict(fieldname='taxable_value', label='Taxable Value',
|
||||
fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
|
||||
print_hide=1)
|
||||
|
||||
purchase_invoice_gst_category = [
|
||||
dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break',
|
||||
@@ -178,15 +190,20 @@ def make_custom_fields(update=True):
|
||||
purchase_invoice_itc_fields = [
|
||||
dict(fieldname='eligibility_for_itc', label='Eligibility For ITC',
|
||||
fieldtype='Select', insert_after='reason_for_issuing_document', print_hide=1,
|
||||
options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nIneligible\nAll Other ITC', default="All Other ITC"),
|
||||
options='Input Service Distributor\nImport Of Service\nImport Of Capital Goods\nITC on Reverse Charge\nIneligible As Per Section 17(5)\nIneligible Others\nAll Other ITC',
|
||||
default="All Other ITC"),
|
||||
dict(fieldname='itc_integrated_tax', label='Availed ITC Integrated Tax',
|
||||
fieldtype='Data', insert_after='eligibility_for_itc', print_hide=1),
|
||||
fieldtype='Currency', insert_after='eligibility_for_itc',
|
||||
options='Company:company:default_currency', print_hide=1),
|
||||
dict(fieldname='itc_central_tax', label='Availed ITC Central Tax',
|
||||
fieldtype='Data', insert_after='itc_integrated_tax', print_hide=1),
|
||||
fieldtype='Currency', insert_after='itc_integrated_tax',
|
||||
options='Company:company:default_currency', print_hide=1),
|
||||
dict(fieldname='itc_state_tax', label='Availed ITC State/UT Tax',
|
||||
fieldtype='Data', insert_after='itc_central_tax', print_hide=1),
|
||||
fieldtype='Currency', insert_after='itc_central_tax',
|
||||
options='Company:company:default_currency', print_hide=1),
|
||||
dict(fieldname='itc_cess_amount', label='Availed ITC Cess',
|
||||
fieldtype='Data', insert_after='itc_state_tax', print_hide=1),
|
||||
fieldtype='Currency', insert_after='itc_state_tax',
|
||||
options='Company:company:default_currency', print_hide=1),
|
||||
]
|
||||
|
||||
sales_invoice_gst_fields = [
|
||||
@@ -216,6 +233,23 @@ def make_custom_fields(update=True):
|
||||
depends_on="eval:doc.gst_category=='Overseas' "),
|
||||
]
|
||||
|
||||
journal_entry_fields = [
|
||||
dict(fieldname='reversal_type', label='Reversal Type',
|
||||
fieldtype='Select', insert_after='voucher_type', print_hide=1,
|
||||
options="As per rules 42 & 43 of CGST Rules\nOthers",
|
||||
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
|
||||
dict(fieldname='company_address', label='Company Address',
|
||||
fieldtype='Link', options='Address', insert_after='reversal_type',
|
||||
print_hide=1, depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'"),
|
||||
dict(fieldname='company_gstin', label='Company GSTIN',
|
||||
fieldtype='Data', read_only=1, insert_after='company_address', print_hide=1,
|
||||
fetch_from='company_address.gstin',
|
||||
depends_on="eval:doc.voucher_type=='Reversal Of ITC'",
|
||||
mandatory_depends_on="eval:doc.voucher_type=='Reversal Of ITC'")
|
||||
]
|
||||
|
||||
inter_state_gst_field = [
|
||||
dict(fieldname='is_inter_state', label='Is Inter State',
|
||||
fieldtype='Check', insert_after='disabled', print_hide=1),
|
||||
@@ -430,6 +464,7 @@ def make_custom_fields(update=True):
|
||||
'Purchase Receipt': purchase_invoice_gst_fields,
|
||||
'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
|
||||
'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
|
||||
'Journal Entry': journal_entry_fields,
|
||||
'Sales Order': sales_invoice_gst_fields,
|
||||
'Tax Category': inter_state_gst_field,
|
||||
'Item': [
|
||||
@@ -444,10 +479,10 @@ def make_custom_fields(update=True):
|
||||
'Supplier Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||
'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
|
||||
'Material Request Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
|
||||
'Salary Component': [
|
||||
dict(fieldname= 'component_type',
|
||||
|
||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||
import frappe, re, json
|
||||
from frappe import _
|
||||
import erpnext
|
||||
from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words
|
||||
from frappe.utils import cstr, flt, cint, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate
|
||||
from erpnext.regional.india import states, state_numbers
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
|
||||
from erpnext.controllers.accounts_controller import get_taxes_and_charges
|
||||
@@ -176,8 +176,6 @@ def get_regional_address_details(party_details, doctype, company):
|
||||
|
||||
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
|
||||
master_doctype = "Sales Taxes and Charges Template"
|
||||
|
||||
get_tax_template_for_sez(party_details, master_doctype, company, 'Customer')
|
||||
get_tax_template_based_on_category(master_doctype, company, party_details)
|
||||
|
||||
if party_details.get('taxes_and_charges'):
|
||||
@@ -188,7 +186,6 @@ def get_regional_address_details(party_details, doctype, company):
|
||||
|
||||
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
|
||||
master_doctype = "Purchase Taxes and Charges Template"
|
||||
get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier')
|
||||
get_tax_template_based_on_category(master_doctype, company, party_details)
|
||||
|
||||
if party_details.get('taxes_and_charges'):
|
||||
@@ -220,6 +217,11 @@ def update_party_details(party_details, doctype):
|
||||
if party_details.get(address_field):
|
||||
party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field)))
|
||||
|
||||
def update_party_details(party_details, doctype):
|
||||
for address_field in ['shipping_address', 'company_address', 'supplier_address', 'shipping_address_name', 'customer_address']:
|
||||
if party_details.get(address_field):
|
||||
party_details.update(get_fetch_values(doctype, address_field, party_details.get(address_field)))
|
||||
|
||||
def is_internal_transfer(party_details, doctype):
|
||||
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
|
||||
destination_gstin = party_details.company_gstin
|
||||
@@ -255,20 +257,6 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code):
|
||||
{'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name')
|
||||
return default_tax
|
||||
|
||||
def get_tax_template_for_sez(party_details, master_doctype, company, party_type):
|
||||
|
||||
gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))},
|
||||
['gst_category', 'export_type'], as_dict=1)
|
||||
|
||||
if gst_details:
|
||||
if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax':
|
||||
default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0,
|
||||
"gst_state": number_state_mapping[party_details.company_gstin[:2]]})
|
||||
|
||||
party_details["taxes_and_charges"] = default_tax
|
||||
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
|
||||
|
||||
|
||||
def calculate_annual_eligible_hra_exemption(doc):
|
||||
basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"])
|
||||
if not (basic_component and hra_component):
|
||||
@@ -669,10 +657,19 @@ def validate_state_code(state_code, address):
|
||||
return int(state_code)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_gst_accounts(company, account_wise=False):
|
||||
def get_gst_accounts(company=None, account_wise=False, only_reverse_charge=0, only_non_reverse_charge=0):
|
||||
filters={"parent": "GST Settings"}
|
||||
|
||||
if company:
|
||||
filters.update({'company': company})
|
||||
if only_reverse_charge:
|
||||
filters.update({'is_reverse_charge_account': 1})
|
||||
elif only_non_reverse_charge:
|
||||
filters.update({'is_reverse_charge_account': 0})
|
||||
|
||||
gst_accounts = frappe._dict()
|
||||
gst_settings_accounts = frappe.get_all("GST Account",
|
||||
filters={"parent": "GST Settings", "company": company},
|
||||
filters=filters,
|
||||
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
|
||||
|
||||
if not gst_settings_accounts and not frappe.flags.in_test:
|
||||
@@ -687,101 +684,63 @@ def get_gst_accounts(company, account_wise=False):
|
||||
|
||||
return gst_accounts
|
||||
|
||||
def update_grand_total_for_rcm(doc, method):
|
||||
def validate_reverse_charge_transaction(doc, method):
|
||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||
|
||||
if country != 'India':
|
||||
return
|
||||
|
||||
gst_tax, base_gst_tax = get_gst_tax_amount(doc)
|
||||
|
||||
if not base_gst_tax:
|
||||
return
|
||||
base_gst_tax = 0
|
||||
base_reverse_charge_booked = 0
|
||||
|
||||
if doc.reverse_charge == 'Y':
|
||||
doc.taxes_and_charges_added -= gst_tax
|
||||
doc.total_taxes_and_charges -= gst_tax
|
||||
doc.base_taxes_and_charges_added -= base_gst_tax
|
||||
doc.base_total_taxes_and_charges -= base_gst_tax
|
||||
gst_accounts = get_gst_accounts(doc.company, only_reverse_charge=1)
|
||||
reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
|
||||
+ gst_accounts.get('igst_account')
|
||||
|
||||
update_totals(gst_tax, base_gst_tax, doc)
|
||||
|
||||
def update_totals(gst_tax, base_gst_tax, doc):
|
||||
doc.base_grand_total -= base_gst_tax
|
||||
doc.grand_total -= gst_tax
|
||||
|
||||
if doc.meta.get_field("rounded_total"):
|
||||
if doc.is_rounded_total_disabled():
|
||||
doc.outstanding_amount = doc.grand_total
|
||||
else:
|
||||
doc.rounded_total = round_based_on_smallest_currency_fraction(doc.grand_total,
|
||||
doc.currency, doc.precision("rounded_total"))
|
||||
|
||||
doc.rounding_adjustment += flt(doc.rounded_total - doc.grand_total,
|
||||
doc.precision("rounding_adjustment"))
|
||||
|
||||
doc.outstanding_amount = doc.rounded_total or doc.grand_total
|
||||
|
||||
doc.in_words = money_in_words(doc.grand_total, doc.currency)
|
||||
doc.base_in_words = money_in_words(doc.base_grand_total, erpnext.get_company_currency(doc.company))
|
||||
doc.set_payment_schedule()
|
||||
|
||||
def make_regional_gl_entries(gl_entries, doc):
|
||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||
|
||||
if country != 'India':
|
||||
return gl_entries
|
||||
|
||||
gst_tax, base_gst_tax = get_gst_tax_amount(doc)
|
||||
|
||||
if not base_gst_tax:
|
||||
return gl_entries
|
||||
|
||||
if doc.reverse_charge == 'Y':
|
||||
gst_accounts = get_gst_accounts(doc.company)
|
||||
gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
|
||||
gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1)
|
||||
non_reverse_charge_accounts = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
|
||||
+ gst_accounts.get('igst_account')
|
||||
|
||||
for tax in doc.get('taxes'):
|
||||
if tax.category not in ("Total", "Valuation and Total"):
|
||||
continue
|
||||
if tax.account_head in non_reverse_charge_accounts:
|
||||
if tax.add_deduct_tax == 'Add':
|
||||
base_gst_tax += tax.base_tax_amount_after_discount_amount
|
||||
else:
|
||||
base_gst_tax += tax.base_tax_amount_after_discount_amount
|
||||
elif tax.account_head in reverse_charge_accounts:
|
||||
if tax.add_deduct_tax == 'Add':
|
||||
base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount
|
||||
else:
|
||||
base_reverse_charge_booked += tax.base_tax_amount_after_discount_amount
|
||||
|
||||
dr_or_cr = "credit" if tax.add_deduct_tax == "Add" else "debit"
|
||||
if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
if base_gst_tax != base_reverse_charge_booked:
|
||||
msg = _("Booked reverse charge is not equal to applied tax amount")
|
||||
msg += "<br>"
|
||||
msg += _("Please refer {gst_document_link} to learn more about how to setup and create reverse charge invoice").format(
|
||||
gst_document_link='<a href="https://docs.erpnext.com/docs/user/manual/en/regional/india/gst-setup">GST Documentation</a>')
|
||||
|
||||
gl_entries.append(doc.get_gl_dict(
|
||||
{
|
||||
"account": tax.account_head,
|
||||
"cost_center": tax.cost_center,
|
||||
"posting_date": doc.posting_date,
|
||||
"against": doc.supplier,
|
||||
dr_or_cr: tax.base_tax_amount_after_discount_amount,
|
||||
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \
|
||||
if account_currency==doc.company_currency \
|
||||
else tax.tax_amount_after_discount_amount
|
||||
}, account_currency, item=tax)
|
||||
)
|
||||
frappe.throw(msg)
|
||||
|
||||
return gl_entries
|
||||
def update_itc_availed_fields(doc, method):
|
||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||
|
||||
def get_gst_tax_amount(doc):
|
||||
gst_accounts = get_gst_accounts(doc.company)
|
||||
gst_account_list = gst_accounts.get('cgst_account', []) + gst_accounts.get('sgst_account', []) \
|
||||
+ gst_accounts.get('igst_account', [])
|
||||
if country != 'India':
|
||||
return
|
||||
|
||||
base_gst_tax = 0
|
||||
gst_tax = 0
|
||||
# Initialize values
|
||||
doc.itc_integrated_tax = doc.itc_state_tax = doc.itc_central_tax = doc.itc_cess_amount = 0
|
||||
gst_accounts = get_gst_accounts(doc.company, only_non_reverse_charge=1)
|
||||
|
||||
for tax in doc.get('taxes'):
|
||||
if tax.category not in ("Total", "Valuation and Total"):
|
||||
continue
|
||||
|
||||
if flt(tax.base_tax_amount_after_discount_amount) and tax.account_head in gst_account_list:
|
||||
base_gst_tax += tax.base_tax_amount_after_discount_amount
|
||||
gst_tax += tax.tax_amount_after_discount_amount
|
||||
|
||||
return gst_tax, base_gst_tax
|
||||
if tax.account_head in gst_accounts.get('igst_account'):
|
||||
doc.itc_integrated_tax += flt(tax.base_tax_amount_after_discount_amount)
|
||||
if tax.account_head in gst_accounts.get('sgst_account'):
|
||||
doc.itc_state_tax += flt(tax.base_tax_amount_after_discount_amount)
|
||||
if tax.account_head in gst_accounts.get('cgst_account'):
|
||||
doc.itc_central_tax += flt(tax.base_tax_amount_after_discount_amount)
|
||||
if tax.account_head in gst_accounts.get('cess_account'):
|
||||
doc.itc_cess_amount += flt(tax.base_tax_amount_after_discount_amount)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_regional_round_off_accounts(company, account_list):
|
||||
@@ -803,3 +762,48 @@ def get_regional_round_off_accounts(company, account_list):
|
||||
account_list.extend(gst_account_list)
|
||||
|
||||
return account_list
|
||||
|
||||
def update_taxable_values(doc, method):
|
||||
country = frappe.get_cached_value('Company', doc.company, 'country')
|
||||
|
||||
if country != 'India':
|
||||
return
|
||||
|
||||
gst_accounts = get_gst_accounts(doc.company)
|
||||
|
||||
# Only considering sgst account to avoid inflating taxable value
|
||||
gst_account_list = gst_accounts.get('sgst_account', []) + gst_accounts.get('sgst_account', []) \
|
||||
+ gst_accounts.get('igst_account', [])
|
||||
|
||||
additional_taxes = 0
|
||||
total_charges = 0
|
||||
item_count = 0
|
||||
considered_rows = []
|
||||
|
||||
for tax in doc.get('taxes'):
|
||||
prev_row_id = cint(tax.row_id) - 1
|
||||
if tax.account_head in gst_account_list and prev_row_id not in considered_rows:
|
||||
if tax.charge_type == 'On Previous Row Amount':
|
||||
additional_taxes += doc.get('taxes')[prev_row_id].tax_amount_after_discount_amount
|
||||
considered_rows.append(prev_row_id)
|
||||
if tax.charge_type == 'On Previous Row Total':
|
||||
additional_taxes += doc.get('taxes')[prev_row_id].base_total - doc.base_net_total
|
||||
considered_rows.append(prev_row_id)
|
||||
|
||||
for item in doc.get('items'):
|
||||
if doc.apply_discount_on == 'Grand Total' and doc.discount_amount:
|
||||
proportionate_value = item.base_amount if doc.base_total else item.qty
|
||||
total_value = doc.base_total if doc.base_total else doc.total_qty
|
||||
else:
|
||||
proportionate_value = item.base_net_amount if doc.base_net_total else item.qty
|
||||
total_value = doc.base_net_total if doc.base_net_total else doc.total_qty
|
||||
|
||||
applicable_charges = flt(flt(proportionate_value * (flt(additional_taxes) / flt(total_value)),
|
||||
item.precision('taxable_value')))
|
||||
item.taxable_value = applicable_charges + proportionate_value
|
||||
total_charges += applicable_charges
|
||||
item_count += 1
|
||||
|
||||
if total_charges != additional_taxes:
|
||||
diff = additional_taxes - total_charges
|
||||
doc.get('items')[item_count - 1].taxable_value += diff
|
||||
|
||||
@@ -46,7 +46,13 @@ frappe.query_reports["GSTR-1"] = {
|
||||
"label": __("Type of Business"),
|
||||
"fieldtype": "Select",
|
||||
"reqd": 1,
|
||||
"options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"],
|
||||
"options": [
|
||||
{ "value": "B2B", "label": __("B2B Invoices - 4A, 4B, 4C, 6B, 6C") },
|
||||
{ "value": "B2C Large", "label": __("B2C(Large) Invoices - 5A, 5B") },
|
||||
{ "value": "B2C Small", "label": __("B2C(Small) Invoices - 7") },
|
||||
{ "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") },
|
||||
{ "value": "EXPORT", "label": __("Export Invoice - 6A") }
|
||||
],
|
||||
"default": "B2B"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -32,6 +32,7 @@ class Gstr1Report(object):
|
||||
reverse_charge,
|
||||
return_against,
|
||||
is_return,
|
||||
is_debit_note,
|
||||
gst_category,
|
||||
export_type,
|
||||
port_code,
|
||||
@@ -42,7 +43,7 @@ class Gstr1Report(object):
|
||||
|
||||
def run(self):
|
||||
self.get_columns()
|
||||
self.gst_accounts = get_gst_accounts(self.filters.company)
|
||||
self.gst_accounts = get_gst_accounts(self.filters.company, only_non_reverse_charge=1)
|
||||
self.get_invoice_data()
|
||||
|
||||
if self.invoices:
|
||||
@@ -62,9 +63,9 @@ class Gstr1Report(object):
|
||||
for rate, items in items_based_on_rate.items():
|
||||
row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
|
||||
|
||||
if self.filters.get("type_of_business") == "CDNR":
|
||||
if self.filters.get("type_of_business") == "CDNR-REG":
|
||||
row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N")
|
||||
row.append("C" if invoice_details.return_against else "R")
|
||||
row.append("C" if invoice_details.is_return else "D")
|
||||
|
||||
if taxable_value:
|
||||
self.data.append(row)
|
||||
@@ -105,7 +106,7 @@ class Gstr1Report(object):
|
||||
def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items):
|
||||
row = []
|
||||
for fieldname in self.invoice_fields:
|
||||
if self.filters.get("type_of_business") == "CDNR" and fieldname == "invoice_value":
|
||||
if self.filters.get("type_of_business") == "CDNR-REG" and fieldname == "invoice_value":
|
||||
row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total))
|
||||
elif fieldname == "invoice_value":
|
||||
row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total)
|
||||
@@ -171,7 +172,7 @@ class Gstr1Report(object):
|
||||
|
||||
|
||||
if self.filters.get("type_of_business") == "B2B":
|
||||
conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1"
|
||||
conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1"
|
||||
|
||||
if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"):
|
||||
b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit')
|
||||
@@ -179,19 +180,19 @@ class Gstr1Report(object):
|
||||
frappe.throw(_("Please set B2C Limit in GST Settings."))
|
||||
|
||||
if self.filters.get("type_of_business") == "B2C Large":
|
||||
conditions += """ and ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
|
||||
and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
|
||||
conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'')
|
||||
AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
|
||||
|
||||
elif self.filters.get("type_of_business") == "B2C Small":
|
||||
conditions += """ and (
|
||||
conditions += """ AND (
|
||||
SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2)
|
||||
or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit))
|
||||
OR grand_total <= {0}) and is_return != 1 AND gst_category ='Unregistered' """.format(flt(b2c_limit))
|
||||
|
||||
elif self.filters.get("type_of_business") == "CDNR":
|
||||
conditions += """ and is_return = 1 """
|
||||
elif self.filters.get("type_of_business") == "CDNR-REG":
|
||||
conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ')"""
|
||||
|
||||
elif self.filters.get("type_of_business") == "EXPORT":
|
||||
conditions += """ and is_return !=1 and gst_category = 'Overseas' """
|
||||
conditions += """ AND is_return !=1 and gst_category = 'Overseas' """
|
||||
return conditions
|
||||
|
||||
def get_invoice_items(self):
|
||||
@@ -199,7 +200,7 @@ class Gstr1Report(object):
|
||||
self.item_tax_rate = frappe._dict()
|
||||
|
||||
items = frappe.db.sql("""
|
||||
select item_code, parent, base_net_amount, item_tax_rate
|
||||
select item_code, parent, taxable_value, base_net_amount, item_tax_rate
|
||||
from `tab%s Item`
|
||||
where parent in (%s)
|
||||
""" % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1)
|
||||
@@ -207,7 +208,7 @@ class Gstr1Report(object):
|
||||
for d in items:
|
||||
if d.item_code not in self.invoice_items.get(d.parent, {}):
|
||||
self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code,
|
||||
sum(i.get('base_net_amount', 0) for i in items
|
||||
sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items
|
||||
if i.item_code == d.item_code and i.parent == d.parent))
|
||||
|
||||
item_tax_rate = {}
|
||||
@@ -403,7 +404,7 @@ class Gstr1Report(object):
|
||||
"width": 100
|
||||
}
|
||||
]
|
||||
elif self.filters.get("type_of_business") == "CDNR":
|
||||
elif self.filters.get("type_of_business") == "CDNR-REG":
|
||||
self.invoice_columns = [
|
||||
{
|
||||
"fieldname": "customer_gstin",
|
||||
@@ -437,6 +438,17 @@ class Gstr1Report(object):
|
||||
"options": "Sales Invoice",
|
||||
"width":120
|
||||
},
|
||||
{
|
||||
"fieldname": "reverse_charge",
|
||||
"label": "Reverse Charge",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "export_type",
|
||||
"label": "Export Type",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reason_for_issuing_document",
|
||||
"label": "Reason For Issuing document",
|
||||
@@ -449,6 +461,11 @@ class Gstr1Report(object):
|
||||
"fieldtype": "Data",
|
||||
"width": 120
|
||||
},
|
||||
{
|
||||
"fieldname": "gst_category",
|
||||
"label": "GST Category",
|
||||
"fieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"fieldname": "invoice_value",
|
||||
"label": "Invoice Value",
|
||||
@@ -458,10 +475,10 @@ class Gstr1Report(object):
|
||||
]
|
||||
self.other_columns = [
|
||||
{
|
||||
"fieldname": "cess_amount",
|
||||
"label": "Cess Amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 100
|
||||
"fieldname": "cess_amount",
|
||||
"label": "Cess Amount",
|
||||
"fieldtype": "Currency",
|
||||
"width": 100
|
||||
},
|
||||
{
|
||||
"fieldname": "pre_gst",
|
||||
@@ -589,6 +606,12 @@ def get_json(filters, report_name, data):
|
||||
|
||||
out = get_export_json(res)
|
||||
gst_json["exp"] = out
|
||||
elif filters["type_of_business"] == 'CDNR-REG':
|
||||
for item in report_data[:-1]:
|
||||
res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item)
|
||||
|
||||
out = get_cdnr_reg_json(res, gstin)
|
||||
gst_json["cdnr"] = out
|
||||
|
||||
return {
|
||||
'report_name': report_name,
|
||||
@@ -628,7 +651,6 @@ def get_b2b_json(res, gstin):
|
||||
return out
|
||||
|
||||
def get_b2cs_json(data, gstin):
|
||||
|
||||
company_state_number = gstin[0:2]
|
||||
|
||||
out = []
|
||||
@@ -713,6 +735,54 @@ def get_export_json(res):
|
||||
|
||||
return out
|
||||
|
||||
def get_cdnr_reg_json(res, gstin):
|
||||
out = []
|
||||
|
||||
for gst_in in res:
|
||||
cdnr_item, inv = {"ctin": gst_in, "nt": []}, []
|
||||
if not gst_in: continue
|
||||
|
||||
for number, invoice in iteritems(res[gst_in]):
|
||||
if not invoice[0]["place_of_supply"]:
|
||||
frappe.throw(_("""{0} not entered in Invoice {1}.
|
||||
Please update and try again""").format(frappe.bold("Place Of Supply"),
|
||||
frappe.bold(invoice[0]['invoice_number'])))
|
||||
|
||||
inv_item = {
|
||||
"nt_num": invoice[0]["invoice_number"],
|
||||
"nt_dt": getdate(invoice[0]["posting_date"]).strftime('%d-%m-%Y'),
|
||||
"val": abs(flt(invoice[0]["invoice_value"])),
|
||||
"ntty": invoice[0]["document_type"],
|
||||
"pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]),
|
||||
"rchrg": invoice[0]["reverse_charge"],
|
||||
"inv_type": get_invoice_type_for_cdnr(invoice[0])
|
||||
}
|
||||
|
||||
inv_item["itms"] = []
|
||||
for item in invoice:
|
||||
inv_item["itms"].append(get_rate_and_tax_details(item, gstin))
|
||||
|
||||
inv.append(inv_item)
|
||||
|
||||
if not inv: continue
|
||||
cdnr_item["nt"] = inv
|
||||
out.append(cdnr_item)
|
||||
|
||||
return out
|
||||
|
||||
def get_invoice_type_for_cdnr(row):
|
||||
if row.get('gst_category') == 'SEZ':
|
||||
if row.get('export_type') == 'WPAY':
|
||||
invoice_type = 'SEWP'
|
||||
else:
|
||||
invoice_type = 'SEWOP'
|
||||
elif row.get('gst_category') == 'Deemed Export':
|
||||
row.invoice_type = 'DE'
|
||||
elif row.get('gst_category') == 'Registered Regular':
|
||||
invoice_type = 'R'
|
||||
|
||||
return invoice_type
|
||||
|
||||
def get_basic_invoice_detail(row):
|
||||
return {
|
||||
"inum": row["invoice_number"],
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
"customer_name",
|
||||
"gender",
|
||||
"customer_type",
|
||||
"pan",
|
||||
"tax_withholding_category",
|
||||
"default_bank_account",
|
||||
"lead_name",
|
||||
"image",
|
||||
@@ -479,13 +481,25 @@
|
||||
"fieldname": "dn_required",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Sales Invoice Creation Without Delivery Note"
|
||||
},
|
||||
{
|
||||
"fieldname": "pan",
|
||||
"fieldtype": "Data",
|
||||
"label": "PAN"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-user",
|
||||
"idx": 363,
|
||||
"image_field": "image",
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-03-17 11:03:42.706907",
|
||||
"modified": "2021-01-28 12:54:57.258959",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Customer",
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
"dn_required",
|
||||
"sales_update_frequency",
|
||||
"maintain_same_sales_rate",
|
||||
"maintain_same_rate_action",
|
||||
"role_to_override_stop_action",
|
||||
"editable_price_list_rate",
|
||||
"allow_multiple_items",
|
||||
"allow_against_multiple_purchase_orders",
|
||||
@@ -133,6 +135,23 @@
|
||||
"fieldname": "hide_tax_id",
|
||||
"fieldtype": "Check",
|
||||
"label": "Hide Customer's Tax ID from Sales Transactions"
|
||||
},
|
||||
{
|
||||
"default": "Stop",
|
||||
"depends_on": "maintain_same_sales_rate",
|
||||
"description": "Configure the action to stop the transaction or just warn if the same rate is not maintained.",
|
||||
"fieldname": "maintain_same_rate_action",
|
||||
"fieldtype": "Select",
|
||||
"label": "Action If Same Rate is Not Maintained",
|
||||
"mandatory_depends_on": "maintain_same_sales_rate",
|
||||
"options": "Stop\nWarn"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.maintain_same_rate_action == 'Stop'",
|
||||
"fieldname": "role_to_override_stop_action",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Override Stop Action",
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -140,7 +159,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-13 12:12:56.784014",
|
||||
"modified": "2021-04-04 20:18:12.814624",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
|
||||
@@ -23,7 +23,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va
|
||||
|
||||
if search_value:
|
||||
data = search_serial_or_batch_or_barcode_number(search_value)
|
||||
|
||||
|
||||
item_code = data.get("item_code") if data.get("item_code") else search_value
|
||||
serial_no = data.get("serial_no") if data.get("serial_no") else ""
|
||||
batch_no = data.get("batch_no") if data.get("batch_no") else ""
|
||||
@@ -31,7 +31,7 @@ def get_items(start, page_length, price_list, item_group, pos_profile, search_va
|
||||
|
||||
if data:
|
||||
item_info = frappe.db.get_value(
|
||||
"Item", data.get("item_code"),
|
||||
"Item", data.get("item_code"),
|
||||
["name as item_code", "item_name", "description", "stock_uom", "image as item_image", "is_stock_item"]
|
||||
, as_dict=1)
|
||||
item_info.setdefault('serial_no', serial_no)
|
||||
@@ -257,4 +257,4 @@ def set_customer_info(fieldname, customer, value=""):
|
||||
elif fieldname == 'mobile_no':
|
||||
contact_doc.set('phone_nos', [{ 'phone': value, 'is_primary_mobile_no': 1}])
|
||||
frappe.db.set_value('Customer', customer, 'mobile_no', value)
|
||||
contact_doc.save()
|
||||
contact_doc.save()
|
||||
|
||||
@@ -45,7 +45,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
{
|
||||
fieldname: "opening_amount", fieldtype: "Currency",
|
||||
in_list_view: 1, label: "Opening Amount",
|
||||
options: "company:company_currency",
|
||||
options: "company:company_currency",
|
||||
change: function () {
|
||||
dialog.fields_dict.balance_details.df.data.some(d => {
|
||||
if (d.idx == this.doc.idx) {
|
||||
@@ -207,7 +207,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
|
||||
if (this.frm.doc.items.length == 0) {
|
||||
frappe.show_alert({
|
||||
message:__("You must add atleast one item to save it as draft."),
|
||||
message:__("You must add atleast one item to save it as draft."),
|
||||
indicator:'red'
|
||||
});
|
||||
frappe.utils.play_sound("error");
|
||||
@@ -216,7 +216,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
|
||||
this.frm.save(undefined, undefined, undefined, () => {
|
||||
frappe.show_alert({
|
||||
message:__("There was an error saving the document."),
|
||||
message:__("There was an error saving the document."),
|
||||
indicator:'red'
|
||||
});
|
||||
frappe.utils.play_sound("error");
|
||||
@@ -439,7 +439,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
toggle_recent_order_list(show) {
|
||||
this.toggle_components(!show);
|
||||
@@ -549,7 +549,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
const qty_needed = field === 'qty' ? value * item_row.conversion_factor : item_row.qty * value;
|
||||
await this.check_stock_availability(item_row, qty_needed, this.frm.doc.set_warehouse);
|
||||
}
|
||||
|
||||
|
||||
if (this.is_current_item_being_edited(item_row) || item_selected_from_selector) {
|
||||
await frappe.model.set_value(item_row.doctype, item_row.name, field, value);
|
||||
this.update_cart_html(item_row);
|
||||
@@ -587,7 +587,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
|
||||
this.check_serial_batch_selection_needed(item_row) && this.edit_item_details_of(item_row);
|
||||
this.update_cart_html(item_row);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
@@ -598,7 +598,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
get_item_from_frm(item_code, batch_no, uom) {
|
||||
const has_batch_no = batch_no;
|
||||
return this.frm.doc.items.find(
|
||||
i => i.item_code === item_code
|
||||
i => i.item_code === item_code
|
||||
&& (!has_batch_no || (has_batch_no && i.batch_no === batch_no))
|
||||
&& (i.uom === uom)
|
||||
);
|
||||
@@ -627,7 +627,7 @@ erpnext.PointOfSale.Controller = class {
|
||||
const no_serial_selected = !item_row.serial_no;
|
||||
const no_batch_selected = !item_row.batch_no;
|
||||
|
||||
if ((serialized && no_serial_selected) || (batched && no_batch_selected) ||
|
||||
if ((serialized && no_serial_selected) || (batched && no_batch_selected) ||
|
||||
(serialized && batched && (no_batch_selected || no_serial_selected))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -259,6 +259,7 @@ erpnext.company.setup_queries = function(frm) {
|
||||
["default_payroll_payable_account", {"root_type": "Liability"}],
|
||||
["round_off_account", {"root_type": "Expense"}],
|
||||
["write_off_account", {"root_type": "Expense"}],
|
||||
["default_discount_account", {}],
|
||||
["discount_allowed_account", {"root_type": "Expense"}],
|
||||
["discount_received_account", {"root_type": "Income"}],
|
||||
["exchange_gain_loss_account", {"root_type": "Expense"}],
|
||||
@@ -275,7 +276,7 @@ erpnext.company.setup_queries = function(frm) {
|
||||
["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
|
||||
["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
|
||||
["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
|
||||
["unrealized_profit_loss_account", {"root_type": "Liability"}]
|
||||
["unrealized_profit_loss_account", {"root_type": "Liability"},]
|
||||
], function(i, v) {
|
||||
erpnext.company.set_custom_query(frm, v);
|
||||
});
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
"default_deferred_expense_account",
|
||||
"default_payroll_payable_account",
|
||||
"default_expense_claim_payable_account",
|
||||
"default_discount_account",
|
||||
"section_break_22",
|
||||
"cost_center",
|
||||
"column_break_26",
|
||||
@@ -733,6 +734,12 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Unrealized Profit / Loss Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "default_discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Payment Discount Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-building",
|
||||
@@ -740,7 +747,7 @@
|
||||
"image_field": "company_logo",
|
||||
"is_tree": 1,
|
||||
"links": [],
|
||||
"modified": "2020-12-03 12:27:27.085094",
|
||||
"modified": "2021-02-16 11:12:03.400217",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Company",
|
||||
|
||||
@@ -73,6 +73,34 @@ frappe.ui.form.on("Purchase Receipt", {
|
||||
})
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
frm.events.add_custom_buttons(frm);
|
||||
},
|
||||
|
||||
add_custom_buttons: function(frm) {
|
||||
if (frm.doc.docstatus == 0) {
|
||||
frm.add_custom_button(__('Purchase Invoice'), function () {
|
||||
if (!frm.doc.supplier) {
|
||||
frappe.throw({
|
||||
title: __("Mandatory"),
|
||||
message: __("Please Select a Supplier")
|
||||
});
|
||||
}
|
||||
erpnext.utils.map_current_doc({
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
|
||||
source_doctype: "Purchase Invoice",
|
||||
target: frm,
|
||||
setters: {
|
||||
supplier: frm.doc.supplier,
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: 1,
|
||||
per_received: ["<", 100],
|
||||
company: frm.doc.company
|
||||
}
|
||||
})
|
||||
}, __("Get Items From"));
|
||||
}
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
|
||||
@@ -53,7 +53,20 @@ class PurchaseReceipt(BuyingController):
|
||||
'target_ref_field': 'stock_qty',
|
||||
'source_field': 'stock_qty',
|
||||
'percent_join_field': 'material_request'
|
||||
},
|
||||
{
|
||||
'source_dt': 'Purchase Receipt Item',
|
||||
'target_dt': 'Purchase Invoice Item',
|
||||
'join_field': 'purchase_invoice_item',
|
||||
'target_field': 'received_qty',
|
||||
'target_parent_dt': 'Purchase Invoice',
|
||||
'target_parent_field': 'per_received',
|
||||
'target_ref_field': 'qty',
|
||||
'source_field': 'received_qty',
|
||||
'percent_join_field': 'purchase_invoice',
|
||||
'overflow_type': 'receipt'
|
||||
}]
|
||||
|
||||
if cint(self.is_return):
|
||||
self.status_updater.extend([
|
||||
{
|
||||
@@ -513,7 +526,9 @@ class PurchaseReceipt(BuyingController):
|
||||
def update_billing_status(self, update_modified=True):
|
||||
updated_pr = [self.name]
|
||||
for d in self.get("items"):
|
||||
if d.purchase_order_item:
|
||||
if d.purchase_invoice and d.purchase_invoice_item:
|
||||
d.db_set('billed_amt', d.amount, update_modified=update_modified)
|
||||
elif d.purchase_order_item:
|
||||
updated_pr += update_billed_amount_based_on_po(d.purchase_order_item, update_modified)
|
||||
|
||||
for pr in set(updated_pr):
|
||||
|
||||
@@ -66,16 +66,18 @@
|
||||
"warehouse",
|
||||
"rejected_warehouse",
|
||||
"from_warehouse",
|
||||
"purchase_order",
|
||||
"material_request",
|
||||
"purchase_order",
|
||||
"purchase_invoice",
|
||||
"column_break_40",
|
||||
"is_fixed_asset",
|
||||
"asset_location",
|
||||
"asset_category",
|
||||
"schedule_date",
|
||||
"quality_inspection",
|
||||
"purchase_order_item",
|
||||
"material_request_item",
|
||||
"purchase_order_item",
|
||||
"purchase_invoice_item",
|
||||
"purchase_receipt_item",
|
||||
"delivery_note_item",
|
||||
"putaway_rule",
|
||||
@@ -890,12 +892,69 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "discount_and_margin_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount and Margin"
|
||||
},
|
||||
{
|
||||
"depends_on": "price_list_rate",
|
||||
"fieldname": "margin_type",
|
||||
"fieldtype": "Select",
|
||||
"label": "Margin Type",
|
||||
"options": "\nPercentage\nAmount",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.margin_type && doc.price_list_rate",
|
||||
"fieldname": "margin_rate_or_amount",
|
||||
"fieldtype": "Float",
|
||||
"label": "Margin Rate or Amount",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
|
||||
"fieldname": "rate_with_margin",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rate With Margin",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_37",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.margin_type && doc.price_list_rate && doc.margin_rate_or_amount",
|
||||
"fieldname": "base_rate_with_margin",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rate With Margin (Company Currency)",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Invoice",
|
||||
"options": "Purchase Invoice",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_invoice_item",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Purchase Invoice Item",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-30 21:44:06.918515",
|
||||
"modified": "2021-03-29 04:17:00.336298",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"column_break_4",
|
||||
"valuation_method",
|
||||
"over_delivery_receipt_allowance",
|
||||
"role_allowed_to_over_deliver_receive",
|
||||
"action_if_quality_inspection_is_not_submitted",
|
||||
"show_barcode_field",
|
||||
"clean_description_html",
|
||||
@@ -234,6 +235,13 @@
|
||||
"fieldname": "disable_serial_no_and_batch_selector",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Serial No And Batch Selector"
|
||||
},
|
||||
{
|
||||
"description": "Users with this role are allowed to over deliver/receive against orders above the allowance perecentage",
|
||||
"fieldname": "role_allowed_to_over_deliver_receive",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Over Deliver/Receive",
|
||||
"options": "Role"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -241,7 +249,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-18 13:15:38.352796",
|
||||
"modified": "2021-03-11 18:48:14.513055",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Settings",
|
||||
|
||||
@@ -13,37 +13,36 @@ def get_level():
|
||||
min_count = 0
|
||||
doctypes = {
|
||||
"Asset": 5,
|
||||
"BOM": 3,
|
||||
"Customer": 5,
|
||||
"BOM": 3,
|
||||
"Customer": 5,
|
||||
"Delivery Note": 5,
|
||||
"Employee": 3,
|
||||
"Instructor": 5,
|
||||
"Instructor": 5,
|
||||
"Employee": 3,
|
||||
"Instructor": 5,
|
||||
"Instructor": 5,
|
||||
"Issue": 5,
|
||||
"Item": 5,
|
||||
"Journal Entry": 3,
|
||||
"Item": 5,
|
||||
"Journal Entry": 3,
|
||||
"Lead": 3,
|
||||
"Leave Application": 5,
|
||||
"Material Request": 5,
|
||||
"Opportunity": 5,
|
||||
"Payment Entry": 2,
|
||||
"Opportunity": 5,
|
||||
"Payment Entry": 2,
|
||||
"Project": 5,
|
||||
"Purchase Order": 2,
|
||||
"Purchase Order": 2,
|
||||
"Purchase Invoice": 5,
|
||||
"Purchase Receipt": 5,
|
||||
"Quotation": 3,
|
||||
"Salary Slip": 5,
|
||||
"Salary Structure": 5,
|
||||
"Sales Order": 2,
|
||||
"Sales Invoice": 2,
|
||||
"Sales Order": 2,
|
||||
"Sales Invoice": 2,
|
||||
"Stock Entry": 3,
|
||||
"Student": 5,
|
||||
"Student": 5,
|
||||
"Supplier": 5,
|
||||
"Task": 5,
|
||||
"User": 5,
|
||||
"User": 5,
|
||||
"Work Order": 5
|
||||
}
|
||||
|
||||
for doctype, min_count in iteritems(doctypes):
|
||||
count = frappe.db.count(doctype)
|
||||
if count > min_count:
|
||||
|
||||
@@ -120,11 +120,11 @@ class TransactionBase(StatusUpdater):
|
||||
buying_doctypes = ["Purchase Order", "Purchase Invoice", "Purchase Receipt"]
|
||||
|
||||
if self.doctype in buying_doctypes:
|
||||
to_disable = "Maintain same rate throughout Purchase cycle"
|
||||
settings_page = "Buying Settings"
|
||||
action = frappe.db.get_single_value("Buying Settings", "maintain_same_rate_action")
|
||||
settings_doc = "Buying Settings"
|
||||
else:
|
||||
to_disable = "Maintain same rate throughout Sales cycle"
|
||||
settings_page = "Selling Settings"
|
||||
action = frappe.db.get_single_value("Selling Settings", "maintain_same_rate_action")
|
||||
settings_doc = "Selling Settings"
|
||||
|
||||
for ref_dt, ref_dn_field, ref_link_field in ref_details:
|
||||
for d in self.get("items"):
|
||||
@@ -132,11 +132,16 @@ class TransactionBase(StatusUpdater):
|
||||
ref_rate = frappe.db.get_value(ref_dt + " Item", d.get(ref_link_field), "rate")
|
||||
|
||||
if abs(flt(d.rate - ref_rate, d.precision("rate"))) >= .01:
|
||||
frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4}) ")
|
||||
.format(d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
|
||||
frappe.throw(_("To allow different rates, disable the {0} checkbox in {1}.")
|
||||
.format(frappe.bold(_(to_disable)),
|
||||
get_link_to_form(settings_page, settings_page, frappe.bold(settings_page))))
|
||||
if action == "Stop":
|
||||
role_allowed_to_override = frappe.db.get_single_value(settings_doc, 'role_to_override_stop_action')
|
||||
|
||||
if role_allowed_to_override not in frappe.get_roles():
|
||||
frappe.throw(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
|
||||
d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate))
|
||||
else:
|
||||
frappe.msgprint(_("Row #{0}: Rate must be same as {1}: {2} ({3} / {4})").format(
|
||||
d.idx, ref_dt, d.get(ref_dn_field), d.rate, ref_rate), title=_("Warning"), indicator="orange")
|
||||
|
||||
|
||||
def get_link_filters(self, for_doctype):
|
||||
if hasattr(self, "prev_link_mapper") and self.prev_link_mapper.get(for_doctype):
|
||||
|
||||
Reference in New Issue
Block a user