mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-21 05:34:02 +00:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd35cd1f84 | ||
|
|
77a6299e8b | ||
|
|
b79ec7cbdd | ||
|
|
927360dd1d | ||
|
|
ab090295d9 | ||
|
|
c4b7b15824 | ||
|
|
cfd3847255 | ||
|
|
dc914adb62 | ||
|
|
41bff45d7a | ||
|
|
8f0310859d | ||
|
|
98de025a09 | ||
|
|
b962a1a0cd | ||
|
|
3dbadfadd5 | ||
|
|
67ad437dd3 | ||
|
|
7086db1e1c | ||
|
|
87b798b936 | ||
|
|
b894b02ebc | ||
|
|
1d20469c99 | ||
|
|
0db7e1e56b | ||
|
|
f24b556336 | ||
|
|
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 | ||
|
|
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.18.2"
|
||||
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
@@ -493,6 +492,8 @@ class PurchaseOrder(BuyingController):
|
||||
|
||||
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 +567,72 @@ 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)
|
||||
)
|
||||
)
|
||||
|
||||
item.received_qty += d.get("qty_change")
|
||||
self.update_receiving_percentage()
|
||||
self.save()
|
||||
|
||||
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 +645,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)
|
||||
|
||||
@@ -622,11 +622,13 @@
|
||||
"width": "100px"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "received_qty",
|
||||
"fieldname": "received_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Qty",
|
||||
"no_copy": 1,
|
||||
"non_negative": 1,
|
||||
"oldfieldname": "received_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"print_hide": 1,
|
||||
@@ -950,7 +952,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-30 16:51:57.761673",
|
||||
"modified": "2026-05-08 20:40:10.683023",
|
||||
"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
|
||||
|
||||
|
||||
@@ -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
@@ -3,7 +3,7 @@ from frappe import _
|
||||
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "demand_planning",
|
||||
"fieldname": "sales_forecast",
|
||||
"transactions": [
|
||||
{
|
||||
"label": _("MPS"),
|
||||
|
||||
@@ -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,5 @@ 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
|
||||
|
||||
@@ -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_dialog.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",
|
||||
|
||||
312
erpnext/public/js/utils/naming_series_dialog.js
Normal file
312
erpnext/public/js/utils/naming_series_dialog.js
Normal file
@@ -0,0 +1,312 @@
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
get_row_data($rows) {
|
||||
this.transactions.forEach((t) => {
|
||||
frappe.model.with_doctype(t.doctype, () => {
|
||||
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);
|
||||
|
||||
$rows.append(this.make_row(t, series));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
`);
|
||||
}
|
||||
|
||||
series_list_background(series_list) {
|
||||
if (!series_list.length) {
|
||||
return `<span class="text-muted">${__("Not configured")}</span>`;
|
||||
}
|
||||
return series_list
|
||||
.map(
|
||||
(s) => `<span class="badge badge-light"
|
||||
style="margin: 2px; font-family: monospace; font-weight: normal;">
|
||||
${frappe.utils.escape_html(s)}
|
||||
</span>`
|
||||
)
|
||||
.join("");
|
||||
}
|
||||
};
|
||||
@@ -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 (
|
||||
@@ -1627,7 +1628,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
|
||||
|
||||
@@ -1619,7 +1619,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,80 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Selling Settings", {
|
||||
refresh(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) {
|
||||
find_naming_series("Customer", "naming_series_details", frm);
|
||||
}
|
||||
load_default_naming_series(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) {
|
||||
find_naming_series("Customer", "naming_series_details", frm);
|
||||
} else {
|
||||
frm.set_value("naming_series_details", "");
|
||||
}
|
||||
},
|
||||
|
||||
configure(frm) {
|
||||
show_naming_series_dialog("Customer", frm);
|
||||
},
|
||||
|
||||
after_save(frm) {
|
||||
frappe.boot.user.defaults.editable_price_list_rate = frm.doc.editable_price_list_rate;
|
||||
},
|
||||
});
|
||||
|
||||
function show_naming_series_dialog(doctype, frm) {
|
||||
if (!frm._naming_series_dialog) {
|
||||
frm._naming_series_dialog = new erpnext.NamingSeriesDialog({
|
||||
doctype: doctype,
|
||||
title: __("Naming Series for {0}", [__(doctype)]),
|
||||
on_update: ({ naming_series_options }) => {
|
||||
frm.set_value("naming_series_details", naming_series_options);
|
||||
},
|
||||
});
|
||||
}
|
||||
frm._naming_series_dialog.show();
|
||||
}
|
||||
function find_naming_series(doctype, field, frm) {
|
||||
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);
|
||||
|
||||
frm.doc[field] = series_list.length ? series_list.join("\n") : __("No naming series defined");
|
||||
|
||||
frm.refresh_field(field);
|
||||
});
|
||||
}
|
||||
|
||||
function load_default_naming_series(frm) {
|
||||
let 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" },
|
||||
{ label: __("Payment Entry"), doctype: "Payment Entry" },
|
||||
{ label: __("POS Invoice"), doctype: "POS Invoice" },
|
||||
];
|
||||
|
||||
if (frm.doc.cust_master_name !== "Naming Series") {
|
||||
transactions = transactions.filter((t) => t.doctype !== "Customer");
|
||||
}
|
||||
new erpnext.NamingSeriesTable({
|
||||
frm: frm,
|
||||
fieldname: "transaction_naming_html",
|
||||
transactions: transactions,
|
||||
}).render();
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1387,6 +1387,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1405,6 +1406,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1423,6 +1425,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1441,6 +1444,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1459,6 +1463,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1477,6 +1482,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1499,6 +1505,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1517,6 +1524,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1535,6 +1543,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1553,6 +1562,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1571,6 +1581,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1589,6 +1600,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1620,6 +1632,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1629,6 +1642,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1638,6 +1652,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1647,6 +1662,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1656,6 +1672,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1665,6 +1682,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1674,6 +1692,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1683,6 +1702,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1692,6 +1712,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1701,6 +1722,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1710,6 +1732,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1719,6 +1742,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -1727,6 +1751,7 @@
|
||||
"account_number": "1433",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
}
|
||||
]
|
||||
@@ -2150,6 +2175,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2168,6 +2194,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2186,6 +2213,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2204,6 +2232,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2222,6 +2251,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2240,6 +2270,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2262,6 +2293,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2280,6 +2312,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2298,6 +2331,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2316,6 +2350,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2334,6 +2369,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2352,6 +2388,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2383,6 +2420,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2392,6 +2430,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2401,6 +2440,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2410,6 +2450,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2419,6 +2460,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2428,6 +2470,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2437,6 +2480,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2446,6 +2490,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2455,6 +2500,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2464,6 +2510,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2473,6 +2520,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2482,6 +2530,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2490,6 +2539,7 @@
|
||||
"account_number": "1588",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
}
|
||||
]
|
||||
@@ -2913,6 +2963,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2931,6 +2982,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2949,6 +3001,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2967,6 +3020,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -2985,6 +3039,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3003,6 +3058,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3025,6 +3081,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3043,6 +3100,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3061,7 +3119,8 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"tax_rate": 19.00
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
"tax_type": {
|
||||
@@ -3079,6 +3138,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3097,6 +3157,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3115,6 +3176,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3146,6 +3208,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3155,6 +3218,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3164,6 +3228,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3173,6 +3238,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3182,6 +3248,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3191,6 +3258,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3200,6 +3268,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3209,6 +3278,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3218,6 +3288,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3227,6 +3298,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3236,6 +3308,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3245,6 +3318,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3253,6 +3327,7 @@
|
||||
"account_number": "1550",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
}
|
||||
]
|
||||
@@ -3645,6 +3720,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3661,6 +3737,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3677,6 +3754,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3693,6 +3771,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3709,6 +3788,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3725,6 +3805,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3745,6 +3826,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3761,6 +3843,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3777,6 +3860,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3793,6 +3877,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3809,6 +3894,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3825,6 +3911,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3853,6 +3940,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3861,6 +3949,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3869,6 +3958,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3877,6 +3967,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3885,6 +3976,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3893,6 +3985,7 @@
|
||||
"root_type": "Liability",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3901,6 +3994,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3909,6 +4003,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3917,6 +4012,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3925,6 +4021,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3933,6 +4030,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 19.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3941,6 +4039,7 @@
|
||||
"root_type": "Asset",
|
||||
"tax_rate": 7.00
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
},
|
||||
{
|
||||
@@ -3948,6 +4047,7 @@
|
||||
"account_name": "Entstandene Einfuhrumsatzsteuer",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"not_applicable": 1,
|
||||
"tax_rate": 0.00
|
||||
}
|
||||
]
|
||||
|
||||
@@ -70,6 +70,7 @@ frappe.ui.form.on("Batch", {
|
||||
item_code: frm.doc.item,
|
||||
for_stock_levels: for_stock_levels,
|
||||
consider_negative_batches: 1,
|
||||
ignore_reserved_stock: 1,
|
||||
},
|
||||
callback: (r) => {
|
||||
if (!r.message || r.message.length === 0) {
|
||||
|
||||
@@ -372,6 +372,15 @@ erpnext.stock.DeliveryNoteController = class DeliveryNoteController extends (
|
||||
});
|
||||
}
|
||||
|
||||
items_add(doc, cdt, cdn) {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
if (doc.project) {
|
||||
frappe.model.set_value(cdt, cdn, "project", doc.project);
|
||||
} else {
|
||||
this.frm.script_manager.copy_from_first_row("items", row, ["project"]);
|
||||
}
|
||||
}
|
||||
|
||||
make_sales_invoice() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.stock.doctype.delivery_note.delivery_note.make_sales_invoice",
|
||||
|
||||
@@ -1265,6 +1265,7 @@
|
||||
"fieldname": "amount_eligible_for_commission",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -1465,7 +1466,7 @@
|
||||
"idx": 146,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-28 06:37:33.600775",
|
||||
"modified": "2026-05-01 02:37:31.430649",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Delivery Note",
|
||||
|
||||
@@ -202,6 +202,7 @@ class Item(Document):
|
||||
self.validate_warehouse_for_reorder()
|
||||
self.update_bom_item_desc()
|
||||
|
||||
self.validate_variant()
|
||||
self.validate_has_variants()
|
||||
self.validate_attributes_in_variants()
|
||||
self.validate_stock_exists_for_template_item()
|
||||
@@ -811,6 +812,43 @@ class Item(Document):
|
||||
enqueue_after_commit=True,
|
||||
)
|
||||
|
||||
def validate_variant(self):
|
||||
if self.variant_of:
|
||||
has_variants, based_on = frappe.get_value(
|
||||
"Item", self.variant_of, ["has_variants", "variant_based_on"]
|
||||
)
|
||||
if not has_variants:
|
||||
frappe.throw(_("Item {0} is not a template item.").format(frappe.bold(self.variant_of)))
|
||||
|
||||
if based_on == "Item Attribute":
|
||||
for d in self.attributes:
|
||||
if not frappe.db.exists(
|
||||
"Item Variant Attribute", {"attribute": d.attribute, "parent": self.variant_of}
|
||||
):
|
||||
frappe.throw(
|
||||
_("Attribute {0} is not valid for the selected template.").format(
|
||||
frappe.bold(d.attribute)
|
||||
)
|
||||
)
|
||||
|
||||
numeric_values, disabled = frappe.get_value(
|
||||
"Item Variant Attribute",
|
||||
{"attribute": d.attribute, "parent": self.variant_of},
|
||||
["numeric_values", "disabled"],
|
||||
)
|
||||
|
||||
if disabled:
|
||||
frappe.throw(_("Attribute {0} is disabled.").format(frappe.bold(d.attribute)))
|
||||
|
||||
if not numeric_values and not frappe.db.exists(
|
||||
"Item Attribute Value", {"parent": d.attribute, "attribute_value": d.attribute_value}
|
||||
):
|
||||
frappe.throw(
|
||||
_("Attribute Value {0} is not valid for the selected attribute {1}.").format(
|
||||
frappe.bold(d.attribute_value), frappe.bold(d.attribute)
|
||||
)
|
||||
)
|
||||
|
||||
def validate_has_variants(self):
|
||||
if self.is_new():
|
||||
return
|
||||
|
||||
@@ -9,6 +9,22 @@ frappe.ui.form.on("Pick List", {
|
||||
}, 500);
|
||||
},
|
||||
|
||||
set_warehouse_query: function (frm, fieldname, parentfield = null) {
|
||||
const query = () => {
|
||||
let filters = { company: frm.doc.company };
|
||||
|
||||
frm.doc.consider_rejected_warehouses ? null : (filters.is_rejected_warehouse = 0);
|
||||
|
||||
return { filters };
|
||||
};
|
||||
|
||||
if (parentfield) {
|
||||
frm.set_query(fieldname, parentfield, query);
|
||||
} else {
|
||||
frm.set_query(fieldname, query);
|
||||
}
|
||||
},
|
||||
|
||||
setup: (frm) => {
|
||||
frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"];
|
||||
|
||||
@@ -21,21 +37,8 @@ frappe.ui.form.on("Pick List", {
|
||||
"Stock Entry": "Stock Entry",
|
||||
};
|
||||
|
||||
frm.set_query("warehouse", "locations", () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("parent_warehouse", () => {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
frm.events.set_warehouse_query(frm, "warehouse", "locations");
|
||||
frm.events.set_warehouse_query(frm, "parent_warehouse");
|
||||
|
||||
frm.set_query("work_order", () => {
|
||||
return {
|
||||
|
||||
@@ -522,9 +522,11 @@ class PickList(TransactionBase):
|
||||
picked_items_details = self.get_picked_items_details(items)
|
||||
self.item_location_map = frappe._dict()
|
||||
|
||||
from_warehouses = [self.parent_warehouse] if self.parent_warehouse else []
|
||||
from_warehouses = []
|
||||
if self.parent_warehouse:
|
||||
from_warehouses = [self.parent_warehouse]
|
||||
|
||||
if self.work_order:
|
||||
elif self.work_order:
|
||||
root_warehouse = frappe.db.get_value(
|
||||
"Warehouse", {"company": self.company, "parent_warehouse": ["IS", "NOT SET"], "is_group": 1}
|
||||
)
|
||||
|
||||
@@ -368,11 +368,13 @@ erpnext.stock.PurchaseReceiptController = class PurchaseReceiptController extend
|
||||
|
||||
items_add(doc, cdt, cdn) {
|
||||
const row = frappe.get_doc(cdt, cdn);
|
||||
this.frm.script_manager.copy_from_first_row("items", row, [
|
||||
"expense_account",
|
||||
"cost_center",
|
||||
"project",
|
||||
]);
|
||||
const field_copy = ["expense_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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -561,14 +561,7 @@ class PurchaseReceipt(BuyingController):
|
||||
else flt(item.net_amount, item.precision("net_amount"))
|
||||
)
|
||||
|
||||
outgoing_amount = (
|
||||
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
|
||||
)
|
||||
outgoing_amount = item.base_net_amount
|
||||
if self.is_internal_transfer() and item.valuation_rate:
|
||||
outgoing_amount = abs(get_stock_value_difference(self.name, item.name, item.from_warehouse))
|
||||
credit_amount = outgoing_amount
|
||||
@@ -724,6 +717,9 @@ class PurchaseReceipt(BuyingController):
|
||||
or stock_asset_rbnb
|
||||
)
|
||||
|
||||
if self.is_return and item.expense_account:
|
||||
loss_account = item.expense_account
|
||||
|
||||
cost_center = item.cost_center or frappe.get_cached_value(
|
||||
"Company", self.company, "cost_center"
|
||||
)
|
||||
|
||||
@@ -4610,7 +4610,7 @@ class TestPurchaseReceipt(ERPNextTestSuite):
|
||||
|
||||
self.assertEqual(srbnb_cost, 1500)
|
||||
|
||||
def test_valuation_rate_for_rejected_materials_without_accepted_materials(self):
|
||||
def test_valuation_rate_for_rejected_materials_withoout_accepted_materials(self):
|
||||
item = make_item("Test Item with Rej Material Valuation WO Accepted", {"is_stock_item": 1})
|
||||
company = "_Test Company with perpetual inventory"
|
||||
|
||||
@@ -5423,33 +5423,6 @@ class TestPurchaseReceipt(ERPNextTestSuite):
|
||||
self.assertEqual(row.warehouse, "_Test Warehouse 1 - _TC")
|
||||
self.assertEqual(row.incoming_rate, 100)
|
||||
|
||||
def test_bill_for_rejected_quantity_in_purchase_invoice(self):
|
||||
item_code = make_item("Test Rejected Qty", {"is_stock_item": 1}).name
|
||||
|
||||
with self.change_settings("Buying Settings", {"bill_for_rejected_quantity_in_purchase_invoice": 0}):
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
qty=10,
|
||||
rejected_qty=2,
|
||||
rate=10,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
self.assertEqual(pr.total_qty, 10)
|
||||
self.assertEqual(pr.total, 100)
|
||||
|
||||
with self.change_settings("Buying Settings", {"bill_for_rejected_quantity_in_purchase_invoice": 1}):
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
qty=10,
|
||||
rejected_qty=2,
|
||||
rate=10,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
self.assertEqual(pr.total_qty, 12)
|
||||
self.assertEqual(pr.total, 120)
|
||||
|
||||
def test_different_exchange_rate_in_pr_and_pi(self):
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
|
||||
|
||||
@@ -1049,7 +1049,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 && parent.docstatus == 0",
|
||||
"fieldname": "add_serial_batch_for_rejected_qty",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No (Rejected Qty)"
|
||||
@@ -1064,7 +1064,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 && parent.docstatus == 0",
|
||||
"fieldname": "add_serial_batch_bundle",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No"
|
||||
@@ -1149,7 +1149,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-04-07 15:40:47.032889",
|
||||
"modified": "2026-04-29 16:01:34.154697",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
|
||||
@@ -411,8 +411,15 @@ def repost(doc):
|
||||
message = message.get("message")
|
||||
|
||||
status = "Failed"
|
||||
# If failed because of timeout, set status to In Progress
|
||||
if traceback and ("timeout" in traceback.lower() or "Deadlock found" in traceback):
|
||||
# If failed because of a recoverable error (timeout, deadlock), set status to In Progress
|
||||
# so the scheduler automatically retries instead of leaving it permanently failed.
|
||||
# NOTE: isinstance check comes first because the traceback string matching is unreliable
|
||||
# when SIGALRM kills the process mid-C-extension (JobTimeoutException may not appear
|
||||
# in the traceback if the exception handler itself was interrupted).
|
||||
traceback_lower = traceback.lower() if traceback else ""
|
||||
if isinstance(e, RecoverableErrors) or (
|
||||
traceback_lower and ("timeout" in traceback_lower or "deadlock found" in traceback_lower)
|
||||
):
|
||||
status = "In Progress"
|
||||
|
||||
if traceback:
|
||||
|
||||
@@ -8,11 +8,10 @@ from collections import Counter, defaultdict
|
||||
|
||||
import frappe
|
||||
import frappe.query_builder
|
||||
import frappe.query_builder.functions
|
||||
from frappe import _, _dict, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.query_builder.functions import Concat_ws, Locate, Sum
|
||||
from frappe.query_builder.functions import Concat_ws, Sum
|
||||
from frappe.utils import (
|
||||
cint,
|
||||
cstr,
|
||||
@@ -3358,22 +3357,21 @@ def get_stock_ledgers_for_serial_nos(kwargs):
|
||||
serial_nos = [serial_nos]
|
||||
|
||||
if serial_nos:
|
||||
import re
|
||||
|
||||
escaped_serial_nos = [re.escape(sn) for sn in serial_nos if sn]
|
||||
regex_pattern = r"\n(" + "|".join(escaped_serial_nos) + r")\n"
|
||||
|
||||
query = (
|
||||
query.left_join(serial_batch_entry)
|
||||
.on(stock_ledger_entry.serial_and_batch_bundle == serial_batch_entry.parent)
|
||||
.where(
|
||||
serial_batch_entry.serial_no.isin(serial_nos)
|
||||
| Concat_ws("", "\n", stock_ledger_entry.serial_no, "\n").regexp(regex_pattern)
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
bundle_match = serial_batch_entry.serial_no.isin(serial_nos)
|
||||
|
||||
padded_serial_no = Concat_ws("", "\n", stock_ledger_entry.serial_no, "\n")
|
||||
direct_match = None
|
||||
for sn in serial_nos:
|
||||
cond = Locate(f"\n{sn}\n", padded_serial_no) > 0
|
||||
direct_match = cond if direct_match is None else (direct_match | cond)
|
||||
|
||||
query = query.where(bundle_match | direct_match)
|
||||
|
||||
if kwargs.ignore_voucher_detail_no:
|
||||
query = query.where(stock_ledger_entry.voucher_detail_no != kwargs.ignore_voucher_detail_no)
|
||||
|
||||
|
||||
@@ -243,7 +243,6 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
self.set_transfer_qty()
|
||||
self.validate_uom_is_integer("uom", "qty")
|
||||
self.validate_uom_is_integer("stock_uom", "transfer_qty")
|
||||
self.validate_warehouse()
|
||||
self.validate_warehouse_of_sabb()
|
||||
self.validate_work_order()
|
||||
self.validate_source_stock_entry()
|
||||
@@ -259,6 +258,7 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
else:
|
||||
self.validate_job_card_fg_item()
|
||||
|
||||
self.validate_warehouse()
|
||||
self.validate_with_material_request()
|
||||
self.validate_batch()
|
||||
self.validate_inspection()
|
||||
@@ -383,6 +383,9 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
def _set_serial_batch_for_disassembly_from_available_materials(self):
|
||||
available_materials = get_available_materials(self.work_order, self)
|
||||
for row in self.items:
|
||||
if row.serial_no or row.batch_no or row.serial_and_batch_bundle:
|
||||
continue
|
||||
|
||||
warehouse = row.s_warehouse or row.t_warehouse
|
||||
materials = available_materials.get((row.item_code, warehouse))
|
||||
if not materials:
|
||||
@@ -850,15 +853,14 @@ class StockEntry(StockController, SubcontractingInwardController):
|
||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||
|
||||
if self.purpose == "Manufacture":
|
||||
if has_bom:
|
||||
if d.is_finished_item or d.type or d.is_legacy_scrap_item:
|
||||
d.s_warehouse = None
|
||||
if not d.t_warehouse:
|
||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||
else:
|
||||
d.t_warehouse = None
|
||||
if not d.s_warehouse:
|
||||
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
|
||||
if d.is_finished_item or d.type or d.is_legacy_scrap_item:
|
||||
d.s_warehouse = None
|
||||
if not d.t_warehouse:
|
||||
frappe.throw(_("Target warehouse is mandatory for row {0}").format(d.idx))
|
||||
else:
|
||||
d.t_warehouse = None
|
||||
if not d.s_warehouse:
|
||||
frappe.throw(_("Source warehouse is mandatory for row {0}").format(d.idx))
|
||||
|
||||
if self.purpose == "Disassemble":
|
||||
if has_bom:
|
||||
|
||||
@@ -726,11 +726,7 @@ def get_item_tax_template(ctx, item=None, out: ItemDetails | None = None):
|
||||
item_tax_template = _get_item_tax_template(ctx, item.taxes, out)
|
||||
|
||||
if not item_tax_template:
|
||||
item_group = item.item_group
|
||||
while item_group and not item_tax_template:
|
||||
item_group_doc = frappe.get_cached_doc("Item Group", item_group)
|
||||
item_tax_template = _get_item_tax_template(ctx, item_group_doc.taxes, out)
|
||||
item_group = item_group_doc.parent_item_group
|
||||
item_tax_template = _get_item_tax_template_from_item_group(ctx, item.item_group, out)
|
||||
|
||||
if out and ctx.get("child_doctype") and item_tax_template:
|
||||
out.update(get_fetch_values(ctx.get("child_doctype"), "item_tax_template", item_tax_template))
|
||||
@@ -738,6 +734,18 @@ def get_item_tax_template(ctx, item=None, out: ItemDetails | None = None):
|
||||
return item_tax_template
|
||||
|
||||
|
||||
def _get_item_tax_template_from_item_group(ctx, item_group, out=None):
|
||||
from frappe.utils.nestedset import get_ancestors_of
|
||||
|
||||
ancestors = get_ancestors_of("Item Group", item_group)
|
||||
for group in [item_group, *ancestors]:
|
||||
group_doc = frappe.get_cached_doc("Item Group", group)
|
||||
item_tax_template = _get_item_tax_template(ctx, group_doc.taxes, out)
|
||||
if item_tax_template:
|
||||
return item_tax_template
|
||||
return None
|
||||
|
||||
|
||||
@erpnext.normalize_ctx_input(ItemDetailsCtx)
|
||||
def _get_item_tax_template(
|
||||
ctx: ItemDetailsCtx, taxes, out: ItemDetails | None = None, for_validate=False
|
||||
@@ -1117,6 +1125,7 @@ def insert_item_price(ctx: ItemDetailsCtx):
|
||||
currency=ctx.currency,
|
||||
uom=ctx.stock_uom,
|
||||
price_list=ctx.price_list,
|
||||
valid_from=transaction_date,
|
||||
)
|
||||
item_price.insert()
|
||||
frappe.msgprint(
|
||||
@@ -1139,6 +1148,7 @@ def insert_item_price(ctx: ItemDetailsCtx):
|
||||
"currency": ctx.currency,
|
||||
"price_list_rate": price_list_rate,
|
||||
"uom": ctx.stock_uom,
|
||||
"valid_from": transaction_date,
|
||||
}
|
||||
)
|
||||
item_price.insert()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.query_reports["Supplier-Wise Sales Analytics"] = {
|
||||
frappe.query_reports["Item Wise Consumption"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "supplier",
|
||||
@@ -10,10 +10,10 @@
|
||||
"modified": "2017-02-24 20:13:38.914651",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Supplier-Wise Sales Analytics",
|
||||
"name": "Item Wise Consumption",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Stock Ledger Entry",
|
||||
"report_name": "Supplier-Wise Sales Analytics",
|
||||
"report_name": "Item Wise Consumption",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
@@ -7,6 +7,7 @@ import frappe
|
||||
from frappe import _
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle
|
||||
from erpnext.stock.serial_batch_bundle import get_serial_no_status
|
||||
from erpnext.stock.stock_ledger import get_stock_ledger_entries
|
||||
|
||||
BUYING_VOUCHER_TYPES = ["Purchase Invoice", "Purchase Receipt", "Subcontracting Receipt"]
|
||||
@@ -111,7 +112,7 @@ def get_data(filters):
|
||||
"posting_time": row.posting_time,
|
||||
"voucher_type": row.voucher_type,
|
||||
"voucher_no": row.voucher_no,
|
||||
"status": "Active" if row.actual_qty > 0 else "Delivered",
|
||||
"status": get_serial_no_status(row),
|
||||
"company": row.company,
|
||||
"warehouse": row.warehouse,
|
||||
"qty": 1 if row.actual_qty > 0 else -1,
|
||||
|
||||
@@ -11,6 +11,7 @@ from frappe.query_builder.functions import Count
|
||||
from frappe.utils import cint, date_diff, flt, get_datetime
|
||||
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
from erpnext.stock.valuation import round_off_if_near_zero
|
||||
|
||||
Filters = frappe._dict
|
||||
|
||||
@@ -117,10 +118,14 @@ def get_range_age(filters: Filters, fifo_queue: list, to_date: str, item_dict: d
|
||||
i *= 2
|
||||
range_values[i] = flt(range_values[i] + qty, precision)
|
||||
range_values[i + 1] = flt(range_values[i + 1] + stock_value, precision)
|
||||
if range_values[i] == 0.0 and round_off_if_near_zero(range_values[i + 1], 2) == 0:
|
||||
range_values[i + 1] = 0.0
|
||||
break
|
||||
else:
|
||||
range_values[-2] = flt(range_values[-2] + qty, precision)
|
||||
range_values[-1] = flt(range_values[-1] + stock_value, precision)
|
||||
if range_values[-2] == 0.0 and round_off_if_near_zero(range_values[-1], 2) == 0:
|
||||
range_values[-1] = 0.0
|
||||
|
||||
return range_values
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ def execute(filters=None):
|
||||
inv_dimension_wise_dict, filters, inv_dimension_key=inv_dimension_key, opening_row=opening_row
|
||||
)
|
||||
|
||||
item_wh_wise_prev_sle = {}
|
||||
for sle in sl_entries:
|
||||
item_detail = item_details[sle.item_code]
|
||||
|
||||
@@ -114,6 +115,21 @@ def execute(filters=None):
|
||||
elif sle.voucher_type == "Stock Reconciliation":
|
||||
sle["in_out_rate"] = sle.valuation_rate
|
||||
|
||||
if (
|
||||
sle.voucher_type == "Stock Reconciliation"
|
||||
and not sle.in_qty
|
||||
and not sle.out_qty
|
||||
and not sle.actual_qty
|
||||
):
|
||||
if prev_sle := item_wh_wise_prev_sle.get((sle.item_code, sle.warehouse)):
|
||||
bal_qty = prev_sle.get("qty_after_transaction", 0)
|
||||
qty = sle.qty_after_transaction - bal_qty
|
||||
if qty > 0:
|
||||
sle.in_qty = qty
|
||||
elif qty < 0:
|
||||
sle.out_qty = qty
|
||||
|
||||
item_wh_wise_prev_sle[(sle.item_code, sle.warehouse)] = sle
|
||||
data.append(sle)
|
||||
|
||||
if include_uom:
|
||||
|
||||
@@ -63,7 +63,7 @@ REPORT_FILTER_TEST_CASES: list[tuple[ReportName, ReportFilters]] = [
|
||||
("Incorrect Stock Value Report", {"company": "_Test Company with perpetual inventory"}),
|
||||
("Incorrect Serial No Valuation", {}),
|
||||
("Incorrect Balance Qty After Transaction", {}),
|
||||
("Supplier-Wise Sales Analytics", {}),
|
||||
("Item Wise Consumption", {}),
|
||||
("Item Prices", {"items": "Enabled Items only"}),
|
||||
("Delayed Item Report", {"based_on": "Sales Invoice"}),
|
||||
("Delayed Item Report", {"based_on": "Delivery Note"}),
|
||||
|
||||
@@ -15,6 +15,45 @@ from erpnext.stock.deprecated_serial_batch import (
|
||||
)
|
||||
from erpnext.stock.valuation import round_off_if_near_zero
|
||||
|
||||
CONSUMED_SERIAL_NO_STOCK_ENTRY_PURPOSES = (
|
||||
"Manufacture",
|
||||
"Material Issue",
|
||||
"Repack",
|
||||
"Material Consumption for Manufacture",
|
||||
)
|
||||
INACTIVE_SERIAL_NO_STOCK_ENTRY_PURPOSES = ("Disassemble", "Material Receipt")
|
||||
|
||||
|
||||
def get_serial_no_status(sle):
|
||||
warehouse = sle.warehouse if sle.actual_qty > 0 else None
|
||||
if warehouse:
|
||||
return "Active"
|
||||
|
||||
status = get_status_for_serial_nos(sle)
|
||||
if sle.voucher_type == "Stock Entry" and sle.actual_qty < 0:
|
||||
purpose = frappe.get_cached_value("Stock Entry", sle.voucher_no, "purpose")
|
||||
if purpose in INACTIVE_SERIAL_NO_STOCK_ENTRY_PURPOSES:
|
||||
status = "Inactive"
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def get_status_for_serial_nos(sle):
|
||||
status = "Inactive"
|
||||
if sle.actual_qty < 0:
|
||||
status = "Delivered"
|
||||
if sle.voucher_type == "Stock Entry":
|
||||
purpose = frappe.get_cached_value("Stock Entry", sle.voucher_no, "purpose")
|
||||
if purpose in CONSUMED_SERIAL_NO_STOCK_ENTRY_PURPOSES:
|
||||
status = "Consumed"
|
||||
|
||||
if sle.is_cancelled == 1 and (
|
||||
sle.voucher_type in ["Purchase Invoice", "Purchase Receipt"] or status == "Consumed"
|
||||
):
|
||||
status = "Inactive"
|
||||
|
||||
return status
|
||||
|
||||
|
||||
class SerialBatchBundle:
|
||||
def __init__(self, **kwargs):
|
||||
@@ -429,25 +468,7 @@ class SerialBatchBundle:
|
||||
self.update_serial_no_status_warehouse(self.sle, serial_nos)
|
||||
|
||||
def get_status_for_serial_nos(self, sle):
|
||||
status = "Inactive"
|
||||
if sle.actual_qty < 0:
|
||||
status = "Delivered"
|
||||
if sle.voucher_type == "Stock Entry":
|
||||
purpose = frappe.get_cached_value("Stock Entry", sle.voucher_no, "purpose")
|
||||
if purpose in [
|
||||
"Manufacture",
|
||||
"Material Issue",
|
||||
"Repack",
|
||||
"Material Consumption for Manufacture",
|
||||
]:
|
||||
status = "Consumed"
|
||||
|
||||
if sle.is_cancelled == 1 and (
|
||||
sle.voucher_type in ["Purchase Invoice", "Purchase Receipt"] or status == "Consumed"
|
||||
):
|
||||
status = "Inactive"
|
||||
|
||||
return status
|
||||
return get_status_for_serial_nos(sle)
|
||||
|
||||
def update_serial_no_status_warehouse(self, sle, serial_nos):
|
||||
warehouse = sle.warehouse if sle.actual_qty > 0 else None
|
||||
@@ -455,19 +476,12 @@ class SerialBatchBundle:
|
||||
if isinstance(serial_nos, str):
|
||||
serial_nos = [serial_nos]
|
||||
|
||||
status = "Active"
|
||||
if not warehouse:
|
||||
status = self.get_status_for_serial_nos(sle)
|
||||
status = get_serial_no_status(sle)
|
||||
|
||||
customer = None
|
||||
if sle.voucher_type in ["Sales Invoice", "Delivery Note"] and sle.actual_qty < 0:
|
||||
customer = frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "customer")
|
||||
|
||||
if sle.voucher_type in ["Stock Entry"] and sle.actual_qty < 0:
|
||||
purpose = frappe.get_cached_value("Stock Entry", sle.voucher_no, "purpose")
|
||||
if purpose in ["Disassemble", "Material Receipt"]:
|
||||
status = "Inactive"
|
||||
|
||||
sn_table = frappe.qb.DocType("Serial No")
|
||||
|
||||
query = (
|
||||
@@ -1055,6 +1069,12 @@ class SerialBatchCreation:
|
||||
self.__dict__.update(item_details)
|
||||
|
||||
def set_other_details(self):
|
||||
from erpnext.stock.utils import get_combine_datetime
|
||||
|
||||
if not self.get("posting_datetime"):
|
||||
if self.get("posting_date") and self.get("posting_time"):
|
||||
self.posting_datetime = get_combine_datetime(self.posting_date, self.posting_time)
|
||||
|
||||
if not self.get("posting_datetime"):
|
||||
self.posting_datetime = now()
|
||||
self.__dict__["posting_datetime"] = self.posting_datetime
|
||||
|
||||
@@ -900,6 +900,13 @@ class BootStrapTestData:
|
||||
"default_currency": "USD",
|
||||
"accounts": [{"company": "_Test Company", "account": "_Test Payable USD - _TC"}],
|
||||
},
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Another Supplier USD",
|
||||
"supplier_group": "_Test Supplier Group",
|
||||
"default_currency": "USD",
|
||||
"accounts": [{"company": "_Test Company", "account": "_Test Payable USD - _TC"}],
|
||||
},
|
||||
{
|
||||
"doctype": "Supplier",
|
||||
"supplier_name": "_Test Supplier With Tax Category",
|
||||
@@ -951,6 +958,13 @@ class BootStrapTestData:
|
||||
"is_group": 0,
|
||||
"parent_cost_center": "_Test Company - _TC",
|
||||
},
|
||||
{
|
||||
"company": "_Test Company",
|
||||
"cost_center_name": "Sub",
|
||||
"doctype": "Cost Center",
|
||||
"is_group": 0,
|
||||
"parent_cost_center": "_Test Company - _TC",
|
||||
},
|
||||
]
|
||||
self.make_records(["cost_center_name", "company"], records)
|
||||
|
||||
|
||||
@@ -18,6 +18,14 @@ class UOMMustBeIntegerError(frappe.ValidationError):
|
||||
|
||||
|
||||
class TransactionBase(StatusUpdater):
|
||||
def on_change(self):
|
||||
# `on_change` also fires for `db_set()`, so only run during an actual insert/save.
|
||||
is_real_save = self.flags.in_insert or (self.doctype, self.name) in frappe.flags.currently_saving
|
||||
if not is_real_save:
|
||||
return
|
||||
|
||||
self.copy_terms_and_conditions_attachments()
|
||||
|
||||
def validate_posting_time(self):
|
||||
# set Edit Posting Date and Time to 1 while data import and restore
|
||||
if (frappe.flags.in_import or self.flags.from_restore) and self.posting_date:
|
||||
@@ -36,6 +44,56 @@ class TransactionBase(StatusUpdater):
|
||||
def validate_uom_is_integer(self, uom_field, qty_fields, child_dt=None):
|
||||
validate_uom_is_integer(self, uom_field, qty_fields, child_dt)
|
||||
|
||||
def copy_terms_and_conditions_attachments(self):
|
||||
if (
|
||||
not self.name
|
||||
or not self.meta.has_field("tc_name")
|
||||
or not self.tc_name
|
||||
or not self.has_value_changed("tc_name")
|
||||
):
|
||||
return
|
||||
|
||||
copy_attachments_to_transaction = frappe.db.get_value(
|
||||
"Terms and Conditions", self.tc_name, "copy_attachments_to_transaction"
|
||||
)
|
||||
if not cint(copy_attachments_to_transaction):
|
||||
return
|
||||
|
||||
source_attachments = frappe.get_all(
|
||||
"File",
|
||||
filters={
|
||||
"attached_to_doctype": "Terms and Conditions",
|
||||
"attached_to_name": self.tc_name,
|
||||
},
|
||||
fields=["name", "file_url"],
|
||||
)
|
||||
if not source_attachments:
|
||||
return
|
||||
|
||||
existing_file_urls = {
|
||||
attachment.file_url
|
||||
for attachment in frappe.get_all(
|
||||
"File",
|
||||
filters={
|
||||
"attached_to_doctype": self.doctype,
|
||||
"attached_to_name": self.name,
|
||||
},
|
||||
fields=["file_url"],
|
||||
)
|
||||
if attachment.file_url
|
||||
}
|
||||
|
||||
for source_attachment in source_attachments:
|
||||
if not source_attachment.file_url or source_attachment.file_url in existing_file_urls:
|
||||
continue
|
||||
|
||||
# Reuse the existing file metadata so the same on-disk blob is shared.
|
||||
new_attachment = frappe.get_doc("File", source_attachment.name).create_attachment_copy(
|
||||
attached_to_doctype=self.doctype,
|
||||
attached_to_name=self.name,
|
||||
)
|
||||
existing_file_urls.add(new_attachment.file_url)
|
||||
|
||||
def validate_with_previous_doc(self, ref):
|
||||
self.exclude_fields = ["conversion_factor", "uom"] if self.get("is_return") else []
|
||||
|
||||
|
||||
@@ -328,8 +328,8 @@
|
||||
"collapsible": 1,
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "Supplier-Wise Sales Analytics",
|
||||
"link_to": "Supplier-Wise Sales Analytics",
|
||||
"label": "Item Wise Consumption",
|
||||
"link_to": "Item Wise Consumption",
|
||||
"link_type": "Report",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
|
||||
@@ -289,17 +289,6 @@
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"child": 1,
|
||||
"collapsible": 1,
|
||||
"indent": 0,
|
||||
"keep_closed": 0,
|
||||
"label": "BOM Stock Report",
|
||||
"link_to": "BOM Stock Report",
|
||||
"link_type": "Report",
|
||||
"show_arrow": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"child": 1,
|
||||
"collapsible": 1,
|
||||
@@ -437,7 +426,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2026-02-20 16:45:00.399936",
|
||||
"modified": "2026-05-05 11:01:50.260118",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"module_onboarding": "Manufacturing Onboarding",
|
||||
|
||||
Reference in New Issue
Block a user