Merge branch 'version-15-hotfix' into mergify/bp/version-15-hotfix/pr-58351

This commit is contained in:
Aarol D'Souza
2026-08-27 15:42:27 +05:30
committed by GitHub
11 changed files with 544 additions and 20 deletions

View File

@@ -54,7 +54,7 @@ class PeriodClosingVoucher(AccountsController):
if for_cancellation and is_immutable_ledger_enabled(): if for_cancellation and is_immutable_ledger_enabled():
posting_date = getdate() posting_date = getdate()
check_freezing_date(posting_date, self.company) check_freezing_date(posting_date)
def validate_start_and_end_date(self): def validate_start_and_end_date(self):
self.fy_start_date, self.fy_end_date = frappe.db.get_value( self.fy_start_date, self.fy_end_date = frappe.db.get_value(

View File

@@ -1395,6 +1395,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1413,6 +1414,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1431,6 +1433,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1449,6 +1452,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1467,6 +1471,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1485,6 +1490,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1507,6 +1513,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1525,6 +1532,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1543,6 +1551,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1561,6 +1570,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1579,6 +1589,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1597,6 +1608,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1628,6 +1640,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1637,6 +1650,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1646,6 +1660,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1655,6 +1670,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1664,6 +1680,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1673,6 +1690,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1682,6 +1700,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1691,6 +1710,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1700,6 +1720,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1709,6 +1730,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1718,6 +1740,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1727,6 +1750,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -1735,6 +1759,7 @@
"account_number": "1433", "account_number": "1433",
"root_type": "Asset" "root_type": "Asset"
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
} }
] ]
@@ -2158,6 +2183,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2176,6 +2202,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2194,6 +2221,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2212,6 +2240,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2230,6 +2259,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2248,6 +2278,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2270,6 +2301,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2288,6 +2320,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2306,6 +2339,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2324,6 +2358,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2342,6 +2377,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2360,6 +2396,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2391,6 +2428,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2400,6 +2438,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2409,6 +2448,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2418,6 +2458,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2427,6 +2468,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2436,6 +2478,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2445,6 +2488,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2454,6 +2498,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2463,6 +2508,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2472,6 +2518,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2481,6 +2528,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2490,6 +2538,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2498,6 +2547,7 @@
"account_number": "1588", "account_number": "1588",
"root_type": "Asset" "root_type": "Asset"
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
} }
] ]
@@ -2921,6 +2971,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2939,6 +2990,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2957,6 +3009,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2975,6 +3028,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -2993,6 +3047,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3011,6 +3066,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3033,6 +3089,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3051,6 +3108,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3069,7 +3127,8 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"tax_rate": 19.00 "not_applicable": 1,
"tax_rate": 0.00
}, },
{ {
"tax_type": { "tax_type": {
@@ -3087,6 +3146,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3105,6 +3165,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3123,6 +3184,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3154,6 +3216,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3163,6 +3226,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3172,6 +3236,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3181,6 +3246,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3190,6 +3256,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3199,6 +3266,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3208,6 +3276,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3217,6 +3286,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3226,6 +3296,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3235,6 +3306,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3244,6 +3316,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3253,6 +3326,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3261,6 +3335,7 @@
"account_number": "1550", "account_number": "1550",
"root_type": "Asset" "root_type": "Asset"
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
} }
] ]
@@ -3653,6 +3728,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3669,6 +3745,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3685,6 +3762,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3701,6 +3779,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3717,6 +3796,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3733,6 +3813,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3753,6 +3834,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3769,6 +3851,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3785,6 +3868,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3801,6 +3885,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3817,6 +3902,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3833,6 +3919,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3861,6 +3948,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3869,6 +3957,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3877,6 +3966,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3885,6 +3975,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3893,6 +3984,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3901,6 +3993,7 @@
"root_type": "Liability", "root_type": "Liability",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3909,6 +4002,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3917,6 +4011,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3925,6 +4020,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3933,6 +4029,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3941,6 +4038,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 19.00 "tax_rate": 19.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3949,6 +4047,7 @@
"root_type": "Asset", "root_type": "Asset",
"tax_rate": 7.00 "tax_rate": 7.00
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
}, },
{ {
@@ -3956,6 +4055,7 @@
"account_name": "Entstandene Einfuhrumsatzsteuer", "account_name": "Entstandene Einfuhrumsatzsteuer",
"root_type": "Asset" "root_type": "Asset"
}, },
"not_applicable": 1,
"tax_rate": 0.00 "tax_rate": 0.00
} }
] ]

View File

@@ -94,6 +94,22 @@ class MaterialRequest(BuyingController):
def check_if_already_pulled(self): def check_if_already_pulled(self):
pass pass
def validate_with_previous_doc(self):
super().validate_with_previous_doc(
{
"Sales Order": {
"ref_dn_field": "sales_order",
"compare_fields": [["company", "="]],
},
"Sales Order Item": {
"ref_dn_field": "sales_order_item",
"compare_fields": [["item_code", "="], ["uom", "="], ["conversion_factor", "="]],
"is_child_table": True,
"allow_duplicate_prev_row_id": True,
},
}
)
def validate_qty_against_so(self): def validate_qty_against_so(self):
so_items = {} # Format --> {'SO/00001': {'Item/001': 120, 'Item/002': 24}} so_items = {} # Format --> {'SO/00001': {'Item/001': 120, 'Item/002': 24}}
for d in self.get("items"): for d in self.get("items"):
@@ -136,6 +152,7 @@ class MaterialRequest(BuyingController):
self.validate_schedule_date() self.validate_schedule_date()
self.check_for_on_hold_or_closed_status("Sales Order", "sales_order") self.check_for_on_hold_or_closed_status("Sales Order", "sales_order")
self.validate_with_previous_doc()
self.validate_uom_is_integer("uom", "qty") self.validate_uom_is_integer("uom", "qty")
self.validate_material_request_type() self.validate_material_request_type()

View File

@@ -919,6 +919,18 @@ class TestMaterialRequest(FrappeTestCase):
self.assertRaises(OverAllowanceError, mr.submit) self.assertRaises(OverAllowanceError, mr.submit)
def test_item_change_on_sales_order_row_is_blocked(self):
from erpnext.selling.doctype.sales_order.sales_order import make_material_request
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
other_item = create_item("_Test MR Item Swap").name
so = make_sales_order()
mr = make_material_request(so.name)
mr.material_request_type = "Purchase"
# swapping the fetched item would leave a stale link to the SO row
mr.items[0].item_code = other_item
self.assertRaises(frappe.ValidationError, mr.insert)
def test_pending_qty_in_pick_list(self): def test_pending_qty_in_pick_list(self):
"""Test for pick list mapped doc qty from partially received Material Request Transfer""" """Test for pick list mapped doc qty from partially received Material Request Transfer"""
import json import json

View File

@@ -1508,6 +1508,7 @@ def add_product_bundles_to_delivery_note(
@frappe.whitelist() @frappe.whitelist()
def create_stock_entry(pick_list: str | dict): def create_stock_entry(pick_list: str | dict):
pick_list = frappe.get_doc(frappe.parse_json(pick_list)) pick_list = frappe.get_doc(frappe.parse_json(pick_list))
pick_list.check_permission("read")
validate_item_locations(pick_list) validate_item_locations(pick_list)
stock_entry = frappe.new_doc("Stock Entry") stock_entry = frappe.new_doc("Stock Entry")

View File

@@ -1078,6 +1078,58 @@ class TestPickList(FrappeTestCase):
self.assertEqual(pick_list.locations[0].transferred_qty, 4) self.assertEqual(pick_list.locations[0].transferred_qty, 4)
self.assertEqual(pick_list.status, "Partially Transferred") self.assertEqual(pick_list.status, "Partially Transferred")
def test_get_items_keeps_pick_list_rows_on_stock_entry(self):
"""Entering fg_completed_qty on a Stock Entry mapped from a Pick List triggers get_items();
it must not refetch from the BOM, or the pick_list_item links transferred_qty rides on are
lost and the Pick List stays Open with every row offered again."""
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.manufacturing.doctype.work_order.work_order import (
create_pick_list as pick_list_for_wo,
)
from erpnext.manufacturing.doctype.work_order.work_order import make_work_order
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
source_warehouse = create_warehouse("_Test Partial Transfer Source")
wip_warehouse = create_warehouse("_Test Partial Transfer WIP", company="_Test Company")
fg_warehouse = create_warehouse("_Test Partial Transfer FG", company="_Test Company")
fg_item = make_item(properties={"is_stock_item": 1}).name
rm_item = make_item(properties={"is_stock_item": 1}).name
bom = make_bom(item=fg_item, rate=100, raw_materials=[rm_item])
make_stock_entry(item=rm_item, to_warehouse=source_warehouse, qty=100)
wo = make_work_order(item=fg_item, qty=10, bom_no=bom.name, company="_Test Company")
wo.required_items[0].source_warehouse = source_warehouse
wo.wip_warehouse = wip_warehouse
wo.fg_warehouse = fg_warehouse
wo.submit()
pick_list = pick_list_for_wo(wo.name, for_qty=wo.qty)
pick_list.save().submit()
self.assertEqual(pick_list.status, "Open")
se = frappe.get_doc(create_stock_entry(pick_list.as_dict()))
self.assertTrue(all(row.pick_list_item for row in se.items))
self.assertEqual(se.fg_completed_qty, 0)
se.fg_completed_qty = 4
se.get_items()
self.assertEqual(len(se.items), len(pick_list.locations))
self.assertTrue(all(row.pick_list_item for row in se.items))
se.fg_completed_qty = 0
for row in se.items:
row.qty = 4
se.save().submit()
self.assertEqual(se.fg_completed_qty, 0)
pick_list.reload()
self.assertEqual(pick_list.locations[0].transferred_qty, 4)
self.assertEqual(pick_list.status, "Partially Transferred")
next_se = frappe.get_doc(create_stock_entry(pick_list.as_dict()))
self.assertEqual(len(next_se.items), 1)
self.assertEqual(next_se.items[0].qty, 6)
def test_create_second_delivery_note_with_fully_delivered_location(self): def test_create_second_delivery_note_with_fully_delivered_location(self):
# When one pick list item is fully delivered by the first Delivery Note # When one pick list item is fully delivered by the first Delivery Note
# and another item is still pending, creating a second Delivery Note from # and another item is still pending, creating a second Delivery Note from

View File

@@ -181,6 +181,13 @@ frappe.ui.form.on("Stock Entry", {
if (!check_should_not_attach_bom_items(frm.doc.bom_no)) { if (!check_should_not_attach_bom_items(frm.doc.bom_no)) {
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype); erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
} }
if (frm.doc.pick_list) {
frm.set_df_property("get_items", "hidden", 1);
if (!frm.doc.job_card) {
frm.set_df_property("fg_completed_qty", "read_only", 1);
}
}
}, },
setup_quality_inspection: function (frm) { setup_quality_inspection: function (frm) {
@@ -1356,10 +1363,16 @@ erpnext.stock.StockEntry = class StockEntry extends erpnext.stock.StockControlle
) { ) {
frappe.model.remove_from_locals("Work Order", this.frm.doc.work_order); frappe.model.remove_from_locals("Work Order", this.frm.doc.work_order);
} }
if (this.frm.doc.pick_list) {
frappe.model.remove_from_locals("Pick List", this.frm.doc.pick_list);
}
} }
fg_completed_qty() { fg_completed_qty() {
this.get_items(); if (!this.frm.doc.pick_list) {
this.get_items();
}
} }
get_items() { get_items() {

View File

@@ -2576,6 +2576,9 @@ class StockEntry(StockController):
@frappe.whitelist() @frappe.whitelist()
def get_items(self): def get_items(self):
if self.pick_list:
return
self.set("items", []) self.set("items", [])
self.validate_work_order() self.validate_work_order()

View File

@@ -7,6 +7,7 @@ import json
import frappe import frappe
from frappe import _, throw from frappe import _, throw
from frappe.model import child_table_fields, default_fields from frappe.model import child_table_fields, default_fields
from frappe.model.document import Document
from frappe.model.meta import get_field_precision from frappe.model.meta import get_field_precision
from frappe.model.utils import get_fetch_values from frappe.model.utils import get_fetch_values
from frappe.query_builder.functions import IfNull, Sum from frappe.query_builder.functions import IfNull, Sum
@@ -34,11 +35,34 @@ purchase_doctypes = [
"Purchase Invoice", "Purchase Invoice",
] ]
maintain_same_rate_source_fields = {
"Purchase Order": {"supplier_quotation_item": "Supplier Quotation Item"},
"Purchase Receipt": {"purchase_order_item": "Purchase Order Item"},
"Purchase Invoice": {"po_detail": "Purchase Order Item", "pr_detail": "Purchase Receipt Item"},
"Sales Order": {"quotation_item": "Quotation Item"},
"Delivery Note": {"so_detail": "Sales Order Item", "si_detail": "Sales Invoice Item"},
"Sales Invoice": {"so_detail": "Sales Order Item", "dn_detail": "Delivery Note Item"},
}
LOCKED_RATE_FIELDS = [
"price_list_rate",
"rate",
"discount_percentage",
"discount_amount",
"margin_type",
"margin_rate_or_amount",
]
NOT_APPLICABLE_TAX = "N/A" NOT_APPLICABLE_TAX = "N/A"
@frappe.whitelist() @frappe.whitelist()
def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): def get_item_details(
args: dict | str,
doc: Document | dict | str | None = None,
for_validate: bool | str = False,
overwrite_warehouse: bool | str = True,
):
""" """
args = { args = {
"item_code": "", "item_code": "",
@@ -100,16 +124,20 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
if args.get("doctype") in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]: if args.get("doctype") in ["Purchase Order", "Purchase Receipt", "Purchase Invoice"]:
args.customer = None args.customer = None
out.update(get_price_list_rate(args, item)) source_row = get_rate_locked_source_row(args, doc)
if source_row:
lock_source_rate(out, source_row)
else:
out.update(get_price_list_rate(args, item))
if ( if (
not out.price_list_rate not out.price_list_rate
and args.transaction_type == "selling" and args.transaction_type == "selling"
and frappe.get_single_value("Selling Settings", "fallback_to_default_price_list") and frappe.get_single_value("Selling Settings", "fallback_to_default_price_list")
): ):
fallback_args = args.copy() fallback_args = args.copy()
fallback_args.price_list = frappe.get_single_value("Selling Settings", "selling_price_list") fallback_args.price_list = frappe.get_single_value("Selling Settings", "selling_price_list")
out.update(get_price_list_rate(fallback_args, item)) out.update(get_price_list_rate(fallback_args, item))
args.customer = current_customer args.customer = current_customer
@@ -124,9 +152,8 @@ def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=Tru
if args.get(key) is None: if args.get(key) is None:
args[key] = value args[key] = value
data = get_pricing_rule_for_item(args, doc=doc, for_validate=for_validate) if not source_row:
out.update(get_pricing_rule_for_item(args, doc=doc, for_validate=for_validate))
out.update(data)
if ( if (
frappe.db.get_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward") frappe.db.get_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward")
@@ -156,6 +183,52 @@ def remove_standard_fields(details):
return details return details
def get_rate_locked_source_row(args, doc):
"""Reads the source row from the DB, not the mutable target row, so an unsaved edit can't override the locked rate."""
if isinstance(doc, str):
doc = json.loads(doc)
source_fields = maintain_same_rate_source_fields.get(args.parenttype or args.doctype)
if not source_fields or not doc or args.get("is_return") or not maintain_same_rate_enabled(args):
return None
row = next((d for d in doc.get("items") or [] if d.get("name") == args.child_docname), None)
if not row:
return None
for link_field, source_doctype in source_fields.items():
if source_name := row.get(link_field):
# don't leak another document's pricing to a caller without read access
source = frappe.db.get_value(
source_doctype, source_name, [*LOCKED_RATE_FIELDS, "parent", "parenttype"], as_dict=True
)
if source and frappe.has_permission(source.parenttype, doc=source.parent):
return source
return None
return None
def maintain_same_rate_enabled(transaction_args):
if (transaction_args.parenttype or transaction_args.doctype) in purchase_doctypes:
if transaction_args.get("is_internal_supplier"):
return False
return bool(cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate")))
if transaction_args.get("is_internal_customer"):
return False
return bool(cint(frappe.get_cached_value("Selling Settings", "None", "maintain_same_sales_rate")))
def lock_source_rate(out, source_row):
"""Copies the full pricing block so a manual discount or margin on the source row survives."""
out.price_list_rate = flt(source_row.get("price_list_rate")) or flt(source_row.get("rate"))
out.rate = flt(source_row.get("rate"))
out.discount_percentage = flt(source_row.get("discount_percentage"))
out.discount_amount = flt(source_row.get("discount_amount"))
out.margin_type = source_row.get("margin_type")
out.margin_rate_or_amount = flt(source_row.get("margin_rate_or_amount"))
def set_valuation_rate(out, args): def set_valuation_rate(out, args):
if frappe.db.exists("Product Bundle", {"name": args.item_code, "disabled": 0}, cache=True): if frappe.db.exists("Product Bundle", {"name": args.item_code, "disabled": 0}, cache=True):
valuation_rate = 0.0 valuation_rate = 0.0
@@ -1522,13 +1595,21 @@ def apply_price_list(args, as_doc=False, doc=None):
def apply_price_list_on_item(args, doc=None): def apply_price_list_on_item(args, doc=None):
item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1) item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1)
item_details = get_price_list_rate(args, item_doc)
source_row = get_rate_locked_source_row(args, doc)
if source_row:
item_details = frappe._dict()
lock_source_rate(item_details, source_row)
else:
item_details = get_price_list_rate(args, item_doc)
args.conversion_factor = flt(args.conversion_factor) or get_conversion_factor( args.conversion_factor = flt(args.conversion_factor) or get_conversion_factor(
args.item_code, args.uom args.item_code, args.uom
).get("conversion_factor", 1) ).get("conversion_factor", 1)
args.stock_qty = flt(args.qty) * flt(args.conversion_factor) args.stock_qty = flt(args.qty) * flt(args.conversion_factor)
item_details.update(get_pricing_rule_for_item(args, doc=doc))
if not source_row:
item_details.update(get_pricing_rule_for_item(args, doc=doc))
return item_details return item_details

View File

@@ -9,9 +9,9 @@
"idx": 0, "idx": 0,
"is_standard": "Yes", "is_standard": "Yes",
"json": "{}", "json": "{}",
"letter_head": "Test", "letter_head": null,
"letterhead": null, "letterhead": null,
"modified": "2025-02-03 15:39:47.613040", "modified": "2026-08-26 14:39:19.102191",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Incorrect Serial and Batch Bundle", "name": "Incorrect Serial and Batch Bundle",

View File

@@ -177,3 +177,248 @@ class TestGetItemDetail(FrappeTestCase):
dn.save() dn.save()
self.assertEqual(dn.items[0].batch_no, "BATCH01") self.assertEqual(dn.items[0].batch_no, "BATCH01")
self.assertEqual(dn.items[0].rate, 50) self.assertEqual(dn.items[0].rate, 50)
def test_maintain_same_rate_keeps_source_rate_on_refetch(self):
"""#57436: with "maintain same rate" on, re-fetching a PR row mapped from a
PO must keep the PO rate instead of pulling a newer, higher Item Price.
The rate is validated on save, so it can never persist changed; assert the
fetched rate directly to prove the newer Item Price is never picked up.
"""
from erpnext.buying.doctype.purchase_order.purchase_order import make_purchase_receipt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.stock.doctype.item.test_item import make_item
def set_maintain_same_rate(value):
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", value)
frappe.clear_cache(doctype="Buying Settings")
set_maintain_same_rate(1)
item_code = make_item(properties={"is_stock_item": 1}).name
po = create_purchase_order(item_code=item_code, qty=1, rate=100)
# The PO may auto-insert an Item Price at 100; bump it to the newer, higher rate.
item_price = frappe.db.get_value(
"Item Price", {"item_code": item_code, "price_list": "Standard Buying"}
)
if item_price:
frappe.db.set_value("Item Price", item_price, "price_list_rate", 120)
else:
frappe.get_doc(
{
"doctype": "Item Price",
"price_list": "Standard Buying",
"item_code": item_code,
"price_list_rate": 120,
}
).insert()
pr = make_purchase_receipt(po.name)
pr.insert()
def fetch_price_list_rate():
args = frappe._dict(
{
"item_code": item_code,
"doctype": "Purchase Receipt",
"name": pr.name,
"company": pr.company,
"supplier": pr.supplier,
"currency": pr.currency,
"conversion_rate": 1.0,
"price_list": "Standard Buying",
"price_list_currency": pr.currency,
"plc_conversion_rate": 1.0,
"warehouse": pr.items[0].warehouse,
"uom": pr.items[0].uom,
"stock_uom": pr.items[0].stock_uom,
"qty": pr.items[0].qty,
"child_doctype": pr.items[0].doctype,
"child_docname": pr.items[0].name,
"is_return": 0,
"is_internal_supplier": 0,
"ignore_pricing_rule": 1,
}
)
return get_item_details(args, pr).get("price_list_rate")
# Rate stays at the PO rate; the newer Item Price (120) is not fetched.
self.assertEqual(fetch_price_list_rate(), 100)
# Control: without the setting the newer Item Price would be fetched.
set_maintain_same_rate(0)
self.assertEqual(fetch_price_list_rate(), 120)
def test_apply_price_list_keeps_source_rate_when_maintain_same_rate(self):
"""#57436: the bulk apply_price_list path (price list / party / conversion rate
change) must also keep the source rate on mapped rows, not just re-fetch of a
single row. Here a PR row carries its PO rate (175) while the current price list
rate is 100; the bulk apply must keep 175.
"""
from frappe.utils import flt, nowdate
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.stock.get_item_details import apply_price_list
item_code = "_Test Item"
price_list = "_Test Buying Price List"
original = frappe.db.get_single_value("Buying Settings", "maintain_same_rate")
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
frappe.clear_cache(doctype="Buying Settings")
try:
po = create_purchase_order(item_code=item_code, rate=175, qty=1)
row_name = "pr-row-1"
pr_doc = {
"doctype": "Purchase Receipt",
"items": [
{
"name": row_name,
"item_code": item_code,
"purchase_order_item": po.items[0].name,
"price_list_rate": 175,
"rate": 175,
}
],
}
args = frappe._dict(
doctype="Purchase Receipt",
supplier=po.supplier,
company=po.company,
currency=po.currency,
conversion_rate=1.0,
price_list=price_list,
plc_conversion_rate=1.0,
transaction_date=nowdate(),
items=[
frappe._dict(
doctype="Purchase Receipt Item",
parenttype="Purchase Receipt",
item_code=item_code,
child_docname=row_name,
qty=1,
uom=po.items[0].uom,
stock_uom=po.items[0].stock_uom,
conversion_factor=1.0,
)
],
)
result = apply_price_list(args, doc=pr_doc)
self.assertEqual(flt(result["children"][0].get("price_list_rate")), 175)
finally:
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", original)
frappe.clear_cache(doctype="Buying Settings")
def test_maintain_same_rate_keeps_source_discount_on_refetch(self):
"""A mapped source row with a discount has rate != price_list_rate. Re-fetch must
return the source's rate and discount, not just the pre-discount price, or the
recomputed rate diverges from the reference and fails maintain-same-rate on save.
"""
from frappe.utils import flt
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
item_code = "_Test Item"
price_list = "_Test Buying Price List"
original = frappe.db.get_single_value("Buying Settings", "maintain_same_rate")
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
frappe.clear_cache(doctype="Buying Settings")
try:
# source PO carries the discount: list rate 100, 10% off, effective rate 90
frappe.flags.dont_fetch_price_list_rate = True
po = create_purchase_order(item_code=item_code, qty=1, do_not_save=True)
po.buying_price_list = price_list
po.items[0].price_list_rate = 100
po.items[0].discount_percentage = 10
po.items[0].rate = 90
po.insert()
po.submit()
frappe.flags.dont_fetch_price_list_rate = False
row_name = "pr-row-1"
pr_doc = {
"doctype": "Purchase Receipt",
"items": [
{"name": row_name, "item_code": item_code, "purchase_order_item": po.items[0].name}
],
}
args = frappe._dict(
item_code=item_code,
doctype="Purchase Receipt",
company=po.company,
supplier=po.supplier,
currency=po.currency,
conversion_rate=1.0,
price_list=price_list,
price_list_currency=po.currency,
plc_conversion_rate=1.0,
warehouse="_Test Warehouse - _TC",
uom=po.items[0].uom,
stock_uom=po.items[0].stock_uom,
qty=1,
child_docname=row_name,
is_return=0,
is_internal_supplier=0,
ignore_pricing_rule=1,
)
out = get_item_details(args, pr_doc)
self.assertEqual(flt(out.get("price_list_rate")), 100)
self.assertEqual(flt(out.get("rate")), 90)
self.assertEqual(flt(out.get("discount_percentage")), 10)
finally:
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", original)
frappe.clear_cache(doctype="Buying Settings")
frappe.flags.dont_fetch_price_list_rate = False
def test_rate_lock_source_lookup_checks_permission(self):
"""The lock reads source pricing via a direct DB read, so it must not disclose a
source document's pricing to a caller who cannot read that document.
"""
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.stock.get_item_details import get_rate_locked_source_row
original = frappe.db.get_single_value("Buying Settings", "maintain_same_rate")
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
frappe.clear_cache(doctype="Buying Settings")
role, email = "_Test Role Without PO Access", "_test_rate_lock_probe@example.com"
try:
po = create_purchase_order(item_code="_Test Item", qty=1, rate=90)
pr_doc = {
"doctype": "Purchase Receipt",
"items": [{"name": "r1", "item_code": "_Test Item", "purchase_order_item": po.items[0].name}],
}
args = frappe._dict(doctype="Purchase Receipt", child_docname="r1")
# an authorized caller receives the source row
self.assertIsNotNone(get_rate_locked_source_row(args.copy(), dict(pr_doc)))
if not frappe.db.exists("Role", role):
frappe.get_doc({"doctype": "Role", "role_name": role, "desk_access": 1}).insert(
ignore_permissions=True
)
if not frappe.db.exists("User", email):
frappe.get_doc(
{
"doctype": "User",
"email": email,
"first_name": "Probe",
"send_welcome_email": 0,
"roles": [{"role": role}],
}
).insert(ignore_permissions=True)
frappe.set_user(email)
# a caller who cannot read the Purchase Order gets nothing
self.assertIsNone(get_rate_locked_source_row(args.copy(), dict(pr_doc)))
finally:
frappe.set_user("Administrator")
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", original)
frappe.clear_cache(doctype="Buying Settings")