mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-22 18:49:40 +00:00
Compare commits
135 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdfcbf72bd | ||
|
|
fb7f820885 | ||
|
|
1941c3b136 | ||
|
|
f272d32f80 | ||
|
|
68a5eae3ff | ||
|
|
1b07844237 | ||
|
|
4015c2b9a4 | ||
|
|
0df9591910 | ||
|
|
4403e1c0f4 | ||
|
|
0b3344bad9 | ||
|
|
5cc335dd53 | ||
|
|
42f6cb40d1 | ||
|
|
88706192d7 | ||
|
|
9857cc64d6 | ||
|
|
ff0533d085 | ||
|
|
b0f770780c | ||
|
|
414319daeb | ||
|
|
1945a2fe39 | ||
|
|
c967792ccb | ||
|
|
253248c8e8 | ||
|
|
10b409005d | ||
|
|
272ea30031 | ||
|
|
098579ffbc | ||
|
|
6bce78c66d | ||
|
|
5e5b5cfa0c | ||
|
|
2c78b6c36a | ||
|
|
48b09eb52e | ||
|
|
799d6d159c | ||
|
|
48f59a033f | ||
|
|
1716026e11 | ||
|
|
b1e356fd97 | ||
|
|
2807c9f08f | ||
|
|
5271773595 | ||
|
|
3c571a1691 | ||
|
|
9a9b330af6 | ||
|
|
dd35cd1f84 | ||
|
|
77a6299e8b | ||
|
|
b79ec7cbdd | ||
|
|
927360dd1d | ||
|
|
ab090295d9 | ||
|
|
76204b920d | ||
|
|
21ada7799c | ||
|
|
f4e66914c6 | ||
|
|
1c44c60dbd | ||
|
|
596c2571f6 | ||
|
|
c4994548c3 | ||
|
|
604f80f043 | ||
|
|
4a4757dbc6 | ||
|
|
436e0269f8 | ||
|
|
5d73da5a85 | ||
|
|
886c7cc5a3 | ||
|
|
c4b7b15824 | ||
|
|
cfd3847255 | ||
|
|
d430736177 | ||
|
|
caa524f661 | ||
|
|
cd69b66761 | ||
|
|
040b31d3a7 | ||
|
|
04893ae0e3 | ||
|
|
0ead2296e6 | ||
|
|
09b19f7a2a | ||
|
|
4e7f2eeaa0 | ||
|
|
059372add5 | ||
|
|
16bc28bd70 | ||
|
|
1c6dc80b70 | ||
|
|
748a3d72a3 | ||
|
|
0d50e03595 | ||
|
|
e1446fc6f4 | ||
|
|
928fab6f7e | ||
|
|
0d07083299 | ||
|
|
c4037daca8 | ||
|
|
9a18d318d9 | ||
|
|
2ff9f00ce0 | ||
|
|
daaa4ca0c8 | ||
|
|
1d08448d1a | ||
|
|
3283c461f1 | ||
|
|
e7ae296614 | ||
|
|
c041cd27b5 | ||
|
|
dc914adb62 | ||
|
|
41bff45d7a | ||
|
|
8f0310859d | ||
|
|
98de025a09 | ||
|
|
b962a1a0cd | ||
|
|
3dbadfadd5 | ||
|
|
67ad437dd3 | ||
|
|
7086db1e1c | ||
|
|
87b798b936 | ||
|
|
b894b02ebc | ||
|
|
1d20469c99 | ||
|
|
0db7e1e56b | ||
|
|
f24b556336 | ||
|
|
6a53982f4a | ||
|
|
1fcd2837e8 | ||
|
|
f64f871d45 | ||
|
|
f36bdaadae | ||
|
|
d3bc629f68 | ||
|
|
338d1904c1 | ||
|
|
4fbaea17f8 | ||
|
|
cf0d9dfbfd | ||
|
|
66ae590adc | ||
|
|
379ebbe8c4 | ||
|
|
7b494dc9e8 | ||
|
|
2bc07f18a7 | ||
|
|
ed69dafbe8 | ||
|
|
c985f94009 | ||
|
|
8b9b83a9df | ||
|
|
0f27881fed | ||
|
|
e60490dceb | ||
|
|
2cd4c1a052 | ||
|
|
982810a700 | ||
|
|
18006b978f | ||
|
|
bbb4e79d0a | ||
|
|
bca893a508 | ||
|
|
0dade2c38c | ||
|
|
a22d773341 | ||
|
|
126e13be25 | ||
|
|
288cdf3bf0 | ||
|
|
2422237c1a | ||
|
|
38cfeb1bb7 | ||
|
|
d27cf48b19 | ||
|
|
c232f1f450 | ||
|
|
bd932da08b | ||
|
|
07a957c164 | ||
|
|
d3c893d08b | ||
|
|
b3001595ab | ||
|
|
86cf256358 | ||
|
|
19a8ebe8a5 | ||
|
|
6dbc17d71a | ||
|
|
7bd360aa29 | ||
|
|
2e438011da | ||
|
|
48ebb4ca61 | ||
|
|
0eb049cd85 | ||
|
|
808214fd95 | ||
|
|
d6f2ff6b87 | ||
|
|
9db03bc520 | ||
|
|
e24ab72c0d |
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "16.16.0"
|
||||
__version__ = "16.19.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -34,6 +34,13 @@
|
||||
"account_number": "0430",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Anlagen im Bau": {
|
||||
"is_group": 1,
|
||||
"Andere Anlagen, Betriebs- und Geschäftsausstattung im Bau": {
|
||||
"account_number": "0498",
|
||||
"account_type": "Capital Work in Progress"
|
||||
}
|
||||
},
|
||||
"Accumulated Depreciation": {
|
||||
"account_type": "Accumulated Depreciation"
|
||||
}
|
||||
@@ -317,13 +324,21 @@
|
||||
"account_number": "3800",
|
||||
"account_type": "Expenses Included In Asset Valuation"
|
||||
},
|
||||
"Bestandsveränderungen Roh-, Hilfs- und Betriebsstoffe sowie bezogene Waren": {
|
||||
"account_number": "3960",
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Herstellungskosten": {
|
||||
"account_number": "4996",
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Anlagenabgänge Sachanlagen (Restbuchwert bei Buchverlust)": {
|
||||
"account_number": "2310",
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Verluste aus dem Abgang von Gegenständen des Anlagevermögens": {
|
||||
"account_number": "2320",
|
||||
"account_type": "Stock Adjustment"
|
||||
"account_type": "Expense Account"
|
||||
},
|
||||
"Verwaltungskosten": {
|
||||
"account_number": "4997",
|
||||
@@ -340,7 +355,7 @@
|
||||
"is_group": 1,
|
||||
"Abschreibungen auf Sachanlagen (ohne AfA auf Kfz und Gebäude)": {
|
||||
"account_number": "4830",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Abschreibungen auf Gebäude": {
|
||||
"account_number": "4831",
|
||||
|
||||
@@ -0,0 +1,840 @@
|
||||
{
|
||||
"name": "Philippines",
|
||||
"country": "Philippines",
|
||||
"tree": {
|
||||
"Asset": {
|
||||
"account_number": "1000",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Current Assets": {
|
||||
"account_number": "1001",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Cash": {
|
||||
"account_number": "1100",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Cash",
|
||||
"Cash on Hand": {
|
||||
"account_number": "1101",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"Petty Cash Fund": {
|
||||
"account_number": "1200",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Cash",
|
||||
"Petty Cash Fund": {
|
||||
"account_number": "1201",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Cash"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Bank Accounts": {
|
||||
"account_number": "1102",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Bank"
|
||||
},
|
||||
"Advances to Officers & Employees": {
|
||||
"account_number": "1290",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Advances to Officers & Employees": {
|
||||
"account_number": "1291",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Accounts Receivable Trade": {
|
||||
"account_number": "1300",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Accounts Receivable - Trade": {
|
||||
"account_number": "1301",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Receivable"
|
||||
}
|
||||
},
|
||||
"Accounts Receivable - Affiliates": {
|
||||
"account_number": "1310",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Due from Company": {
|
||||
"account_number": "1311",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Accounts Receivable - Others": {
|
||||
"account_number": "1400",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Accounts Receivable - Others": {
|
||||
"account_number": "1401",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Parts, Materials and Supplies": {
|
||||
"account_number": "1500",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Parts, Materials and Supplies": {
|
||||
"account_number": "1501",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Raw Materials - Demo": {
|
||||
"account_number": "1502",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Project in Progress": {
|
||||
"account_number": "1510",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Project in Progress": {
|
||||
"account_number": "1511",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Factory Overhead Variance": {
|
||||
"account_number": "1512",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Finished Goods": {
|
||||
"account_number": "1520",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Finished Goods Inventory": {
|
||||
"account_number": "1531",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Stock"
|
||||
},
|
||||
"Inventory in Transit": {
|
||||
"account_number": "1532",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Stock Adjustment"
|
||||
}
|
||||
},
|
||||
"Prepayments": {
|
||||
"account_number": "1600",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Prepaid Insurance & Bonds": {
|
||||
"account_number": "1601",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Prepaid Rent": {
|
||||
"account_number": "1602",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"VAT Input Tax": {
|
||||
"account_number": "1610",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"VAT Input Tax - Goods": {
|
||||
"account_number": "1611",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Tax"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Non - Current Assets": {
|
||||
"account_number": "1002",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Property, Plants And Equipments": {
|
||||
"account_number": "1700",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Land": {
|
||||
"account_number": "1701",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Buildings & Improvements": {
|
||||
"account_number": "1702",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Delivery & Trans Equipment": {
|
||||
"account_number": "1703",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Furniture & Fixtures": {
|
||||
"account_number": "1704",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Machinery & Equipment": {
|
||||
"account_number": "1705",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Fixed Asset"
|
||||
}
|
||||
},
|
||||
"Accum Depr. - Property, Plants and Equipment": {
|
||||
"account_number": "1800",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Accumulated Dep Bdgs & Improv": {
|
||||
"account_number": "1801",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"Accumulated Dep Delivery & Trans": {
|
||||
"account_number": "1802",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"Accumulated Dep Furniture & Fixture": {
|
||||
"account_number": "1803",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"Accumulated Depreciation - Machinery & Equipment": {
|
||||
"account_number": "1804",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Other Assets": {
|
||||
"account_number": "1003",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Advances To Supplier": {
|
||||
"account_number": "1900",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Advances To Supplier": {
|
||||
"account_number": "1901",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Miscellaneous Deposits": {
|
||||
"account_number": "1910",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Miscellaneous Deposits": {
|
||||
"account_number": "1911",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Retirement Fund": {
|
||||
"account_number": "1920",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Retirement Fund": {
|
||||
"account_number": "1921",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"Investment": {
|
||||
"account_number": "1930",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"Investment": {
|
||||
"account_number": "1931",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
},
|
||||
"System Development": {
|
||||
"account_number": "1940",
|
||||
"is_group": 1,
|
||||
"root_type": "Asset",
|
||||
"System Development": {
|
||||
"account_number": "1941",
|
||||
"is_group": 0,
|
||||
"root_type": "Asset"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Liability": {
|
||||
"account_number": "2000",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Current Liabilities": {
|
||||
"account_number": "2001",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Accounts Payable Trade": {
|
||||
"account_number": "2100",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Accounts Payable - Trade": {
|
||||
"account_number": "2101",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
"Accounts Payable Others": {
|
||||
"account_number": "2110",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Accounts Payable - Payroll": {
|
||||
"account_number": "2111",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"VAT Output Tax": {
|
||||
"account_number": "2200",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"VAT Output Tax": {
|
||||
"account_number": "2201",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Tax"
|
||||
}
|
||||
},
|
||||
"Withholding Taxes Payable Wages": {
|
||||
"account_number": "2210",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Withholding Taxes Payable Wages": {
|
||||
"account_number": "2211",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Tax"
|
||||
}
|
||||
},
|
||||
"Withholding Taxes Payable Expanded": {
|
||||
"account_number": "2220",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Withholding Taxes Payable Expanded": {
|
||||
"account_number": "2221",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Tax"
|
||||
}
|
||||
},
|
||||
"Accruals And Other Current Payables": {
|
||||
"account_number": "2300",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Stock Received But Not Billed": {
|
||||
"account_number": "2301",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
}
|
||||
},
|
||||
"Payable to Government and Other Institutions": {
|
||||
"account_number": "2400",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"SSS Premium Payable": {
|
||||
"account_number": "2401",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"SSS Salary Loan Payable": {
|
||||
"account_number": "2402",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"PhilHealth Premium": {
|
||||
"account_number": "2403",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Pag-ibig Loan Payable": {
|
||||
"account_number": "2404",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Coop Loans": {
|
||||
"account_number": "2405",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Coop Contributions": {
|
||||
"account_number": "2406",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Canteen": {
|
||||
"account_number": "2407",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"AUB Loan Payable": {
|
||||
"account_number": "2408",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"HSBC Loan Payable": {
|
||||
"account_number": "2409",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"Customer Deposits": {
|
||||
"account_number": "2500",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability",
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
"Non Current Liabilities": {
|
||||
"account_number": "2002",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Due To Associated Company": {
|
||||
"account_number": "2600",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Due To Associated Company": {
|
||||
"account_number": "2601",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"Deferred Income": {
|
||||
"account_number": "2700",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Deferred Income": {
|
||||
"account_number": "2701",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"Notes Payable": {
|
||||
"account_number": "2800",
|
||||
"is_group": 1,
|
||||
"root_type": "Liability",
|
||||
"Notes Payable": {
|
||||
"account_number": "2801",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
},
|
||||
"Dividends Payable": {
|
||||
"account_number": "2900",
|
||||
"is_group": 0,
|
||||
"root_type": "Liability"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Equity": {
|
||||
"account_number": "3000",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"STOCKHOLDER'S EQUITY": {
|
||||
"account_number": "3001",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Capital Stocks": {
|
||||
"account_number": "3100",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Capital Stocks": {
|
||||
"account_number": "3101",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
}
|
||||
},
|
||||
"Subscription Receivable": {
|
||||
"account_number": "3200",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Subscription Receivable": {
|
||||
"account_number": "3201",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
}
|
||||
},
|
||||
"Retained Earnings": {
|
||||
"account_number": "3300",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Retained Earnings": {
|
||||
"account_number": "3301",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
}
|
||||
},
|
||||
"Current Year (Profit/Loss)": {
|
||||
"account_number": "3400",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
},
|
||||
"Drawings": {
|
||||
"account_number": "3500",
|
||||
"is_group": 1,
|
||||
"root_type": "Equity",
|
||||
"Drawings": {
|
||||
"account_number": "3501",
|
||||
"is_group": 0,
|
||||
"root_type": "Equity"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Income": {
|
||||
"account_number": "4000",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Gross Sales": {
|
||||
"account_number": "4100",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Sales": {
|
||||
"account_number": "4101",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
},
|
||||
"Sales Adjustment": {
|
||||
"account_number": "4200",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Sales Return And Allowance": {
|
||||
"account_number": "4201",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
},
|
||||
"Sales Discount": {
|
||||
"account_number": "4300",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Sales Discount": {
|
||||
"account_number": "4301",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
},
|
||||
"Other Income": {
|
||||
"account_number": "6000",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Interest Income Bank": {
|
||||
"account_number": "6010",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Interest Income Bank": {
|
||||
"account_number": "6011",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
},
|
||||
"Dividend Income": {
|
||||
"account_number": "6020",
|
||||
"is_group": 1,
|
||||
"root_type": "Income",
|
||||
"Dividend Income": {
|
||||
"account_number": "6021",
|
||||
"is_group": 0,
|
||||
"root_type": "Income"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Expense": {
|
||||
"account_number": "5000",
|
||||
"is_group": 1,
|
||||
"root_type": "Expense",
|
||||
"Operating Expenses": {
|
||||
"account_number": "5100",
|
||||
"is_group": 1,
|
||||
"root_type": "Expense",
|
||||
"Salaries, Wages": {
|
||||
"account_number": "5101",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"13th Month Pay & Bonus": {
|
||||
"account_number": "5102",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Overtime & Night Diff": {
|
||||
"account_number": "5103",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Incentive/Performance Bonus": {
|
||||
"account_number": "5104",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Employees Benefits": {
|
||||
"account_number": "5105",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Advertising & Promotions": {
|
||||
"account_number": "5106",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Amortization of Leasehold Improvement": {
|
||||
"account_number": "5107",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Amortization of Pre-Operating": {
|
||||
"account_number": "5108",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Amortization of System Development": {
|
||||
"account_number": "5109",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Audit & Legal Fee": {
|
||||
"account_number": "5110",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Bad Debts Expenses": {
|
||||
"account_number": "5111",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Client Service & Maintenance": {
|
||||
"account_number": "5112",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Commission Expenses": {
|
||||
"account_number": "5113",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Communications": {
|
||||
"account_number": "5114",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Contractual Services": {
|
||||
"account_number": "5115",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Depreciation Expenses": {
|
||||
"account_number": "5116",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Donation & Contribution": {
|
||||
"account_number": "5117",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Dues & Subscription": {
|
||||
"account_number": "5118",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Employee Med/Dental/Hosp Expenses": {
|
||||
"account_number": "5119",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Employee Uniforms": {
|
||||
"account_number": "5120",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Equipage": {
|
||||
"account_number": "5121",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Expenses for Reclassification": {
|
||||
"account_number": "5122",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Gas & Oil": {
|
||||
"account_number": "5123",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Insurance Expenses": {
|
||||
"account_number": "5124",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Light & Water": {
|
||||
"account_number": "5125",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Local/Overseas Travel": {
|
||||
"account_number": "5126",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Meals & Transportation Expenses": {
|
||||
"account_number": "5127",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Meeting & Conferences": {
|
||||
"account_number": "5128",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Miscellaneous Expenses": {
|
||||
"account_number": "5129",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Mockup Expenses": {
|
||||
"account_number": "5130",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Obsolescence Expenses": {
|
||||
"account_number": "5131",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Other Support Cost": {
|
||||
"account_number": "5132",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Pag-ibig Contribution": {
|
||||
"account_number": "5133",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Performance Bonds": {
|
||||
"account_number": "5134",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Pre Employment Expenses": {
|
||||
"account_number": "5135",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Professional Fees": {
|
||||
"account_number": "5136",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Recruitment & Employment": {
|
||||
"account_number": "5137",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Rent Expenses": {
|
||||
"account_number": "5138",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Rent Expenses Others": {
|
||||
"account_number": "5139",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Repairs & Maintenance": {
|
||||
"account_number": "5140",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Representation Expenses": {
|
||||
"account_number": "5141",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Research & Development": {
|
||||
"account_number": "5142",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Security Expenses": {
|
||||
"account_number": "5143",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Shared Services Fee": {
|
||||
"account_number": "5144",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"SSS/Medicare/EC Contributions": {
|
||||
"account_number": "5145",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Stationery & Supplies": {
|
||||
"account_number": "5146",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Taxes & Licenses": {
|
||||
"account_number": "5147",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Training & Seminar": {
|
||||
"account_number": "5148",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense"
|
||||
}
|
||||
},
|
||||
"Stock Adjustment": {
|
||||
"account_number": "5200",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Round Off": {
|
||||
"account_number": "5300",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Round Off"
|
||||
},
|
||||
"Expenses Included In Valuation": {
|
||||
"account_number": "5400",
|
||||
"is_group": 0,
|
||||
"root_type": "Expense",
|
||||
"account_type": "Expenses Included In Valuation"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ class AccountingDimension(Document):
|
||||
def validate(self):
|
||||
self.validate_doctype()
|
||||
validate_column_name(self.fieldname)
|
||||
self.validate_fieldname_conflict()
|
||||
self.validate_dimension_defaults()
|
||||
|
||||
def validate_doctype(self):
|
||||
@@ -74,6 +75,27 @@ class AccountingDimension(Document):
|
||||
message += _("Please create a new Accounting Dimension if required.")
|
||||
frappe.throw(message)
|
||||
|
||||
def validate_fieldname_conflict(self):
|
||||
conflicting_doctypes = []
|
||||
for doctype in get_doctypes_with_dimensions():
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
if any(f.fieldname == self.fieldname for f in meta.get("fields")):
|
||||
conflicting_doctypes.append(doctype)
|
||||
|
||||
if conflicting_doctypes:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Fieldname {0} already exists in the following doctypes: {1}. "
|
||||
"A separate dimension field will not be added to these doctypes. "
|
||||
"GL Entries will use the value of the existing field as the dimension value."
|
||||
).format(
|
||||
frappe.bold(self.fieldname),
|
||||
", ".join(frappe.bold(d) for d in conflicting_doctypes),
|
||||
),
|
||||
title=_("Fieldname Conflict"),
|
||||
indicator="orange",
|
||||
)
|
||||
|
||||
def validate_dimension_defaults(self):
|
||||
companies = []
|
||||
for default in self.get("dimension_defaults"):
|
||||
|
||||
@@ -38,16 +38,6 @@ frappe.ui.form.on("Accounts Settings", {
|
||||
add_taxes_from_item_tax_template(frm) {
|
||||
toggle_tax_settings(frm, "add_taxes_from_item_tax_template");
|
||||
},
|
||||
|
||||
drop_ar_procedures: function (frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: "drop_ar_sql_procedures",
|
||||
callback: function (r) {
|
||||
frappe.show_alert(__("Procedures dropped"), 5);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function toggle_tax_settings(frm, field_name) {
|
||||
|
||||
@@ -96,7 +96,6 @@
|
||||
"receivable_payable_fetch_method",
|
||||
"default_ageing_range",
|
||||
"column_break_ntmi",
|
||||
"drop_ar_procedures",
|
||||
"legacy_section",
|
||||
"ignore_is_opening_check_for_reporting",
|
||||
"tab_break_dpet",
|
||||
@@ -523,7 +522,7 @@
|
||||
"fieldname": "receivable_payable_fetch_method",
|
||||
"fieldtype": "Select",
|
||||
"label": "Data Fetch Method",
|
||||
"options": "Buffered Cursor\nUnBuffered Cursor\nRaw SQL"
|
||||
"options": "Buffered Cursor\nUnBuffered Cursor"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts_receivable_payable_tuning_section",
|
||||
@@ -592,13 +591,6 @@
|
||||
"fieldname": "column_break_ntmi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.receivable_payable_fetch_method == \"Raw SQL\"",
|
||||
"description": "Drops existing SQL Procedures and Function setup by Accounts Receivable report",
|
||||
"fieldname": "drop_ar_procedures",
|
||||
"fieldtype": "Button",
|
||||
"label": "Drop Procedures"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "fetch_valuation_rate_for_internal_transaction",
|
||||
@@ -725,7 +717,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-13 15:30:28.729627",
|
||||
"modified": "2026-05-18 12:16:33.679345",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -89,7 +89,7 @@ class AccountsSettings(Document):
|
||||
make_payment_via_journal_entry: DF.Check
|
||||
merge_similar_account_heads: DF.Check
|
||||
over_billing_allowance: DF.Currency
|
||||
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"]
|
||||
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
|
||||
receivable_payable_remarks_length: DF.Int
|
||||
reconciliation_queue_size: DF.Int
|
||||
repost_allowed_types: DF.Table[RepostAllowedTypes]
|
||||
@@ -209,13 +209,6 @@ class AccountsSettings(Document):
|
||||
|
||||
set_allow_on_submit_for_dimension_fields(doctypes)
|
||||
|
||||
@frappe.whitelist()
|
||||
def drop_ar_sql_procedures(self):
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import InitSQLProceduresForAR
|
||||
|
||||
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
|
||||
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")
|
||||
|
||||
|
||||
def toggle_accounting_dimension_sections(hide):
|
||||
accounting_dimension_doctypes = frappe.get_hooks("accounting_dimension_doctypes")
|
||||
|
||||
@@ -70,6 +70,10 @@ frappe.ui.form.on("Journal Entry", {
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
if (frm.doc.reversal_of && (frm.is_new() || frm.doc.docstatus == 0)) {
|
||||
frm.set_read_only();
|
||||
}
|
||||
|
||||
erpnext.toggle_naming_series();
|
||||
|
||||
if (frm.doc.docstatus > 0) {
|
||||
|
||||
@@ -710,31 +710,12 @@ frappe.ui.form.on("Payment Entry", {
|
||||
if (!frm.doc.paid_from_account_currency || !frm.doc.company) return;
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
|
||||
if (frm.doc.paid_from_account_currency == company_currency) {
|
||||
frm.set_value("source_exchange_rate", 1);
|
||||
} else if (frm.doc.paid_from) {
|
||||
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency;
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
from_currency: frm.doc.paid_from_account_currency,
|
||||
to_currency: company_currency,
|
||||
transaction_date: frm.doc.posting_date,
|
||||
},
|
||||
callback: function (r, rt) {
|
||||
frm.set_value("source_exchange_rate", r.message);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
frm.events.set_current_exchange_rate(
|
||||
frm,
|
||||
"source_exchange_rate",
|
||||
frm.doc.paid_from_account_currency,
|
||||
company_currency
|
||||
);
|
||||
}
|
||||
}
|
||||
frm.events.set_current_exchange_rate(
|
||||
frm,
|
||||
"source_exchange_rate",
|
||||
frm.doc.paid_from_account_currency,
|
||||
company_currency
|
||||
);
|
||||
},
|
||||
|
||||
paid_to_account_currency: function (frm) {
|
||||
@@ -766,49 +747,24 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
posting_date: function (frm) {
|
||||
frm.events.paid_from_account_currency(frm);
|
||||
frm.events.paid_to_account_currency(frm);
|
||||
},
|
||||
|
||||
source_exchange_rate: function (frm) {
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
if (frm.doc.paid_amount) {
|
||||
frm.set_value("base_paid_amount", flt(frm.doc.paid_amount) * flt(frm.doc.source_exchange_rate));
|
||||
// target exchange rate should always be same as source if both account currencies is same
|
||||
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
|
||||
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
|
||||
} else if (company_currency == frm.doc.paid_to_account_currency) {
|
||||
frm.set_value("received_amount", frm.doc.base_paid_amount);
|
||||
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
|
||||
}
|
||||
|
||||
// set_unallocated_amount is called by below method,
|
||||
// no need trigger separately
|
||||
frm.events.set_total_allocated_amount(frm);
|
||||
}
|
||||
|
||||
// Make read only if Accounts Settings doesn't allow stale rates
|
||||
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
|
||||
},
|
||||
|
||||
target_exchange_rate: function (frm) {
|
||||
frm.set_paid_amount_based_on_received_amount = true;
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
|
||||
if (frm.doc.received_amount) {
|
||||
frm.set_value(
|
||||
"base_received_amount",
|
||||
flt(frm.doc.received_amount) * flt(frm.doc.target_exchange_rate)
|
||||
);
|
||||
if (frm.doc.base_received_amount && frm.doc.source_exchange_rate) {
|
||||
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
|
||||
|
||||
if (
|
||||
!frm.doc.source_exchange_rate &&
|
||||
frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
|
||||
) {
|
||||
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
|
||||
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
|
||||
} else if (company_currency == frm.doc.paid_from_account_currency) {
|
||||
frm.set_value("paid_amount", frm.doc.base_received_amount);
|
||||
frm.set_value("base_paid_amount", frm.doc.base_received_amount);
|
||||
// target exchange rate should always be same as source if both account currencies is same
|
||||
if (frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency) {
|
||||
frm.set_value("target_exchange_rate", frm.doc.source_exchange_rate);
|
||||
} else {
|
||||
frm.set_value(
|
||||
"paid_amount",
|
||||
flt(frm.doc.base_paid_amount) / flt(frm.doc.source_exchange_rate)
|
||||
);
|
||||
}
|
||||
|
||||
// set_unallocated_amount is called by below method,
|
||||
@@ -817,6 +773,32 @@ frappe.ui.form.on("Payment Entry", {
|
||||
}
|
||||
frm.set_paid_amount_based_on_received_amount = false;
|
||||
|
||||
// Make read only if Accounts Settings doesn't allow stale rates
|
||||
frm.set_df_property("source_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
|
||||
},
|
||||
|
||||
target_exchange_rate: function (frm) {
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
|
||||
if (frm.doc.base_paid_amount && frm.doc.target_exchange_rate) {
|
||||
frm.set_value("base_received_amount", frm.doc.base_paid_amount);
|
||||
if (
|
||||
!frm.doc.source_exchange_rate &&
|
||||
frm.doc.paid_from_account_currency == frm.doc.paid_to_account_currency
|
||||
) {
|
||||
frm.set_value("source_exchange_rate", frm.doc.target_exchange_rate);
|
||||
} else {
|
||||
frm.set_value(
|
||||
"received_amount",
|
||||
flt(frm.doc.base_received_amount) / flt(frm.doc.target_exchange_rate)
|
||||
);
|
||||
}
|
||||
|
||||
// set_unallocated_amount is called by below method,
|
||||
// no need trigger separately
|
||||
frm.events.set_total_allocated_amount(frm);
|
||||
}
|
||||
|
||||
// Make read only if Accounts Settings doesn't allow stale rates
|
||||
frm.set_df_property("target_exchange_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
|
||||
},
|
||||
|
||||
@@ -322,7 +322,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "doc.received_amount",
|
||||
"depends_on": "eval:doc.received_amount;",
|
||||
"fieldname": "base_received_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Received Amount (Company Currency)",
|
||||
@@ -795,7 +795,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2026-03-09 17:15:30.453920",
|
||||
"modified": "2026-05-15 13:31:01.166010",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -2328,16 +2328,19 @@ def get_outstanding_reference_documents(args, validate=False):
|
||||
}
|
||||
|
||||
for fieldname, date_fields in date_fields_dict.items():
|
||||
from_date = frappe.db.escape(str(args.get(date_fields[0]))) if args.get(date_fields[0]) else None
|
||||
to_date = frappe.db.escape(str(args.get(date_fields[1]))) if args.get(date_fields[1]) else None
|
||||
|
||||
if args.get(date_fields[0]) and args.get(date_fields[1]):
|
||||
condition += f" and {fieldname} between {frappe.db.escape(args.get(date_fields[0]))} and {frappe.db.escape(args.get(date_fields[1]))}"
|
||||
condition += f" and {fieldname} between {from_date} and {to_date}"
|
||||
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
|
||||
elif args.get(date_fields[0]):
|
||||
# if only from date is supplied
|
||||
condition += f" and {fieldname} >= {frappe.db.escape(args.get(date_fields[0]))}"
|
||||
condition += f" and {fieldname} >= {from_date}"
|
||||
posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0])))
|
||||
elif args.get(date_fields[1]):
|
||||
# if only to date is supplied
|
||||
condition += f" and {fieldname} <= {frappe.db.escape(args.get(date_fields[1]))}"
|
||||
condition += f" and {fieldname} <= {to_date}"
|
||||
posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1])))
|
||||
|
||||
if args.get("company"):
|
||||
|
||||
@@ -21,122 +21,23 @@ from erpnext.tests.utils import ERPNextTestSuite
|
||||
|
||||
class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_item()
|
||||
self.create_customer()
|
||||
self.create_account()
|
||||
self.create_cost_center()
|
||||
self.clear_old_entries()
|
||||
|
||||
def create_company(self):
|
||||
company = None
|
||||
if frappe.db.exists("Company", "_Test Payment Reconciliation"):
|
||||
company = frappe.get_doc("Company", "_Test Payment Reconciliation")
|
||||
else:
|
||||
company = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": "_Test Payment Reconciliation",
|
||||
"country": "India",
|
||||
"default_currency": "INR",
|
||||
"create_chart_of_accounts_based_on": "Standard Template",
|
||||
"chart_of_accounts": "Standard",
|
||||
}
|
||||
)
|
||||
company = company.save()
|
||||
|
||||
self.company = company.name
|
||||
self.cost_center = company.cost_center
|
||||
self.warehouse = "All Warehouses - _PR"
|
||||
self.income_account = "Sales - _PR"
|
||||
self.expense_account = "Cost of Goods Sold - _PR"
|
||||
self.debit_to = "Debtors - _PR"
|
||||
self.creditors = "Creditors - _PR"
|
||||
self.cash = "Cash - _PR"
|
||||
|
||||
# create bank account
|
||||
if frappe.db.exists("Account", "HDFC - _PR"):
|
||||
self.bank = "HDFC - _PR"
|
||||
else:
|
||||
bank_acc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_name": "HDFC",
|
||||
"parent_account": "Bank Accounts - _PR",
|
||||
"company": self.company,
|
||||
}
|
||||
)
|
||||
bank_acc.save()
|
||||
self.bank = bank_acc.name
|
||||
|
||||
def create_item(self):
|
||||
item = create_item(
|
||||
item_code="_Test PR Item", is_stock_item=0, company=self.company, warehouse=self.warehouse
|
||||
)
|
||||
self.item = item if isinstance(item, str) else item.item_code
|
||||
|
||||
def create_customer(self):
|
||||
self.customer = make_customer("_Test PR Customer")
|
||||
self.customer2 = make_customer("_Test PR Customer 2")
|
||||
self.customer3 = make_customer("_Test PR Customer 3", "EUR")
|
||||
self.customer4 = make_customer("_Test PR Customer 4", "EUR")
|
||||
self.customer5 = make_customer("_Test PR Customer 5", "EUR")
|
||||
|
||||
def create_account(self):
|
||||
accounts = [
|
||||
{
|
||||
"attribute": "debtors_eur",
|
||||
"account_name": "Debtors EUR",
|
||||
"parent_account": "Accounts Receivable - _PR",
|
||||
"account_currency": "EUR",
|
||||
"account_type": "Receivable",
|
||||
},
|
||||
{
|
||||
"attribute": "creditors_usd",
|
||||
"account_name": "Payable USD",
|
||||
"parent_account": "Accounts Payable - _PR",
|
||||
"account_currency": "USD",
|
||||
"account_type": "Payable",
|
||||
},
|
||||
# 'Payable' account for capturing advance paid, under 'Assets' group
|
||||
{
|
||||
"attribute": "advance_payable_account",
|
||||
"account_name": "Advance Paid",
|
||||
"parent_account": "Current Assets - _PR",
|
||||
"account_currency": "INR",
|
||||
"account_type": "Payable",
|
||||
},
|
||||
# 'Receivable' account for capturing advance received, under 'Liabilities' group
|
||||
{
|
||||
"attribute": "advance_receivable_account",
|
||||
"account_name": "Advance Received",
|
||||
"parent_account": "Current Liabilities - _PR",
|
||||
"account_currency": "INR",
|
||||
"account_type": "Receivable",
|
||||
},
|
||||
]
|
||||
|
||||
for x in accounts:
|
||||
x = frappe._dict(x)
|
||||
if not frappe.db.get_value(
|
||||
"Account", filters={"account_name": x.account_name, "company": self.company}
|
||||
):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = x.account_name
|
||||
acc.parent_account = x.parent_account
|
||||
acc.company = self.company
|
||||
acc.account_currency = x.account_currency
|
||||
acc.account_type = x.account_type
|
||||
acc.insert()
|
||||
else:
|
||||
name = frappe.db.get_value(
|
||||
"Account",
|
||||
filters={"account_name": x.account_name, "company": self.company},
|
||||
fieldname="name",
|
||||
pluck=True,
|
||||
)
|
||||
acc = frappe.get_doc("Account", name)
|
||||
setattr(self, x.attribute, acc.name)
|
||||
self.company = "_Test Company"
|
||||
self.debit_to = "Debtors - _TC"
|
||||
self.creditors = "Creditors - _TC"
|
||||
self.bank = "HDFC - _TC"
|
||||
self.cash = "Cash - _TC"
|
||||
self.item = "_Test Item"
|
||||
self.cost_center = self.main_cc = "Main - _TC"
|
||||
self.sub_cc = "Sub - _TC"
|
||||
self.customer = "_Test Customer"
|
||||
self.advance_receivable_account = "Advance Received - _TC"
|
||||
self.advance_payable_account = "Advance Paid - _TC"
|
||||
self.income_account = "Sales - _TC"
|
||||
self.expense_account = "Cost of Goods Sold - _TC"
|
||||
self.warehouse = "All Warehouses - _TC"
|
||||
self.customer_usd = "_Test Customer USD"
|
||||
self.debtors_usd = "_Test Receivable USD - _TC"
|
||||
self.creditors_usd = "_Test Payable USD - _TC"
|
||||
|
||||
def create_sales_invoice(
|
||||
self, qty=1, rate=100, posting_date=None, do_not_save=False, do_not_submit=False
|
||||
@@ -253,18 +154,6 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
)
|
||||
return pord
|
||||
|
||||
def clear_old_entries(self):
|
||||
doctype_list = [
|
||||
"GL Entry",
|
||||
"Payment Ledger Entry",
|
||||
"Sales Invoice",
|
||||
"Purchase Invoice",
|
||||
"Payment Entry",
|
||||
"Journal Entry",
|
||||
]
|
||||
for doctype in doctype_list:
|
||||
qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
|
||||
|
||||
def create_payment_reconciliation(self, party_is_customer=True):
|
||||
pr = frappe.new_doc("Payment Reconciliation")
|
||||
pr.company = self.company
|
||||
@@ -300,22 +189,6 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
)
|
||||
return je
|
||||
|
||||
def create_cost_center(self):
|
||||
# Setup cost center
|
||||
cc_name = "Sub"
|
||||
|
||||
self.main_cc = frappe.get_doc("Cost Center", get_default_cost_center(self.company))
|
||||
|
||||
cc_exists = frappe.db.get_list("Cost Center", filters={"cost_center_name": cc_name})
|
||||
if cc_exists:
|
||||
self.sub_cc = frappe.get_doc("Cost Center", cc_exists[0].name)
|
||||
else:
|
||||
sub_cc = frappe.new_doc("Cost Center")
|
||||
sub_cc.cost_center_name = "Sub"
|
||||
sub_cc.parent_cost_center = self.main_cc.parent_cost_center
|
||||
sub_cc.company = self.main_cc.company
|
||||
self.sub_cc = sub_cc.save()
|
||||
|
||||
def test_filter_min_max(self):
|
||||
# check filter condition minimum and maximum amount
|
||||
self.create_sales_invoice(qty=1, rate=300)
|
||||
@@ -478,7 +351,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
def test_payment_against_journal(self):
|
||||
transaction_date = nowdate()
|
||||
|
||||
sales = "Sales - _PR"
|
||||
sales = "Sales - _TC"
|
||||
amount = 921
|
||||
# debit debtors account to record an invoice
|
||||
je = self.create_journal_entry(self.debit_to, sales, amount, transaction_date)
|
||||
@@ -513,7 +386,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
transaction_date = nowdate()
|
||||
|
||||
self.supplier = "_Test Supplier USD"
|
||||
self.supplier2 = make_supplier("_Test Supplier2 USD", "USD")
|
||||
self.supplier2 = "_Test Another Supplier USD"
|
||||
amount = 100
|
||||
exc_rate1 = 80
|
||||
exc_rate2 = 83
|
||||
@@ -666,7 +539,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
|
||||
def test_journal_against_journal(self):
|
||||
transaction_date = nowdate()
|
||||
sales = "Sales - _PR"
|
||||
sales = "Sales - _TC"
|
||||
amount = 100
|
||||
|
||||
# debit debtors account to simulate a invoice
|
||||
@@ -841,47 +714,49 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
def test_pr_output_foreign_currency_and_amount(self):
|
||||
# test for currency and amount invoices and payments
|
||||
transaction_date = nowdate()
|
||||
# In EUR
|
||||
# In USD
|
||||
amount = 100
|
||||
exchange_rate = 80
|
||||
|
||||
si = self.create_sales_invoice(
|
||||
qty=1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
|
||||
)
|
||||
si.customer = self.customer3
|
||||
si.currency = "EUR"
|
||||
si.customer = self.customer_usd
|
||||
si.currency = "USD"
|
||||
si.conversion_rate = exchange_rate
|
||||
si.debit_to = self.debtors_eur
|
||||
si.debit_to = self.debtors_usd
|
||||
si = si.save().submit()
|
||||
|
||||
cr_note = self.create_sales_invoice(
|
||||
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
|
||||
)
|
||||
cr_note.customer = self.customer3
|
||||
cr_note.customer = self.customer_usd
|
||||
cr_note.is_return = 1
|
||||
cr_note.currency = "EUR"
|
||||
cr_note.currency = "USD"
|
||||
cr_note.conversion_rate = exchange_rate
|
||||
cr_note.debit_to = self.debtors_eur
|
||||
cr_note.debit_to = self.debtors_usd
|
||||
cr_note = cr_note.save().submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party = self.customer3
|
||||
pr.receivable_payable_account = self.debtors_eur
|
||||
pr.party = self.customer_usd
|
||||
pr.receivable_payable_account = self.debtors_usd
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
|
||||
self.assertEqual(pr.invoices[0].amount, amount)
|
||||
self.assertEqual(pr.invoices[0].currency, "EUR")
|
||||
self.assertEqual(pr.invoices[0].currency, "USD")
|
||||
self.assertEqual(pr.payments[0].amount, amount)
|
||||
self.assertEqual(pr.payments[0].currency, "EUR")
|
||||
self.assertEqual(pr.payments[0].currency, "USD")
|
||||
|
||||
cr_note.cancel()
|
||||
|
||||
pay = self.create_payment_entry(amount=amount, posting_date=transaction_date, customer=self.customer3)
|
||||
pay.paid_from = self.debtors_eur
|
||||
pay.paid_from_account_currency = "EUR"
|
||||
pay = self.create_payment_entry(
|
||||
amount=amount, posting_date=transaction_date, customer=self.customer_usd
|
||||
)
|
||||
pay.paid_from = self.debtors_usd
|
||||
pay.paid_from_account_currency = "USD"
|
||||
pay.source_exchange_rate = exchange_rate
|
||||
pay.received_amount = exchange_rate * amount
|
||||
pay = pay.save().submit()
|
||||
@@ -890,21 +765,21 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
self.assertEqual(pr.payments[0].amount, amount)
|
||||
self.assertEqual(pr.payments[0].currency, "EUR")
|
||||
self.assertEqual(pr.payments[0].currency, "USD")
|
||||
|
||||
def test_difference_amount_via_journal_entry(self):
|
||||
# Make Sale Invoice
|
||||
si = self.create_sales_invoice(
|
||||
qty=1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
|
||||
)
|
||||
si.customer = self.customer4
|
||||
si.currency = "EUR"
|
||||
si.customer = self.customer_usd
|
||||
si.currency = "USD"
|
||||
si.conversion_rate = 85
|
||||
si.debit_to = self.debtors_eur
|
||||
si.debit_to = self.debtors_usd
|
||||
si.save().submit()
|
||||
|
||||
# Make payment using Journal Entry
|
||||
je1 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 100, nowdate())
|
||||
je1 = self.create_journal_entry("HDFC - _TC", self.debtors_usd, 100, nowdate())
|
||||
je1.multi_currency = 1
|
||||
je1.accounts[0].exchange_rate = 1
|
||||
je1.accounts[0].credit_in_account_currency = 0
|
||||
@@ -912,7 +787,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je1.accounts[0].debit_in_account_currency = 8000
|
||||
je1.accounts[0].debit = 8000
|
||||
je1.accounts[1].party_type = "Customer"
|
||||
je1.accounts[1].party = self.customer4
|
||||
je1.accounts[1].party = self.customer_usd
|
||||
je1.accounts[1].exchange_rate = 80
|
||||
je1.accounts[1].credit_in_account_currency = 100
|
||||
je1.accounts[1].credit = 8000
|
||||
@@ -921,7 +796,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je1.save()
|
||||
je1.submit()
|
||||
|
||||
je2 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 200, nowdate())
|
||||
je2 = self.create_journal_entry("HDFC - _TC", self.debtors_usd, 200, nowdate())
|
||||
je2.multi_currency = 1
|
||||
je2.accounts[0].exchange_rate = 1
|
||||
je2.accounts[0].credit_in_account_currency = 0
|
||||
@@ -929,7 +804,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je2.accounts[0].debit_in_account_currency = 16000
|
||||
je2.accounts[0].debit = 16000
|
||||
je2.accounts[1].party_type = "Customer"
|
||||
je2.accounts[1].party = self.customer4
|
||||
je2.accounts[1].party = self.customer_usd
|
||||
je2.accounts[1].exchange_rate = 80
|
||||
je2.accounts[1].credit_in_account_currency = 200
|
||||
je1.accounts[1].credit = 16000
|
||||
@@ -939,8 +814,8 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je2.submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party = self.customer4
|
||||
pr.receivable_payable_account = self.debtors_eur
|
||||
pr.party = self.customer_usd
|
||||
pr.receivable_payable_account = self.debtors_usd
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
@@ -960,7 +835,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [pr.payments[1].as_dict()]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.allocation[0].difference_account = "Exchange Gain/Loss - _PR"
|
||||
pr.allocation[0].difference_account = "Exchange Gain/Loss - _TC"
|
||||
|
||||
self.assertEqual(pr.allocation[0].allocated_amount, 100)
|
||||
self.assertEqual(pr.allocation[0].difference_amount, -500)
|
||||
@@ -969,7 +844,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
pr.reconcile()
|
||||
total_credit_amount = frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
{"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name},
|
||||
{"account": self.debtors_usd, "docstatus": 1, "reference_name": si.name},
|
||||
[{"SUM": "credit", "as": "amount"}],
|
||||
group_by="reference_name",
|
||||
)[0].amount
|
||||
@@ -979,7 +854,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
|
||||
jea_parent = frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
filters={"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name, "credit": 500},
|
||||
filters={"account": self.debtors_usd, "docstatus": 1, "reference_name": si.name, "credit": 500},
|
||||
fields=["parent"],
|
||||
)[0]
|
||||
self.assertEqual(
|
||||
@@ -991,14 +866,14 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
si = self.create_sales_invoice(
|
||||
qty=1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
|
||||
)
|
||||
si.customer = self.customer4
|
||||
si.currency = "EUR"
|
||||
si.customer = self.customer_usd
|
||||
si.currency = "USD"
|
||||
si.conversion_rate = 85
|
||||
si.debit_to = self.debtors_eur
|
||||
si.debit_to = self.debtors_usd
|
||||
si.save().submit()
|
||||
|
||||
# Make payment using Journal Entry
|
||||
je1 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 100, nowdate())
|
||||
je1 = self.create_journal_entry("HDFC - _TC", self.debtors_usd, 100, nowdate())
|
||||
je1.multi_currency = 1
|
||||
je1.accounts[0].exchange_rate = 1
|
||||
je1.accounts[0].credit_in_account_currency = -8000
|
||||
@@ -1006,7 +881,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je1.accounts[0].debit_in_account_currency = 0
|
||||
je1.accounts[0].debit = 0
|
||||
je1.accounts[1].party_type = "Customer"
|
||||
je1.accounts[1].party = self.customer4
|
||||
je1.accounts[1].party = self.customer_usd
|
||||
je1.accounts[1].exchange_rate = 80
|
||||
je1.accounts[1].credit_in_account_currency = 100
|
||||
je1.accounts[1].credit = 8000
|
||||
@@ -1015,7 +890,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je1.save()
|
||||
je1.submit()
|
||||
|
||||
je2 = self.create_journal_entry("HDFC - _PR", self.debtors_eur, 200, nowdate())
|
||||
je2 = self.create_journal_entry("HDFC - _TC", self.debtors_usd, 200, nowdate())
|
||||
je2.multi_currency = 1
|
||||
je2.accounts[0].exchange_rate = 1
|
||||
je2.accounts[0].credit_in_account_currency = -16000
|
||||
@@ -1023,7 +898,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je2.accounts[0].debit_in_account_currency = 0
|
||||
je2.accounts[0].debit = 0
|
||||
je2.accounts[1].party_type = "Customer"
|
||||
je2.accounts[1].party = self.customer4
|
||||
je2.accounts[1].party = self.customer_usd
|
||||
je2.accounts[1].exchange_rate = 80
|
||||
je2.accounts[1].credit_in_account_currency = 200
|
||||
je1.accounts[1].credit = 16000
|
||||
@@ -1033,8 +908,8 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je2.submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party = self.customer4
|
||||
pr.receivable_payable_account = self.debtors_eur
|
||||
pr.party = self.customer_usd
|
||||
pr.receivable_payable_account = self.debtors_usd
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
@@ -1054,7 +929,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [pr.payments[1].as_dict()]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.allocation[0].difference_account = "Exchange Gain/Loss - _PR"
|
||||
pr.allocation[0].difference_account = "Exchange Gain/Loss - _TC"
|
||||
|
||||
self.assertEqual(pr.allocation[0].allocated_amount, 100)
|
||||
self.assertEqual(pr.allocation[0].difference_amount, -500)
|
||||
@@ -1063,7 +938,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
pr.reconcile()
|
||||
total_credit_amount = frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
{"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name},
|
||||
{"account": self.debtors_usd, "docstatus": 1, "reference_name": si.name},
|
||||
[{"SUM": "credit", "as": "amount"}],
|
||||
group_by="reference_name",
|
||||
)[0].amount
|
||||
@@ -1073,7 +948,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
|
||||
jea_parent = frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
filters={"account": self.debtors_eur, "docstatus": 1, "reference_name": si.name, "credit": 500},
|
||||
filters={"account": self.debtors_usd, "docstatus": 1, "reference_name": si.name, "credit": 500},
|
||||
fields=["parent"],
|
||||
)[0]
|
||||
self.assertEqual(
|
||||
@@ -1085,10 +960,10 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
si = self.create_sales_invoice(
|
||||
qty=1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
|
||||
)
|
||||
si.customer = self.customer5
|
||||
si.currency = "EUR"
|
||||
si.customer = self.customer_usd
|
||||
si.currency = "USD"
|
||||
si.conversion_rate = 85
|
||||
si.debit_to = self.debtors_eur
|
||||
si.debit_to = self.debtors_usd
|
||||
si.save().submit()
|
||||
|
||||
# Make payment using Payment Entry
|
||||
@@ -1096,8 +971,8 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
company=self.company,
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party=self.customer5,
|
||||
paid_from=self.debtors_eur,
|
||||
party=self.customer_usd,
|
||||
paid_from=self.debtors_usd,
|
||||
paid_to=self.bank,
|
||||
paid_amount=100,
|
||||
)
|
||||
@@ -1111,8 +986,8 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
company=self.company,
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party=self.customer5,
|
||||
paid_from=self.debtors_eur,
|
||||
party=self.customer_usd,
|
||||
paid_from=self.debtors_usd,
|
||||
paid_to=self.bank,
|
||||
paid_amount=200,
|
||||
)
|
||||
@@ -1123,8 +998,8 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
pe2.submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party = self.customer5
|
||||
pr.receivable_payable_account = self.debtors_eur
|
||||
pr.party = self.customer_usd
|
||||
pr.receivable_payable_account = self.debtors_usd
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
@@ -1152,14 +1027,14 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
"""
|
||||
|
||||
si = self.create_sales_invoice(qty=1, rate=100, do_not_submit=True)
|
||||
si.cost_center = self.main_cc.name
|
||||
si.cost_center = self.main_cc
|
||||
si.submit()
|
||||
pr = get_payment_entry(si.doctype, si.name)
|
||||
pr.cost_center = self.sub_cc.name
|
||||
pr.cost_center = self.sub_cc
|
||||
pr = pr.save().submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.cost_center = self.main_cc.name
|
||||
pr.cost_center = self.main_cc
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
@@ -1176,38 +1051,38 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
|
||||
# 'Main - PR' Cost Center
|
||||
si1 = self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True)
|
||||
si1.cost_center = self.main_cc.name
|
||||
si1.cost_center = self.main_cc
|
||||
si1.submit()
|
||||
|
||||
pe1 = self.create_payment_entry(posting_date=transaction_date, amount=rate)
|
||||
pe1.cost_center = self.main_cc.name
|
||||
pe1.cost_center = self.main_cc
|
||||
pe1 = pe1.save().submit()
|
||||
|
||||
je1 = self.create_journal_entry(self.bank, self.debit_to, 100, transaction_date)
|
||||
je1.accounts[0].cost_center = self.main_cc.name
|
||||
je1.accounts[1].cost_center = self.main_cc.name
|
||||
je1.accounts[0].cost_center = self.main_cc
|
||||
je1.accounts[1].cost_center = self.main_cc
|
||||
je1.accounts[1].party_type = "Customer"
|
||||
je1.accounts[1].party = self.customer
|
||||
je1 = je1.save().submit()
|
||||
|
||||
# 'Sub - PR' Cost Center
|
||||
si2 = self.create_sales_invoice(qty=1, rate=rate, posting_date=transaction_date, do_not_submit=True)
|
||||
si2.cost_center = self.sub_cc.name
|
||||
si2.cost_center = self.sub_cc
|
||||
si2.submit()
|
||||
|
||||
pe2 = self.create_payment_entry(posting_date=transaction_date, amount=rate)
|
||||
pe2.cost_center = self.sub_cc.name
|
||||
pe2.cost_center = self.sub_cc
|
||||
pe2 = pe2.save().submit()
|
||||
|
||||
je2 = self.create_journal_entry(self.bank, self.debit_to, 100, transaction_date)
|
||||
je2.accounts[0].cost_center = self.sub_cc.name
|
||||
je2.accounts[1].cost_center = self.sub_cc.name
|
||||
je2.accounts[0].cost_center = self.sub_cc
|
||||
je2.accounts[1].cost_center = self.sub_cc
|
||||
je2.accounts[1].party_type = "Customer"
|
||||
je2.accounts[1].party = self.customer
|
||||
je2 = je2.save().submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.cost_center = self.main_cc.name
|
||||
pr.cost_center = self.main_cc
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
@@ -1219,7 +1094,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
self.assertCountEqual(payment_vouchers, [pe1.name, je1.name])
|
||||
|
||||
# Change cost center
|
||||
pr.cost_center = self.sub_cc.name
|
||||
pr.cost_center = self.sub_cc
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
@@ -1242,7 +1117,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
qty=1, rate=1, posting_date=nowdate(), do_not_save=True, do_not_submit=True
|
||||
)
|
||||
si.customer = self.customer
|
||||
si.currency = "EUR"
|
||||
si.currency = "USD"
|
||||
si.conversion_rate = 85
|
||||
si.debit_to = self.debit_to
|
||||
si.save().submit()
|
||||
@@ -1624,7 +1499,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
"credit": 0.0,
|
||||
},
|
||||
{
|
||||
"account": "Cash - _PR",
|
||||
"account": "Cash - _TC",
|
||||
"voucher_no": pe.name,
|
||||
"against_voucher": None,
|
||||
"debit": 0.0,
|
||||
@@ -1782,10 +1657,10 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
)
|
||||
amount = 200.0
|
||||
je = self.create_journal_entry(self.debit_to, self.bank, amount)
|
||||
je.accounts[0].cost_center = self.main_cc.name
|
||||
je.accounts[0].cost_center = self.main_cc
|
||||
je.accounts[0].party_type = "Customer"
|
||||
je.accounts[0].party = self.customer
|
||||
je.accounts[1].cost_center = self.main_cc.name
|
||||
je.accounts[1].cost_center = self.main_cc
|
||||
je = je.save().submit()
|
||||
|
||||
pe = self.create_payment_entry(amount=amount).save().submit()
|
||||
@@ -1879,7 +1754,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
self.assertEqual(pl_entries, expected_ple)
|
||||
|
||||
def test_advance_payment_reconciliation_against_journal_for_supplier(self):
|
||||
self.supplier = make_supplier("_Test Supplier")
|
||||
self.supplier = "_Test Supplier"
|
||||
frappe.db.set_value(
|
||||
"Company",
|
||||
self.company,
|
||||
@@ -1891,10 +1766,10 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
)
|
||||
amount = 200.0
|
||||
je = self.create_journal_entry(self.creditors, self.bank, -amount)
|
||||
je.accounts[0].cost_center = self.main_cc.name
|
||||
je.accounts[0].cost_center = self.main_cc
|
||||
je.accounts[0].party_type = "Supplier"
|
||||
je.accounts[0].party = self.supplier
|
||||
je.accounts[1].cost_center = self.main_cc.name
|
||||
je.accounts[1].cost_center = self.main_cc
|
||||
je = je.save().submit()
|
||||
|
||||
pe = self.create_payment_entry(amount=amount)
|
||||
@@ -2062,13 +1937,13 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
},
|
||||
{
|
||||
"account": self.bank,
|
||||
"cost_center": self.sub_cc.name,
|
||||
"cost_center": self.sub_cc,
|
||||
"credit_in_account_currency": 0,
|
||||
"debit_in_account_currency": 500,
|
||||
},
|
||||
{
|
||||
"account": self.cash,
|
||||
"cost_center": self.sub_cc.name,
|
||||
"cost_center": self.sub_cc,
|
||||
"credit_in_account_currency": 0,
|
||||
"debit_in_account_currency": 500,
|
||||
},
|
||||
@@ -2337,7 +2212,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
|
||||
def test_foreign_currency_reverse_payment_entry_against_payment_entry_for_customer(self):
|
||||
transaction_date = nowdate()
|
||||
customer = self.customer3
|
||||
customer = self.customer_usd
|
||||
amount = 1000
|
||||
exchange_rate_at_payment = 100
|
||||
exchange_rate_at_reverse_payment = 95
|
||||
@@ -2345,8 +2220,8 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
# Receive amount from customer - 1,00,000
|
||||
pe = self.create_payment_entry(amount=amount, posting_date=transaction_date, customer=customer)
|
||||
pe.payment_type = "Receive"
|
||||
pe.paid_from = self.debtors_eur
|
||||
pe.paid_from_account_currency = "EUR"
|
||||
pe.paid_from = self.debtors_usd
|
||||
pe.paid_from_account_currency = "USD"
|
||||
pe.source_exchange_rate = exchange_rate_at_payment
|
||||
pe.paid_amount = amount
|
||||
pe.received_amount = exchange_rate_at_payment * amount
|
||||
@@ -2364,14 +2239,14 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
reverse_pe.target_exchange_rate = exchange_rate_at_reverse_payment
|
||||
reverse_pe.paid_amount = exchange_rate_at_reverse_payment * amount
|
||||
reverse_pe.received_amount = amount
|
||||
reverse_pe.paid_to = self.debtors_eur
|
||||
reverse_pe.paid_to_account_currency = "EUR"
|
||||
reverse_pe.paid_to = self.debtors_usd
|
||||
reverse_pe.paid_to_account_currency = "USD"
|
||||
reverse_pe.save().submit()
|
||||
|
||||
# Reconcile payments
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party = customer
|
||||
pr.receivable_payable_account = self.debtors_eur
|
||||
pr.receivable_payable_account = self.debtors_usd
|
||||
pr.get_unreconciled_entries()
|
||||
invoices = [invoice.as_dict() for invoice in pr.invoices]
|
||||
payments = [payment.as_dict() for payment in pr.payments]
|
||||
@@ -2436,13 +2311,13 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
|
||||
def test_foreign_currency_reverse_journal_entry_against_journal_entry_for_customer(self):
|
||||
transaction_date = nowdate()
|
||||
customer = self.customer3
|
||||
customer = self.customer_usd
|
||||
amount = 1000
|
||||
exchange_rate_at_payment = 95
|
||||
exchange_rate_at_reverse_payment = 100
|
||||
|
||||
# Receive amount from customer - 95,000
|
||||
je1 = self.create_journal_entry(self.cash, self.debtors_eur, amount, transaction_date)
|
||||
je1 = self.create_journal_entry(self.cash, self.debtors_usd, amount, transaction_date)
|
||||
je1.multi_currency = 1
|
||||
je1.accounts[0].exchange_rate = 1
|
||||
je1.accounts[0].debit_in_account_currency = exchange_rate_at_payment * amount
|
||||
@@ -2456,7 +2331,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
je1.submit()
|
||||
|
||||
# Pay amount to customer - 1,00,000
|
||||
je2 = self.create_journal_entry(self.debtors_eur, self.cash, amount, transaction_date)
|
||||
je2 = self.create_journal_entry(self.debtors_usd, self.cash, amount, transaction_date)
|
||||
je2.multi_currency = 1
|
||||
je2.accounts[0].party_type = "Customer"
|
||||
je2.accounts[0].party = customer
|
||||
@@ -2472,7 +2347,7 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
# Reconcile payments
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party = customer
|
||||
pr.receivable_payable_account = self.debtors_eur
|
||||
pr.receivable_payable_account = self.debtors_usd
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
@@ -2540,34 +2415,6 @@ class TestPaymentReconciliation(ERPNextTestSuite):
|
||||
pr.reconcile()
|
||||
|
||||
|
||||
def make_customer(customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = customer_name
|
||||
customer.type = "Individual"
|
||||
|
||||
if currency:
|
||||
customer.default_currency = currency
|
||||
customer.save()
|
||||
return customer.name
|
||||
else:
|
||||
return customer_name
|
||||
|
||||
|
||||
def make_supplier(supplier_name, currency=None):
|
||||
if not frappe.db.exists("Supplier", supplier_name):
|
||||
supplier = frappe.new_doc("Supplier")
|
||||
supplier.supplier_name = supplier_name
|
||||
supplier.type = "Individual"
|
||||
|
||||
if currency:
|
||||
supplier.default_currency = currency
|
||||
supplier.save()
|
||||
return supplier.name
|
||||
else:
|
||||
return supplier_name
|
||||
|
||||
|
||||
def create_fiscal_year(company, year_start_date, year_end_date):
|
||||
fy_docname = frappe.db.exists(
|
||||
"Fiscal Year", {"year_start_date": year_start_date, "year_end_date": year_end_date}
|
||||
|
||||
@@ -664,6 +664,7 @@
|
||||
"fieldname": "total_billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Billing Amount",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1531,6 +1532,7 @@
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -1639,7 +1641,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-28 06:06:14.283612",
|
||||
"modified": "2026-05-01 02:37:30.580568",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -115,7 +115,12 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.docstatus == 1 && doc.outstanding_amount != 0 && !doc.on_hold) {
|
||||
if (
|
||||
doc.docstatus == 1 &&
|
||||
doc.outstanding_amount != 0 &&
|
||||
!doc.on_hold &&
|
||||
frappe.model.can_create("Payment Entry")
|
||||
) {
|
||||
this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create"));
|
||||
this.frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||
}
|
||||
@@ -130,7 +135,13 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.docstatus == 1 && doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
|
||||
if (
|
||||
doc.docstatus == 1 &&
|
||||
doc.outstanding_amount > 0 &&
|
||||
!cint(doc.is_return) &&
|
||||
!doc.on_hold &&
|
||||
frappe.boot.user.in_create.includes("Payment Request")
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
@@ -443,13 +454,14 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
}
|
||||
|
||||
items_add(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
this.frm.script_manager.copy_from_first_row("items", row, [
|
||||
"expense_account",
|
||||
"discount_account",
|
||||
"cost_center",
|
||||
"project",
|
||||
]);
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
const field_copy = ["expense_account", "discount_account", "cost_center"];
|
||||
if (doc.project) {
|
||||
frappe.model.set_value(cdt, cdn, "project", doc.project);
|
||||
} else {
|
||||
field_copy.push("project");
|
||||
}
|
||||
this.frm.script_manager.copy_from_first_row("items", row, field_copy);
|
||||
}
|
||||
|
||||
on_submit() {
|
||||
@@ -558,12 +570,6 @@ cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function
|
||||
};
|
||||
};
|
||||
|
||||
cur_frm.fields_dict["items"].grid.get_field("project").get_query = function (doc, cdt, cdn) {
|
||||
return {
|
||||
filters: [["Project", "status", "not in", "Completed, Cancelled"]],
|
||||
};
|
||||
};
|
||||
|
||||
frappe.ui.form.on("Purchase Invoice", {
|
||||
setup: function (frm) {
|
||||
frm.custom_make_buttons = {
|
||||
|
||||
@@ -110,6 +110,7 @@
|
||||
"sales_invoice_item",
|
||||
"material_request",
|
||||
"material_request_item",
|
||||
"delivered_by_supplier",
|
||||
"item_weight_details",
|
||||
"weight_per_unit",
|
||||
"total_weight",
|
||||
@@ -1001,13 +1002,22 @@
|
||||
"label": "Tax Withholding Category",
|
||||
"options": "Tax Withholding Category",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "delivered_by_supplier",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Delivered by Supplier",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-07 15:40:45.687554",
|
||||
"modified": "2026-05-06 08:08:40.782395",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -31,6 +31,7 @@ class PurchaseInvoiceItem(Document):
|
||||
conversion_factor: DF.Float
|
||||
cost_center: DF.Link | None
|
||||
deferred_expense_account: DF.Link | None
|
||||
delivered_by_supplier: DF.Check
|
||||
description: DF.TextEditor | None
|
||||
discount_amount: DF.Currency
|
||||
discount_percentage: DF.Percent
|
||||
|
||||
@@ -94,7 +94,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm);
|
||||
}
|
||||
|
||||
if (doc.docstatus == 1 && doc.outstanding_amount != 0) {
|
||||
if (doc.docstatus == 1 && doc.outstanding_amount != 0 && frappe.model.can_create("Payment Entry")) {
|
||||
this.frm.add_custom_button(__("Payment"), () => this.make_payment_entry(), __("Create"));
|
||||
this.frm.page.set_inner_btn_group_as_primary(__("Create"));
|
||||
}
|
||||
@@ -135,13 +135,15 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
}
|
||||
|
||||
if (doc.outstanding_amount > 0) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
if (frappe.boot.user.in_create.includes("Payment Request")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
this.frm.add_custom_button(
|
||||
__("Invoice Discounting"),
|
||||
this.make_invoice_discounting.bind(this),
|
||||
@@ -552,12 +554,14 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
}
|
||||
|
||||
items_add(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
this.frm.script_manager.copy_from_first_row("items", row, [
|
||||
"income_account",
|
||||
"discount_account",
|
||||
"cost_center",
|
||||
]);
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
const field_copy = ["income_account", "discount_account", "cost_center"];
|
||||
if (doc.project) {
|
||||
frappe.model.set_value(cdt, cdn, "project", doc.project);
|
||||
} else {
|
||||
field_copy.push("project");
|
||||
}
|
||||
this.frm.script_manager.copy_from_first_row("items", row, field_copy);
|
||||
}
|
||||
|
||||
set_dynamic_labels() {
|
||||
|
||||
@@ -1152,6 +1152,7 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Rounding Adjustment",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1164,6 +1165,7 @@
|
||||
"label": "Rounded Total",
|
||||
"oldfieldname": "rounded_total",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -2365,7 +2367,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2026-04-28 13:08:19.849783",
|
||||
"modified": "2026-05-01 02:37:29.742764",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -677,7 +677,7 @@ def validate_due_date_with_template(posting_date, due_date, bill_date, template_
|
||||
if not default_due_date:
|
||||
return
|
||||
|
||||
if default_due_date != posting_date and getdate(due_date) > getdate(default_due_date):
|
||||
if getdate(default_due_date) != getdate(posting_date) and getdate(due_date) > getdate(default_due_date):
|
||||
if frappe.get_single_value("Accounts Settings", "credit_controller") in frappe.get_roles():
|
||||
party_type = "supplier" if doctype == "Purchase Invoice" else "customer"
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,161 +0,0 @@
|
||||
{%- macro add_header(page_num, max_pages, doc, letter_head, no_letterhead, footer, print_settings=None, print_heading_template=None) -%}
|
||||
{% if letter_head and not no_letterhead %}
|
||||
<div class="letter-head">{{ letter_head }}</div>
|
||||
{% endif %}
|
||||
{% if print_heading_template %}
|
||||
{{ frappe.render_template(print_heading_template, {"doc":doc}) }}
|
||||
{% else %}
|
||||
{% endif %}
|
||||
{%- if doc.meta.is_submittable and doc.docstatus==2-%}
|
||||
<div class="text-center" document-status="cancelled">
|
||||
<h4 style="margin: 0px;">{{ _("CANCELLED") }}</h4>
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endmacro -%}
|
||||
{% for page in layout %}
|
||||
<div class="page-break">
|
||||
<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
|
||||
{{ add_header(loop.index, layout|len, doc, letter_head, no_letterhead, footer, print_settings) }}
|
||||
</div>
|
||||
<style>
|
||||
.taxes-section .order-taxes.mt-5{
|
||||
margin-top: 0px !important;
|
||||
}
|
||||
.taxes-section .order-taxes .border-btm.pb-5{
|
||||
padding-bottom: 0px !important;
|
||||
}
|
||||
.print-format label{
|
||||
color: #74808b;
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
{% if print_settings.repeat_header_footer %}
|
||||
<div id="footer-html" class="visible-pdf">
|
||||
{% if not no_letterhead and footer %}
|
||||
<div class="letter-head-footer">
|
||||
{{ footer }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-center small page-number visible-pdf">
|
||||
{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row section-break" style="margin-bottom: 10px;">
|
||||
<div class="col-xs-6 p-0">
|
||||
<div class="col-xs-12 value text-uppercase"><b>{{ doc.customer }}</b></div>
|
||||
<div class="col-xs-12">
|
||||
{{ doc.address_display }}
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
{{ _("Contact: ")+doc.contact_display if doc.contact_display else '' }}
|
||||
</div>
|
||||
<div class="col-xs-12">
|
||||
{{ _("Mobile: ")+doc.contact_mobile if doc.contact_mobile else '' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-3"></div>
|
||||
<div class="col-xs-3" style="padding-left: 5px;">
|
||||
<div>
|
||||
<div><label>{{ _("Invoice ID") }}</label></div>
|
||||
<div>{{ doc.name }}</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
<div><label>{{ _("Invoice Date") }}</label></div>
|
||||
<div>{{ frappe.utils.format_date(doc.posting_date) }}</div>
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
<div><label>{{ _("Due Date") }}</label></div>
|
||||
<div>{{ frappe.utils.format_date(doc.due_date) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-break">
|
||||
<table class="table table-bordered table-condensed mb-0" style="width: 100%; border-collapse: collapse; font-size: 12px;">
|
||||
<colgroup>
|
||||
<col style="width: 5%">
|
||||
<col style="width: 45%">
|
||||
<col style="width: 10%">
|
||||
<col style="width: 20%">
|
||||
<col style="width: 20%">
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-uppercase" style="text-align:center">{{ _("Sr") }}</th>
|
||||
<th class="text-uppercase" style="text-align:center">{{ _("Details") }}</th>
|
||||
<th class="text-uppercase" style="text-align:center">{{ _("Qty") }}</th>
|
||||
<th class="text-uppercase" style="text-align:right">{{ _("Rate") }}</th>
|
||||
<th class="text-uppercase" style="text-align:right">{{ _("Amount") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
{% for item in doc.items %}
|
||||
<tr>
|
||||
<td style="text-align:center">{{ loop.index }}</td>
|
||||
<td>
|
||||
<b>{{ item.item_code }}: {{ item.item_name }}</b>
|
||||
{% if (item.description != item.item_name) %}
|
||||
<br>{{ item.description }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align: center;">
|
||||
{{ item.get_formatted("qty", 0) }}
|
||||
{{ item.get_formatted("uom", 0) }}
|
||||
</td>
|
||||
<td style="text-align: right;">{{ item.get_formatted("net_rate", doc) }}</td>
|
||||
<td style="text-align: right;">{{ item.get_formatted("net_amount", doc) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<!-- total -->
|
||||
<div class="row">
|
||||
|
||||
<div class="col-xs-6">
|
||||
<div>
|
||||
<label>{{ _("Amount in Words") }}</label>
|
||||
{{ doc.in_words }}
|
||||
</div>
|
||||
<div style="margin-top: 20px;">
|
||||
<label>{{ _("Payment Status") }}</label>
|
||||
{{ doc.status }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<div class="row section-break">
|
||||
<div class="col-xs-7"><div>{{ _("Sub Total") }}</div></div>
|
||||
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("net_total", doc) }}</div>
|
||||
</div>
|
||||
<div>
|
||||
{% for d in doc.taxes %}
|
||||
{% if d.tax_amount %}
|
||||
<div class="row">
|
||||
<div class="col-xs-8"><div>{{ _(d.description) }}</div></div>
|
||||
<div class="col-xs-4" style="text-align: right;">{{ d.get_formatted("tax_amount") }}</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-7"><div>{{ _("Total") }}</div></div>
|
||||
<div class="col-xs-5" style="text-align: right;">{{ doc.get_formatted("grand_total", doc) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="row important data-field">
|
||||
<div class="col-xs-12"><label>{{ _("Terms and Conditions") }}: </label></div>
|
||||
<div class="col-xs-12">{{ doc.terms if doc.terms else '' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -1,32 +0,0 @@
|
||||
{
|
||||
"absolute_value": 0,
|
||||
"align_labels_right": 0,
|
||||
"creation": "2025-01-22 16:23:51.012200",
|
||||
"css": "",
|
||||
"custom_format": 0,
|
||||
"default_print_language": "en",
|
||||
"disabled": 0,
|
||||
"doc_type": "Sales Invoice",
|
||||
"docstatus": 0,
|
||||
"doctype": "Print Format",
|
||||
"font": "",
|
||||
"font_size": 14,
|
||||
"idx": 0,
|
||||
"line_breaks": 0,
|
||||
"margin_bottom": 0.0,
|
||||
"margin_left": 0.0,
|
||||
"margin_right": 0.0,
|
||||
"margin_top": 0.0,
|
||||
"modified": "2025-01-22 16:23:51.012200",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Print",
|
||||
"owner": "Administrator",
|
||||
"page_number": "Hide",
|
||||
"print_format_builder": 0,
|
||||
"print_format_builder_beta": 0,
|
||||
"print_format_type": "Jinja",
|
||||
"raw_printing": 0,
|
||||
"show_section_headings": 0,
|
||||
"standard": "Yes"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1,227 @@
|
||||
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
|
||||
<style type="text/css">
|
||||
body, html {
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.title-letter-spacing {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.report-table tbody td {
|
||||
padding: 6px 8px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.text-left { text-align: left; }
|
||||
.text-bold { font-weight: 700; }
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
thead { display: table-header-group; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __(report.report_name) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div>
|
||||
<strong>{%= __("Supplier") %}:</strong>
|
||||
{%= (filters.party.length && filters.party.join(", ")) || __("All Parties") %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div>
|
||||
<strong>{%= __("Report Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.report_date) %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
|
||||
<th style="text-align: left;">{%= __("Reference") %}</th>
|
||||
|
||||
{% if(filters.show_remarks) { %}
|
||||
<th style="text-align: left;">{%= __("Remarks") %}</th>
|
||||
{% } %}
|
||||
|
||||
<th style="width: 10em; text-align: right;">{%= __("Age (Days)") %}</th>
|
||||
<th style="width: 10em; text-align: right;">{%= __("Invoiced Amount") %}</th>
|
||||
<th style="width: 11em; text-align: right;">{%= __("Outstanding Amount") %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||
<tr>
|
||||
<td class="text-left">{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
|
||||
|
||||
<td class="{% if(i == data.length - 1) { %}text-left text-bold{% } %}">
|
||||
{% if(i == data.length - 1) { %}
|
||||
{%= __("Total") %}
|
||||
{% } else { %}
|
||||
{%= data[i]["voucher_no"] %}
|
||||
{% } %}
|
||||
</td>
|
||||
|
||||
{% if(filters.show_remarks) { %}
|
||||
<td class="text-left">
|
||||
{% if(data[i]["remarks"] && data[i]["remarks"] != "No Remarks") { %}
|
||||
{%= data[i]["remarks"] %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
|
||||
<td class="text-right">{%= data[i]["age"] %}</td>
|
||||
<td class="text-right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
|
||||
<td class="text-right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if(filters.show_future_payments) { %}
|
||||
{%
|
||||
var balance_row = data.slice(-1).pop();
|
||||
var start = report.columns.findIndex(e => e.fieldname == 'age');
|
||||
var currency = data[data.length - 1]["currency"];
|
||||
|
||||
var ranges = [
|
||||
report.columns[start].label,
|
||||
report.columns[start+1].label,
|
||||
report.columns[start+2].label,
|
||||
report.columns[start+3].label,
|
||||
report.columns[start+4].label,
|
||||
report.columns[start+5].label
|
||||
];
|
||||
%}
|
||||
|
||||
{% if(balance_row) { %}
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: right;"></th>
|
||||
{% for(var i = 0; i < ranges.length; i++) { %}
|
||||
<th style="text-align: right;">{%= __(ranges[i]) %}</th>
|
||||
{% } %}
|
||||
<th style="text-align: right;">{%= __("Total") %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{%= __("Total Outstanding") %}</td>
|
||||
<td class="text-right">{%= format_number(balance_row["age"], null, 2) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range1"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range2"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range3"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range4"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range5"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(flt(balance_row["outstanding"]), currency) %}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% } %}
|
||||
{% } %}
|
||||
|
||||
<p class="text-right">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -1 +1,180 @@
|
||||
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
|
||||
<style type="text/css">
|
||||
body, html {
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.title-letter-spacing {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.report-table tbody td {
|
||||
padding: 6px 8px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.text-left { text-align: left; }
|
||||
.text-bold { font-weight: 700; }
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
thead { display: table-header-group; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __(report.report_name) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div>
|
||||
<strong>{%= __("Supplier") %}:</strong>
|
||||
{%= (filters.party && filters.party.length && filters.party.join(", ")) || __("All Parties") %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div>
|
||||
<strong>{%= __("Ageing Based On") %}:</strong>
|
||||
{%= __(filters.ageing_based_on) %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("As on Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.report_date) %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">{%= __("Supplier") %}</th>
|
||||
<th class="text-right">{%= __("Total Invoiced Amount") %}</th>
|
||||
<th class="text-right">{%= __("Total Paid Amount") %}</th>
|
||||
<th class="text-right">{%= __("Debit Note Amount") %}</th>
|
||||
<th class="text-right">{%= __("Total Outstanding Amount") %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for (var i = 0, l = data.length; i < l; i++) {
|
||||
var row = data[i];
|
||||
if (!(row.party || row.is_total_row)) continue;
|
||||
%}
|
||||
<tr>
|
||||
<td class="{% if (row.is_total_row) { %}text-bold{% } %}">
|
||||
{% if (row.is_total_row) { %}
|
||||
{%= __("Total") %}
|
||||
{% } else { %}
|
||||
{%= row.party %}
|
||||
{% } %}
|
||||
</td>
|
||||
|
||||
<td class="text-right">
|
||||
{%= format_currency(row.invoiced, row.currency) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(row.paid, row.currency) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(row.debit_note, row.currency) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(row.outstanding, row.currency) %}
|
||||
</td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-right">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -1,291 +1,225 @@
|
||||
<style>
|
||||
.print-format {
|
||||
padding: 4mm;
|
||||
font-size: 8.0pt !important;
|
||||
}
|
||||
.print-format td {
|
||||
vertical-align:middle !important;
|
||||
}
|
||||
</style>
|
||||
<style type="text/css">
|
||||
body, html {
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
|
||||
<h4 class="text-center">
|
||||
{% if (filters.party) { %}
|
||||
{%= __(filters.party) %}
|
||||
{% } %}
|
||||
</h4>
|
||||
<h6 class="text-center">
|
||||
{% if (filters.tax_id) { %}
|
||||
{%= __("Tax Id: ")%} {%= filters.tax_id %}
|
||||
{% } %}
|
||||
</h6>
|
||||
<h5 class="text-center">
|
||||
{%= __(filters.ageing_based_on) %}
|
||||
{%= __("Until") %}
|
||||
{%= frappe.datetime.str_to_user(filters.report_date) %}
|
||||
</h5>
|
||||
.title-letter-spacing {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
<div class="clearfix">
|
||||
<div class="pull-left">
|
||||
{% if(filters.payment_terms) { %}
|
||||
<strong>{%= __("Payment Terms") %}:</strong> {%= filters.payment_terms %}
|
||||
{% } %}
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if(filters.credit_limit) { %}
|
||||
<strong>{%= __("Credit Limit") %}:</strong> {%= format_currency(filters.credit_limit) %}
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
{% if(filters.show_future_payments) { %}
|
||||
{% var balance_row = data.slice(-1).pop();
|
||||
var start = report.columns.findIndex((elem) => (elem.fieldname == 'age'));
|
||||
var range1 = report.columns[start].label;
|
||||
var range2 = report.columns[start+1].label;
|
||||
var range3 = report.columns[start+2].label;
|
||||
var range4 = report.columns[start+3].label;
|
||||
var range5 = report.columns[start+4].label;
|
||||
var range6 = report.columns[start+5].label;
|
||||
%}
|
||||
{% if(balance_row) { %}
|
||||
<table class="table table-bordered table-condensed">
|
||||
<caption class="text-right">(Amount in {%= data[0]["currency"] || "" %})</caption>
|
||||
<colgroup>
|
||||
<col style="width: 30mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
</colgroup>
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.report-table tbody td {
|
||||
padding: 6px 8px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.text-left { text-align: left; }
|
||||
|
||||
.text-bold { font-weight: 700;}
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
thead { display: table-header-group; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __(report.report_name) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div>
|
||||
<strong>{%= __("Customer") %}:</strong>
|
||||
{%= (filters.party.length && filters.party.join(", ")) || __("All Parties") %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div>
|
||||
<strong>{%= __("Report Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.report_date) %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{%= __(" ") %}</th>
|
||||
<th>{%= __(range1) %}</th>
|
||||
<th>{%= __(range2) %}</th>
|
||||
<th>{%= __(range3) %}</th>
|
||||
<th>{%= __(range4) %}</th>
|
||||
<th>{%= __(range5) %}</th>
|
||||
<th>{%= __(range6) %}</th>
|
||||
<th>{%= __("Total") %}</th>
|
||||
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
|
||||
<th style="text-align: left;">{%= __("Reference") %}</th>
|
||||
|
||||
{% if(filters.show_remarks) { %}
|
||||
<th style="text-align: left;">{%= __("Remarks") %}</th>
|
||||
{% } %}
|
||||
|
||||
<th style="width: 10em; text-align: right;">{%= __("Age (Days)") %}</th>
|
||||
<th style="width: 10em; text-align: right;">{%= __("Invoiced Amount") %}</th>
|
||||
<th style="width: 11em; text-align: right;">{%= __("Outstanding Amount") %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{%= __("Total Outstanding") %}</td>
|
||||
<td class="text-right">
|
||||
{%= format_number(balance_row["age"], null, 2) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(balance_row["range1"], data[data.length-1]["currency"]) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(balance_row["range2"], data[data.length-1]["currency"]) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(balance_row["range3"], data[data.length-1]["currency"]) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(balance_row["range4"], data[data.length-1]["currency"]) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(balance_row["range5"], data[data.length-1]["currency"]) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) %}
|
||||
</td>
|
||||
</tr>
|
||||
<td>{%= __("Future Payments") %}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) %}
|
||||
</td>
|
||||
<tr class="cvs-footer">
|
||||
<th class="text-left">{%= __("Cheques Required") %}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th class="text-right">
|
||||
{%= format_currency(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) %}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
<tbody>
|
||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||
<tr>
|
||||
<td class="text-left">{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
|
||||
<td class="{% if(i == data.length - 1) { %}text-left text-bold{% } %}">
|
||||
{% if(i == data.length - 1) { %}
|
||||
{%= __("Total") %}
|
||||
{% } else { %}
|
||||
{%= data[i]["voucher_no"] %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% if(filters.show_remarks) { %}
|
||||
<td class="text-left">
|
||||
{% if(data[i]["remarks"] && data[i]["remarks"] != "No Remarks") { %}
|
||||
{%= data[i]["remarks"] %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
<td class="text-right">{%= data[i]["age"] %}</td>
|
||||
<td class="text-right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
|
||||
<td class="text-right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{% if(filters.show_future_payments) { %}
|
||||
{%
|
||||
var balance_row = data.slice(-1).pop();
|
||||
var start = report.columns.findIndex(e => e.fieldname == 'age');
|
||||
var currency = data[data.length - 1]["currency"];
|
||||
|
||||
var ranges = [
|
||||
report.columns[start].label,
|
||||
report.columns[start+1].label,
|
||||
report.columns[start+2].label,
|
||||
report.columns[start+3].label,
|
||||
report.columns[start+4].label,
|
||||
report.columns[start+5].label
|
||||
];
|
||||
%}
|
||||
|
||||
{% if(balance_row) { %}
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align: right;"></th>
|
||||
{% for(var i = 0; i < ranges.length; i++) { %}
|
||||
<th style="text-align: right;">{%= __(ranges[i]) %}</th>
|
||||
{% } %}
|
||||
<th style="text-align: right;">{%= __("Total") %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{%= __("Total Outstanding") %}</td>
|
||||
<td class="text-right">{%= format_number(balance_row["age"], null, 2) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range1"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range2"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range3"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range4"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(balance_row["range5"], currency) %}</td>
|
||||
<td class="text-right">{%= format_currency(flt(balance_row["outstanding"]), currency) %}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% } %}
|
||||
{% } %}
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
|
||||
<th style="width: 10%">{%= __("Date") %}</th>
|
||||
<th style="width: 4%">{%= __("Age (Days)") %}</th>
|
||||
|
||||
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
|
||||
<th style="width: 14%">{%= __("Reference") %}</th>
|
||||
<th style="width: 10%">{%= __("Sales Person") %}</th>
|
||||
{% } else { %}
|
||||
<th style="width: 24%">{%= __("Reference") %}</th>
|
||||
{% } %}
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
<th style="width: 20%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
|
||||
{% } %}
|
||||
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
<th style="width: 10%; text-align: right">{%= __("Paid Amount") %}</th>
|
||||
<th style="width: 10%; text-align: right">{%= report.report_name === "Accounts Receivable" ? __('Credit Note') : __('Debit Note') %}</th>
|
||||
{% } %}
|
||||
<th style="width: 10%; text-align: right">{%= __("Outstanding Amount") %}</th>
|
||||
{% if(filters.show_future_payments) { %}
|
||||
{% if(report.report_name === "Accounts Receivable") { %}
|
||||
<th style="width: 12%">{%= __("Customer LPO No.") %}</th>
|
||||
{% } %}
|
||||
<th style="width: 10%">{%= __("Future Payment Ref") %}</th>
|
||||
<th style="width: 10%">{%= __("Future Payment Amount") %}</th>
|
||||
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
|
||||
{% } %}
|
||||
{% } else { %}
|
||||
<th style="width: 40%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
|
||||
<th style="width: 15%">{%= __("Total Invoiced Amount") %}</th>
|
||||
<th style="width: 15%">{%= __("Total Paid Amount") %}</th>
|
||||
<th style="width: 15%">{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}</th>
|
||||
<th style="width: 15%">{%= __("Total Outstanding Amount") %}</th>
|
||||
{% } %}
|
||||
</tr>
|
||||
</thead>
|
||||
<div class="show-filters">
|
||||
{% if subtitle %}
|
||||
{{ subtitle }}
|
||||
<hr>
|
||||
{% endif %}
|
||||
</div>
|
||||
<tbody>
|
||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||
<tr>
|
||||
{% if(report.report_name === "Accounts Receivable" || report.report_name === "Accounts Payable") { %}
|
||||
{% if(data[i]["party"]) { %}
|
||||
<td>{%= frappe.datetime.str_to_user(data[i]["posting_date"]) %}</td>
|
||||
<td style="text-align: right">{%= data[i]["age"] %}</td>
|
||||
<td>
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
{%= data[i]["voucher_type"] %}
|
||||
<br>
|
||||
{% } %}
|
||||
{%= data[i]["voucher_no"] %}
|
||||
</td>
|
||||
<p class="text-right">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
|
||||
<td>{%= data[i]["sales_person"] %}</td>
|
||||
{% } %}
|
||||
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
<td>
|
||||
{% if(!filters.party?.length) { %}
|
||||
{%= data[i]["party"] %}
|
||||
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
|
||||
<br> {%= data[i]["customer_name"] %}
|
||||
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
|
||||
<br> {%= data[i]["supplier_name"] %}
|
||||
{% } %}
|
||||
{% } %}
|
||||
<div>
|
||||
{% if data[i]["remarks"] %}
|
||||
{%= __("Remarks") %}:
|
||||
{%= data[i]["remarks"] %}
|
||||
{% } %}
|
||||
</div>
|
||||
</td>
|
||||
{% } %}
|
||||
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
|
||||
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
|
||||
{% } %}
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||
|
||||
{% if(filters.show_future_payments) { %}
|
||||
{% if(report.report_name === "Accounts Receivable") { %}
|
||||
<td style="text-align: right">
|
||||
{%= data[i]["po_no"] %}</td>
|
||||
{% } %}
|
||||
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
|
||||
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
|
||||
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
|
||||
{% } %}
|
||||
{% } else { %}
|
||||
<td></td>
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
<td></td>
|
||||
{% } %}
|
||||
{% if(report.report_name === "Accounts Receivable" && filters.show_sales_person) { %}
|
||||
<td></td>
|
||||
{% } %}
|
||||
<td></td>
|
||||
<td style="text-align: right"><b>{%= __("Total") %}</b></td>
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i]["invoiced"], data[i]["currency"] ) %}</td>
|
||||
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
|
||||
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %} </td>
|
||||
{% } %}
|
||||
<td style="text-align: right">
|
||||
{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||
|
||||
{% if(filters.show_future_payments) { %}
|
||||
{% if(report.report_name === "Accounts Receivable") { %}
|
||||
<td style="text-align: right">
|
||||
{%= data[i]["po_no"] %}</td>
|
||||
{% } %}
|
||||
<td style="text-align: right">{%= data[i]["future_ref"] %}</td>
|
||||
<td style="text-align: right">{%= format_currency(data[i]["future_amount"], data[i]["currency"]) %}</td>
|
||||
<td style="text-align: right">{%= format_currency(data[i]["remaining_balance"], data[i]["currency"]) %}</td>
|
||||
{% } %}
|
||||
{% } %}
|
||||
{% } else { %}
|
||||
{% if(data[i]["party"]|| " ") { %}
|
||||
{% if(!data[i]["is_total_row"]) { %}
|
||||
<td>
|
||||
{% if(!filters.party?.length) { %}
|
||||
{%= data[i]["party"] %}
|
||||
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
|
||||
<br> {%= data[i]["customer_name"] %}
|
||||
{% } else if(data[i]["supplier_name"] != data[i]["party"]) { %}
|
||||
<br> {%= data[i]["supplier_name"] %}
|
||||
{% } %}
|
||||
{% } %}
|
||||
<br>{%= __("Remarks") %}:
|
||||
{%= data[i]["remarks"] %}
|
||||
</td>
|
||||
{% } else { %}
|
||||
<td><b>{%= __("Total") %}</b></td>
|
||||
{% } %}
|
||||
<td style="text-align: right">{%= format_currency(data[i]["invoiced"], data[i]["currency"]) %}</td>
|
||||
<td style="text-align: right">{%= format_currency(data[i]["paid"], data[i]["currency"]) %}</td>
|
||||
<td style="text-align: right">{%= format_currency(data[i]["credit_note"], data[i]["currency"]) %}</td>
|
||||
<td style="text-align: right">{%= format_currency(data[i]["outstanding"], data[i]["currency"]) %}</td>
|
||||
{% } %}
|
||||
{% } %}
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>
|
||||
</div>
|
||||
@@ -131,8 +131,6 @@ class ReceivablePayableReport:
|
||||
self.fetch_ple_in_buffered_cursor()
|
||||
elif self.ple_fetch_method == "UnBuffered Cursor":
|
||||
self.fetch_ple_in_unbuffered_cursor()
|
||||
elif self.ple_fetch_method == "Raw SQL":
|
||||
self.fetch_ple_in_sql_procedures()
|
||||
|
||||
# Build delivery note map against all sales invoices
|
||||
self.build_delivery_note_map()
|
||||
@@ -323,81 +321,6 @@ class ReceivablePayableReport:
|
||||
row.paid -= amount
|
||||
row.paid_in_account_currency -= amount_in_account_currency
|
||||
|
||||
def fetch_ple_in_sql_procedures(self):
|
||||
self.proc = InitSQLProceduresForAR()
|
||||
|
||||
build_balance = f"""
|
||||
begin not atomic
|
||||
declare done boolean default false;
|
||||
declare rec1 row type of `{self.proc._row_def_table_name}`;
|
||||
declare ple cursor for {self.ple_query.get_sql()};
|
||||
declare continue handler for not found set done = true;
|
||||
|
||||
open ple;
|
||||
fetch ple into rec1;
|
||||
while not done do
|
||||
call {self.proc.init_procedure_name}(rec1);
|
||||
fetch ple into rec1;
|
||||
end while;
|
||||
close ple;
|
||||
|
||||
set done = false;
|
||||
open ple;
|
||||
fetch ple into rec1;
|
||||
while not done do
|
||||
call {self.proc.allocate_procedure_name}(rec1);
|
||||
fetch ple into rec1;
|
||||
end while;
|
||||
close ple;
|
||||
end;
|
||||
"""
|
||||
frappe.db.sql(build_balance)
|
||||
|
||||
balances = frappe.db.sql(
|
||||
f"""select
|
||||
name,
|
||||
voucher_type,
|
||||
voucher_no,
|
||||
party,
|
||||
party_account `account`,
|
||||
posting_date,
|
||||
account_currency,
|
||||
cost_center,
|
||||
project,
|
||||
sum(invoiced) `invoiced`,
|
||||
sum(paid) `paid`,
|
||||
sum(credit_note) `credit_note`,
|
||||
sum(invoiced) - sum(paid) - sum(credit_note) `outstanding`,
|
||||
sum(invoiced_in_account_currency) `invoiced_in_account_currency`,
|
||||
sum(paid_in_account_currency) `paid_in_account_currency`,
|
||||
sum(credit_note_in_account_currency) `credit_note_in_account_currency`,
|
||||
sum(invoiced_in_account_currency) - sum(paid_in_account_currency) - sum(credit_note_in_account_currency) `outstanding_in_account_currency`
|
||||
from `{self.proc._voucher_balance_name}` group by name order by posting_date;""",
|
||||
as_dict=True,
|
||||
)
|
||||
for x in balances:
|
||||
if self.filters.get("ignore_accounts"):
|
||||
key = (x.voucher_type, x.voucher_no, x.party)
|
||||
else:
|
||||
key = (x.account, x.voucher_type, x.voucher_no, x.party)
|
||||
|
||||
_d = self.build_voucher_dict(x)
|
||||
for field in [
|
||||
"invoiced",
|
||||
"paid",
|
||||
"credit_note",
|
||||
"outstanding",
|
||||
"invoiced_in_account_currency",
|
||||
"paid_in_account_currency",
|
||||
"credit_note_in_account_currency",
|
||||
"outstanding_in_account_currency",
|
||||
"cost_center",
|
||||
"project",
|
||||
]:
|
||||
_d[field] = x.get(field)
|
||||
|
||||
self.voucher_balance[key] = _d
|
||||
|
||||
def update_sub_total_row(self, row, party):
|
||||
total_row = self.total_row_map.get(party)
|
||||
|
||||
@@ -1400,120 +1323,3 @@ def get_party_group_with_children(party, party_groups):
|
||||
frappe.throw(_("{0}: {1} does not exist").format(group_dtype, d))
|
||||
|
||||
return list(set(all_party_groups))
|
||||
|
||||
|
||||
class InitSQLProceduresForAR:
|
||||
"""
|
||||
Initialize SQL Procedures, Functions and Temporary tables to build Receivable / Payable report
|
||||
"""
|
||||
|
||||
_varchar_type = get_definition("Data")
|
||||
_currency_type = get_definition("Currency")
|
||||
# Temporary Tables
|
||||
_voucher_balance_name = "_ar_voucher_balance"
|
||||
_voucher_balance_definition = f"""
|
||||
create temporary table `{_voucher_balance_name}`(
|
||||
name {_varchar_type},
|
||||
voucher_type {_varchar_type},
|
||||
voucher_no {_varchar_type},
|
||||
party {_varchar_type},
|
||||
party_account {_varchar_type},
|
||||
posting_date date,
|
||||
account_currency {_varchar_type},
|
||||
cost_center {_varchar_type},
|
||||
project {_varchar_type},
|
||||
invoiced {_currency_type},
|
||||
paid {_currency_type},
|
||||
credit_note {_currency_type},
|
||||
invoiced_in_account_currency {_currency_type},
|
||||
paid_in_account_currency {_currency_type},
|
||||
credit_note_in_account_currency {_currency_type}) engine=memory;
|
||||
"""
|
||||
|
||||
_row_def_table_name = "_ar_ple_row"
|
||||
_row_def_table_definition = f"""
|
||||
create temporary table `{_row_def_table_name}`(
|
||||
name {_varchar_type},
|
||||
account {_varchar_type},
|
||||
voucher_type {_varchar_type},
|
||||
voucher_no {_varchar_type},
|
||||
against_voucher_type {_varchar_type},
|
||||
against_voucher_no {_varchar_type},
|
||||
party_type {_varchar_type},
|
||||
cost_center {_varchar_type},
|
||||
project {_varchar_type},
|
||||
party {_varchar_type},
|
||||
posting_date date,
|
||||
due_date date,
|
||||
account_currency {_varchar_type},
|
||||
amount {_currency_type},
|
||||
amount_in_account_currency {_currency_type}) engine=memory;
|
||||
"""
|
||||
|
||||
# Procedures
|
||||
init_procedure_name = "ar_init_tmp_table"
|
||||
init_procedure_sql = f"""
|
||||
create procedure ar_init_tmp_table(in ple row type of `{_row_def_table_name}`)
|
||||
begin
|
||||
if not exists (select name from `{_voucher_balance_name}` where name = sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)))
|
||||
then
|
||||
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.against_voucher_type, ple.against_voucher_no, ple.party)), ple.voucher_type, ple.voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency, ple.cost_center, ple.project, 0, 0, 0, 0, 0, 0);
|
||||
end if;
|
||||
end;
|
||||
"""
|
||||
|
||||
allocate_procedure_name = "ar_allocate_to_tmp_table"
|
||||
allocate_procedure_sql = f"""
|
||||
create procedure ar_allocate_to_tmp_table(in ple row type of `{_row_def_table_name}`)
|
||||
begin
|
||||
declare invoiced {_currency_type} default 0;
|
||||
declare invoiced_in_account_currency {_currency_type} default 0;
|
||||
declare paid {_currency_type} default 0;
|
||||
declare paid_in_account_currency {_currency_type} default 0;
|
||||
declare credit_note {_currency_type} default 0;
|
||||
declare credit_note_in_account_currency {_currency_type} default 0;
|
||||
|
||||
|
||||
if ple.amount > 0 then
|
||||
if (ple.voucher_type in ("Journal Entry", "Payment Entry") and (ple.voucher_no != ple.against_voucher_no)) then
|
||||
set paid = -1 * ple.amount;
|
||||
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
|
||||
else
|
||||
set invoiced = ple.amount;
|
||||
set invoiced_in_account_currency = ple.amount_in_account_currency;
|
||||
end if;
|
||||
else
|
||||
|
||||
if ple.voucher_type in ("Sales Invoice", "Purchase Invoice") then
|
||||
if (ple.voucher_no = ple.against_voucher_no) then
|
||||
set paid = -1 * ple.amount;
|
||||
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
|
||||
else
|
||||
set credit_note = -1 * ple.amount;
|
||||
set credit_note_in_account_currency = -1 * ple.amount_in_account_currency;
|
||||
end if;
|
||||
else
|
||||
set paid = -1 * ple.amount;
|
||||
set paid_in_account_currency = -1 * ple.amount_in_account_currency;
|
||||
end if;
|
||||
|
||||
end if;
|
||||
|
||||
insert into `{_voucher_balance_name}` values (sha1(concat_ws(',', ple.account, ple.voucher_type, ple.voucher_no, ple.party)), ple.against_voucher_type, ple.against_voucher_no, ple.party, ple.account, ple.posting_date, ple.account_currency,'', '', invoiced, paid, 0, invoiced_in_account_currency, paid_in_account_currency, 0);
|
||||
end;
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
existing_procedures = frappe.db.get_routines()
|
||||
|
||||
if self.init_procedure_name not in existing_procedures:
|
||||
frappe.db.sql(self.init_procedure_sql)
|
||||
|
||||
if self.allocate_procedure_name not in existing_procedures:
|
||||
frappe.db.sql(self.allocate_procedure_sql)
|
||||
|
||||
frappe.db.sql(f"drop table if exists `{self._voucher_balance_name}`")
|
||||
frappe.db.sql(self._voucher_balance_definition)
|
||||
|
||||
frappe.db.sql(f"drop table if exists `{self._row_def_table_name}`")
|
||||
frappe.db.sql(self._row_def_table_definition)
|
||||
|
||||
@@ -1 +1,180 @@
|
||||
{% include "accounts/report/accounts_receivable/accounts_receivable.html" %}
|
||||
<style type="text/css">
|
||||
body, html {
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.title-letter-spacing {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.report-table tbody td {
|
||||
padding: 6px 8px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.text-left { text-align: left; }
|
||||
.text-bold { font-weight: 700; }
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
thead { display: table-header-group; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __(report.report_name) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div>
|
||||
<strong>{%= __("Customer") %}:</strong>
|
||||
{%= (filters.party && filters.party.length && filters.party.join(", ")) || __("All Parties") %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div>
|
||||
<strong>{%= __("Ageing Based On") %}:</strong>
|
||||
{%= __(filters.ageing_based_on) %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("As on Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.report_date) %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left">{%= __("Customer") %}</th>
|
||||
<th class="text-right">{%= __("Total Invoiced Amount") %}</th>
|
||||
<th class="text-right">{%= __("Total Paid Amount") %}</th>
|
||||
<th class="text-right">{%= __("Credit Note Amount") %}</th>
|
||||
<th class="text-right">{%= __("Total Outstanding Amount") %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for (var i = 0, l = data.length; i < l; i++) {
|
||||
var row = data[i];
|
||||
if (!(row.party || row.is_total_row)) continue;
|
||||
%}
|
||||
<tr>
|
||||
<td class="{% if (row.is_total_row) { %}text-bold{% } %}">
|
||||
{% if (row.is_total_row) { %}
|
||||
{%= __("Total") %}
|
||||
{% } else { %}
|
||||
{%= row.party %}
|
||||
{% } %}
|
||||
</td>
|
||||
|
||||
<td class="text-right">
|
||||
{%= format_currency(row.invoiced, row.currency) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(row.paid, row.currency) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(row.credit_note, row.currency) %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{%= format_currency(row.outstanding, row.currency) %}
|
||||
</td>
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-right">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -1 +1,224 @@
|
||||
{% include "accounts/report/financial_statements.html" %}
|
||||
{%
|
||||
const report_columns = report
|
||||
.get_columns_for_print()
|
||||
.filter(col => !col.hidden);
|
||||
|
||||
if (report_columns.length > 8) {
|
||||
frappe.throw(
|
||||
__("Too many columns. Export the report and print it using a spreadsheet application.")
|
||||
);
|
||||
}
|
||||
%}
|
||||
|
||||
<style>
|
||||
body, html {
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.title-letter-spacing {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.financial-statements-important td { font-weight: bold; }
|
||||
.financial-statements-blank-row td { height: 20px; }
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-right {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.text-left { text-align: left; }
|
||||
.text-bold { font-weight: 700; }
|
||||
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.report-table th,
|
||||
.report-table td {
|
||||
padding: 6px 8px;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
.report-table tbody td {
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
thead { display: table-header-group; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __(report.report_name) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div>
|
||||
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("Currency") %}:</strong>
|
||||
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div>
|
||||
<strong>{%= __("Period Based On") %}:</strong>
|
||||
{%= filters.filter_based_on %}
|
||||
</div>
|
||||
|
||||
{% if (filters.filter_based_on === "Fiscal Year") { %}
|
||||
<div>
|
||||
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
|
||||
</div>
|
||||
|
||||
{% } else if (filters.filter_based_on === "Date Range") { %}
|
||||
<div>
|
||||
<strong>{%= __("Start Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("End Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
|
||||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
|
||||
{%
|
||||
const col = report_columns[i];
|
||||
const align = i === 0 ? "text-left" : "text-right";
|
||||
%}
|
||||
<th class="{%= align %}">
|
||||
{%= col.label %}
|
||||
</th>
|
||||
{% } %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for (let j = 0, k = data.length; j < k; j++) { %}
|
||||
{%
|
||||
const row = data[j];
|
||||
|
||||
let row_class = "";
|
||||
if (!(row.parent_account || row.parent_section)) {
|
||||
row_class = "financial-statements-important";
|
||||
}
|
||||
if (!(row.account_name || row.section)) {
|
||||
row_class += " financial-statements-blank-row";
|
||||
}
|
||||
%}
|
||||
|
||||
<tr class="{%= row_class %}">
|
||||
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
|
||||
{%
|
||||
const col = report_columns[i];
|
||||
const value = row[col.fieldname];
|
||||
const align = i === 0 ? "text-left" : "text-right";
|
||||
%}
|
||||
|
||||
<td class="{%= align %}">
|
||||
{% if (i === 0) { %}
|
||||
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
|
||||
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
|
||||
</span>
|
||||
{% } else if (!is_null(value)) { %}
|
||||
{%= frappe.format(value, col, {}, row) %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-right text-muted">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -1 +1,224 @@
|
||||
{% include "accounts/report/financial_statements.html" %}
|
||||
{%
|
||||
const report_columns = report
|
||||
.get_columns_for_print()
|
||||
.filter(col => !col.hidden);
|
||||
|
||||
if (report_columns.length > 8) {
|
||||
frappe.throw(
|
||||
__("Too many columns. Export the report and print it using a spreadsheet application.")
|
||||
);
|
||||
}
|
||||
%}
|
||||
|
||||
<style>
|
||||
body, html {
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.title-letter-spacing {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.financial-statements-important td { font-weight: bold; }
|
||||
.financial-statements-blank-row td { height: 20px; }
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-right {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.text-left { text-align: left; }
|
||||
.text-bold { font-weight: 700; }
|
||||
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.report-table th,
|
||||
.report-table td {
|
||||
padding: 6px 8px;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
.report-table tbody td {
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
thead { display: table-header-group; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __(report.report_name) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div>
|
||||
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("Currency") %}:</strong>
|
||||
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div>
|
||||
<strong>{%= __("Period Based On") %}:</strong>
|
||||
{%= filters.filter_based_on %}
|
||||
</div>
|
||||
|
||||
{% if (filters.filter_based_on === "Fiscal Year") { %}
|
||||
<div>
|
||||
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
|
||||
</div>
|
||||
|
||||
{% } else if (filters.filter_based_on === "Date Range") { %}
|
||||
<div>
|
||||
<strong>{%= __("Start Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("End Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
|
||||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
|
||||
{%
|
||||
const col = report_columns[i];
|
||||
const align = i === 0 ? "text-left" : "text-right";
|
||||
%}
|
||||
<th class="{%= align %}">
|
||||
{%= col.label %}
|
||||
</th>
|
||||
{% } %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for (let j = 0, k = data.length; j < k; j++) { %}
|
||||
{%
|
||||
const row = data[j];
|
||||
|
||||
let row_class = "";
|
||||
if (!(row.parent_account || row.parent_section)) {
|
||||
row_class = "financial-statements-important";
|
||||
}
|
||||
if (!(row.account_name || row.section)) {
|
||||
row_class += " financial-statements-blank-row";
|
||||
}
|
||||
%}
|
||||
|
||||
<tr class="{%= row_class %}">
|
||||
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
|
||||
{%
|
||||
const col = report_columns[i];
|
||||
const value = row[col.fieldname];
|
||||
const align = i === 0 ? "text-left" : "text-right";
|
||||
%}
|
||||
|
||||
<td class="{%= align %}">
|
||||
{% if (i === 0) { %}
|
||||
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
|
||||
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
|
||||
</span>
|
||||
{% } else if (!is_null(value)) { %}
|
||||
{%= frappe.format(value, col, {}, row) %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-right text-muted">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -1,51 +1,114 @@
|
||||
<!-- Modified on 25-11-2024
|
||||
-->
|
||||
|
||||
<style type="text/css">
|
||||
/* General styles for both screen display and print */
|
||||
body, html {
|
||||
margin-top: 10;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto; /* Allow content to expand */
|
||||
font-family: Arial, sans-serif; /* Example font */
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
/* Ensure consistent letter spacing across all media */
|
||||
.title-letter-spacing {
|
||||
letter-spacing: .2rem;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.report-table tbody td {
|
||||
padding: 6px 8px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.date-col {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
|
||||
.text-bold { font-weight: 700; }
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
color: #171717;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta .filter-row {
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
/* Styles specific to printing and PDF generation */
|
||||
@media print {
|
||||
/* Set page size and margins for printing */
|
||||
@page {
|
||||
size: A4; /* Use fixed A4 page size */
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
|
||||
/* Force a page break before elements with the class "page-break" */
|
||||
.page-break {
|
||||
page-break-before: always;
|
||||
margin-top: 10mm; /* Add some space after the break */
|
||||
}
|
||||
|
||||
/* Ensure table headers repeat on each printed page */
|
||||
thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
|
||||
/* Ensure table footers repeat on each printed page */
|
||||
tfoot {
|
||||
display: table-footer-group;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 1px;
|
||||
border: 1px solid black; /* Example border for clarity */
|
||||
tr {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
/* Hide elements that should not appear in print (optional) */
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
@@ -53,129 +116,154 @@
|
||||
</style>
|
||||
|
||||
<br>
|
||||
<div style="font-family:Arial">
|
||||
<div>
|
||||
<div class="title-letter-spacing" style="text-align:center; font-size:15px; text-decoration:underline;">
|
||||
<b>
|
||||
{%= __("STATEMENT OF ACCOUNTS") %}<br>
|
||||
{% if (filters.party_name) { %}
|
||||
<br>{%= filters.party_name %}
|
||||
{% } else if (filters.party && filters.party.length) { %}
|
||||
<br>{%= filters.party %}
|
||||
{% } else if (filters.account) { %}
|
||||
<br>{%= filters.account %}
|
||||
{% } else { %}
|
||||
<br>{%= __("All Parties ") %}
|
||||
{% } %}
|
||||
</b>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __("Statement Of Accounts") %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div class="filter-row">
|
||||
<strong>{%= __("Customer") %}:</strong>
|
||||
{%=
|
||||
(filters.party.length && filters.party.join(", ")) || filters.party_name || "All Parties"
|
||||
%}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div class="filter-row">
|
||||
<strong>{%= __("Statement Period") %}:</strong>
|
||||
{%= __("{0} to {1}", [
|
||||
frappe.datetime.str_to_user(filters.from_date),
|
||||
frappe.datetime.str_to_user(filters.to_date)
|
||||
]) %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align:center; font-size:13px;">
|
||||
<b>
|
||||
{%= __("{0} to {1}", [frappe.datetime.str_to_user(filters.from_date), frappe.datetime.str_to_user(filters.to_date)]) %}<br><br>
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="show-filters">
|
||||
{% if subtitle %}
|
||||
{{ subtitle }}
|
||||
<hr>
|
||||
{% endif %}
|
||||
</div>
|
||||
<table style="width:100%; font-size: 11px">
|
||||
<thead>
|
||||
<tr class="title-letter-spacing" style="text-align: center; font-weight:bold">
|
||||
<td style="border: 1.5px solid black; width: 7em">{%= __("Date").toLocaleUpperCase() %}</td>
|
||||
<td style="border: 1.5px solid black">{%= __("Particulars").toLocaleUpperCase() %}</td>
|
||||
{% if(filters.show_remarks) { %}
|
||||
<td style="border: 1.5px solid black">{%= __("Remarks").toLocaleUpperCase() %}</td>
|
||||
{% } %}
|
||||
<td style="border: 1.5px solid black; width: 9em">{%= __("Debit").toLocaleUpperCase() %}</td>
|
||||
<td style="border: 1.5px solid black; width: 9em">{%= __("Credit").toLocaleUpperCase() %}</td>
|
||||
<td style="border: 1.5px solid black; width: 10.2em">{%= __("Balance").toLocaleUpperCase() %}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||
<tr style="border-bottom: 1px solid black">
|
||||
{% if(data[i].posting_date) { %}
|
||||
<td style="text-align: center; border: 1px dotted black">
|
||||
{%= frappe.datetime.str_to_user(data[i].posting_date) %}
|
||||
</td>
|
||||
<td style="border-right: 1px dotted black">
|
||||
{%= data[i].voucher_type %} {%= data[i].voucher_no %}
|
||||
{% if(!(filters.party || filters.account)) { %}
|
||||
{%= data[i].party || data[i].account %}
|
||||
{% } %}<br>
|
||||
{% if(data[i].bill_no) { %}
|
||||
{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% if(filters.show_remarks) { %}
|
||||
<td style="border-right: 1px dotted black; font-size: 10px">
|
||||
{% if(data[i].remarks != "No Remarks" && data[i].remarks != "") { %}
|
||||
{%= __("Remarks") %}: {%= data[i].remarks %}<br>
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
<td style="text-align: right; border-right: 1px dotted black">
|
||||
{% if data[i].debit != 0 %}
|
||||
{%= format_currency(data[i].debit, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
</td>
|
||||
<td style="text-align: right; border-right: 1px dotted black">
|
||||
{% if data[i].credit != 0 %}
|
||||
{%= format_currency(data[i].credit, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } else { %}
|
||||
<td style="text-align: center; border: 1px dotted black">
|
||||
{% if(i == 0) { %}
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 8em; text-align: left;">{%= __("Date") %}</th>
|
||||
<th style="text-align: left;">{%= __("Voucher Details") %}</th>
|
||||
|
||||
{% if(filters.show_remarks) { %}
|
||||
<th style="text-align: left;">{%= __("Remarks") %}</th>
|
||||
{% } %}
|
||||
|
||||
<th style="width: 10em; text-align: right;">{%= __("Debit") %}</th>
|
||||
<th style="width: 10em; text-align: right;">{%= __("Credit") %}</th>
|
||||
<th style="width: 10em; text-align: right;">{%= __("Balance") %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for(var i=0, l=data.length; i<l; i++) { %}
|
||||
{% var row = data[i]; %}
|
||||
{% var is_entry = row.posting_date; %}
|
||||
{% var is_last = i == l-1; %}
|
||||
{% var is_second_last = i == l-2; %}
|
||||
|
||||
<tr>
|
||||
|
||||
<td class="text-left date-col">
|
||||
{% if(is_entry) { %}
|
||||
{%= frappe.datetime.str_to_user(row.posting_date) %}
|
||||
{% } else if(i == 0) { %}
|
||||
{%= frappe.datetime.str_to_user(filters.from_date) %}
|
||||
{% } %}
|
||||
</td>
|
||||
<td style="text-align: left; border-right: 1px dotted black"><b>
|
||||
{% if(i == l-2) { %}
|
||||
{%= __("Total") %}
|
||||
|
||||
<td class="{% if(!is_entry) { %}text-left text-bold{% } %}">
|
||||
{% if(is_entry) { %}
|
||||
|
||||
{%= row.voucher_type %} {%= row.voucher_no %}
|
||||
|
||||
{% if(!(filters.party || filters.account)) { %}
|
||||
<div style="margin-top: 2px;">
|
||||
{%= row.party || row.account %}
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
{% if(row.bill_no) { %}
|
||||
<div style="margin-top: 2px;">
|
||||
{%= __("Supplier Invoice No") %}: {%= row.bill_no %}
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
{% } else { %}
|
||||
{% if(i == l-1) { %}
|
||||
|
||||
{% if(is_second_last) { %}
|
||||
{%= __("Total") %}
|
||||
{% } else if(is_last) { %}
|
||||
{%= __("Closing [Opening + Total] ") %}
|
||||
{% } else { %}
|
||||
{%= frappe.format(data[i].account, {fieldtype: "Link"}) || " " %}
|
||||
{% } %}
|
||||
{% } %}</b>
|
||||
</td>
|
||||
{% if(filters.show_remarks) { %} <td style="text-align: left; border-right: 1px dotted black"></td>{% } %}
|
||||
<td style="text-align: right; border-right: 1px dotted black">
|
||||
{% if(i != 0){ %}
|
||||
{% if(i != l-1){ %}
|
||||
{%= data[i].account && format_currency(data[i].debit, filters.presentation_currency) %}
|
||||
{%= frappe.format(row.account, {fieldtype: "Link"}) || " " %}
|
||||
{% } %}
|
||||
|
||||
{% } %}
|
||||
</td>
|
||||
<td style="text-align: right; border-right: 1px dotted black">
|
||||
{% if(i != 0){ %}
|
||||
{% if(i != l-1){ %}
|
||||
{%= data[i].account && format_currency(data[i].credit, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
|
||||
{% if(filters.show_remarks) { %}
|
||||
<td class="text-left">
|
||||
{% if(is_entry && row.remarks && row.remarks != "No Remarks") { %}
|
||||
{%= row.remarks %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
{% if(i == l-1) { %}
|
||||
<td style="text-align: right; font-weight:bold; border-right: 1px dotted black">
|
||||
{%= format_currency(data[i].balance, filters.presentation_currency) %}
|
||||
{% if(data[i].balance < 0){ %}Cr{% } %}
|
||||
{% if(data[i].balance > 0){ %}Dr{% } %}
|
||||
</td>
|
||||
{% } else { %}
|
||||
<td style="text-align: right; border-right: 1px dotted black">
|
||||
{% if(i != l-2) { %}
|
||||
{%= format_currency(data[i].balance, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
</tr>
|
||||
{% endfor%}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-right text-muted">{%= __("Printed on {0}", [frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())]) %}</p>
|
||||
</div>
|
||||
|
||||
<td class="text-right">
|
||||
{% if(is_entry) { %}
|
||||
{% if(row.debit != 0) { %}
|
||||
{%= format_currency(row.debit, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
{% } else if(i != 0 && !is_last) { %}
|
||||
{%= row.account && format_currency(row.debit, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
</td>
|
||||
|
||||
<td class="text-right">
|
||||
{% if(is_entry) { %}
|
||||
{% if(row.credit != 0) { %}
|
||||
{%= format_currency(row.credit, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
{% } else if(i != 0 && !is_last) { %}
|
||||
{%= row.account && format_currency(row.credit, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
</td>
|
||||
|
||||
<td class="text-right {% if(is_last) { %}text-bold{% } %}">
|
||||
{% if(is_last) { %}
|
||||
{%= format_currency(row.balance, filters.presentation_currency) %}
|
||||
{% if(row.balance < 0) { %} Cr{% } %}
|
||||
{% if(row.balance > 0) { %} Dr{% } %}
|
||||
{% } else { %}
|
||||
{%= format_currency(row.balance, filters.presentation_currency) %}
|
||||
{% } %}
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-right">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -177,10 +177,16 @@ frappe.query_reports["General Ledger"] = {
|
||||
fieldtype: "Check",
|
||||
default: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "disable_opening_balance_calculation",
|
||||
label: __("Disable Opening Balance Calculation"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
{
|
||||
fieldname: "show_opening_entries",
|
||||
label: __("Show Opening Entries"),
|
||||
fieldtype: "Check",
|
||||
depends_on: "eval: !doc.disable_opening_balance_calculation",
|
||||
},
|
||||
{
|
||||
fieldname: "include_default_book_entries",
|
||||
|
||||
@@ -283,7 +283,15 @@ def get_conditions(filters):
|
||||
if filters.get("party"):
|
||||
conditions.append("party in %(party)s")
|
||||
|
||||
if not (
|
||||
if filters.get("disable_opening_balance_calculation"):
|
||||
if not ignore_is_opening:
|
||||
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
|
||||
else:
|
||||
conditions.append("posting_date >=%(from_date)s")
|
||||
|
||||
# opening balance calculation is done only if filtered on account/party
|
||||
# so from_date filter is not applied
|
||||
elif not (
|
||||
filters.get("account")
|
||||
or filters.get("party")
|
||||
or filters.get("categorize_by") in ["Categorize by Account", "Categorize by Party"]
|
||||
@@ -417,7 +425,13 @@ def get_data_with_opening_closing(filters, account_details, accounting_dimension
|
||||
# Opening for filtered account
|
||||
add_total_to_data(totals, "opening")
|
||||
|
||||
if filters.get("categorize_by") != "Categorize by Voucher (Consolidated)":
|
||||
if not filters.get("categorize_by"):
|
||||
all_entries = []
|
||||
for acc_dict in gle_map.values():
|
||||
all_entries.extend(acc_dict.entries)
|
||||
data += all_entries
|
||||
|
||||
elif filters.get("categorize_by") != "Categorize by Voucher (Consolidated)":
|
||||
set_opening_closing = (not filters.get("categorize_by") and not filters.get("voucher_no")) or (
|
||||
filters.get("categorize_by") and filters.get("categorize_by") != "Categorize by Voucher"
|
||||
)
|
||||
@@ -483,7 +497,6 @@ def initialize_gle_map(gl_entries, filters):
|
||||
totals=get_totals_dict(),
|
||||
entries=[],
|
||||
)
|
||||
|
||||
return gle_map
|
||||
|
||||
|
||||
@@ -548,7 +561,11 @@ def get_accountwise_gle(filters, accounting_dimensions, gl_entries, gle_map):
|
||||
gle.remarks = _(gle.remarks)
|
||||
gle.party_type = _(gle.party_type)
|
||||
|
||||
if gle.posting_date < from_date or (cstr(gle.is_opening) == "Yes" and not show_opening_entries):
|
||||
if gle.posting_date < from_date or (
|
||||
cstr(gle.is_opening) == "Yes"
|
||||
and not show_opening_entries
|
||||
and not filters.disable_opening_balance_calculation
|
||||
):
|
||||
if not group_by_voucher_consolidated:
|
||||
update_value_in_dict(gle_map[group_by_value].totals, "opening", gle, True)
|
||||
update_value_in_dict(gle_map[group_by_value].totals, "closing", gle, True)
|
||||
|
||||
@@ -812,19 +812,11 @@ class GrossProfitGenerator:
|
||||
return self.calculate_buying_amount_from_sle(
|
||||
row, my_sle, parenttype, parent, row.item_row, item_code
|
||||
)
|
||||
elif self.delivery_notes.get((row.parent, row.item_code), None):
|
||||
# check if Invoice has delivery notes
|
||||
dn = self.delivery_notes.get((row.parent, row.item_code))
|
||||
parenttype, parent, item_row, dn_warehouse = (
|
||||
"Delivery Note",
|
||||
dn["delivery_note"],
|
||||
dn["item_row"],
|
||||
dn["warehouse"],
|
||||
)
|
||||
my_sle = self.get_stock_ledger_entries(item_code, dn_warehouse)
|
||||
return self.calculate_buying_amount_from_sle(
|
||||
row, my_sle, parenttype, parent, item_row, item_code
|
||||
)
|
||||
elif row.item_row and self.delivery_notes.get(row.item_row):
|
||||
dn = self.delivery_notes[row.item_row]
|
||||
if flt(dn.total_qty):
|
||||
return flt(row.qty) * flt(dn.total_incoming_value) / flt(dn.total_qty)
|
||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||
elif row.sales_order and row.so_detail:
|
||||
incoming_amount = self.get_buying_amount_from_so_dn(row.sales_order, row.so_detail, item_code)
|
||||
if incoming_amount:
|
||||
@@ -1076,25 +1068,29 @@ class GrossProfitGenerator:
|
||||
def get_delivery_notes(self):
|
||||
self.delivery_notes = frappe._dict({})
|
||||
if self.si_list:
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
invoices = [x.parent for x in self.si_list]
|
||||
dni = qb.DocType("Delivery Note Item")
|
||||
delivery_notes = (
|
||||
qb.from_(dni)
|
||||
.select(
|
||||
dni.against_sales_invoice.as_("sales_invoice"),
|
||||
dni.item_code,
|
||||
dni.warehouse,
|
||||
dni.parent.as_("delivery_note"),
|
||||
dni.name.as_("item_row"),
|
||||
dni.si_detail,
|
||||
Sum(dni.stock_qty * dni.incoming_rate).as_("total_incoming_value"),
|
||||
Sum(dni.stock_qty).as_("total_qty"),
|
||||
)
|
||||
.where((dni.docstatus == 1) & (dni.against_sales_invoice.isin(invoices)))
|
||||
.groupby(dni.against_sales_invoice, dni.item_code)
|
||||
.orderby(dni.creation, order=Order.desc)
|
||||
.where(
|
||||
(dni.docstatus == 1)
|
||||
& (dni.against_sales_invoice.isin(invoices))
|
||||
& (dni.si_detail.isnotnull())
|
||||
& (dni.si_detail != "")
|
||||
)
|
||||
.groupby(dni.si_detail)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
for entry in delivery_notes:
|
||||
self.delivery_notes[(entry.sales_invoice, entry.item_code)] = entry
|
||||
self.delivery_notes[entry.si_detail] = entry
|
||||
|
||||
def group_items_by_invoice(self):
|
||||
"""
|
||||
|
||||
@@ -1 +1,224 @@
|
||||
{% include "accounts/report/financial_statements.html" %}
|
||||
{%
|
||||
const report_columns = report
|
||||
.get_columns_for_print()
|
||||
.filter(col => !col.hidden);
|
||||
|
||||
if (report_columns.length > 8) {
|
||||
frappe.throw(
|
||||
__("Too many columns. Export the report and print it using a spreadsheet application.")
|
||||
);
|
||||
}
|
||||
%}
|
||||
|
||||
<style>
|
||||
body, html {
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.title-letter-spacing {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.financial-statements-important td { font-weight: bold; }
|
||||
.financial-statements-blank-row td { height: 20px; }
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
.text-right {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.text-left { text-align: left; }
|
||||
.text-bold { font-weight: 700; }
|
||||
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.report-table th,
|
||||
.report-table td {
|
||||
padding: 6px 8px;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
.report-table tbody td {
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
thead { display: table-header-group; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __(report.report_name) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div>
|
||||
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("Currency") %}:</strong>
|
||||
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div>
|
||||
<strong>{%= __("Period Based On") %}:</strong>
|
||||
{%= filters.filter_based_on %}
|
||||
</div>
|
||||
|
||||
{% if (filters.filter_based_on === "Fiscal Year") { %}
|
||||
<div>
|
||||
<strong>{%= __("Start Year") %}:</strong> {%= filters.from_fiscal_year %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("End Year") %}:</strong> {%= filters.to_fiscal_year %}
|
||||
</div>
|
||||
|
||||
{% } else if (filters.filter_based_on === "Date Range") { %}
|
||||
<div>
|
||||
<strong>{%= __("Start Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.period_start_date) %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("End Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.period_end_date) %}
|
||||
</div>
|
||||
{% } %}
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
|
||||
{%
|
||||
const col = report_columns[i];
|
||||
const align = i === 0 ? "text-left" : "text-right";
|
||||
%}
|
||||
<th class="{%= align %}">
|
||||
{%= col.label %}
|
||||
</th>
|
||||
{% } %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for (let j = 0, k = data.length; j < k; j++) { %}
|
||||
{%
|
||||
const row = data[j];
|
||||
|
||||
let row_class = "";
|
||||
if (!(row.parent_account || row.parent_section)) {
|
||||
row_class = "financial-statements-important";
|
||||
}
|
||||
if (!(row.account_name || row.section)) {
|
||||
row_class += " financial-statements-blank-row";
|
||||
}
|
||||
%}
|
||||
|
||||
<tr class="{%= row_class %}">
|
||||
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
|
||||
{%
|
||||
const col = report_columns[i];
|
||||
const value = row[col.fieldname];
|
||||
const align = i === 0 ? "text-left" : "text-right";
|
||||
%}
|
||||
|
||||
<td class="{%= align %}">
|
||||
{% if (i === 0) { %}
|
||||
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
|
||||
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
|
||||
</span>
|
||||
{% } else if (!is_null(value)) { %}
|
||||
{%= frappe.format(value, col, {}, row) %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-right text-muted">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -1 +1,215 @@
|
||||
{% include "accounts/report/financial_statements.html" %}
|
||||
{%
|
||||
const report_columns = report
|
||||
.get_columns_for_print()
|
||||
.filter(col => !col.hidden);
|
||||
|
||||
if (report_columns.length > 8) {
|
||||
frappe.throw(
|
||||
__("Too many columns. Export the report and print it using a spreadsheet application.")
|
||||
);
|
||||
}
|
||||
%}
|
||||
|
||||
<style>
|
||||
body, html {
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
font-family: Inter, sans-serif;
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.title-letter-spacing {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #171717;
|
||||
}
|
||||
|
||||
.financial-statements-important td { font-weight: bold; }
|
||||
.financial-statements-blank-row td { height: 20px; }
|
||||
|
||||
.report-meta {
|
||||
margin: 10px 0 14px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.report-meta .left,
|
||||
.report-meta .right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.report-meta strong {
|
||||
color: #7c7c7c;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.report-subtitle {
|
||||
margin: 10px 0 14px;
|
||||
}
|
||||
|
||||
.text-center { text-align: center; }
|
||||
|
||||
.text-right {
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.text-left { text-align: left; }
|
||||
.text-bold { font-weight: 700; }
|
||||
|
||||
.report-table table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.report-table th,
|
||||
.report-table td {
|
||||
padding: 6px 8px;
|
||||
font-size: 14px;
|
||||
border-top: 1px solid #ededed;
|
||||
border-bottom: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th {
|
||||
background: #f8f8f8;
|
||||
font-weight: 500;
|
||||
color: #7c7c7c;
|
||||
}
|
||||
|
||||
.report-table tbody td.text-left {
|
||||
vertical-align: top;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.report-table thead th:first-child {
|
||||
border-left: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table thead th:last-child {
|
||||
border-right: 1px solid #ededed;
|
||||
}
|
||||
|
||||
.report-table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin-top: 10mm;
|
||||
}
|
||||
thead { display: table-header-group; }
|
||||
tr { page-break-inside: avoid; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>
|
||||
|
||||
<div class="text-center" style="margin-bottom: 12px;">
|
||||
<div class="title-letter-spacing">
|
||||
{%= __(report.report_name) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if (subtitle && subtitle.trim()) { %}
|
||||
<div class="report-subtitle">
|
||||
{{ subtitle }}
|
||||
</div>
|
||||
{% } else { %}
|
||||
<div class="report-meta">
|
||||
<div class="left">
|
||||
<div>
|
||||
<strong>{%= __("Company") %}:</strong> {%= filters.company %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("Currency") %}:</strong>
|
||||
{%= filters.presentation_currency || erpnext.get_currency(filters.company) %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right text-right">
|
||||
<div>
|
||||
<strong>{%= __("From Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.from_date) %}
|
||||
</div>
|
||||
<div>
|
||||
<strong>{%= __("To Date") %}:</strong>
|
||||
{%= frappe.datetime.str_to_user(filters.to_date) %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% } %}
|
||||
|
||||
<div class="report-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
|
||||
{%
|
||||
const col = report_columns[i];
|
||||
const align = i === 0 ? "text-left" : "text-right";
|
||||
const styling = i === 0 ? "" : "width: 9em";
|
||||
%}
|
||||
<th class="{%= align %}" style= "{%= styling%}">
|
||||
{%= col.label %}
|
||||
</th>
|
||||
{% } %}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for (let j = 0, k = data.length; j < k; j++) { %}
|
||||
{%
|
||||
const row = data[j];
|
||||
|
||||
let row_class = "";
|
||||
if (!(row.parent_account || row.parent_section)) {
|
||||
row_class = "financial-statements-important";
|
||||
}
|
||||
if (!(row.account_name || row.section)) {
|
||||
row_class += " financial-statements-blank-row";
|
||||
}
|
||||
%}
|
||||
|
||||
<tr class="{%= row_class %}">
|
||||
{% for (let i = 0, l = report_columns.length; i < l; i++) { %}
|
||||
{%
|
||||
const col = report_columns[i];
|
||||
const value = row[col.fieldname];
|
||||
const align = i === 0 ? "text-left" : "text-right";
|
||||
%}
|
||||
|
||||
<td class="{%= align %}">
|
||||
{% if (i === 0) { %}
|
||||
<span style="padding-left: {%= cint(row.indent) * 2 %}em">
|
||||
{%= String(row.account_name || row.section || "").replace(/^['"]|['"]$/g, "") %}
|
||||
</span>
|
||||
{% } else if (!is_null(value)) { %}
|
||||
{%= frappe.format(value, col, {}, row) %}
|
||||
{% } %}
|
||||
</td>
|
||||
{% } %}
|
||||
</tr>
|
||||
{% } %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="text-right text-muted">
|
||||
{%= __("Printed on {0}", [
|
||||
frappe.datetime.str_to_user(frappe.datetime.get_datetime_as_string())
|
||||
]) %}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
@@ -14,7 +14,7 @@
|
||||
"for_user": "",
|
||||
"hide_custom": 0,
|
||||
"icon": "table",
|
||||
"idx": 0,
|
||||
"idx": 1,
|
||||
"indicator_color": "",
|
||||
"is_hidden": 0,
|
||||
"label": "Financial Reports",
|
||||
@@ -266,13 +266,13 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2025-12-24 12:49:25.266357",
|
||||
"modified": "2026-05-18 09:49:45.138296",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Financial Reports",
|
||||
"number_cards": [],
|
||||
"owner": "Administrator",
|
||||
"parent_page": "Accounting",
|
||||
"parent_page": "",
|
||||
"public": 1,
|
||||
"quick_lists": [],
|
||||
"restrict_to_domain": "",
|
||||
|
||||
@@ -1977,7 +1977,7 @@ def create_asset_category(enable_cwip=1):
|
||||
asset_category.insert()
|
||||
|
||||
|
||||
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0):
|
||||
def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_asset=0, asset_category=None):
|
||||
meta = frappe.get_meta("Asset")
|
||||
naming_series = meta.get_field("naming_series").options.splitlines()[0] or "ACC-ASS-.YYYY.-"
|
||||
try:
|
||||
@@ -1987,7 +1987,7 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass
|
||||
"item_code": item_code or "Macbook Pro",
|
||||
"item_name": "Macbook Pro",
|
||||
"description": "Macbook Pro Retina Display",
|
||||
"asset_category": "Computers",
|
||||
"asset_category": asset_category or "Computers",
|
||||
"item_group": "All Item Groups",
|
||||
"stock_uom": "Nos",
|
||||
"is_stock_item": 0,
|
||||
|
||||
@@ -195,6 +195,9 @@ def reschedule_depreciation(asset_doc, notes, disposal_date=None):
|
||||
for row in asset_doc.get("finance_books"):
|
||||
current_schedule = get_asset_depr_schedule_doc(asset_doc.name, None, row.finance_book)
|
||||
|
||||
if disposal_date and flt(row.value_after_depreciation) <= flt(row.expected_value_after_useful_life):
|
||||
continue
|
||||
|
||||
if current_schedule:
|
||||
if current_schedule.docstatus == 1:
|
||||
new_schedule = frappe.copy_doc(current_schedule)
|
||||
|
||||
@@ -2,10 +2,59 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Buying Settings", {
|
||||
// refresh: function(frm) {
|
||||
// }
|
||||
refresh(frm) {
|
||||
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
|
||||
|
||||
const display = frm.doc.supp_master_name === "Naming Series";
|
||||
frm.set_df_property("naming_series_details", "hidden", !display);
|
||||
frm.set_df_property("configure", "hidden", !display);
|
||||
|
||||
if (display) {
|
||||
frm.naming_controller.load_master_series("Supplier", "naming_series_details");
|
||||
}
|
||||
|
||||
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
|
||||
},
|
||||
|
||||
supp_master_name(frm) {
|
||||
const display = frm.doc.supp_master_name === "Naming Series";
|
||||
frm.set_df_property("naming_series_details", "hidden", !display);
|
||||
frm.set_df_property("configure", "hidden", !display);
|
||||
|
||||
if (display) {
|
||||
frm.naming_controller.load_master_series("Supplier", "naming_series_details");
|
||||
} else {
|
||||
frm.doc.naming_series_details = "";
|
||||
frm.refresh_field("naming_series_details");
|
||||
}
|
||||
|
||||
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
|
||||
},
|
||||
|
||||
configure(frm) {
|
||||
frm.naming_controller.show_naming_series_dialog("Supplier", ({ naming_series_options }) => {
|
||||
frm.doc.naming_series_details = naming_series_options;
|
||||
frm.refresh_field("naming_series_details");
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
function get_transactions(frm) {
|
||||
const transactions = [
|
||||
{ label: __("Supplier"), doctype: "Supplier" },
|
||||
{ label: __("Material Request"), doctype: "Material Request" },
|
||||
{ label: __("Request for Quotation"), doctype: "Request for Quotation" },
|
||||
{ label: __("Purchase Order"), doctype: "Purchase Order" },
|
||||
{ label: __("Purchase Invoice"), doctype: "Purchase Invoice" },
|
||||
{ label: __("Purchase Receipt"), doctype: "Purchase Receipt" },
|
||||
];
|
||||
|
||||
if (frm.doc.supp_master_name !== "Naming Series") {
|
||||
return transactions.filter((t) => t.doctype !== "Supplier");
|
||||
}
|
||||
|
||||
return transactions;
|
||||
}
|
||||
frappe.tour["Buying Settings"] = [
|
||||
{
|
||||
fieldname: "supp_master_name",
|
||||
|
||||
@@ -6,44 +6,51 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"supplier_and_price_defaults_section",
|
||||
"supplier_defaults_section",
|
||||
"supp_master_name",
|
||||
"supplier_group",
|
||||
"buying_price_list",
|
||||
"naming_series_details",
|
||||
"configure",
|
||||
"column_break_4",
|
||||
"supplier_group",
|
||||
"pricing_tab",
|
||||
"buying_price_list",
|
||||
"section_break_vwgg",
|
||||
"maintain_same_rate",
|
||||
"column_break_lwxs",
|
||||
"maintain_same_rate_action",
|
||||
"role_to_override_stop_action",
|
||||
"section_break_xmlt",
|
||||
"po_required",
|
||||
"blanket_order_allowance",
|
||||
"column_break_sbwq",
|
||||
"pr_required",
|
||||
"project_update_frequency",
|
||||
"transaction_settings_section",
|
||||
"column_break_fcyl",
|
||||
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||
"allow_zero_qty_in_supplier_quotation",
|
||||
"use_transaction_date_exchange_rate",
|
||||
"allow_zero_qty_in_request_for_quotation",
|
||||
"allow_negative_rates_for_items",
|
||||
"po_required",
|
||||
"pr_required",
|
||||
"project_update_frequency",
|
||||
"column_break_12",
|
||||
"maintain_same_rate",
|
||||
"allow_multiple_items",
|
||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||
"allow_negative_rates_for_items",
|
||||
"set_valuation_rate_for_rejected_materials",
|
||||
"disable_last_purchase_rate",
|
||||
"show_pay_button",
|
||||
"purchase_invoice_settings_section",
|
||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||
"use_transaction_date_exchange_rate",
|
||||
"set_landed_cost_based_on_purchase_invoice_rate",
|
||||
"zero_quantity_line_items_section",
|
||||
"allow_zero_qty_in_supplier_quotation",
|
||||
"allow_zero_qty_in_request_for_quotation",
|
||||
"allow_zero_qty_in_purchase_order",
|
||||
"blanket_order_section",
|
||||
"blanket_order_allowance",
|
||||
"subcontract",
|
||||
"backflush_raw_materials_of_subcontract_based_on",
|
||||
"column_break_11",
|
||||
"over_transfer_allowance",
|
||||
"validate_consumed_qty",
|
||||
"section_break_xcug",
|
||||
"auto_create_subcontracting_order",
|
||||
"column_break_izrr",
|
||||
"auto_create_purchase_receipt",
|
||||
"request_for_quotation_tab",
|
||||
"fixed_email"
|
||||
"fixed_email",
|
||||
"document_naming_tab",
|
||||
"transaction_naming_html"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -54,6 +61,7 @@
|
||||
"options": "Supplier Name\nNaming Series\nAuto Name"
|
||||
},
|
||||
{
|
||||
"documentation_url": "https://docs.frappe.io/erpnext/buying-settings#2-default-supplier-group",
|
||||
"fieldname": "supplier_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Default Supplier Group",
|
||||
@@ -68,26 +76,27 @@
|
||||
{
|
||||
"fieldname": "po_required",
|
||||
"fieldtype": "Select",
|
||||
"label": "Is Purchase Order Required for Purchase Invoice & Receipt Creation?",
|
||||
"label": "Is Purchase Order required for Purchase Invoice & Receipt creation?",
|
||||
"options": "No\nYes"
|
||||
},
|
||||
{
|
||||
"fieldname": "pr_required",
|
||||
"fieldtype": "Select",
|
||||
"label": "Is Purchase Receipt Required for Purchase Invoice Creation?",
|
||||
"label": "Is Purchase Receipt required for Purchase Invoice creation?",
|
||||
"options": "No\nYes"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Warn or stop if Item rate is changed in Purchase Invoice or Purchase Receipt generated from a Purchase Order.",
|
||||
"fieldname": "maintain_same_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Maintain Same Rate Throughout the Purchase Cycle"
|
||||
"label": "Maintain same rate throughout the purchase cycle"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_multiple_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Item To Be Added Multiple Times in a Transaction"
|
||||
"label": "Allow Item to be added multiple times in a transaction"
|
||||
},
|
||||
{
|
||||
"fieldname": "subcontract",
|
||||
@@ -96,9 +105,10 @@
|
||||
},
|
||||
{
|
||||
"default": "BOM",
|
||||
"documentation_url": "https://docs.frappe.io/erpnext/buying-settings#1-backflush-raw-materials-of-subcontract-based-on",
|
||||
"fieldname": "backflush_raw_materials_of_subcontract_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Backflush Raw Materials of Subcontract Based On",
|
||||
"label": "Backflush raw materials of subcontract based on",
|
||||
"options": "BOM\nMaterial Transferred for Subcontract"
|
||||
},
|
||||
{
|
||||
@@ -108,25 +118,21 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Over Transfer Allowance (%)"
|
||||
},
|
||||
{
|
||||
"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",
|
||||
"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'",
|
||||
"depends_on": "maintain_same_rate",
|
||||
"fieldname": "role_to_override_stop_action",
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Override Stop Action",
|
||||
"label": "Role allowed to override stop action",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
@@ -134,12 +140,12 @@
|
||||
"description": "If checked, Rejected Quantity will be included while making Purchase Invoice from Purchase Receipt.",
|
||||
"fieldname": "bill_for_rejected_quantity_in_purchase_invoice",
|
||||
"fieldtype": "Check",
|
||||
"label": "Bill for Rejected Quantity in Purchase Invoice"
|
||||
"label": "Bill for rejected quantity in Purchase Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_and_price_defaults_section",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Naming Series and Price Defaults"
|
||||
"label": "Defaults"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
@@ -156,16 +162,17 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Prevents the system from automatically using the rate from the last purchase transaction when creating new purchase orders or transactions.",
|
||||
"fieldname": "disable_last_purchase_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Disable Last Purchase Rate"
|
||||
"label": "Disable last purchase rate"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval: frappe.boot.versions && frappe.boot.versions.payments",
|
||||
"fieldname": "show_pay_button",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Pay Button in Purchase Order Portal"
|
||||
"label": "Show pay button in Purchase Order portal"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -193,30 +200,25 @@
|
||||
"fieldname": "section_break_xcug",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_izrr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Subcontracting Order (Draft) will be auto-created on submission of Purchase Order.",
|
||||
"fieldname": "auto_create_subcontracting_order",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Create Subcontracting Order"
|
||||
"label": "Auto create Subcontracting Order"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Purchase Receipt (Draft) will be auto-created on submission of Subcontracting Receipt.",
|
||||
"fieldname": "auto_create_purchase_receipt",
|
||||
"fieldtype": "Check",
|
||||
"label": "Auto Create Purchase Receipt"
|
||||
"label": "Auto create Purchase Receipt"
|
||||
},
|
||||
{
|
||||
"default": "Each Transaction",
|
||||
"description": "How often should Project be updated of Total Purchase Cost ?",
|
||||
"fieldname": "project_update_frequency",
|
||||
"fieldtype": "Select",
|
||||
"label": "Update frequency of Project",
|
||||
"label": "How often should project be updated of Total Purchase Cost ?",
|
||||
"options": "Each Transaction\nManual"
|
||||
},
|
||||
{
|
||||
@@ -240,14 +242,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Supplier Quotation with Zero Quantity"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_xmlt",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_sbwq",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_fcyl",
|
||||
"fieldtype": "Column Break"
|
||||
@@ -258,7 +252,7 @@
|
||||
"description": "If enabled, the system will generate an accounting entry for materials rejected in the Purchase Receipt.",
|
||||
"fieldname": "set_valuation_rate_for_rejected_materials",
|
||||
"fieldtype": "Check",
|
||||
"label": "Set Valuation Rate for Rejected Materials"
|
||||
"label": "Set valuation rate for rejected Materials"
|
||||
},
|
||||
{
|
||||
"fieldname": "request_for_quotation_tab",
|
||||
@@ -279,23 +273,77 @@
|
||||
"description": "Raw materials consumed qty will be validated based on FG BOM required qty",
|
||||
"fieldname": "validate_consumed_qty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Validate Consumed Qty (as per BOM)"
|
||||
"label": "Validate consumed quantity (as per BOM)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_negative_rates_for_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Negative rates for Items"
|
||||
"label": "Allow negative rates for Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "supplier_defaults_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Supplier Defaults"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_vwgg",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "blanket_order_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Blanket Orders"
|
||||
},
|
||||
{
|
||||
"fieldname": "zero_quantity_line_items_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Zero-Quantity Line Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_invoice_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Purchase Invoice Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lwxs",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "pricing_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Pricing"
|
||||
},
|
||||
{
|
||||
"fieldname": "document_naming_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Document Naming"
|
||||
},
|
||||
{
|
||||
"fieldname": "configure",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 1,
|
||||
"label": "Configure Series"
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_naming_html",
|
||||
"fieldtype": "HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series_details",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"is_virtual": 1,
|
||||
"label": "Naming Series options"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-15 16:07:35.484787",
|
||||
"modified": "2026-05-05 16:30:37.184607",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
@@ -313,7 +361,6 @@
|
||||
{
|
||||
"create": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Purchase Manager",
|
||||
|
||||
@@ -354,9 +354,9 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
}
|
||||
|
||||
if (is_drop_ship && doc.status != "Delivered") {
|
||||
if (is_drop_ship && !["Completed", "Delivered"].includes(doc.status)) {
|
||||
this.frm.add_custom_button(
|
||||
__("Delivered"),
|
||||
__("Deliver (Dropship)"),
|
||||
this.delivered_by_supplier.bind(this),
|
||||
__("Status")
|
||||
);
|
||||
@@ -374,7 +374,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
if (doc.status != "Closed") {
|
||||
if (doc.status != "On Hold") {
|
||||
if (flt(doc.per_received) < 100 && allow_receipt) {
|
||||
if (
|
||||
doc.items
|
||||
.filter((item) => !item.delivered_by_supplier)
|
||||
.some((item) => item.received_qty < item.qty) &&
|
||||
allow_receipt
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Purchase Receipt"),
|
||||
() => {
|
||||
@@ -416,7 +421,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
__("Create")
|
||||
);
|
||||
|
||||
if (flt(doc.per_billed) < 100 && doc.status != "Delivered") {
|
||||
if (
|
||||
frappe.model.can_create("Payment Entry") &&
|
||||
flt(doc.per_billed) < 100 &&
|
||||
doc.status != "Delivered"
|
||||
) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment"),
|
||||
() => this.make_payment_entry(),
|
||||
@@ -424,7 +433,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
);
|
||||
}
|
||||
|
||||
if (flt(doc.per_billed) < 100) {
|
||||
if (flt(doc.per_billed) < 100 && frappe.boot.user.in_create.includes("Payment Request")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
@@ -660,12 +669,20 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
|
||||
items_add(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
if (doc.schedule_date) {
|
||||
row.schedule_date = doc.schedule_date;
|
||||
refresh_field("schedule_date", cdn, "items");
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
const field_copy = [];
|
||||
if (doc.project) {
|
||||
frappe.model.set_value(cdt, cdn, "project", doc.project);
|
||||
} else {
|
||||
this.frm.script_manager.copy_from_first_row("items", row, ["schedule_date"]);
|
||||
field_copy.push("project");
|
||||
}
|
||||
if (doc.schedule_date) {
|
||||
frappe.model.set_value(cdt, cdn, "schedule_date", doc.schedule_date);
|
||||
} else {
|
||||
field_copy.push("schedule_date");
|
||||
}
|
||||
if (field_copy.length) {
|
||||
this.frm.script_manager.copy_from_first_row("items", row, field_copy);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,7 +735,108 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
}
|
||||
|
||||
delivered_by_supplier() {
|
||||
this.frm.cscript.update_status("Deliver", "Delivered");
|
||||
const data = this.frm.doc.items
|
||||
.filter((item) => item.delivered_by_supplier == 1)
|
||||
.map((item) => {
|
||||
return {
|
||||
__checked: item.qty > item.received_qty,
|
||||
name: item.name,
|
||||
item_code: item.item_code,
|
||||
item_name: item.item_name,
|
||||
qty: item.qty,
|
||||
uom: item.uom,
|
||||
delivered_qty: item.received_qty || 0,
|
||||
qty_change: item.qty - item.received_qty,
|
||||
};
|
||||
});
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Set Dropship Items Delivered Quantity"),
|
||||
size: "extra-large",
|
||||
fields: [
|
||||
{
|
||||
fieldname: "items",
|
||||
fieldtype: "Table",
|
||||
data: data,
|
||||
cannot_add_rows: true,
|
||||
cannot_delete_rows: true,
|
||||
fields: [
|
||||
{
|
||||
fieldname: "name",
|
||||
fieldtype: "Data",
|
||||
read_only: true,
|
||||
hidden: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "item_code",
|
||||
fieldtype: "Link",
|
||||
options: "Item",
|
||||
label: __("Item Code"),
|
||||
in_list_view: 1,
|
||||
read_only: true,
|
||||
},
|
||||
{
|
||||
fieldname: "item_name",
|
||||
fieldtype: "Data",
|
||||
label: __("Item Name"),
|
||||
in_list_view: 1,
|
||||
read_only: true,
|
||||
},
|
||||
{
|
||||
fieldname: "qty",
|
||||
fieldtype: "Float",
|
||||
label: __("Quantity"),
|
||||
in_list_view: 1,
|
||||
read_only: true,
|
||||
},
|
||||
{
|
||||
fieldname: "uom",
|
||||
fieldtype: "Data",
|
||||
label: __("UOM"),
|
||||
in_list_view: 1,
|
||||
read_only: true,
|
||||
},
|
||||
{
|
||||
fieldname: "delivered_qty",
|
||||
fieldtype: "Float",
|
||||
label: __("Delivered Qty"),
|
||||
read_only: true,
|
||||
in_list_view: 1,
|
||||
},
|
||||
{
|
||||
fieldname: "qty_change",
|
||||
fieldtype: "Float",
|
||||
label: __("Qty Change"),
|
||||
in_list_view: 1,
|
||||
reqd: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
primary_action: (values) => {
|
||||
const frm = this.frm;
|
||||
frappe.call({
|
||||
doc: frm.doc,
|
||||
method: "update_dropship_received_qty",
|
||||
args: {
|
||||
data: values.items
|
||||
.filter((item) => item.__checked)
|
||||
.map((item) => ({
|
||||
name: item.name,
|
||||
current_qty: item.delivered_qty,
|
||||
qty_change: item.qty_change,
|
||||
})),
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
frm.reload_doc();
|
||||
frappe.toast(__("Quantities updated successfully."));
|
||||
dialog.hide();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
items_on_form_rendered() {
|
||||
@@ -740,12 +858,6 @@ cur_frm.cscript.update_status = function (label, status) {
|
||||
});
|
||||
};
|
||||
|
||||
cur_frm.fields_dict["items"].grid.get_field("project").get_query = function (doc, cdt, cdn) {
|
||||
return {
|
||||
filters: [["Project", "status", "not in", "Completed, Cancelled"]],
|
||||
};
|
||||
};
|
||||
|
||||
if (cur_frm.doc.is_old_subcontracting_flow) {
|
||||
cur_frm.fields_dict["items"].grid.get_field("bom").get_query = function (doc, cdt, cdn) {
|
||||
var d = locals[cdt][cdn];
|
||||
|
||||
@@ -218,7 +218,6 @@ class PurchaseOrder(BuyingController):
|
||||
self.create_raw_materials_supplied()
|
||||
|
||||
self.validate_fg_item_for_subcontracting()
|
||||
self.set_received_qty_for_drop_ship_items()
|
||||
|
||||
if not self.advance_payment_status:
|
||||
self.advance_payment_status = "Not Initiated"
|
||||
@@ -492,7 +491,8 @@ class PurchaseOrder(BuyingController):
|
||||
self.update_status_updater_if_from_pp()
|
||||
|
||||
if self.has_drop_ship_item():
|
||||
self.update_delivered_qty_in_sales_order()
|
||||
self.set_received_qty_to_zero_for_drop_ship_items()
|
||||
self.update_receiving_percentage()
|
||||
|
||||
self.update_reserved_qty_for_subcontract()
|
||||
self.check_on_hold_or_closed_status()
|
||||
@@ -566,20 +566,80 @@ class PurchaseOrder(BuyingController):
|
||||
so.set_status(update=True)
|
||||
so.notify_update()
|
||||
|
||||
def set_received_qty_to_zero_for_drop_ship_items(self):
|
||||
for item in self.items:
|
||||
if item.delivered_by_supplier:
|
||||
item.db_set("received_qty", 0)
|
||||
|
||||
def has_drop_ship_item(self):
|
||||
return any(d.delivered_by_supplier for d in self.items)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_dropship_received_qty(self, data: list[dict]):
|
||||
if not data:
|
||||
frappe.throw(_("Please select at least one item to update delivered quantity."))
|
||||
|
||||
for d in data:
|
||||
item = next((item for item in self.items if item.name == d.get("name")), None)
|
||||
|
||||
if not item:
|
||||
frappe.throw(
|
||||
_("Item with name {0} not found in the Purchase Order").format(frappe.bold(d.get("name")))
|
||||
)
|
||||
|
||||
if not item.delivered_by_supplier:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Item {0} is not a drop ship item. Only drop ship items can have Delivered Qty updated."
|
||||
).format(frappe.bold(item.item_code))
|
||||
)
|
||||
|
||||
if not item.has_permlevel_access_to("received_qty", permission_type="write"):
|
||||
frappe.throw(
|
||||
_("You don't have permission to update Received Qty DocField for item {0}").format(
|
||||
frappe.bold(item.item_code)
|
||||
)
|
||||
)
|
||||
|
||||
if not d.get("qty_change"):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Item {0} has no changes in delivered quantity. Please unselect the row if you do not wish to update its quantity."
|
||||
).format(frappe.bold(item.item_code))
|
||||
)
|
||||
|
||||
if d.get("qty_change") < 0 and abs(d.get("qty_change")) > item.received_qty:
|
||||
frappe.throw(
|
||||
_("Delivered Qty cannot be reduced by more than {0} for item {1}").format(
|
||||
item.received_qty, frappe.bold(item.item_code)
|
||||
)
|
||||
)
|
||||
|
||||
if d.get("qty_change") > 0 and item.received_qty + d.get("qty_change") > item.qty:
|
||||
frappe.throw(
|
||||
_("Delivered Qty cannot be increased by more than {0} for item {1}").format(
|
||||
item.qty - item.received_qty, frappe.bold(item.item_code)
|
||||
)
|
||||
)
|
||||
|
||||
qty_change = item.received_qty + d.get("qty_change")
|
||||
item.db_set("received_qty", qty_change, update_modified=True)
|
||||
self.add_comment(
|
||||
"Label",
|
||||
_("updated delivered quantity for item {0} to {1}").format(
|
||||
frappe.bold(item.item_code), frappe.bold(qty_change)
|
||||
),
|
||||
)
|
||||
self.update_receiving_percentage()
|
||||
self.set_status(update=True)
|
||||
self.update_delivered_qty_in_sales_order()
|
||||
|
||||
def is_against_so(self):
|
||||
return any(d.sales_order for d in self.items if d.sales_order)
|
||||
|
||||
def is_against_pp(self):
|
||||
return any(d.production_plan for d in self.items if d.production_plan)
|
||||
|
||||
def set_received_qty_for_drop_ship_items(self):
|
||||
for item in self.items:
|
||||
if item.delivered_by_supplier == 1:
|
||||
item.received_qty = item.qty
|
||||
|
||||
def update_reserved_qty_for_subcontract(self):
|
||||
if self.is_old_subcontracting_flow:
|
||||
for d in self.supplied_items:
|
||||
@@ -592,7 +652,7 @@ class PurchaseOrder(BuyingController):
|
||||
for item in self.items:
|
||||
received_qty += min(item.received_qty, item.qty)
|
||||
total_qty += item.qty
|
||||
if total_qty:
|
||||
if total_qty and received_qty:
|
||||
self.db_set("per_received", flt(received_qty / total_qty) * 100, update_modified=False)
|
||||
else:
|
||||
self.db_set("per_received", 0, update_modified=False)
|
||||
|
||||
@@ -627,6 +627,7 @@
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Qty",
|
||||
"no_copy": 1,
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "received_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"print_hide": 1,
|
||||
@@ -950,7 +951,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-30 16:51:57.761673",
|
||||
"modified": "2026-05-14 12:16:16.192936",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order Item",
|
||||
|
||||
@@ -126,9 +126,3 @@ erpnext.buying.SupplierQuotationController = class SupplierQuotationController e
|
||||
|
||||
// for backward compatibility: combine new and previous states
|
||||
extend_cscript(cur_frm.cscript, new erpnext.buying.SupplierQuotationController({ frm: cur_frm }));
|
||||
|
||||
cur_frm.fields_dict["items"].grid.get_field("project").get_query = function (doc, cdt, cdn) {
|
||||
return {
|
||||
filters: [["Project", "status", "not in", "Completed, Cancelled"]],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -345,9 +345,9 @@
|
||||
"dependencies": "",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "Supplier-Wise Sales Analytics",
|
||||
"label": "Item Wise Consumption",
|
||||
"link_count": 0,
|
||||
"link_to": "Supplier-Wise Sales Analytics",
|
||||
"link_to": "Item Wise Consumption",
|
||||
"link_type": "Report",
|
||||
"onboard": 1,
|
||||
"type": "Link"
|
||||
|
||||
@@ -71,6 +71,7 @@ from erpnext.stock.get_item_details import (
|
||||
NOT_APPLICABLE_TAX,
|
||||
ItemDetailsCtx,
|
||||
_get_item_tax_template,
|
||||
_get_item_tax_template_from_item_group,
|
||||
get_conversion_factor,
|
||||
get_item_details,
|
||||
get_item_tax_map,
|
||||
@@ -327,6 +328,7 @@ class AccountsController(TransactionBase):
|
||||
# Determine if drop ship applies
|
||||
is_drop_ship = self.doctype in {
|
||||
"Purchase Order",
|
||||
"Purchase Invoice",
|
||||
"Sales Order",
|
||||
"Sales Invoice",
|
||||
} and self.is_drop_ship(self.items)
|
||||
@@ -3672,7 +3674,12 @@ def set_child_tax_template_and_map(item, child_item, parent_doc):
|
||||
}
|
||||
)
|
||||
|
||||
child_item.item_tax_template = _get_item_tax_template(ctx, item.taxes)
|
||||
item_tax_template = _get_item_tax_template(ctx, item.taxes)
|
||||
|
||||
if not item_tax_template:
|
||||
item_tax_template = _get_item_tax_template_from_item_group(ctx, item.item_group)
|
||||
|
||||
child_item.item_tax_template = item_tax_template
|
||||
child_item.item_tax_rate = get_item_tax_map(
|
||||
doc=parent_doc,
|
||||
tax_template=child_item.item_tax_template,
|
||||
@@ -3901,8 +3908,8 @@ def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, chil
|
||||
)
|
||||
|
||||
qty_limits = {
|
||||
"Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity")),
|
||||
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity")),
|
||||
"Sales Order": ("delivered_qty", _("Cannot set quantity less than delivered quantity.")),
|
||||
"Purchase Order": ("received_qty", _("Cannot set quantity less than received quantity.")),
|
||||
}
|
||||
|
||||
if parent_doctype in qty_limits:
|
||||
|
||||
@@ -461,17 +461,7 @@ class BuyingController(SubcontractingController):
|
||||
get_conversion_factor(item.item_code, item.uom).get("conversion_factor") or 1.0
|
||||
)
|
||||
|
||||
net_rate = (
|
||||
flt(
|
||||
(item.base_net_amount / item.received_qty) * item.qty,
|
||||
item.precision("base_net_amount"),
|
||||
)
|
||||
if item.received_qty
|
||||
and frappe.get_single_value(
|
||||
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
|
||||
)
|
||||
else item.base_net_amount
|
||||
)
|
||||
net_rate = item.base_net_amount
|
||||
if item.sales_incoming_rate: # for internal transfer
|
||||
net_rate = item.qty * item.sales_incoming_rate
|
||||
|
||||
|
||||
@@ -269,14 +269,15 @@ class StockController(AccountsController):
|
||||
)
|
||||
|
||||
is_asset_pr = any(d.get("is_fixed_asset") for d in self.get("items"))
|
||||
|
||||
if (
|
||||
need_inventory_map = (self.get_stock_items() or self.get("packed_items")) and (
|
||||
cint(erpnext.is_perpetual_inventory_enabled(self.company))
|
||||
or provisional_accounting_for_non_stock_items
|
||||
or is_asset_pr
|
||||
):
|
||||
)
|
||||
|
||||
inventory_account_map = frappe._dict()
|
||||
if need_inventory_map:
|
||||
inventory_account_map = self.get_inventory_account_map()
|
||||
|
||||
if need_inventory_map or provisional_accounting_for_non_stock_items or is_asset_pr:
|
||||
if self.docstatus == 1:
|
||||
if not gl_entries:
|
||||
gl_entries = (
|
||||
|
||||
@@ -169,10 +169,6 @@ class calculate_taxes_and_totals:
|
||||
return
|
||||
|
||||
if not self.discount_amount_applied:
|
||||
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
|
||||
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
|
||||
)
|
||||
|
||||
do_not_round_fields = ["valuation_rate", "incoming_rate"]
|
||||
|
||||
for item in self.doc.items:
|
||||
@@ -236,13 +232,7 @@ class calculate_taxes_and_totals:
|
||||
elif not item.qty and self.doc.get("is_debit_note"):
|
||||
item.amount = flt(item.rate, item.precision("amount"))
|
||||
else:
|
||||
qty = (
|
||||
(item.qty + item.rejected_qty)
|
||||
if bill_for_rejected_quantity_in_purchase_invoice
|
||||
and self.doc.doctype == "Purchase Receipt"
|
||||
else item.qty
|
||||
)
|
||||
item.amount = flt(item.rate * qty, item.precision("amount"))
|
||||
item.amount = flt(item.rate * item.qty, item.precision("amount"))
|
||||
|
||||
item.net_amount = item.amount
|
||||
|
||||
@@ -402,16 +392,9 @@ class calculate_taxes_and_totals:
|
||||
self.doc.total
|
||||
) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
|
||||
|
||||
bill_for_rejected_quantity_in_purchase_invoice = frappe.get_single_value(
|
||||
"Buying Settings", "bill_for_rejected_quantity_in_purchase_invoice"
|
||||
)
|
||||
for item in self._items:
|
||||
self.doc.total += item.amount
|
||||
self.doc.total_qty += (
|
||||
(item.qty + item.rejected_qty)
|
||||
if bill_for_rejected_quantity_in_purchase_invoice and self.doc.doctype == "Purchase Receipt"
|
||||
else item.qty
|
||||
)
|
||||
self.doc.total_qty += item.qty
|
||||
self.doc.base_total += item.base_amount
|
||||
self.doc.net_total += item.net_amount
|
||||
self.doc.base_net_total += item.base_net_amount
|
||||
|
||||
@@ -235,10 +235,13 @@ def _get_agents_sorted_by_asc_workload(date):
|
||||
return agent_list
|
||||
appointment_counter = Counter(agent_list)
|
||||
for appointment in appointments:
|
||||
assigned_to = frappe.parse_json(appointment._assign)
|
||||
if not assigned_to:
|
||||
assign_data = appointment._assign
|
||||
if isinstance(assign_data, str):
|
||||
assign_data = assign_data.strip()
|
||||
if not assign_data:
|
||||
continue
|
||||
if (assigned_to[0] in agent_list) and getdate(appointment.scheduled_time) == date:
|
||||
assigned_to = frappe.parse_json(assign_data)
|
||||
if assigned_to and (assigned_to[0] in agent_list) and getdate(appointment.scheduled_time) == date:
|
||||
appointment_counter[assigned_to[0]] += 1
|
||||
sorted_agent_list = appointment_counter.most_common()
|
||||
sorted_agent_list.reverse()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -292,7 +292,7 @@ class MasterProductionSchedule(Document):
|
||||
return item_wise_data
|
||||
|
||||
def add_mps_data(self, data):
|
||||
data = frappe._dict(sorted(data.items(), key=lambda x: x[0][1]))
|
||||
data = frappe._dict(sorted(data.items(), key=lambda x: x[0][1] or ""))
|
||||
|
||||
for key in data:
|
||||
row = data[key]
|
||||
|
||||
@@ -3,7 +3,7 @@ from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "demand_planning",
|
||||
"fieldname": "sales_forecast",
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("MPS"),
|
||||
|
||||
@@ -94,6 +94,7 @@ def create_items():
|
||||
"is_stock_item": 1,
|
||||
"standard_rate": 100,
|
||||
"opening_stock": 100,
|
||||
"valuation_rate": 100,
|
||||
"last_purchase_rate": 100,
|
||||
"item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}],
|
||||
}
|
||||
@@ -103,6 +104,7 @@ def create_items():
|
||||
"is_stock_item": 1,
|
||||
"standard_rate": 200,
|
||||
"opening_stock": 200,
|
||||
"valuation_rate": 200,
|
||||
"last_purchase_rate": 200,
|
||||
"item_defaults": [{"company": "_Test Company", "default_warehouse": "Stores - _TC"}],
|
||||
}
|
||||
|
||||
@@ -305,17 +305,6 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "BOM",
|
||||
"hidden": 0,
|
||||
"is_query_report": 1,
|
||||
"label": "BOM Stock Report",
|
||||
"link_count": 0,
|
||||
"link_to": "BOM Stock Report",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Work Order",
|
||||
"hidden": 0,
|
||||
@@ -443,7 +432,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2026-01-02 15:07:36.968043",
|
||||
"modified": "2026-05-05 11:00:26.131777",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Manufacturing",
|
||||
|
||||
@@ -479,4 +479,6 @@ erpnext.patches.v16_0.merge_repost_settings_to_accounts_settings
|
||||
erpnext.patches.v16_0.set_root_type_in_account_categories
|
||||
erpnext.patches.v16_0.scr_inv_dimension
|
||||
erpnext.patches.v16_0.packed_item_inv_dimen
|
||||
erpnext.patches.v16_0.correct_po_titles
|
||||
erpnext.patches.v16_0.fix_titles
|
||||
erpnext.patches.v16_0.set_not_applicable_on_german_item_tax_templates
|
||||
erpnext.patches.v16_0.clear_procedures_from_receivable_report
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
frappe.db.sql("drop function if exists ar_genkey")
|
||||
frappe.db.sql("drop procedure if exists ar_init_tmp_table")
|
||||
frappe.db.sql("drop procedure if exists ar_allocate_to_tmp_table")
|
||||
|
||||
if frappe.db.get_single_value("Accounts Settings", "receivable_payable_fetch_method") == "Raw SQL":
|
||||
frappe.db.set_single_value(
|
||||
"Accounts Settings", "receivable_payable_fetch_method", "UnBuffered Cursor"
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
"""
|
||||
This patch corrects the titles of purchase orders that were set to
|
||||
the text string "{supplier_name}" instead of the actual supplier name.
|
||||
"""
|
||||
|
||||
purchase_order = frappe.qb.DocType("Purchase Order")
|
||||
(
|
||||
frappe.qb.update(purchase_order)
|
||||
.set(purchase_order.title, purchase_order.supplier_name)
|
||||
.where(purchase_order.title == "{supplier_name}")
|
||||
).run()
|
||||
28
erpnext/patches/v16_0/fix_titles.py
Normal file
28
erpnext/patches/v16_0/fix_titles.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import frappe
|
||||
|
||||
|
||||
def execute():
|
||||
"""
|
||||
This patch corrects the titles of doctypes set to
|
||||
the text strings "{customer_name}" or "{supplier_name}"
|
||||
instead of the actual customer or supplier name.
|
||||
"""
|
||||
|
||||
customer_doctypes = ["POS Invoice", "Sales Invoice", "Quotation", "Sales Order", "Delivery Note"]
|
||||
supplier_doctypes = ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]
|
||||
|
||||
for doctype in customer_doctypes:
|
||||
customer_doctype = frappe.qb.DocType(doctype)
|
||||
(
|
||||
frappe.qb.update(customer_doctype)
|
||||
.set(customer_doctype.title, customer_doctype.customer_name)
|
||||
.where(customer_doctype.title == "{customer_name}")
|
||||
).run()
|
||||
|
||||
for doctype in supplier_doctypes:
|
||||
supplier_doctype = frappe.qb.DocType(doctype)
|
||||
(
|
||||
frappe.qb.update(supplier_doctype)
|
||||
.set(supplier_doctype.title, supplier_doctype.supplier_name)
|
||||
.where(supplier_doctype.title == "{supplier_name}")
|
||||
).run()
|
||||
@@ -0,0 +1,218 @@
|
||||
import frappe
|
||||
|
||||
# Snapshot of the relevant German defaults when this migration was written.
|
||||
# Migration patches must not read mutable setup data, otherwise future edits to
|
||||
# country_wise_tax.json would change what this patch does on sites that have not
|
||||
# run it yet.
|
||||
#
|
||||
# For numbered charts, compare account_number + root_type because Account.account_name
|
||||
# is not unique within a company.
|
||||
SKR04_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("3801", "Liability"),
|
||||
("3802", "Liability"),
|
||||
("3835", "Liability"),
|
||||
("1401", "Asset"),
|
||||
("1402", "Asset"),
|
||||
("1541", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
SKR04_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("3806", "Liability"),
|
||||
("3804", "Liability"),
|
||||
("3837", "Liability"),
|
||||
("1406", "Asset"),
|
||||
("1404", "Asset"),
|
||||
("1540", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
SKR03_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("1771", "Liability"),
|
||||
("1772", "Liability"),
|
||||
("1785", "Liability"),
|
||||
("1571", "Asset"),
|
||||
("1572", "Asset"),
|
||||
("1541", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
SKR03_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("1776", "Liability"),
|
||||
("1774", "Liability"),
|
||||
("1787", "Liability"),
|
||||
("1576", "Asset"),
|
||||
("1574", "Asset"),
|
||||
("1540", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
STANDARD_NOT_APPLICABLE_7_PERCENT_ACCOUNT_LABELS = frozenset(
|
||||
{
|
||||
("Umsatzsteuer 7 %", "Liability"),
|
||||
("Umsatzsteuer aus innergemeinschaftlichem Erwerb", "Liability"),
|
||||
("Umsatzsteuer nach § 13b UStG", "Liability"),
|
||||
("Abziehbare Vorsteuer 7 %", "Asset"),
|
||||
("Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb", "Asset"),
|
||||
("Abziehbare Vorsteuer nach § 13b UStG", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
STANDARD_NOT_APPLICABLE_19_PERCENT_ACCOUNT_LABELS = frozenset(
|
||||
{
|
||||
("Umsatzsteuer 19 %", "Liability"),
|
||||
("Umsatzsteuer aus innergemeinschaftlichem Erwerb 19 %", "Liability"),
|
||||
("Umsatzsteuer nach § 13b UStG 19 %", "Liability"),
|
||||
("Abziehbare Vorsteuer 19 %", "Asset"),
|
||||
("Abziehbare Vorsteuer aus innergemeinschaftlichem Erwerb 19 %", "Asset"),
|
||||
("Abziehbare Vorsteuer nach § 13b UStG 19 %", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
STANDARD_WITH_NUMBERS_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("2321", "Liability"),
|
||||
("2331", "Liability"),
|
||||
("2341", "Liability"),
|
||||
("1521", "Asset"),
|
||||
("1531", "Asset"),
|
||||
("1541", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
STANDARD_WITH_NUMBERS_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS = frozenset(
|
||||
{
|
||||
("2320", "Liability"),
|
||||
("2330", "Liability"),
|
||||
("2340", "Liability"),
|
||||
("1520", "Asset"),
|
||||
("1530", "Asset"),
|
||||
("1540", "Asset"),
|
||||
}
|
||||
)
|
||||
|
||||
GERMAN_ITEM_TAX_TEMPLATE_NOT_APPLICABLE_ACCOUNTS = {
|
||||
"SKR03 mit Kontonummern": {
|
||||
"identifier_field": "account_number",
|
||||
"templates": {
|
||||
"19 %": SKR03_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS,
|
||||
"7 %": SKR03_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS,
|
||||
"0 %": SKR03_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS
|
||||
| SKR03_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS
|
||||
| frozenset({("1588", "Asset")}),
|
||||
},
|
||||
},
|
||||
"SKR04 mit Kontonummern": {
|
||||
"identifier_field": "account_number",
|
||||
"templates": {
|
||||
"19 %": SKR04_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS,
|
||||
"7 %": SKR04_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS,
|
||||
"0 %": SKR04_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS
|
||||
| SKR04_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS
|
||||
| frozenset({("1433", "Asset")}),
|
||||
},
|
||||
},
|
||||
"Standard": {
|
||||
"identifier_field": "account_name",
|
||||
"templates": {
|
||||
"19 %": STANDARD_NOT_APPLICABLE_7_PERCENT_ACCOUNT_LABELS,
|
||||
"7 %": STANDARD_NOT_APPLICABLE_19_PERCENT_ACCOUNT_LABELS,
|
||||
"0%": STANDARD_NOT_APPLICABLE_7_PERCENT_ACCOUNT_LABELS
|
||||
| STANDARD_NOT_APPLICABLE_19_PERCENT_ACCOUNT_LABELS
|
||||
| frozenset({("Entstandene Einfuhrumsatzsteuer", "Asset")}),
|
||||
},
|
||||
},
|
||||
"Standard with Numbers": {
|
||||
"identifier_field": "account_number",
|
||||
"templates": {
|
||||
"19%": STANDARD_WITH_NUMBERS_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS,
|
||||
"7%": STANDARD_WITH_NUMBERS_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS,
|
||||
"0 %": STANDARD_WITH_NUMBERS_NOT_APPLICABLE_7_PERCENT_ACCOUNT_IDS
|
||||
| STANDARD_WITH_NUMBERS_NOT_APPLICABLE_19_PERCENT_ACCOUNT_IDS
|
||||
| frozenset({("1550", "Asset")}),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def update_account_cache(accounts, account_cache):
|
||||
missing_accounts = set(accounts) - set(account_cache)
|
||||
if not missing_accounts:
|
||||
return
|
||||
|
||||
for account in frappe.get_all(
|
||||
"Account",
|
||||
filters={"name": ("in", tuple(sorted(missing_accounts)))},
|
||||
fields=["name", "account_name", "account_number", "root_type"],
|
||||
):
|
||||
account_cache[account.name] = account
|
||||
|
||||
|
||||
def get_account_identifier(account, identifier_field, account_cache):
|
||||
cached_account = account_cache.get(account)
|
||||
if not cached_account:
|
||||
return None
|
||||
|
||||
return cached_account.get(identifier_field), cached_account.root_type
|
||||
|
||||
|
||||
def execute():
|
||||
"""Backfill `not_applicable` on Item Tax Template Details for German companies.
|
||||
|
||||
Before the `not_applicable` flag existed, German default templates used
|
||||
`tax_rate: 0` to mean "this tax does not apply to the item" (as opposed to
|
||||
an explicit 0% rate). For each German company, this patch looks up the
|
||||
historical defaults for its Chart of Accounts and sets
|
||||
`not_applicable = 1` on detail rows that still match those defaults
|
||||
(same template title, same zero-rate tax account identifier set, flag still unset),
|
||||
leaving any user-customised rows untouched.
|
||||
"""
|
||||
companies = frappe.get_all(
|
||||
"Company",
|
||||
filters={"country": "Germany"},
|
||||
fields=["name", "chart_of_accounts"],
|
||||
)
|
||||
account_cache = {}
|
||||
|
||||
for company in companies:
|
||||
chart = GERMAN_ITEM_TAX_TEMPLATE_NOT_APPLICABLE_ACCOUNTS.get(company.chart_of_accounts)
|
||||
if not chart:
|
||||
continue
|
||||
|
||||
identifier_field = chart["identifier_field"]
|
||||
for template_title, target_accounts in chart["templates"].items():
|
||||
itt_names = frappe.get_all(
|
||||
"Item Tax Template",
|
||||
filters={"company": company.name, "title": template_title},
|
||||
pluck="name",
|
||||
)
|
||||
for itt_name in itt_names:
|
||||
zero_rate_details = frappe.get_all(
|
||||
"Item Tax Template Detail",
|
||||
filters={"parent": itt_name, "tax_rate": 0},
|
||||
fields=["name", "tax_type", "not_applicable"],
|
||||
)
|
||||
update_account_cache((d.tax_type for d in zero_rate_details), account_cache)
|
||||
zero_rate_accounts_by_detail = {
|
||||
d.name: get_account_identifier(d.tax_type, identifier_field, account_cache)
|
||||
for d in zero_rate_details
|
||||
}
|
||||
if any(identifier is None for identifier in zero_rate_accounts_by_detail.values()):
|
||||
continue
|
||||
|
||||
if set(zero_rate_accounts_by_detail.values()) != target_accounts:
|
||||
continue
|
||||
|
||||
for d in zero_rate_details:
|
||||
if not d.not_applicable:
|
||||
frappe.db.set_value(
|
||||
"Item Tax Template Detail",
|
||||
d.name,
|
||||
"not_applicable",
|
||||
1,
|
||||
update_modified=False,
|
||||
)
|
||||
@@ -364,13 +364,18 @@ class Project(Document):
|
||||
)
|
||||
|
||||
for user in self.users:
|
||||
# process only users who haven't received the welcome email yet
|
||||
if user.welcome_email_sent == 0:
|
||||
frappe.sendmail(
|
||||
user.user,
|
||||
subject=_("Project Collaboration Invitation"),
|
||||
content=content,
|
||||
)
|
||||
user.welcome_email_sent = 1
|
||||
# fetch canonical User data (enabled status + latest email)
|
||||
user_info = frappe.db.get_value("User", user.user, ["enabled", "email"], as_dict=True)
|
||||
# send email only if user is enabled and has a valid email
|
||||
if user_info and user_info.enabled and user_info.email:
|
||||
frappe.sendmail(
|
||||
recipients=[user_info.email],
|
||||
subject=_("Project Collaboration Invitation"),
|
||||
content=content,
|
||||
)
|
||||
user.welcome_email_sent = 1
|
||||
|
||||
|
||||
def get_timeline_data(doctype: str, name: str) -> dict[int, int]:
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
"label": "Additional Info"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.status == \"Closed\" || doc.status == \"Pending Review\"",
|
||||
"depends_on": "eval:doc.status == \"Completed\" || doc.status == \"Pending Review\"",
|
||||
"fieldname": "review_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Review Date",
|
||||
@@ -313,7 +313,7 @@
|
||||
"oldfieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.status == \"Closed\"",
|
||||
"depends_on": "eval:doc.status == \"Completed\"",
|
||||
"fieldname": "closing_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Closing Date",
|
||||
|
||||
@@ -25,14 +25,16 @@ erpnext.buying = {
|
||||
};
|
||||
});
|
||||
|
||||
this.frm.set_query("project", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company,
|
||||
},
|
||||
};
|
||||
const get_project_filters = () => ({
|
||||
query: "erpnext.controllers.queries.get_project_name",
|
||||
filters: {
|
||||
company: this.frm.doc.company,
|
||||
},
|
||||
});
|
||||
|
||||
this.frm.set_query("project", get_project_filters);
|
||||
this.frm.set_query("project", "items", get_project_filters);
|
||||
|
||||
if (
|
||||
this.frm.doc.__islocal &&
|
||||
frappe.meta.has_field(this.frm.doc.doctype, "disable_rounded_total")
|
||||
@@ -176,9 +178,14 @@ erpnext.buying = {
|
||||
callback: (r) => {
|
||||
if (!r.message) return;
|
||||
|
||||
this.frm.set_value("billing_address", r.message.primary_address || "");
|
||||
if (!this.frm.doc.billing_address) {
|
||||
this.frm.set_value("billing_address", r.message.primary_address || "");
|
||||
}
|
||||
|
||||
if (frappe.meta.has_field(this.frm.doc.doctype, "shipping_address")) {
|
||||
if (
|
||||
frappe.meta.has_field(this.frm.doc.doctype, "shipping_address") &&
|
||||
!this.frm.doc.shipping_address
|
||||
) {
|
||||
this.frm.set_value("shipping_address", r.message.shipping_address || "");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -870,10 +870,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
me.apply_rule_on_other_items({ key: item });
|
||||
}
|
||||
},
|
||||
() => {
|
||||
var company_currency = me.get_company_currency();
|
||||
me.update_item_grid_labels(company_currency);
|
||||
},
|
||||
]);
|
||||
}
|
||||
},
|
||||
@@ -1296,7 +1292,7 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
["Purchase Order", "Purchase Receipt", "Purchase Invoice"].includes(this.frm.doctype) &&
|
||||
!this.frm.doc.shipping_address
|
||||
) {
|
||||
let is_drop_ship = me.frm.doc.items.some((item) => item.delivered_by_supplier);
|
||||
const is_drop_ship = me.frm.doc.items.some((item) => item.delivered_by_supplier);
|
||||
|
||||
if (!is_drop_ship) {
|
||||
erpnext.utils.get_shipping_address(this.frm, function () {
|
||||
@@ -1824,63 +1820,51 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
if (
|
||||
this._last_currency === this.frm.doc.currency &&
|
||||
this._last_price_list_currency === this.frm.doc.price_list_currency
|
||||
this._last_price_list_currency === this.frm.doc.price_list_currency &&
|
||||
this._last_party_account_currency === this.frm.doc.party_account_currency &&
|
||||
this._last_company_currency === company_currency
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._last_currency = this.frm.doc.currency;
|
||||
this._last_price_list_currency = this.frm.doc.price_list_currency;
|
||||
this._last_party_account_currency = this.frm.doc.party_account_currency;
|
||||
this._last_company_currency = company_currency;
|
||||
|
||||
this.change_form_labels(company_currency);
|
||||
this.change_grid_labels(company_currency);
|
||||
this.frm.refresh_fields();
|
||||
}
|
||||
|
||||
get_currency_label_options(company_currency) {
|
||||
return {
|
||||
currency: this.frm.doc.currency,
|
||||
"Company:company:default_currency": company_currency,
|
||||
party_account_currency: this.frm.doc.party_account_currency,
|
||||
};
|
||||
}
|
||||
|
||||
set_currency_labels_from_options(currency_options, parentfield) {
|
||||
const doctype = parentfield ? this.frm.fields_dict[parentfield].grid.doctype : this.frm.doc.doctype;
|
||||
const docfields = frappe.meta.get_docfields(doctype);
|
||||
|
||||
Object.entries(currency_options).forEach(([options, currency]) => {
|
||||
const fields = docfields
|
||||
.filter((df) => df.fieldtype === "Currency" && df.options === options)
|
||||
.map((df) => df.fieldname);
|
||||
|
||||
this.frm.set_currency_labels(fields, currency, parentfield);
|
||||
});
|
||||
}
|
||||
|
||||
change_form_labels(company_currency) {
|
||||
let me = this;
|
||||
const currency_options = this.get_currency_label_options(company_currency);
|
||||
|
||||
this.frm.set_currency_labels(
|
||||
[
|
||||
"advance_paid",
|
||||
"base_total",
|
||||
"base_net_total",
|
||||
"base_total_taxes_and_charges",
|
||||
"base_discount_amount",
|
||||
"base_taxes_and_charges_added",
|
||||
"base_taxes_and_charges_deducted",
|
||||
"total_amount_to_pay",
|
||||
"base_paid_amount",
|
||||
"base_write_off_amount",
|
||||
"base_change_amount",
|
||||
"base_operating_cost",
|
||||
"base_raw_material_cost",
|
||||
"base_total_cost",
|
||||
"base_secondary_items_cost",
|
||||
"base_totals_section",
|
||||
],
|
||||
company_currency
|
||||
);
|
||||
|
||||
this.frm.set_currency_labels(
|
||||
[
|
||||
"total",
|
||||
"net_total",
|
||||
"total_taxes_and_charges",
|
||||
"discount_amount",
|
||||
"taxes_and_charges_added",
|
||||
"taxes_and_charges_deducted",
|
||||
"tax_withholding_net_total",
|
||||
"paid_amount",
|
||||
"write_off_amount",
|
||||
"operating_cost",
|
||||
"secondary_items_cost",
|
||||
"raw_material_cost",
|
||||
"total_cost",
|
||||
"totals_section",
|
||||
],
|
||||
this.frm.doc.currency
|
||||
);
|
||||
this.set_currency_labels_from_options(currency_options);
|
||||
this.frm.set_currency_labels(["totals_section"], this.frm.doc.currency);
|
||||
this.frm.set_currency_labels(["base_totals_section"], company_currency);
|
||||
|
||||
this.frm.set_currency_labels(
|
||||
["outstanding_amount", "total_advance"],
|
||||
@@ -1961,23 +1945,25 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
|
||||
change_grid_labels(company_currency) {
|
||||
var me = this;
|
||||
|
||||
this.update_item_grid_labels(company_currency);
|
||||
const currency_options = this.get_currency_label_options(company_currency);
|
||||
|
||||
this.toggle_item_grid_columns(company_currency);
|
||||
|
||||
if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
|
||||
this.frm.set_currency_labels(
|
||||
["operating_cost", "hour_rate"],
|
||||
this.frm.doc.currency,
|
||||
"operations"
|
||||
);
|
||||
this.frm.set_currency_labels(
|
||||
["base_operating_cost", "base_hour_rate"],
|
||||
company_currency,
|
||||
"operations"
|
||||
);
|
||||
for (const child_table of [
|
||||
"items",
|
||||
"operations",
|
||||
"secondary_items",
|
||||
"taxes",
|
||||
"advances",
|
||||
"payment_schedule",
|
||||
"sales_team",
|
||||
]) {
|
||||
if (this.frm.fields_dict[child_table]) {
|
||||
this.set_currency_labels_from_options(currency_options, child_table);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.frm.doc.operations && this.frm.doc.operations.length > 0) {
|
||||
var item_grid = this.frm.fields_dict["operations"].grid;
|
||||
$.each(["base_operating_cost", "base_hour_rate"], function (i, fname) {
|
||||
if (frappe.meta.get_docfield(item_grid.doctype, fname))
|
||||
@@ -1986,9 +1972,6 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
}
|
||||
|
||||
if (this.frm.doc.secondary_items && this.frm.doc.secondary_items.length > 0) {
|
||||
this.frm.set_currency_labels(["rate", "amount"], this.frm.doc.currency, "secondary_items");
|
||||
this.frm.set_currency_labels(["base_rate", "base_amount"], company_currency, "secondary_items");
|
||||
|
||||
var item_grid = this.frm.fields_dict["secondary_items"].grid;
|
||||
$.each(["base_rate", "base_amount"], function (i, fname) {
|
||||
if (frappe.meta.get_docfield(item_grid.doctype, fname))
|
||||
@@ -1996,74 +1979,12 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
});
|
||||
}
|
||||
|
||||
if (this.frm.doc.taxes && this.frm.doc.taxes.length > 0) {
|
||||
this.frm.set_currency_labels(
|
||||
["tax_amount", "total", "tax_amount_after_discount"],
|
||||
this.frm.doc.currency,
|
||||
"taxes"
|
||||
);
|
||||
|
||||
this.frm.set_currency_labels(
|
||||
["base_tax_amount", "base_total", "base_tax_amount_after_discount"],
|
||||
company_currency,
|
||||
"taxes"
|
||||
);
|
||||
}
|
||||
|
||||
if (this.frm.doc.advances && this.frm.doc.advances.length > 0) {
|
||||
this.frm.set_currency_labels(
|
||||
["advance_amount", "allocated_amount"],
|
||||
this.frm.doc.party_account_currency,
|
||||
"advances"
|
||||
);
|
||||
}
|
||||
|
||||
this.update_payment_schedule_grid_labels(company_currency);
|
||||
}
|
||||
|
||||
update_item_grid_labels(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(company_currency) {
|
||||
const me = this;
|
||||
if (this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length > 0) {
|
||||
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))
|
||||
|
||||
@@ -37,5 +37,6 @@ import "./utils/demo.js";
|
||||
import "./financial_statements.js";
|
||||
import "./sales_trends_filters.js";
|
||||
import "./purchase_trends_filters.js";
|
||||
import "./utils/naming_series.js";
|
||||
|
||||
// import { sum } from 'frappe/public/utils/util.js'
|
||||
|
||||
@@ -106,15 +106,19 @@ $.extend(erpnext.queries, {
|
||||
});
|
||||
}
|
||||
|
||||
let filters = { link_doctype: "Company", link_name: doc.company || "" };
|
||||
const is_drop_ship = doc.items.some((item) => item.delivered_by_supplier);
|
||||
if (is_drop_ship) filters = {};
|
||||
|
||||
return {
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
filters: { link_doctype: "Company", link_name: doc.company },
|
||||
filters: filters,
|
||||
};
|
||||
},
|
||||
|
||||
dispatch_address_query: function (doc) {
|
||||
var filters = { link_doctype: "Company", link_name: doc.company || "" };
|
||||
var is_drop_ship = doc.items.some((item) => item.delivered_by_supplier);
|
||||
let filters = { link_doctype: "Company", link_name: doc.company || "" };
|
||||
const is_drop_ship = doc.items.some((item) => item.delivered_by_supplier);
|
||||
if (is_drop_ship) filters = {};
|
||||
return {
|
||||
query: "frappe.contacts.doctype.address.address.address_query",
|
||||
|
||||
417
erpnext/public/js/utils/naming_series.js
Normal file
417
erpnext/public/js/utils/naming_series.js
Normal file
@@ -0,0 +1,417 @@
|
||||
frappe.provide("erpnext");
|
||||
|
||||
erpnext.NamingSeriesDialog = class NamingSeriesDialog {
|
||||
constructor(opts = {}) {
|
||||
this.opts = Object.assign(
|
||||
{
|
||||
title: __("Document Naming"),
|
||||
single_doctype: "Document Naming Settings",
|
||||
},
|
||||
opts
|
||||
);
|
||||
|
||||
this.current_doctype = null;
|
||||
this.loaded = false;
|
||||
this.make_dialog();
|
||||
}
|
||||
|
||||
make_dialog() {
|
||||
this.dialog = new frappe.ui.Dialog({
|
||||
title: this.opts.title,
|
||||
size: "medium",
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Table",
|
||||
fieldname: "naming_series_options",
|
||||
label: __("Add Series Prefix"),
|
||||
reqd: 1,
|
||||
in_place_edit: true,
|
||||
data: [],
|
||||
fields: [
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "series",
|
||||
label: __("Series"),
|
||||
in_list_view: 1,
|
||||
change: async function () {
|
||||
const preview = await this.grid_row.grid._naming_dialog.get_series_preview(
|
||||
this.doc.series
|
||||
);
|
||||
this.doc.preview = preview;
|
||||
this.grid_row.refresh_field("preview");
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldtype: "Data",
|
||||
fieldname: "preview",
|
||||
label: __("Preview"),
|
||||
in_list_view: 1,
|
||||
placeholder: " ",
|
||||
read_only: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ fieldtype: "Section Break", label: __("Rules for configuring series"), collapsible: 1 },
|
||||
{
|
||||
fieldtype: "HTML",
|
||||
fieldname: "naming_series_description",
|
||||
},
|
||||
],
|
||||
primary_action_label: __("Update"),
|
||||
primary_action: () => this.save(),
|
||||
});
|
||||
|
||||
this.dialog.fields_dict.naming_series_options.grid._naming_dialog = this;
|
||||
}
|
||||
|
||||
async show() {
|
||||
this.dialog.show();
|
||||
this.render_help();
|
||||
|
||||
if (this.opts.doctype && !this.loaded) {
|
||||
await this.get_transaction(this.opts.doctype);
|
||||
this.loaded = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
render_help() {
|
||||
this.dialog.get_field("naming_series_description").$wrapper.html(`
|
||||
<ul>
|
||||
<li>${__("Allowed special characters are '/' and '-'")}</li>
|
||||
<li>
|
||||
${__(
|
||||
"Optionally, set the number of digits in the series using dot (.) followed by hashes (#). For example, '.####' means that the series will have four digits. Default is five digits."
|
||||
)}
|
||||
</li>
|
||||
<li> ${__("You can also use variables in the series name by putting them between (.) dots")}
|
||||
<br>
|
||||
${__("Supported Variables:")}
|
||||
<ul>
|
||||
<li><code>.YYYY.</code> - ${__("Year in 4 digits")}</li>
|
||||
<li><code>.YY.</code> - ${__("Year in 2 digits")}</li>
|
||||
<li><code>.MM.</code> - ${__("Month")}</li>
|
||||
<li><code>.DD.</code> - ${__("Day of month")}</li>
|
||||
<li><code>.WW.</code> - ${__("Week of the year")}</li>
|
||||
<li>
|
||||
<code>.{fieldname}.</code> - ${__("fieldname on the document e.g.")}
|
||||
<code>branch</code>
|
||||
</li>
|
||||
<li><code>.FY.</code> - ${__("Fiscal Year (requires ERPNext to be installed)")}</li>
|
||||
<li><code>.ABBR.</code> - ${__("Company Abbreviation (requires ERPNext to be installed)")}</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
Examples:
|
||||
<ul>
|
||||
<li>INV-</li>
|
||||
<li>INV-10-</li>
|
||||
<li>INVK-</li>
|
||||
<li>INV-.YYYY.-._{branch}.-.MM.-.####</li>
|
||||
</ul>
|
||||
<br>`);
|
||||
}
|
||||
|
||||
get_series_preview(series) {
|
||||
if (!series) return "";
|
||||
|
||||
return this.get_document_naming_doc().then((doc) => {
|
||||
doc.try_naming_series = series;
|
||||
doc.transaction_type = this.current_doctype;
|
||||
return frappe
|
||||
.call({
|
||||
doc: doc,
|
||||
method: "preview_series",
|
||||
freeze: true,
|
||||
})
|
||||
.then((r) => (r.message || "").split("\n")[0] || "");
|
||||
});
|
||||
}
|
||||
|
||||
get_document_naming_doc() {
|
||||
const dt = this.opts.single_doctype;
|
||||
return frappe.model.with_doc(dt, dt).then(() => {
|
||||
return frappe.model.get_doc(dt, dt);
|
||||
});
|
||||
}
|
||||
|
||||
async get_transaction(doctype) {
|
||||
this.current_doctype = doctype;
|
||||
|
||||
await frappe.model.with_doctype(doctype, async () => {
|
||||
const meta = frappe.get_meta(doctype);
|
||||
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
|
||||
const series_list = (naming_df?.options || "").split("\n").filter(Boolean);
|
||||
const rows = await Promise.all(
|
||||
series_list.map(async (series) => ({
|
||||
series: series,
|
||||
preview: await this.get_series_preview(series),
|
||||
}))
|
||||
);
|
||||
|
||||
this.dialog.fields_dict.naming_series_options.df.data = rows;
|
||||
this.dialog.fields_dict.naming_series_options.grid.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
save() {
|
||||
const rows = this.dialog.fields_dict.naming_series_options.grid.get_data();
|
||||
const naming_series_options = rows
|
||||
.map((r) => (r.series || "").trim())
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
|
||||
if (!this.current_doctype) {
|
||||
frappe.msgprint(__("Please select a transaction."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!naming_series_options) {
|
||||
frappe.msgprint(__("Please add at least one naming series."));
|
||||
return;
|
||||
}
|
||||
|
||||
this.get_document_naming_doc().then((doc) => {
|
||||
doc.transaction_type = this.current_doctype;
|
||||
doc.naming_series_options = naming_series_options;
|
||||
|
||||
frappe.call({
|
||||
doc: doc,
|
||||
method: "update_series",
|
||||
freeze: true,
|
||||
callback: async () => {
|
||||
const updated_rows = await Promise.all(
|
||||
naming_series_options
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map(async (series) => ({
|
||||
series: series,
|
||||
preview: await this.get_series_preview(series),
|
||||
}))
|
||||
);
|
||||
|
||||
this.dialog.fields_dict.naming_series_options.df.data = updated_rows;
|
||||
this.dialog.fields_dict.naming_series_options.grid.refresh();
|
||||
|
||||
frappe.show_alert({ message: __("Naming Series updated"), indicator: "green" });
|
||||
this.dialog.hide();
|
||||
this.opts.on_update?.({ doctype: this.current_doctype, naming_series_options });
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
erpnext.NamingSeriesTable = class NamingSeriesTable {
|
||||
constructor(opts = {}) {
|
||||
this.frm = opts.frm;
|
||||
this.transactions = opts.transactions || [];
|
||||
this.$wrapper = opts.frm.get_field(opts.fieldname).$wrapper;
|
||||
this.theme_observer = null;
|
||||
}
|
||||
render() {
|
||||
this.$wrapper.html(`
|
||||
<div class="form-grid" style="margin-bottom: 24px;">
|
||||
<table class="table" style="margin: 0;">
|
||||
<thead class="grid-heading-row" style="background-color: var(--subtle-fg);">
|
||||
<tr>
|
||||
<td style="width: 25%; padding: 8px 12px; text-align: left;">
|
||||
${__("Transaction")}
|
||||
</td>
|
||||
<td colspan="2"
|
||||
style="width: 75%; padding: 8px 12px; text-align: left; border-left: 1px solid var(--border-color);">
|
||||
${__("Current Series")}
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="naming-series-table-rows"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
`);
|
||||
|
||||
const $rows = this.$wrapper.find(".naming-series-table-rows");
|
||||
this.map_configure_button($rows);
|
||||
this.get_row_data($rows);
|
||||
|
||||
if (this.theme_observer) {
|
||||
this.theme_observer.disconnect();
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const badge_class = this.get_current_badge_class();
|
||||
|
||||
this.$wrapper.find(".badge").removeClass("badge-light badge-dark").addClass(badge_class);
|
||||
});
|
||||
|
||||
observer.observe(document.documentElement, {
|
||||
attributes: true,
|
||||
attributeFilter: ["data-theme"],
|
||||
});
|
||||
}
|
||||
|
||||
map_configure_button($rows) {
|
||||
$rows.on("click", ".configure-btn", (e) => {
|
||||
const $btn = $(e.currentTarget);
|
||||
const doctype = $btn.data("doctype");
|
||||
const label = $btn.data("label");
|
||||
|
||||
if (!this.frm._naming_dialogs) this.frm._naming_dialogs = {};
|
||||
|
||||
if (!this.frm._naming_dialogs[doctype]) {
|
||||
this.frm._naming_dialogs[doctype] = new erpnext.NamingSeriesDialog({
|
||||
doctype: doctype,
|
||||
title: __("{0} Naming Series", [__(label)]),
|
||||
on_update: ({ naming_series_options }) => {
|
||||
const series = naming_series_options.split("\n").filter(Boolean);
|
||||
this.$wrapper
|
||||
.find(`.series-cell-${frappe.scrub(doctype)}`)
|
||||
.html(this.series_list_background(series));
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.frm._naming_dialogs[doctype].show();
|
||||
});
|
||||
}
|
||||
|
||||
async get_row_data($rows) {
|
||||
const rows = await Promise.all(
|
||||
this.transactions.map(async (t) => {
|
||||
await new Promise((resolve) => frappe.model.with_doctype(t.doctype, resolve));
|
||||
|
||||
const meta = frappe.get_meta(t.doctype);
|
||||
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
|
||||
const series = (naming_df?.options || "")
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
return this.make_row(t, series);
|
||||
})
|
||||
);
|
||||
|
||||
$rows.empty();
|
||||
rows.forEach(($row) => $rows.append($row));
|
||||
}
|
||||
|
||||
make_row(t, series) {
|
||||
return $(`
|
||||
<tr>
|
||||
<td style="width: 25%; padding: 8px 12px; vertical-align: top; background-color: var(--card-bg);">
|
||||
${frappe.utils.escape_html(t.label)}
|
||||
</td>
|
||||
<td class="series-cell-${frappe.scrub(t.doctype)}"
|
||||
style="width: 70%; padding: 8px 12px; border-left: 1px solid var(--border-color); white-space: normal; vertical-align: top; background-color: var(--card-bg);">
|
||||
${this.series_list_background(series)}
|
||||
</td>
|
||||
<td class="text-center"
|
||||
style="width: 5%; padding: 8px 12px; border-left: 1px solid var(--border-color); vertical-align: middle; background-color: var(--card-bg);">
|
||||
<a class="btn-link configure-btn"
|
||||
data-doctype="${frappe.utils.escape_html(t.doctype)}"
|
||||
data-label="${frappe.utils.escape_html(t.label)}"
|
||||
style="cursor: pointer; color: var(--text-muted);">
|
||||
${frappe.utils.icon("edit", "sm")}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
|
||||
get_current_badge_class() {
|
||||
return document.documentElement.getAttribute("data-theme") === "dark" ? "badge-dark" : "badge-light";
|
||||
}
|
||||
|
||||
series_list_background(series_list) {
|
||||
if (!series_list.length) {
|
||||
return `<span class="text-muted">${__("Not configured")}</span>`;
|
||||
}
|
||||
|
||||
const badge_class = this.get_current_badge_class();
|
||||
|
||||
return series_list
|
||||
.map(
|
||||
(s) => `<span class="badge ${badge_class}"
|
||||
style="margin: 2px; font-family: monospace; font-weight: normal;">
|
||||
${frappe.utils.escape_html(s)}
|
||||
</span>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Object} frm - Frappe form instance.
|
||||
*/
|
||||
erpnext.NamingSeriesController = class NamingSeriesController {
|
||||
constructor(frm) {
|
||||
this.frm = frm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the naming series table in the given field.
|
||||
*
|
||||
* @param {string} fieldname - Fieldname where the table should be rendered.
|
||||
* @param {Array<{doctype: string, label: string}>} [transactions=[]] - Transactions to display.
|
||||
* @returns {void}
|
||||
*/
|
||||
render_table(fieldname, transactions = []) {
|
||||
this.frm._naming_series_table = new erpnext.NamingSeriesTable({
|
||||
frm: this.frm,
|
||||
fieldname: fieldname,
|
||||
transactions: transactions,
|
||||
});
|
||||
this.frm._naming_series_table.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads naming series from the given master doctype into a field.
|
||||
*
|
||||
* @param {string} doctype - Master doctype name.
|
||||
* @param {string} field - Fieldname where series should be shown.
|
||||
* @returns {void}
|
||||
*/
|
||||
load_master_series(doctype, field) {
|
||||
frappe.model.with_doctype(doctype, () => {
|
||||
const meta = frappe.get_meta(doctype);
|
||||
const naming_df = (meta?.fields || []).find((df) => df.fieldname === "naming_series");
|
||||
const options = naming_df?.options || "";
|
||||
const series_list = options
|
||||
.split("\n")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
this.frm.doc[field] = series_list.length
|
||||
? series_list.join("\n")
|
||||
: __("No naming series defined");
|
||||
this.frm.refresh_field(field);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the naming series dialog for a doctype.
|
||||
*
|
||||
* @param {string} doctype - Transaction doctype.
|
||||
* @param {Function} [on_update] - Called after series are updated.
|
||||
* @returns {void}
|
||||
*/
|
||||
show_naming_series_dialog(doctype, on_update) {
|
||||
if (!this.frm._naming_dialogs) this.frm._naming_dialogs = {};
|
||||
|
||||
if (!this.frm._naming_dialogs[doctype]) {
|
||||
this.frm._naming_dialogs[doctype] = new erpnext.NamingSeriesDialog({
|
||||
doctype: doctype,
|
||||
title: __("{0} Naming Series", [__(doctype)]),
|
||||
on_update: ({ naming_series_options }) => {
|
||||
const series = naming_series_options.split("\n").filter(Boolean);
|
||||
this.frm
|
||||
.get_field(this.opts.table_field)
|
||||
.$wrapper.find(`.series-cell-${frappe.scrub(doctype)}`)
|
||||
.html(this.frm._naming_series_table?.series_list_background(series));
|
||||
on_update?.({ doctype, naming_series_options });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.frm._naming_dialogs[doctype].show();
|
||||
}
|
||||
};
|
||||
@@ -6,6 +6,7 @@ import erpnext
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.regional.report.uae_vat_201.uae_vat_201 import (
|
||||
execute,
|
||||
get_exempt_total,
|
||||
get_standard_rated_expenses_tax,
|
||||
get_standard_rated_expenses_total,
|
||||
@@ -32,6 +33,13 @@ class TestUaeVat201(ERPNextTestSuite):
|
||||
make_item("_Test UAE VAT Zero Rated Item", properties={"is_zero_rated": 1, "is_exempt": 0})
|
||||
make_item("_Test UAE VAT Exempt Item", properties={"is_zero_rated": 0, "is_exempt": 1})
|
||||
|
||||
def test_validate_company_region(self):
|
||||
self.assertRaises(
|
||||
frappe.exceptions.ValidationError,
|
||||
execute,
|
||||
{"company": "_Test Company"},
|
||||
)
|
||||
|
||||
def test_uae_vat_201_report(self):
|
||||
make_sales_invoices()
|
||||
create_purchase_invoices()
|
||||
|
||||
@@ -10,6 +10,13 @@ frappe.query_reports["UAE VAT 201"] = {
|
||||
options: "Company",
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
get_query: function () {
|
||||
return {
|
||||
filters: {
|
||||
country: "United Arab Emirates",
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "from_date",
|
||||
|
||||
@@ -5,13 +5,25 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
|
||||
from erpnext import get_region
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
validate_company_region(filters)
|
||||
columns = get_columns()
|
||||
data, emirates, amounts_by_emirate = get_data(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def validate_company_region(filters):
|
||||
if filters.get("company") and get_region(filters.get("company")) != "United Arab Emirates":
|
||||
frappe.throw(
|
||||
_(
|
||||
"The company {0} is not in United Arab Emirates. UAE VAT 201 report is only available for companies in United Arab Emirates."
|
||||
).format(frappe.bold(filters.get("company")))
|
||||
)
|
||||
|
||||
|
||||
def get_columns():
|
||||
"""Creates a list of dictionaries that are used to generate column headers of the data table."""
|
||||
return [
|
||||
|
||||
@@ -565,7 +565,9 @@ def _make_customer(source_name, ignore_permissions=False):
|
||||
if quotation.quotation_to == "Customer":
|
||||
return frappe.get_doc("Customer", quotation.party_name)
|
||||
elif quotation.quotation_to == "CRM Deal":
|
||||
return frappe.get_doc("Customer", {"crm_deal": quotation.party_name})
|
||||
customer_name = frappe.get_value("Customer", {"crm_deal": quotation.party_name})
|
||||
if customer_name:
|
||||
return frappe.get_doc("Customer", customer_name)
|
||||
|
||||
# Check if a Customer already exists for the Lead or Prospect.
|
||||
existing_customer = None
|
||||
|
||||
@@ -183,6 +183,61 @@ class TestQuotation(ERPNextTestSuite):
|
||||
|
||||
self.assertTrue(quotation.payment_schedule)
|
||||
|
||||
def test_terms_attachments_are_copied_to_quotation(self):
|
||||
terms = make_terms_and_conditions(copy_attachments_to_transaction=True)
|
||||
first_attachment = make_file_attachment(
|
||||
"Terms and Conditions",
|
||||
terms.name,
|
||||
content="First terms attachment",
|
||||
)
|
||||
|
||||
quotation = make_quotation(do_not_save=1)
|
||||
quotation.tc_name = terms.name
|
||||
quotation.insert()
|
||||
|
||||
self.assertEqual(get_attachment_urls("Quotation", quotation.name), {first_attachment.file_url})
|
||||
|
||||
second_attachment = make_file_attachment(
|
||||
"Terms and Conditions",
|
||||
terms.name,
|
||||
content="Second terms attachment",
|
||||
)
|
||||
quotation.valid_till = add_days(getdate(quotation.valid_till), 1)
|
||||
quotation.save()
|
||||
|
||||
quotation_attachments = get_attachment_urls("Quotation", quotation.name)
|
||||
self.assertEqual(quotation_attachments, {first_attachment.file_url})
|
||||
self.assertNotIn(second_attachment.file_url, quotation_attachments)
|
||||
|
||||
new_terms = make_terms_and_conditions(copy_attachments_to_transaction=True)
|
||||
new_terms_attachment = make_file_attachment(
|
||||
"Terms and Conditions",
|
||||
new_terms.name,
|
||||
content="Attachment from updated terms",
|
||||
)
|
||||
quotation.tc_name = new_terms.name
|
||||
quotation.valid_till = add_days(getdate(quotation.valid_till), 1)
|
||||
quotation.save()
|
||||
|
||||
self.assertEqual(
|
||||
get_attachment_urls("Quotation", quotation.name),
|
||||
{first_attachment.file_url, new_terms_attachment.file_url},
|
||||
)
|
||||
|
||||
def test_terms_attachments_are_not_copied_when_disabled(self):
|
||||
terms = make_terms_and_conditions(copy_attachments_to_transaction=False)
|
||||
make_file_attachment(
|
||||
"Terms and Conditions",
|
||||
terms.name,
|
||||
content="Terms attachment should stay on the template",
|
||||
)
|
||||
|
||||
quotation = make_quotation(do_not_save=1)
|
||||
quotation.tc_name = terms.name
|
||||
quotation.insert()
|
||||
|
||||
self.assertFalse(get_attachment_urls("Quotation", quotation.name))
|
||||
|
||||
@ERPNextTestSuite.change_settings(
|
||||
"Accounts Settings",
|
||||
{"automatically_fetch_payment_terms": 1},
|
||||
@@ -1148,6 +1203,42 @@ def get_quotation_dict(party_name=None, item_code=None):
|
||||
}
|
||||
|
||||
|
||||
def make_terms_and_conditions(copy_attachments_to_transaction=False):
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "Terms and Conditions",
|
||||
"title": f"_Test Terms and Conditions {frappe.generate_hash(length=8)}",
|
||||
"selling": 1,
|
||||
"terms": "Test terms",
|
||||
"copy_attachments_to_transaction": 1 if copy_attachments_to_transaction else 0,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def make_file_attachment(doctype, docname, content):
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "File",
|
||||
"file_name": f"terms-attachment-{frappe.generate_hash(length=8)}.txt",
|
||||
"attached_to_doctype": doctype,
|
||||
"attached_to_name": docname,
|
||||
"content": content,
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
def get_attachment_urls(doctype, docname):
|
||||
return {
|
||||
file.file_url
|
||||
for file in frappe.get_all(
|
||||
"File",
|
||||
filters={"attached_to_doctype": doctype, "attached_to_name": docname},
|
||||
fields=["file_url"],
|
||||
)
|
||||
if file.file_url
|
||||
}
|
||||
|
||||
|
||||
def make_quotation(**args):
|
||||
qo = frappe.new_doc("Quotation")
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -1162,11 +1162,13 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
}
|
||||
// payment request
|
||||
if (flt(doc.per_billed) < 100 + frappe.boot.sysdefaults.over_billing_allowance) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
() => this.make_payment_request_with_schedule(),
|
||||
__("Create")
|
||||
);
|
||||
if (frappe.boot.user.in_create.includes("Payment Request")) {
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
() => this.make_payment_request_with_schedule(),
|
||||
__("Create")
|
||||
);
|
||||
}
|
||||
|
||||
if (frappe.model.can_create("Payment Entry")) {
|
||||
this.frm.add_custom_button(
|
||||
@@ -1218,6 +1220,24 @@ erpnext.selling.SalesOrderController = class SalesOrderController extends erpnex
|
||||
this.order_type(doc);
|
||||
}
|
||||
|
||||
items_add(doc, cdt, cdn) {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
const field_copy = [];
|
||||
if (doc.project) {
|
||||
frappe.model.set_value(cdt, cdn, "project", doc.project);
|
||||
} else {
|
||||
field_copy.push("project");
|
||||
}
|
||||
if (doc.delivery_date) {
|
||||
frappe.model.set_value(cdt, cdn, "delivery_date", doc.delivery_date);
|
||||
} else {
|
||||
field_copy.push("delivery_date");
|
||||
}
|
||||
if (field_copy.length) {
|
||||
this.frm.script_manager.copy_from_first_row("items", row, field_copy);
|
||||
}
|
||||
}
|
||||
|
||||
create_pick_list() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.selling.doctype.sales_order.sales_order.create_pick_list",
|
||||
|
||||
@@ -847,6 +847,7 @@
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Loyalty Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1480,6 +1481,7 @@
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -1763,7 +1765,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-28 06:30:35.902868",
|
||||
"modified": "2026-05-01 02:37:30.937916",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Sales Order",
|
||||
|
||||
@@ -610,6 +610,7 @@ class SalesOrder(SellingController):
|
||||
self.update_subcontracting_order_status()
|
||||
self.notify_update()
|
||||
clear_doctype_notifications(self)
|
||||
self.update_blanket_order()
|
||||
|
||||
def update_subcontracting_order_status(self):
|
||||
from erpnext.subcontracting.doctype.subcontracting_inward_order.subcontracting_inward_order import (
|
||||
@@ -683,18 +684,12 @@ class SalesOrder(SellingController):
|
||||
|
||||
for item in self.items:
|
||||
if item.delivered_by_supplier:
|
||||
item_delivered_qty = frappe.db.sql(
|
||||
"""select sum(qty)
|
||||
from `tabPurchase Order Item` poi, `tabPurchase Order` po
|
||||
where poi.sales_order_item = %s
|
||||
and poi.item_code = %s
|
||||
and poi.parent = po.name
|
||||
and po.docstatus = 1
|
||||
and po.status = 'Delivered'""",
|
||||
(item.name, item.item_code),
|
||||
)
|
||||
|
||||
item_delivered_qty = item_delivered_qty[0][0] if item_delivered_qty else 0
|
||||
item_delivered_qty = frappe.get_all(
|
||||
"Purchase Order Item",
|
||||
{"sales_order_item": item.name, "docstatus": 1},
|
||||
[{"SUM": "received_qty", "AS": "received_qty"}],
|
||||
pluck="received_qty",
|
||||
)[0]
|
||||
item.db_set("delivered_qty", flt(item_delivered_qty), update_modified=False)
|
||||
|
||||
delivered_qty += min(item.delivered_qty, item.qty)
|
||||
@@ -1627,7 +1622,7 @@ def make_purchase_order(source_name, selected_items=None, target_doc=None):
|
||||
if default_payment_terms:
|
||||
target.payment_terms_template = default_payment_terms
|
||||
|
||||
if any(item.delivered_by_supplier == 1 for item in source.items):
|
||||
if any(item.delivered_by_supplier for item in target.items):
|
||||
if source.shipping_address_name:
|
||||
target.shipping_address = source.shipping_address_name
|
||||
target.shipping_address_display = source.shipping_address
|
||||
|
||||
@@ -1224,9 +1224,14 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
self.assertEqual(abs(flt(reserved_qty)), 0)
|
||||
|
||||
# test per_delivered status
|
||||
update_status("Delivered", po.name)
|
||||
self.assertEqual(po.status, "To Receive and Bill")
|
||||
self.assertEqual(so.status, "To Deliver and Bill")
|
||||
po.update_dropship_received_qty([{"name": po.items[0].name, "qty_change": 2}])
|
||||
self.assertEqual(flt(frappe.db.get_value("Sales Order", so.name, "per_delivered"), 2), 100.00)
|
||||
po.load_from_db()
|
||||
so.reload()
|
||||
self.assertEqual(po.status, "To Bill")
|
||||
self.assertEqual(so.status, "To Bill")
|
||||
|
||||
# test after closing so
|
||||
so.db_set("status", "Closed")
|
||||
@@ -1619,7 +1624,7 @@ class TestSalesOrder(ERPNextTestSuite):
|
||||
make_item( # template item
|
||||
"Test-WO-Tshirt",
|
||||
{
|
||||
"has_variant": 1,
|
||||
"has_variants": 1,
|
||||
"variant_based_on": "Item Attribute",
|
||||
"attributes": [{"attribute": "Test Colour"}],
|
||||
},
|
||||
|
||||
@@ -2,7 +2,59 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Selling Settings", {
|
||||
refresh(frm) {
|
||||
if (!frm.naming_controller) frm.naming_controller = new erpnext.NamingSeriesController(frm);
|
||||
|
||||
const display = frm.doc.cust_master_name === "Naming Series";
|
||||
frm.set_df_property("naming_series_details", "hidden", !display);
|
||||
frm.set_df_property("configure", "hidden", !display);
|
||||
|
||||
if (display) {
|
||||
frm.naming_controller.load_master_series("Customer", "naming_series_details");
|
||||
}
|
||||
|
||||
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
|
||||
},
|
||||
|
||||
cust_master_name(frm) {
|
||||
const display = frm.doc.cust_master_name === "Naming Series";
|
||||
frm.set_df_property("naming_series_details", "hidden", !display);
|
||||
frm.set_df_property("configure", "hidden", !display);
|
||||
|
||||
if (display) {
|
||||
frm.naming_controller.load_master_series("Customer", "naming_series_details");
|
||||
} else {
|
||||
frm.doc.naming_series_details = "";
|
||||
frm.refresh_field("naming_series_details");
|
||||
}
|
||||
|
||||
frm.naming_controller.render_table("transaction_naming_html", get_transactions(frm));
|
||||
},
|
||||
|
||||
configure(frm) {
|
||||
frm.naming_controller.show_naming_series_dialog("Customer", ({ naming_series_options }) => {
|
||||
frm.doc.naming_series_details = naming_series_options;
|
||||
frm.refresh_field("naming_series_details");
|
||||
});
|
||||
},
|
||||
|
||||
after_save(frm) {
|
||||
frappe.boot.user.defaults.editable_price_list_rate = frm.doc.editable_price_list_rate;
|
||||
},
|
||||
});
|
||||
|
||||
function get_transactions(frm) {
|
||||
const transactions = [
|
||||
{ label: __("Customer"), doctype: "Customer" },
|
||||
{ label: __("Quotation"), doctype: "Quotation" },
|
||||
{ label: __("Sales Order"), doctype: "Sales Order" },
|
||||
{ label: __("Sales Invoice"), doctype: "Sales Invoice" },
|
||||
{ label: __("Delivery Note"), doctype: "Delivery Note" },
|
||||
];
|
||||
|
||||
if (frm.doc.cust_master_name !== "Naming Series") {
|
||||
return transactions.filter((t) => t.doctype !== "Customer");
|
||||
}
|
||||
|
||||
return transactions;
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
"customer_defaults_tab",
|
||||
"customer_defaults_section",
|
||||
"cust_master_name",
|
||||
"customer_group",
|
||||
"naming_series_details",
|
||||
"configure",
|
||||
"column_break_4",
|
||||
"customer_group",
|
||||
"territory",
|
||||
"item_price_tab",
|
||||
"item_price_settings_section",
|
||||
@@ -57,7 +59,9 @@
|
||||
"section_break_zwh6",
|
||||
"allow_delivery_of_overproduced_qty",
|
||||
"column_break_mla9",
|
||||
"deliver_secondary_items"
|
||||
"deliver_secondary_items",
|
||||
"default_naming_tab",
|
||||
"transaction_naming_html"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -279,7 +283,7 @@
|
||||
{
|
||||
"fieldname": "item_price_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Item Price"
|
||||
"label": "Pricing"
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_tab",
|
||||
@@ -377,6 +381,29 @@
|
||||
"fieldname": "blanket_orders_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Blanket Orders"
|
||||
},
|
||||
{
|
||||
"fieldname": "configure",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 1,
|
||||
"label": "Configure Series"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series_details",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"is_virtual": 1,
|
||||
"label": "Naming Series options",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "default_naming_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Document Naming"
|
||||
},
|
||||
{
|
||||
"fieldname": "transaction_naming_html",
|
||||
"fieldtype": "HTML"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -385,7 +412,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-21 21:29:32.890098",
|
||||
"modified": "2026-04-29 11:05:48.836362",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Selling",
|
||||
"name": "Selling Settings",
|
||||
|
||||
@@ -1085,7 +1085,6 @@ def create_transaction_deletion_request(company):
|
||||
tdr.reload()
|
||||
|
||||
tdr.submit()
|
||||
tdr.start_deletion_tasks()
|
||||
|
||||
frappe.msgprint(
|
||||
_("Transaction Deletion Document {0} has been triggered for company {1}").format(
|
||||
|
||||
@@ -417,6 +417,7 @@ def is_holiday(employee, date=None, raise_exception=True, only_non_weekly=False,
|
||||
|
||||
@frappe.whitelist()
|
||||
def deactivate_sales_person(status=None, employee=None):
|
||||
frappe.has_permission("Employee", doc=employee, ptype="write", throw=True)
|
||||
if status == "Left":
|
||||
sales_person = frappe.db.get_value("Sales Person", {"Employee": employee})
|
||||
if sales_person:
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"field_order": [
|
||||
"title",
|
||||
"disabled",
|
||||
"column_break_ofhb",
|
||||
"copy_attachments_to_transaction",
|
||||
"applicable_modules_section",
|
||||
"selling",
|
||||
"buying",
|
||||
@@ -72,12 +74,22 @@
|
||||
{
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ofhb",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "copy_attachments_to_transaction",
|
||||
"fieldtype": "Check",
|
||||
"label": "Copy Attachments to Transaction"
|
||||
}
|
||||
],
|
||||
"icon": "icon-legal",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-14 18:22:49.285298",
|
||||
"modified": "2026-04-29 22:51:49.285298",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Setup",
|
||||
"name": "Terms and Conditions",
|
||||
|
||||
@@ -21,6 +21,7 @@ class TermsandConditions(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
buying: DF.Check
|
||||
copy_attachments_to_transaction: DF.Check
|
||||
disabled: DF.Check
|
||||
selling: DF.Check
|
||||
terms: DF.TextEditor | None
|
||||
|
||||
@@ -396,7 +396,6 @@ def create_and_submit_transaction_deletion_doc(company):
|
||||
|
||||
tdr.process_in_single_transaction = True
|
||||
tdr.submit()
|
||||
tdr.start_deletion_tasks()
|
||||
return tdr
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user