mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-12 18:02:27 +00:00
Compare commits
811 Commits
coderabbit
...
locale-rev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d66be710f | ||
|
|
f67cfc48e0 | ||
|
|
56345f4354 | ||
|
|
b51c59d20d | ||
|
|
decd9343ee | ||
|
|
950bf682c3 | ||
|
|
1f4d20413e | ||
|
|
11a6db32ae | ||
|
|
805fc807a9 | ||
|
|
b4fb74c84d | ||
|
|
ac351bce4b | ||
|
|
208112e7a9 | ||
|
|
616bae1f84 | ||
|
|
573eb25d5b | ||
|
|
8267482ee9 | ||
|
|
a1c378c16f | ||
|
|
e6bebbfe81 | ||
|
|
9358a122cd | ||
|
|
3bd023d640 | ||
|
|
6702506f58 | ||
|
|
913ce9454e | ||
|
|
24f5fb7b44 | ||
|
|
6aaca38ce3 | ||
|
|
fbdb3a1f48 | ||
|
|
d2e039ad4e | ||
|
|
d6abbce4ec | ||
|
|
f316229f9e | ||
|
|
a15e5fdc4e | ||
|
|
82c3da5b1e | ||
|
|
76d8e8fec9 | ||
|
|
702adda000 | ||
|
|
2bc3c146a6 | ||
|
|
ba05608ce6 | ||
|
|
4e9a2a327f | ||
|
|
06e0effbf7 | ||
|
|
a3eafe5b18 | ||
|
|
50d6971d20 | ||
|
|
7e368c16b7 | ||
|
|
edac82f6e3 | ||
|
|
3c9f520e68 | ||
|
|
6603005822 | ||
|
|
15eb5d9827 | ||
|
|
7801dd5353 | ||
|
|
c1431105f4 | ||
|
|
6f93210b9b | ||
|
|
8aae46a25e | ||
|
|
99ed1c34f3 | ||
|
|
f07a6eb199 | ||
|
|
5fead1d17a | ||
|
|
8b9e02fd44 | ||
|
|
f23dc07914 | ||
|
|
5611e9168e | ||
|
|
b1149fe950 | ||
|
|
70b401e610 | ||
|
|
58c198a58b | ||
|
|
f2decc852c | ||
|
|
ee19c32c3a | ||
|
|
74b658fabf | ||
|
|
083b571641 | ||
|
|
e37d4a6f7c | ||
|
|
9cb3dad079 | ||
|
|
700c14d5b3 | ||
|
|
c0148c7266 | ||
|
|
2d83069b82 | ||
|
|
2ec02e477f | ||
|
|
42ae954c16 | ||
|
|
e3a55d31d3 | ||
|
|
767925a481 | ||
|
|
e970578c47 | ||
|
|
723993fdf6 | ||
|
|
4335318482 | ||
|
|
17c715fde0 | ||
|
|
c76be620a8 | ||
|
|
2ce276df5b | ||
|
|
e1bad10e6c | ||
|
|
93f2a8ad6b | ||
|
|
81acefa8ad | ||
|
|
800810d23d | ||
|
|
a1124449c2 | ||
|
|
6342e78305 | ||
|
|
d2e04750b5 | ||
|
|
7ec0354a79 | ||
|
|
9a5b476d9c | ||
|
|
7ee08c9964 | ||
|
|
b666d9d5c1 | ||
|
|
bdf4e51da3 | ||
|
|
397de1274f | ||
|
|
4c39cf2d65 | ||
|
|
260fc6a7ce | ||
|
|
c031e345db | ||
|
|
d7fdab99cb | ||
|
|
35d814c7b3 | ||
|
|
91c6475f1c | ||
|
|
735631899f | ||
|
|
868a7eb5ca | ||
|
|
8c94396ad9 | ||
|
|
47b08eaa41 | ||
|
|
9b8f685c82 | ||
|
|
c5b3673a30 | ||
|
|
9538a9870c | ||
|
|
6b1aac4aee | ||
|
|
066442c236 | ||
|
|
79d0708ea7 | ||
|
|
7b3d85c74e | ||
|
|
733528a9f5 | ||
|
|
49d363b174 | ||
|
|
c37a56ec89 | ||
|
|
1592229b24 | ||
|
|
36726b0f7b | ||
|
|
3fad5fb9b9 | ||
|
|
cffb59ae73 | ||
|
|
9563af9ae1 | ||
|
|
e68d9af498 | ||
|
|
04127019f9 | ||
|
|
81fd3c86f4 | ||
|
|
30c3ff2efe | ||
|
|
5f12b0db3f | ||
|
|
b9d95711a2 | ||
|
|
86ade52dc6 | ||
|
|
74def423ed | ||
|
|
061d2e45a9 | ||
|
|
df6780bf1a | ||
|
|
6fc0ea02a1 | ||
|
|
81281d88c7 | ||
|
|
3dcdde83c7 | ||
|
|
27381ae77b | ||
|
|
63fe68bf28 | ||
|
|
6fbdcc3dd1 | ||
|
|
8397b1ee4b | ||
|
|
a5e4256491 | ||
|
|
eb2f986b11 | ||
|
|
5bae06ae96 | ||
|
|
2712bc8661 | ||
|
|
290f979fd3 | ||
|
|
a22ea1de86 | ||
|
|
13859dfb3c | ||
|
|
3078c4a451 | ||
|
|
73f72bda42 | ||
|
|
aec400c12d | ||
|
|
0874cbc268 | ||
|
|
048608556c | ||
|
|
397d806f0f | ||
|
|
61e01aff67 | ||
|
|
560c2511cc | ||
|
|
f582fdbf71 | ||
|
|
ffa6280431 | ||
|
|
565542879e | ||
|
|
b33f06701c | ||
|
|
1811bb7643 | ||
|
|
94ea72948e | ||
|
|
cfda569af1 | ||
|
|
4b72ac7d22 | ||
|
|
235e3adbcb | ||
|
|
c57e452efd | ||
|
|
4dfbacfd92 | ||
|
|
d82a0a9455 | ||
|
|
c2ed02d4e4 | ||
|
|
b10b205394 | ||
|
|
485166b668 | ||
|
|
153ad99f85 | ||
|
|
2d07b346de | ||
|
|
56732df641 | ||
|
|
8c46e06e04 | ||
|
|
b0d6751777 | ||
|
|
5d46e1a7b1 | ||
|
|
1cdf439e38 | ||
|
|
4c374e182d | ||
|
|
4c7175fad1 | ||
|
|
39b0e3522a | ||
|
|
f220960d91 | ||
|
|
7ef187b1eb | ||
|
|
d90ec49241 | ||
|
|
2517369270 | ||
|
|
d9ac198bad | ||
|
|
cf09f725ca | ||
|
|
a37ffe6e2a | ||
|
|
dd0bcf4dbd | ||
|
|
1d4c835843 | ||
|
|
61661a159d | ||
|
|
1e297a0e5f | ||
|
|
9ef7f05712 | ||
|
|
10257bbb6e | ||
|
|
bd9e5e97d7 | ||
|
|
bf8b730259 | ||
|
|
cecea63627 | ||
|
|
d6888a32ae | ||
|
|
1a6a16be5b | ||
|
|
dced32088c | ||
|
|
bb7fdd59dc | ||
|
|
c2a1dbeb67 | ||
|
|
8a2cb96c2a | ||
|
|
7aa3dbcd86 | ||
|
|
570f574758 | ||
|
|
fbf5529ddd | ||
|
|
0ea22f9796 | ||
|
|
bdcb2c1512 | ||
|
|
ddb497acb5 | ||
|
|
539d1a4e42 | ||
|
|
13995a64b8 | ||
|
|
ed7315d78e | ||
|
|
9541cb226d | ||
|
|
dd910bb4cc | ||
|
|
2f3ac06eff | ||
|
|
e476dff842 | ||
|
|
60108590b0 | ||
|
|
f7ff61be5d | ||
|
|
a15e2ecf78 | ||
|
|
4ba43a538e | ||
|
|
d638f3e033 | ||
|
|
792a1a7ab7 | ||
|
|
ced0c94e8e | ||
|
|
0e356dc2e3 | ||
|
|
99a5886f3a | ||
|
|
e5e164c536 | ||
|
|
bde15cb64b | ||
|
|
ae3453c84b | ||
|
|
a0c8ac524b | ||
|
|
30e0285ae7 | ||
|
|
2f635a1021 | ||
|
|
9a253c4f54 | ||
|
|
68144c5be2 | ||
|
|
7df9d951c6 | ||
|
|
db00860662 | ||
|
|
a85a0aef52 | ||
|
|
3e87059939 | ||
|
|
96aa37eff5 | ||
|
|
a8b538cc67 | ||
|
|
60d2f2d304 | ||
|
|
8b2a971019 | ||
|
|
9478ccca27 | ||
|
|
9198bc1b8f | ||
|
|
7b3c10769b | ||
|
|
a2afabab79 | ||
|
|
5e5487faae | ||
|
|
2b72aab671 | ||
|
|
b7f45e6963 | ||
|
|
cbea4493c1 | ||
|
|
371efce88a | ||
|
|
f133b96cb4 | ||
|
|
853090729a | ||
|
|
3677609838 | ||
|
|
8e14249335 | ||
|
|
e088a596c7 | ||
|
|
021aa63e24 | ||
|
|
7100d61f3c | ||
|
|
3584d2bf23 | ||
|
|
60e96633c4 | ||
|
|
cfbdfcf515 | ||
|
|
d58171987c | ||
|
|
d54d0c25a2 | ||
|
|
15dfc08a31 | ||
|
|
e2682d2690 | ||
|
|
ad511755ec | ||
|
|
d790c60a93 | ||
|
|
f53ea90113 | ||
|
|
a4652e0d5f | ||
|
|
da16cc1727 | ||
|
|
a8fe1e24b3 | ||
|
|
a24efc6e34 | ||
|
|
61f6daf55e | ||
|
|
ddc3842eb7 | ||
|
|
ad7a52e2cb | ||
|
|
589f1246cb | ||
|
|
cbb3bf6f6a | ||
|
|
3ce05fc0dc | ||
|
|
7388b051ed | ||
|
|
e4c4f1a0c2 | ||
|
|
d4f2ea3358 | ||
|
|
d490b2a3c6 | ||
|
|
8915df90ae | ||
|
|
0ac09df5d9 | ||
|
|
0f0f8bd64b | ||
|
|
3eb0d5fd73 | ||
|
|
6f77a11522 | ||
|
|
3f2603a34d | ||
|
|
48fc867b7b | ||
|
|
4674b5a207 | ||
|
|
c2b8b8469d | ||
|
|
50daec2972 | ||
|
|
63defa40cb | ||
|
|
f7830fddeb | ||
|
|
f65d50b7e4 | ||
|
|
dcd47223fa | ||
|
|
d6531189f9 | ||
|
|
b49adbd74c | ||
|
|
4c76786ce4 | ||
|
|
7cff0ba626 | ||
|
|
94fb7e11b4 | ||
|
|
74ac28fc70 | ||
|
|
d0323dea65 | ||
|
|
519e115eb2 | ||
|
|
1a4d7ad937 | ||
|
|
c2f19036f3 | ||
|
|
969f01fca3 | ||
|
|
d6e1ca0f10 | ||
|
|
32d09acba9 | ||
|
|
59492cf08e | ||
|
|
9902473321 | ||
|
|
ba96d37c11 | ||
|
|
1352dc79bb | ||
|
|
548cdb55d7 | ||
|
|
b2afda0462 | ||
|
|
866bca70f9 | ||
|
|
d3911cd7d8 | ||
|
|
387fb1b202 | ||
|
|
0ef15e6364 | ||
|
|
6e95eebfa4 | ||
|
|
a4175553fc | ||
|
|
b8d7bb2c08 | ||
|
|
2504f0fc0c | ||
|
|
732c98b72f | ||
|
|
6342e9a3e2 | ||
|
|
8fe0bf4ba3 | ||
|
|
58b8af0fa8 | ||
|
|
fa5eae08a0 | ||
|
|
4561e3a389 | ||
|
|
21423676c9 | ||
|
|
531112e364 | ||
|
|
4d40c84a31 | ||
|
|
fc54565d95 | ||
|
|
37323480dd | ||
|
|
29bc300282 | ||
|
|
21452b4c6e | ||
|
|
c98e1454fc | ||
|
|
1fc2eddf6f | ||
|
|
024c268fe0 | ||
|
|
85c8296548 | ||
|
|
b15716f65a | ||
|
|
a6f5130db1 | ||
|
|
37b9402214 | ||
|
|
390294a92b | ||
|
|
a6bb44b421 | ||
|
|
d3ebcd85b4 | ||
|
|
73d5aa35a1 | ||
|
|
39d3f4580e | ||
|
|
40391252d5 | ||
|
|
1f62cecdfd | ||
|
|
e5f138d004 | ||
|
|
23ccc2a8c5 | ||
|
|
fad53f6fe4 | ||
|
|
7f9066c05a | ||
|
|
eb17284711 | ||
|
|
e159fc2779 | ||
|
|
747595082d | ||
|
|
beffc016da | ||
|
|
f9ec45eda7 | ||
|
|
1bc0b40a2e | ||
|
|
f36962fc58 | ||
|
|
36dcf8b2a5 | ||
|
|
e317ab1479 | ||
|
|
ea04f55d12 | ||
|
|
cac6b65dee | ||
|
|
c6a292f6a9 | ||
|
|
e6825476e7 | ||
|
|
54144f72a5 | ||
|
|
debe868950 | ||
|
|
773cc80ed4 | ||
|
|
3fd5a0f100 | ||
|
|
83f5c03494 | ||
|
|
eb0fa4d26d | ||
|
|
4f4bd98943 | ||
|
|
34edbed00b | ||
|
|
84773a3a32 | ||
|
|
08529964b4 | ||
|
|
2c011bb9ad | ||
|
|
ee9ce0dee8 | ||
|
|
1d3d09f48c | ||
|
|
e8ef6f1afb | ||
|
|
a374d0b3e7 | ||
|
|
de2843d9f1 | ||
|
|
ae0be7f6ce | ||
|
|
7860673855 | ||
|
|
fd72132743 | ||
|
|
1eb9f5b803 | ||
|
|
b3e6b304e4 | ||
|
|
c8c54a42e2 | ||
|
|
72f4fd08ee | ||
|
|
4defb104db | ||
|
|
48cafc8e57 | ||
|
|
8adf8aed41 | ||
|
|
341dc4be7a | ||
|
|
0f2ed28ab7 | ||
|
|
c2282eaf08 | ||
|
|
38f35acffe | ||
|
|
25f979a825 | ||
|
|
afa14e6cd4 | ||
|
|
802507d321 | ||
|
|
12ff8e6279 | ||
|
|
219cf6bc57 | ||
|
|
a7ec72b25c | ||
|
|
4bd3d4d36c | ||
|
|
d425270c88 | ||
|
|
f8c8c1cc2d | ||
|
|
cc02c411b6 | ||
|
|
65dd7d4851 | ||
|
|
47b80cbc2b | ||
|
|
20d2317f61 | ||
|
|
d2a4b8bfd8 | ||
|
|
2b26d859c3 | ||
|
|
3516cda350 | ||
|
|
4d51d0d351 | ||
|
|
303b7a117c | ||
|
|
83006a3ed1 | ||
|
|
b3e9fa2142 | ||
|
|
45d7a398b6 | ||
|
|
ffb88c30b9 | ||
|
|
40fd9a8008 | ||
|
|
eb357a2805 | ||
|
|
fd2cadd431 | ||
|
|
e66bbe4eec | ||
|
|
f23a49a25e | ||
|
|
1c749c01cd | ||
|
|
f95ff68c10 | ||
|
|
5815a92273 | ||
|
|
bb8d84e65e | ||
|
|
c28020a1bd | ||
|
|
95b0ec70cb | ||
|
|
1c0f2e1fba | ||
|
|
83338675f9 | ||
|
|
a6f808702f | ||
|
|
8e71060931 | ||
|
|
659112842e | ||
|
|
a8636e4f59 | ||
|
|
45febbabd7 | ||
|
|
cdc62e7327 | ||
|
|
4499e974a0 | ||
|
|
2017edca88 | ||
|
|
894771029c | ||
|
|
bdf909e94e | ||
|
|
63323a2611 | ||
|
|
36393e447e | ||
|
|
fe01206a73 | ||
|
|
e766bb0cd7 | ||
|
|
53e3222935 | ||
|
|
47353fbceb | ||
|
|
a23c2f2166 | ||
|
|
07db5941aa | ||
|
|
dd9e1814b0 | ||
|
|
0e262de96f | ||
|
|
6b7fed7f59 | ||
|
|
7ba2ed6f3f | ||
|
|
5e34325604 | ||
|
|
9bb60405e7 | ||
|
|
be3d2422a7 | ||
|
|
bd9d9c3cc7 | ||
|
|
1218c0f518 | ||
|
|
7105e3fb69 | ||
|
|
ae985c00e9 | ||
|
|
803c09a4ae | ||
|
|
1b288682e8 | ||
|
|
7d14833856 | ||
|
|
2c6054d51a | ||
|
|
464e1929db | ||
|
|
33d0e9559a | ||
|
|
20af546b26 | ||
|
|
7e99a1f4b0 | ||
|
|
2adde497c8 | ||
|
|
97d6a35edc | ||
|
|
5c8cb1e7ec | ||
|
|
d1217dfedd | ||
|
|
4ddb3c7a6c | ||
|
|
3ed7627ec3 | ||
|
|
1b5d5c5be8 | ||
|
|
4946a13d45 | ||
|
|
578783af8e | ||
|
|
223d560b32 | ||
|
|
71e8941285 | ||
|
|
7de0ca164d | ||
|
|
0bafa347a5 | ||
|
|
2cfdcc1af4 | ||
|
|
678d261f40 | ||
|
|
c00bf7df6a | ||
|
|
047b278791 | ||
|
|
aaf5f923b0 | ||
|
|
fdfa7bc963 | ||
|
|
3ab978ab46 | ||
|
|
4da3d43013 | ||
|
|
51709f032f | ||
|
|
67d8223f73 | ||
|
|
da07f84e44 | ||
|
|
23a73c9cdb | ||
|
|
7b197cbe1c | ||
|
|
99cd29d88f | ||
|
|
0cf4c2ded5 | ||
|
|
271a982cb4 | ||
|
|
f22b9e297b | ||
|
|
61d0863239 | ||
|
|
803cc7c653 | ||
|
|
b4341df44c | ||
|
|
4a77a8b840 | ||
|
|
680117e97e | ||
|
|
4bab80faaa | ||
|
|
74a1707e3f | ||
|
|
f145331ae5 | ||
|
|
be5df6b2b7 | ||
|
|
14a5ff2b54 | ||
|
|
f585ddea71 | ||
|
|
958ba61f66 | ||
|
|
f7b18159a6 | ||
|
|
ae759481e1 | ||
|
|
c2bafcf83b | ||
|
|
04b5e1b3a0 | ||
|
|
3207a9e642 | ||
|
|
67e8286a57 | ||
|
|
7c0c41f3de | ||
|
|
c5da02dbd0 | ||
|
|
ee1ae88ecd | ||
|
|
e7b8c257a3 | ||
|
|
93078bcf0c | ||
|
|
566072a17f | ||
|
|
31191096ad | ||
|
|
0975222e3a | ||
|
|
576279fa92 | ||
|
|
21eec60924 | ||
|
|
e98b68c38f | ||
|
|
155f6afb48 | ||
|
|
1e45195ef9 | ||
|
|
3a2e4ff5e5 | ||
|
|
b2a0d6559f | ||
|
|
22123dd955 | ||
|
|
376ab0e346 | ||
|
|
da0322e994 | ||
|
|
479b44c610 | ||
|
|
a34e8c99cd | ||
|
|
b8e1758dcf | ||
|
|
2660907ac8 | ||
|
|
9f8c08fd18 | ||
|
|
47ac67f7a2 | ||
|
|
0cf9915698 | ||
|
|
dd1027f165 | ||
|
|
bd521d9089 | ||
|
|
ebce5d2635 | ||
|
|
277ba9cb79 | ||
|
|
86389b46e2 | ||
|
|
bf20ecca60 | ||
|
|
8c960620cc | ||
|
|
7be1c885cd | ||
|
|
3957df6511 | ||
|
|
a9829f5f7b | ||
|
|
9db3fa1b65 | ||
|
|
93d1716eb5 | ||
|
|
4f59d580c4 | ||
|
|
d1b81b96a5 | ||
|
|
9f7bfc0e36 | ||
|
|
eb7932ed73 | ||
|
|
e8e8d233ab | ||
|
|
f2f509234b | ||
|
|
e90a3b5a56 | ||
|
|
c36fa5bdb6 | ||
|
|
f7b9221324 | ||
|
|
4e7794bfc3 | ||
|
|
71ad323cb6 | ||
|
|
32ea37035e | ||
|
|
81dceceb4f | ||
|
|
d02a85df50 | ||
|
|
000af2bc96 | ||
|
|
cfdc554a19 | ||
|
|
6e744bcaa7 | ||
|
|
ee501e884a | ||
|
|
edfcaee99b | ||
|
|
31ec3af164 | ||
|
|
70053fea8e | ||
|
|
ac9232d888 | ||
|
|
1a22e3cb61 | ||
|
|
57d14f695f | ||
|
|
1415472674 | ||
|
|
dca2cfd009 | ||
|
|
5fb5b7b30e | ||
|
|
a632d5f313 | ||
|
|
4136ac26c4 | ||
|
|
a67b548f44 | ||
|
|
ef44528ba5 | ||
|
|
6c49d5dc7d | ||
|
|
95baf953a8 | ||
|
|
c86e316670 | ||
|
|
7e584dd84a | ||
|
|
cb4e6ad825 | ||
|
|
4584893542 | ||
|
|
6b0fe32180 | ||
|
|
e08fe10f31 | ||
|
|
99ddc36c26 | ||
|
|
26854d8d42 | ||
|
|
1c5e36017c | ||
|
|
611f7d07c2 | ||
|
|
481deee4b2 | ||
|
|
116ea657cd | ||
|
|
e9befff0cb | ||
|
|
1770d92ea6 | ||
|
|
a64b5f2c5d | ||
|
|
812d7de7f7 | ||
|
|
21d0ee8db1 | ||
|
|
14818b8311 | ||
|
|
c9dbd58e7f | ||
|
|
e63ff83f24 | ||
|
|
d3e269a3d3 | ||
|
|
b33a805702 | ||
|
|
efc4c900f3 | ||
|
|
e4df0a393a | ||
|
|
a3bd73b9db | ||
|
|
8db29b0a81 | ||
|
|
8cf31548f2 | ||
|
|
3a91f6793f | ||
|
|
ced2f33a63 | ||
|
|
954173d629 | ||
|
|
f3ea1863ae | ||
|
|
175fe9279c | ||
|
|
3b45485351 | ||
|
|
b755ca12ca | ||
|
|
0687e683f8 | ||
|
|
7e08154217 | ||
|
|
a734f90920 | ||
|
|
27091e5168 | ||
|
|
3f8af9b3c9 | ||
|
|
e8d1e9d946 | ||
|
|
6d64a6a1d1 | ||
|
|
2f2e16fc33 | ||
|
|
16f09141da | ||
|
|
036f64013d | ||
|
|
51cfd6119f | ||
|
|
b719a6c767 | ||
|
|
6fde0a6261 | ||
|
|
671726a3e4 | ||
|
|
f1b4fe12a2 | ||
|
|
4c4d8859f6 | ||
|
|
a3ba0a18c4 | ||
|
|
2e51e7fb78 | ||
|
|
6387aebed8 | ||
|
|
b478f0da89 | ||
|
|
6d41e35242 | ||
|
|
eb2119e292 | ||
|
|
c5df570262 | ||
|
|
12f8bb2937 | ||
|
|
7fd6278c18 | ||
|
|
c90f77af44 | ||
|
|
c29bcbfed8 | ||
|
|
a29710dc07 | ||
|
|
f45a5a63a7 | ||
|
|
82aa32f74e | ||
|
|
5793322c30 | ||
|
|
61d8308e81 | ||
|
|
b41c1858a3 | ||
|
|
4a7d606099 | ||
|
|
de8f8ef9f4 | ||
|
|
93a4b9d49f | ||
|
|
9160aeb30e | ||
|
|
ee2f8d8ebc | ||
|
|
2d312bcfe8 | ||
|
|
3ee11307f1 | ||
|
|
8e9365eb3b | ||
|
|
77893933a2 | ||
|
|
e565d2283e | ||
|
|
ff2f015d67 | ||
|
|
be0040ddc7 | ||
|
|
12ec997027 | ||
|
|
7767000ccf | ||
|
|
d54259c99c | ||
|
|
46b4cf3add | ||
|
|
42f2abf5ae | ||
|
|
dbbeca6308 | ||
|
|
06a7c85c93 | ||
|
|
7a1c4a5ded | ||
|
|
6cc802147b | ||
|
|
4d9412181c | ||
|
|
0d372a62a1 | ||
|
|
62a98a0504 | ||
|
|
135a433018 | ||
|
|
af6a1eb189 | ||
|
|
a8068b89ed | ||
|
|
75b2c2c83d | ||
|
|
d1cba1073f | ||
|
|
c176af4fe3 | ||
|
|
2d6b43fd54 | ||
|
|
20ce96e358 | ||
|
|
43e2495df8 | ||
|
|
0e4c37bb6d | ||
|
|
f3eb6c7078 | ||
|
|
35e53d28df | ||
|
|
6180e5eb53 | ||
|
|
c58887b44a | ||
|
|
c7fd855092 | ||
|
|
53e58f6678 | ||
|
|
5ea7a91fe1 | ||
|
|
200cefa0a5 | ||
|
|
f5d74b4572 | ||
|
|
d9998a977c | ||
|
|
b24ae5e9a2 | ||
|
|
54d96bb761 | ||
|
|
6423ce2fa7 | ||
|
|
6066fef1c9 | ||
|
|
b20f57321f | ||
|
|
33b6fd408f | ||
|
|
36f1e3572c | ||
|
|
4cc306d2d8 | ||
|
|
3e9a3c1185 | ||
|
|
d3f44a425c | ||
|
|
058be326f0 | ||
|
|
46b742cd4c | ||
|
|
95fdbe55f9 | ||
|
|
fc8647d1da | ||
|
|
f82f1da706 | ||
|
|
6e17ccf499 | ||
|
|
40bfd08866 | ||
|
|
89f6f0f46f | ||
|
|
838d245215 | ||
|
|
b565dd3da8 | ||
|
|
e1b6ec340c | ||
|
|
c38f884095 | ||
|
|
464560a949 | ||
|
|
f7f2e73f79 | ||
|
|
646688c291 | ||
|
|
dd4fd89ef8 | ||
|
|
f0dccc3cd7 | ||
|
|
91b1df49a4 | ||
|
|
7f6f39f5e7 | ||
|
|
cdcf3fa593 | ||
|
|
728c678cf9 | ||
|
|
e11ba21b42 | ||
|
|
1a4ecba742 | ||
|
|
4e19c7e8bd | ||
|
|
578b06e027 | ||
|
|
2c19c1fd06 | ||
|
|
3d65db2ac3 | ||
|
|
fabc26bb69 | ||
|
|
27226b1d82 | ||
|
|
0dc804f9b4 | ||
|
|
3192f3f011 | ||
|
|
3c6eb9a531 | ||
|
|
8dae178728 | ||
|
|
6f9cd8c261 | ||
|
|
d6189b8101 | ||
|
|
48f4a44fb5 | ||
|
|
f0332c4dc7 | ||
|
|
ec41f1b0f5 | ||
|
|
7e9647f3f0 | ||
|
|
fb9656b975 | ||
|
|
1b6fe8498d | ||
|
|
5eeebbde7f | ||
|
|
f7abf9c1da | ||
|
|
99406ccc15 | ||
|
|
1295d7aa30 | ||
|
|
5a680d5037 | ||
|
|
7528d42187 | ||
|
|
cbdc945287 | ||
|
|
faf0dcb102 | ||
|
|
5e02b4009e | ||
|
|
8125f9035c | ||
|
|
efa3973b77 | ||
|
|
71371b0ba5 | ||
|
|
543b6e51c0 | ||
|
|
3460a7efb5 | ||
|
|
e78c750b4e | ||
|
|
d82c92a237 | ||
|
|
826cf66af8 | ||
|
|
b49c679a50 | ||
|
|
5f05714e9d | ||
|
|
37cdae2f34 | ||
|
|
3b27f49d79 | ||
|
|
525b3960e1 | ||
|
|
04cdf88715 | ||
|
|
f8f626975f | ||
|
|
31c536e33f | ||
|
|
c1fef8269a | ||
|
|
e5ba0e6401 | ||
|
|
696ea68f86 | ||
|
|
71d00f5290 | ||
|
|
0fb37ad792 | ||
|
|
88069779b2 | ||
|
|
c5a4164a6b | ||
|
|
334e8ada30 | ||
|
|
ba8eadda52 | ||
|
|
297a2ea259 | ||
|
|
e129e1438e | ||
|
|
e810cd8440 | ||
|
|
50b3396064 | ||
|
|
9322095786 | ||
|
|
8a1b8259bd | ||
|
|
d905f78984 | ||
|
|
7250ee4429 | ||
|
|
2068299766 | ||
|
|
f5378b6573 | ||
|
|
c4e35f1284 | ||
|
|
6a9c0e22de | ||
|
|
809b29fe90 | ||
|
|
d68a04ad16 | ||
|
|
6954811c55 | ||
|
|
8cdc21c264 | ||
|
|
466668c6b8 | ||
|
|
e7e22c809e | ||
|
|
2606ca6fa9 | ||
|
|
54acaa2aec | ||
|
|
3f8a0a4833 | ||
|
|
9e51701e2a | ||
|
|
57bd1facf5 | ||
|
|
a378fee8e0 | ||
|
|
63d71ff90a | ||
|
|
e2c3d0fa94 | ||
|
|
b8d4522ea1 | ||
|
|
22fd1a1cfd | ||
|
|
a1192e34d7 | ||
|
|
3e39d13172 | ||
|
|
7c7ba0154a | ||
|
|
02e96039ac | ||
|
|
323636b396 | ||
|
|
58cdb9503b | ||
|
|
ec1eb6d222 | ||
|
|
7b89c12470 | ||
|
|
b8e06b9636 | ||
|
|
2ea6508fa5 | ||
|
|
a41297d841 | ||
|
|
4ad1474e32 | ||
|
|
87c59f471c |
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
2
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -60,7 +60,7 @@ body:
|
||||
description: Share exact version number of Frappe and ERPNext you are using.
|
||||
placeholder: |
|
||||
Frappe version -
|
||||
ERPNext Verion -
|
||||
ERPNext version -
|
||||
validations:
|
||||
required: true
|
||||
|
||||
|
||||
39
.github/helper/locale_review.js
vendored
Normal file
39
.github/helper/locale_review.js
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
module.exports = async ({ github, context, localeChanged }) => {
|
||||
const pr = context.payload.pull_request.number
|
||||
const { owner, repo } = context.repo
|
||||
|
||||
const { data: me } = await github.rest.users.getAuthenticated()
|
||||
const botUser = me.login
|
||||
|
||||
const reviews = await github.rest.pulls.listReviews({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr
|
||||
})
|
||||
|
||||
const botReview = reviews.data
|
||||
.reverse()
|
||||
.find(r => r.user.login === botUser && r.state === "CHANGES_REQUESTED")
|
||||
|
||||
if (localeChanged) {
|
||||
if (!botReview) {
|
||||
await github.rest.pulls.createReview({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr,
|
||||
event: "REQUEST_CHANGES",
|
||||
body: "Translations are managed through [Crowdin](https://crowdin.com/project/frappe/). Consider contributing through Crowdin."
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (botReview) {
|
||||
await github.rest.pulls.dismissReview({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: pr,
|
||||
review_id: botReview.id,
|
||||
message: ""
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
44
.github/workflows/prevent-locale-changes.yml
vendored
Normal file
44
.github/workflows/prevent-locale-changes.yml
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
name: Prevent Locale Changes
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
check-locale:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
if: github.actor != 'frappe-pr-bot'
|
||||
# Exemption for the bot used for Crowdin Sync and POT File generation.
|
||||
|
||||
concurrency:
|
||||
group: prevent-locale-changes-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect locale changes
|
||||
id: check
|
||||
run: |
|
||||
git fetch origin ${{ github.base_ref }}
|
||||
|
||||
if git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -q '^erpnext/locale/'; then
|
||||
echo "locale_changed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "locale_changed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Manage locale review
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.RELEASE_TOKEN }}
|
||||
script: |
|
||||
const localeChanged = "${{ steps.check.outputs.locale_changed }}" === "true"
|
||||
const handler = require('./.github/helper/locale_review.js')
|
||||
await handler({ github, context, localeChanged })
|
||||
@@ -7,6 +7,7 @@ on:
|
||||
paths:
|
||||
- "**.js"
|
||||
- "**.css"
|
||||
- "**.svg"
|
||||
- "**.md"
|
||||
- "**.html"
|
||||
- 'crowdin.yml'
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
<div align="center">
|
||||
<a href="https://frappe.io/erpnext">
|
||||
<img src="./erpnext/public/images/v16/erpnext.svg" alt="ERPNext Logo" height="80px" width="80xp"/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Security Policy
|
||||
|
||||
The ERPNext team and community take security issues seriously. To report a security issue, fill out the form at [https://erpnext.com/security/report](https://erpnext.com/security/report).
|
||||
The ERPNext team and community take security issues seriously. To report a security issue, please go through the information mentioned [here](https://frappe.io/security).
|
||||
|
||||
You can help us make ERPNext and all it's users more secure by following the [Reporting guidelines](https://erpnext.com/security).
|
||||
You can help us make ERPNext and all its users more secure by following the [Reporting guidelines](https://frappe.io/security).
|
||||
|
||||
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
|
||||
We appreciate your efforts to responsibly disclose your findings. We'll endeavor to respond quickly, and will keep you updated throughout the process.
|
||||
|
||||
50
erpnext/accounts/accounts_dashboard/payments/payments.json
Normal file
50
erpnext/accounts/accounts_dashboard/payments/payments.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"cards": [
|
||||
{
|
||||
"card": "Total Outgoing Bills"
|
||||
},
|
||||
{
|
||||
"card": "Total Incoming Bills"
|
||||
},
|
||||
{
|
||||
"card": "Total Incoming Payment"
|
||||
},
|
||||
{
|
||||
"card": "Total Outgoing Payment"
|
||||
}
|
||||
],
|
||||
"charts": [
|
||||
{
|
||||
"chart": "Incoming Bills (Purchase Invoice)",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Outgoing Bills (Sales Invoice)",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Accounts Receivable Ageing",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Accounts Payable Ageing",
|
||||
"width": "Half"
|
||||
},
|
||||
{
|
||||
"chart": "Bank Balance",
|
||||
"width": "Full"
|
||||
}
|
||||
],
|
||||
"creation": "2026-01-26 21:25:12.793893",
|
||||
"dashboard_name": "Payments",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard",
|
||||
"idx": 0,
|
||||
"is_default": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2026-01-26 21:25:12.793893",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payments",
|
||||
"owner": "Administrator"
|
||||
}
|
||||
@@ -52,7 +52,7 @@ class ERPNextAddress(Address):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_shipping_address(company, address=None):
|
||||
def get_shipping_address(company: str, address: str | None = None):
|
||||
filters = [
|
||||
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||
["Dynamic Link", "link_name", "=", company],
|
||||
|
||||
@@ -13,15 +13,15 @@ from frappe.utils.nestedset import get_descendants_of
|
||||
@frappe.whitelist()
|
||||
@cache_source
|
||||
def get(
|
||||
chart_name=None,
|
||||
chart=None,
|
||||
no_cache=None,
|
||||
filters=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
timespan=None,
|
||||
time_interval=None,
|
||||
heatmap_year=None,
|
||||
chart_name: str | None = None,
|
||||
chart: str | dict | None = None,
|
||||
no_cache: bool | None = None,
|
||||
filters: str | dict | None = None,
|
||||
from_date: str | None = None,
|
||||
to_date: str | None = None,
|
||||
timespan: str | None = None,
|
||||
time_interval: str | None = None,
|
||||
heatmap_year: str | None = None,
|
||||
):
|
||||
if chart_name:
|
||||
chart = frappe.get_doc("Dashboard Chart", chart_name)
|
||||
|
||||
@@ -471,7 +471,7 @@ class Account(NestedSet):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_parent_account(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
return frappe.db.sql(
|
||||
"""select name from tabAccount
|
||||
where is_group = 1 and docstatus != 2 and company = {}
|
||||
@@ -515,7 +515,9 @@ def get_account_autoname(account_number, account_name, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
||||
def update_account_number(
|
||||
name: str, account_name: str, account_number: str | None = None, from_descendant: bool = False
|
||||
):
|
||||
_ensure_idle_system()
|
||||
account = frappe.get_cached_doc("Account", name)
|
||||
if not account:
|
||||
@@ -577,7 +579,7 @@ def update_account_number(name, account_name, account_number=None, from_descenda
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def merge_account(old, new):
|
||||
def merge_account(old: str, new: str):
|
||||
_ensure_idle_system()
|
||||
# Validate properties before merging
|
||||
new_account = frappe.get_cached_doc("Account", new)
|
||||
@@ -614,7 +616,7 @@ def merge_account(old, new):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_root_company(company):
|
||||
def get_root_company(company: str):
|
||||
# return the topmost company in the hierarchy
|
||||
ancestors = get_ancestors_of("Company", company, "lft asc")
|
||||
return [ancestors[0]] if ancestors else []
|
||||
|
||||
@@ -99,7 +99,7 @@ def identify_is_group(child):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_chart(chart_template, existing_company=None):
|
||||
def get_chart(chart_template: str | None, existing_company: str | None = None):
|
||||
chart = {}
|
||||
if existing_company:
|
||||
return get_account_tree_from_existing_company(existing_company)
|
||||
@@ -132,7 +132,7 @@ def get_chart(chart_template, existing_company=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_charts_for_country(country, with_standard=False):
|
||||
def get_charts_for_country(country: str, with_standard: bool = False):
|
||||
charts = []
|
||||
|
||||
def _get_chart_name(content):
|
||||
@@ -225,7 +225,7 @@ def build_account_tree(tree, parent, all_accounts):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_bank_account(coa, bank_account):
|
||||
def validate_bank_account(coa: str, bank_account: str):
|
||||
accounts = []
|
||||
chart = get_chart(coa)
|
||||
|
||||
@@ -244,7 +244,9 @@ def validate_bank_account(coa, bank_account):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
|
||||
def build_tree_from_json(
|
||||
chart_template: str, chart_data: dict | None = None, from_coa_importer: bool = False
|
||||
):
|
||||
"""get chart template from its folder and parse the json to be rendered as tree"""
|
||||
chart = chart_data or get_chart(chart_template)
|
||||
|
||||
|
||||
@@ -33,6 +33,17 @@
|
||||
},
|
||||
"account_number": "1151.000"
|
||||
},
|
||||
"Pajak Dibayar di Muka": {
|
||||
"PPN Masukan": {
|
||||
"account_number": "1152.001",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"PPh 23 Dibayar di Muka": {
|
||||
"account_number": "1152.002",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"account_number": "1152.000"
|
||||
},
|
||||
"account_number": "1150.000"
|
||||
},
|
||||
"Kas": {
|
||||
@@ -97,17 +108,6 @@
|
||||
},
|
||||
"account_number": "1130.000"
|
||||
},
|
||||
"Pajak Dibayar di Muka": {
|
||||
"PPN Masukan": {
|
||||
"account_number": "1151.001",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"PPh 23 Dibayar di Muka": {
|
||||
"account_number": "1152.001",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"account_number": "1150.000"
|
||||
},
|
||||
"account_number": "1100.000"
|
||||
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -103,10 +103,6 @@ class AccountingDimension(Document):
|
||||
if not self.fieldname:
|
||||
self.fieldname = scrub(self.label)
|
||||
|
||||
def on_update(self):
|
||||
frappe.flags.accounting_dimensions = None
|
||||
frappe.flags.accounting_dimensions_details = None
|
||||
|
||||
|
||||
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||
if not doclist:
|
||||
@@ -210,7 +206,7 @@ def delete_accounting_dimension(doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def disable_dimension(doc):
|
||||
def disable_dimension(doc: str):
|
||||
if frappe.in_test:
|
||||
toggle_disabling(doc=doc)
|
||||
else:
|
||||
@@ -241,34 +237,26 @@ def get_doctypes_with_dimensions():
|
||||
return frappe.get_hooks("accounting_dimension_doctypes")
|
||||
|
||||
|
||||
def get_accounting_dimensions(as_list=True, filters=None):
|
||||
if not filters:
|
||||
filters = {"disabled": 0}
|
||||
|
||||
if frappe.flags.accounting_dimensions is None:
|
||||
frappe.flags.accounting_dimensions = frappe.get_all(
|
||||
"Accounting Dimension",
|
||||
fields=["label", "fieldname", "disabled", "document_type"],
|
||||
filters=filters,
|
||||
)
|
||||
def get_accounting_dimensions(as_list=True):
|
||||
accounting_dimensions = frappe.get_all(
|
||||
"Accounting Dimension",
|
||||
fields=["label", "fieldname", "disabled", "document_type"],
|
||||
filters={"disabled": 0},
|
||||
)
|
||||
|
||||
if as_list:
|
||||
return [d.fieldname for d in frappe.flags.accounting_dimensions]
|
||||
return [d.fieldname for d in accounting_dimensions]
|
||||
else:
|
||||
return frappe.flags.accounting_dimensions
|
||||
return accounting_dimensions
|
||||
|
||||
|
||||
def get_checks_for_pl_and_bs_accounts():
|
||||
if frappe.flags.accounting_dimensions_details is None:
|
||||
# nosemgrep
|
||||
frappe.flags.accounting_dimensions_details = frappe.db.sql(
|
||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||
return frappe.db.sql(
|
||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
|
||||
WHERE p.name = c.parent AND p.disabled = 0""",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
return frappe.flags.accounting_dimensions_details
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
|
||||
def get_dimension_with_children(doctype, dimensions):
|
||||
@@ -286,7 +274,7 @@ def get_dimension_with_children(doctype, dimensions):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_dimensions(with_cost_center_and_project=False):
|
||||
def get_dimensions(with_cost_center_and_project: str | bool = False):
|
||||
c = frappe.qb.DocType("Accounting Dimension Detail")
|
||||
p = frappe.qb.DocType("Accounting Dimension")
|
||||
dimension_filters = (
|
||||
|
||||
@@ -69,37 +69,34 @@ class AccountingDimensionFilter(Document):
|
||||
|
||||
|
||||
def get_dimension_filter_map():
|
||||
if not frappe.flags.get("dimension_filter_map"):
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, p.fieldname, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
""",
|
||||
as_dict=1,
|
||||
filters = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, p.fieldname, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
|
||||
WHERE
|
||||
p.name = a.parent
|
||||
AND p.disabled = 0
|
||||
""",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
f.applicable_on_account,
|
||||
f.dimension_value,
|
||||
f.allow_or_restrict,
|
||||
f.is_mandatory,
|
||||
)
|
||||
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
f.applicable_on_account,
|
||||
f.dimension_value,
|
||||
f.allow_or_restrict,
|
||||
f.is_mandatory,
|
||||
)
|
||||
frappe.flags.dimension_filter_map = dimension_filter_map
|
||||
|
||||
return frappe.flags.dimension_filter_map
|
||||
return dimension_filter_map
|
||||
|
||||
|
||||
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
||||
|
||||
@@ -97,7 +97,7 @@ def validate_accounting_period_on_doc_save(doc, method=None):
|
||||
if doc.doctype == "Bank Clearance":
|
||||
return
|
||||
elif doc.doctype == "Asset":
|
||||
if doc.is_existing_asset:
|
||||
if doc.asset_type == "Existing Asset":
|
||||
return
|
||||
else:
|
||||
date = doc.available_for_use_date
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
"enable_common_party_accounting",
|
||||
"allow_multi_currency_invoices_against_single_party_account",
|
||||
"confirm_before_resetting_posting_date",
|
||||
"analytics_section",
|
||||
"enable_accounting_dimensions",
|
||||
"column_break_vtnr",
|
||||
"enable_discounts_and_margin",
|
||||
"journals_section",
|
||||
"merge_similar_account_heads",
|
||||
"deferred_accounting_settings_section",
|
||||
@@ -51,12 +55,16 @@
|
||||
"allow_pegged_currencies_exchange_rates",
|
||||
"column_break_yuug",
|
||||
"stale_days",
|
||||
"payments_tab",
|
||||
"section_break_jpd0",
|
||||
"auto_reconcile_payments",
|
||||
"auto_reconciliation_job_trigger",
|
||||
"reconciliation_queue_size",
|
||||
"column_break_resa",
|
||||
"exchange_gain_loss_posting_date",
|
||||
"payment_options_section",
|
||||
"enable_loyalty_point_program",
|
||||
"column_break_ctam",
|
||||
"invoicing_settings_tab",
|
||||
"accounts_transactions_settings_section",
|
||||
"over_billing_allowance",
|
||||
@@ -197,7 +205,7 @@
|
||||
"description": "Payment Terms from orders will be fetched into the invoices as is",
|
||||
"fieldname": "automatically_fetch_payment_terms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Automatically Fetch Payment Terms from Order"
|
||||
"label": "Automatically Fetch Payment Terms from Order/Quotation"
|
||||
},
|
||||
{
|
||||
"description": "The percentage you are allowed to bill more against the amount ordered. For example, if the order value is $100 for an item and tolerance is set as 10%, then you are allowed to bill up to $110 ",
|
||||
@@ -281,7 +289,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\">Common Party</a>",
|
||||
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\" rel=\"noopener noreferrer\">Common Party</a>",
|
||||
"fieldname": "enable_common_party_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Common Party Accounting"
|
||||
@@ -637,16 +645,59 @@
|
||||
"fieldname": "budget_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Budget"
|
||||
},
|
||||
{
|
||||
"fieldname": "analytics_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Analytical Accounting"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vtnr",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Apply discounts and margins on products",
|
||||
"fieldname": "enable_discounts_and_margin",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Discounts and Margin"
|
||||
},
|
||||
{
|
||||
"fieldname": "payments_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payments"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_options_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Payment Options"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "enable_loyalty_point_program",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Loyalty Point Program"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ctam",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enable cost center, projects and other custom accounting dimensions",
|
||||
"fieldname": "enable_accounting_dimensions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Accounting Dimensions"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 1,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-11 18:30:45.968531",
|
||||
"modified": "2026-02-27 01:04:09.415288",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -12,6 +12,28 @@ from frappe.utils import cint
|
||||
|
||||
from erpnext.accounts.utils import sync_auto_reconcile_config
|
||||
|
||||
SELLING_DOCTYPES = [
|
||||
"Sales Invoice",
|
||||
"Sales Order",
|
||||
"Delivery Note",
|
||||
"Quotation",
|
||||
"Sales Invoice Item",
|
||||
"Sales Order Item",
|
||||
"Delivery Note Item",
|
||||
"Quotation Item",
|
||||
"POS Invoice",
|
||||
"POS Invoice Item",
|
||||
]
|
||||
|
||||
BUYING_DOCTYPES = [
|
||||
"Purchase Invoice",
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice Item",
|
||||
"Purchase Order Item",
|
||||
"Purchase Receipt Item",
|
||||
]
|
||||
|
||||
|
||||
class AccountsSettings(Document):
|
||||
# begin: auto-generated types
|
||||
@@ -43,9 +65,12 @@ class AccountsSettings(Document):
|
||||
default_ageing_range: DF.Data | None
|
||||
delete_linked_ledger_entries: DF.Check
|
||||
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
|
||||
enable_accounting_dimensions: DF.Check
|
||||
enable_common_party_accounting: DF.Check
|
||||
enable_discounts_and_margin: DF.Check
|
||||
enable_fuzzy_matching: DF.Check
|
||||
enable_immutable_ledger: DF.Check
|
||||
enable_loyalty_point_program: DF.Check
|
||||
enable_party_matching: DF.Check
|
||||
exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
|
||||
fetch_valuation_rate_for_internal_transaction: DF.Check
|
||||
@@ -98,6 +123,18 @@ class AccountsSettings(Document):
|
||||
if old_doc.show_payment_schedule_in_print != self.show_payment_schedule_in_print:
|
||||
self.enable_payment_schedule_in_print()
|
||||
|
||||
if old_doc.enable_accounting_dimensions != self.enable_accounting_dimensions:
|
||||
toggle_accounting_dimension_sections(not self.enable_accounting_dimensions)
|
||||
clear_cache = True
|
||||
|
||||
if old_doc.enable_discounts_and_margin != self.enable_discounts_and_margin:
|
||||
toggle_sales_discount_section(not self.enable_discounts_and_margin)
|
||||
clear_cache = True
|
||||
|
||||
if old_doc.enable_loyalty_point_program != self.enable_loyalty_point_program:
|
||||
toggle_loyalty_point_program_section(not self.enable_loyalty_point_program)
|
||||
clear_cache = True
|
||||
|
||||
if clear_cache:
|
||||
frappe.clear_cache()
|
||||
|
||||
@@ -154,3 +191,36 @@ class AccountsSettings(Document):
|
||||
|
||||
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.init_procedure_name}")
|
||||
frappe.db.sql(f"drop procedure if exists {InitSQLProceduresForAR.allocate_procedure_name}")
|
||||
|
||||
|
||||
def toggle_accounting_dimension_sections(hide):
|
||||
accounting_dimension_doctypes = frappe.get_hooks("accounting_dimension_doctypes")
|
||||
for doctype in accounting_dimension_doctypes:
|
||||
create_property_setter_for_hiding_field(doctype, "accounting_dimensions_section", hide)
|
||||
|
||||
|
||||
def toggle_sales_discount_section(hide):
|
||||
for doctype in SELLING_DOCTYPES + BUYING_DOCTYPES:
|
||||
meta = frappe.get_meta(doctype)
|
||||
if meta.has_field("additional_discount_section"):
|
||||
create_property_setter_for_hiding_field(doctype, "additional_discount_section", hide)
|
||||
if meta.has_field("discount_and_margin"):
|
||||
create_property_setter_for_hiding_field(doctype, "discount_and_margin", hide)
|
||||
|
||||
|
||||
def toggle_loyalty_point_program_section(hide):
|
||||
for doctype in SELLING_DOCTYPES:
|
||||
meta = frappe.get_meta(doctype)
|
||||
if meta.has_field("loyalty_points_redemption"):
|
||||
create_property_setter_for_hiding_field(doctype, "loyalty_points_redemption", hide)
|
||||
|
||||
|
||||
def create_property_setter_for_hiding_field(doctype, field_name, hide):
|
||||
make_property_setter(
|
||||
doctype,
|
||||
field_name,
|
||||
"hidden",
|
||||
hide,
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
frappe.provide("erpnext.integrations");
|
||||
|
||||
frappe.ui.form.on("Bank", {
|
||||
onload: function (frm) {
|
||||
add_fields_to_mapping_table(frm);
|
||||
},
|
||||
refresh: function (frm) {
|
||||
add_fields_to_mapping_table(frm);
|
||||
frm.toggle_display(["address_html", "contact_html"], !frm.doc.__islocal);
|
||||
@@ -37,11 +34,11 @@ let add_fields_to_mapping_table = function (frm) {
|
||||
});
|
||||
});
|
||||
|
||||
frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
|
||||
"bank_transaction_field",
|
||||
"options",
|
||||
options
|
||||
);
|
||||
const grid = frm.fields_dict.bank_transaction_mapping?.grid;
|
||||
|
||||
if (grid) {
|
||||
grid.update_docfield_property("bank_transaction_field", "options", options);
|
||||
}
|
||||
};
|
||||
|
||||
erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||
@@ -116,7 +113,7 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||
"There was an issue connecting to Plaid's authentication server. Check browser console for more information"
|
||||
)
|
||||
);
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
plaid_success(token, response) {
|
||||
|
||||
@@ -115,7 +115,7 @@ def get_default_company_bank_account(company, party_type, party):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_account_details(bank_account):
|
||||
def get_bank_account_details(bank_account: str):
|
||||
return frappe.get_cached_value(
|
||||
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -47,7 +47,9 @@ class BankReconciliationTool(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_transactions(bank_account, from_date=None, to_date=None):
|
||||
def get_bank_transactions(
|
||||
bank_account: str, from_date: str | date | None = None, to_date: str | date | None = None
|
||||
):
|
||||
# returns bank transactions for a bank account
|
||||
filters = []
|
||||
filters.append(["bank_account", "=", bank_account])
|
||||
@@ -80,7 +82,7 @@ def get_bank_transactions(bank_account, from_date=None, to_date=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_balance(bank_account, till_date, company):
|
||||
def get_account_balance(bank_account: str, till_date: str | date, company: str):
|
||||
# returns account balance till the specified date
|
||||
account = frappe.db.get_value("Bank Account", bank_account, "account")
|
||||
filters = frappe._dict(
|
||||
@@ -106,7 +108,9 @@ def get_account_balance(bank_account, till_date, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_bank_transaction(bank_transaction_name, reference_number, party_type=None, party=None):
|
||||
def update_bank_transaction(
|
||||
bank_transaction_name: str, reference_number: str, party_type: str | None = None, party: str | None = None
|
||||
):
|
||||
# updates bank transaction based on the new parameters provided by the user from Vouchers
|
||||
bank_transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
bank_transaction.reference_number = reference_number
|
||||
@@ -135,16 +139,16 @@ def update_bank_transaction(bank_transaction_name, reference_number, party_type=
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_journal_entry_bts(
|
||||
bank_transaction_name,
|
||||
reference_number=None,
|
||||
reference_date=None,
|
||||
posting_date=None,
|
||||
entry_type=None,
|
||||
second_account=None,
|
||||
mode_of_payment=None,
|
||||
party_type=None,
|
||||
party=None,
|
||||
allow_edit=None,
|
||||
bank_transaction_name: str,
|
||||
reference_number: str | None = None,
|
||||
reference_date: str | None = None,
|
||||
posting_date: str | date | None = None,
|
||||
entry_type: str | None = None,
|
||||
second_account: str | None = None,
|
||||
mode_of_payment: str | None = None,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
allow_edit: bool | None = None,
|
||||
):
|
||||
# Create a new journal entry based on the bank transaction
|
||||
bank_transaction = frappe.db.get_values(
|
||||
@@ -294,17 +298,17 @@ def create_journal_entry_bts(
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_payment_entry_bts(
|
||||
bank_transaction_name,
|
||||
reference_number=None,
|
||||
reference_date=None,
|
||||
party_type=None,
|
||||
party=None,
|
||||
posting_date=None,
|
||||
mode_of_payment=None,
|
||||
project=None,
|
||||
cost_center=None,
|
||||
allow_edit=None,
|
||||
company_bank_account=None,
|
||||
bank_transaction_name: str,
|
||||
reference_number: str | None = None,
|
||||
reference_date: str | None = None,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
posting_date: str | None = None,
|
||||
mode_of_payment: str | None = None,
|
||||
project: str | None = None,
|
||||
cost_center: str | None = None,
|
||||
allow_edit: bool | None = None,
|
||||
company_bank_account: str | None = None,
|
||||
):
|
||||
# Create a new payment entry based on the bank transaction
|
||||
bank_transaction = frappe.db.get_values(
|
||||
@@ -371,12 +375,12 @@ def create_payment_entry_bts(
|
||||
|
||||
@frappe.whitelist()
|
||||
def auto_reconcile_vouchers(
|
||||
bank_account,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
bank_account: str,
|
||||
from_date: str | date | None = None,
|
||||
to_date: str | date | None = None,
|
||||
filter_by_reference_date: bool | None = None,
|
||||
from_reference_date: bool | None = None,
|
||||
to_reference_date: str | None = None,
|
||||
):
|
||||
bank_transactions = get_bank_transactions(bank_account)
|
||||
|
||||
@@ -471,7 +475,7 @@ def get_auto_reconcile_message(partially_reconciled, reconciled):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def reconcile_vouchers(bank_transaction_name, vouchers):
|
||||
def reconcile_vouchers(bank_transaction_name: str, vouchers: str):
|
||||
# updated clear date of all the vouchers based on the bank transaction
|
||||
vouchers = json.loads(vouchers)
|
||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
@@ -487,13 +491,13 @@ def reconcile_vouchers(bank_transaction_name, vouchers):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_linked_payments(
|
||||
bank_transaction_name,
|
||||
document_types=None,
|
||||
from_date=None,
|
||||
to_date=None,
|
||||
filter_by_reference_date=None,
|
||||
from_reference_date=None,
|
||||
to_reference_date=None,
|
||||
bank_transaction_name: str,
|
||||
document_types: str | list[str] | None = None,
|
||||
from_date: str | date | None = None,
|
||||
to_date: str | date | None = None,
|
||||
filter_by_reference_date: bool | None = None,
|
||||
from_reference_date: bool | None = None,
|
||||
to_reference_date: str | None = None,
|
||||
):
|
||||
# get all matching payments for a bank transaction
|
||||
transaction = frappe.get_doc("Bank Transaction", bank_transaction_name)
|
||||
|
||||
@@ -143,7 +143,7 @@ def preprocess_mt940_content(content: str) -> str:
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def convert_mt940_to_csv(data_import, mt940_file_path):
|
||||
def convert_mt940_to_csv(data_import: str, mt940_file_path: str):
|
||||
doc = frappe.get_doc("Bank Statement Import", data_import)
|
||||
|
||||
_file_doc, content = get_file(mt940_file_path)
|
||||
@@ -208,26 +208,28 @@ def convert_mt940_to_csv(data_import, mt940_file_path):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_preview_from_template(data_import, import_file=None, google_sheets_url=None):
|
||||
def get_preview_from_template(
|
||||
data_import: str, import_file: str | None = None, google_sheets_url: str | None = None
|
||||
):
|
||||
return frappe.get_doc("Bank Statement Import", data_import).get_preview_from_template(
|
||||
import_file, google_sheets_url
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def form_start_import(data_import):
|
||||
def form_start_import(data_import: str):
|
||||
job_id = frappe.get_doc("Bank Statement Import", data_import).start_import()
|
||||
return job_id is not None
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_errored_template(data_import_name):
|
||||
def download_errored_template(data_import_name: str):
|
||||
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
|
||||
data_import.export_errored_rows()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_import_log(data_import_name):
|
||||
def download_import_log(data_import_name: str):
|
||||
return frappe.get_doc("Bank Statement Import", data_import_name).download_import_log()
|
||||
|
||||
|
||||
@@ -363,7 +365,7 @@ def write_xlsx(data, sheet_name, wb=None, column_widths=None, file_path=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_import_status(docname):
|
||||
def get_import_status(docname: str):
|
||||
import_status = {}
|
||||
|
||||
data_import = frappe.get_doc("Bank Statement Import", docname)
|
||||
|
||||
@@ -139,6 +139,8 @@ class BankTransaction(Document):
|
||||
self.set_status()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ["GL Entry"]
|
||||
|
||||
for payment_entry in self.payment_entries:
|
||||
self.delink_payment_entry(payment_entry)
|
||||
|
||||
@@ -373,11 +375,12 @@ def get_clearance_details(transaction, payment_entry, bt_allocations, gl_entries
|
||||
("unallocated_amount", "bank_account"),
|
||||
as_dict=True,
|
||||
)
|
||||
bt_bank_account = frappe.db.get_value("Bank Account", bt.bank_account, "account")
|
||||
|
||||
if bt.bank_account != gl_bank_account:
|
||||
if bt_bank_account != gl_bank_account:
|
||||
frappe.throw(
|
||||
_("Bank Account {} in Bank Transaction {} is not matching with Bank Account {}").format(
|
||||
bt.bank_account, payment_entry.payment_entry, gl_bank_account
|
||||
bt_bank_account, payment_entry.payment_entry, gl_bank_account
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ def upload_bank_statement():
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_bank_entries(columns, data, bank_account):
|
||||
def create_bank_entries(columns: str, data: str, bank_account: str):
|
||||
header_map = get_header_mapping(columns, bank_account)
|
||||
|
||||
success = 0
|
||||
|
||||
@@ -845,7 +845,7 @@ def get_fiscal_year_date_range(from_fiscal_year, to_fiscal_year):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def revise_budget(budget_name):
|
||||
def revise_budget(budget_name: str):
|
||||
old_budget = frappe.get_doc("Budget", budget_name)
|
||||
|
||||
if old_budget.docstatus == 1:
|
||||
|
||||
@@ -57,7 +57,7 @@ def validate_columns(data):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_company(company):
|
||||
def validate_company(company: str):
|
||||
parent_company, allow_account_creation_against_child_company = frappe.get_cached_value(
|
||||
"Company", company, ["parent_company", "allow_account_creation_against_child_company"]
|
||||
)
|
||||
@@ -74,7 +74,7 @@ def validate_company(company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def import_coa(file_name, company):
|
||||
def import_coa(file_name: str, company: str):
|
||||
# delete existing data for accounts
|
||||
unset_existing_data(company)
|
||||
|
||||
@@ -159,7 +159,9 @@ def generate_data_from_excel(file_doc, extension, as_dict=False):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_coa(doctype, parent, is_root=False, file_name=None, for_validate=0):
|
||||
def get_coa(
|
||||
doctype: str, parent: str, is_root: bool = False, file_name: str | None = None, for_validate: int = 0
|
||||
):
|
||||
"""called by tree view (to fetch node's children)"""
|
||||
|
||||
file_doc, extension = get_file(file_name)
|
||||
@@ -307,7 +309,7 @@ def build_response_as_excel(writer):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_template(file_type, template_type, company):
|
||||
def download_template(file_type: str, template_type: str, company: str):
|
||||
writer = get_template(template_type, company)
|
||||
|
||||
if file_type == "CSV":
|
||||
@@ -361,7 +363,7 @@ def get_sample_template(writer, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_accounts(file_doc, extension):
|
||||
def validate_accounts(file_doc: Document, extension: str):
|
||||
if extension == "csv":
|
||||
accounts = generate_data_from_csv(file_doc, as_dict=True)
|
||||
else:
|
||||
|
||||
@@ -47,7 +47,7 @@ class ChequePrintTemplate(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_or_update_cheque_print_format(template_name):
|
||||
def create_or_update_cheque_print_format(template_name: str):
|
||||
if not frappe.db.exists("Print Format", template_name):
|
||||
cheque_print = frappe.new_doc("Print Format")
|
||||
cheque_print.update(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
@@ -614,7 +615,12 @@ def calculate_exchange_rate_using_last_gle(company, account, party_type, party):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details(
|
||||
company, posting_date, account, party_type=None, party=None, rounding_loss_allowance: float | None = None
|
||||
company: str,
|
||||
posting_date: str | date,
|
||||
account: str,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
rounding_loss_allowance: float = 0.0,
|
||||
):
|
||||
if not (company and posting_date):
|
||||
frappe.throw(_("Company and Posting Date is mandatory"))
|
||||
|
||||
@@ -15,7 +15,7 @@ from frappe.database.operator_map import OPERATOR_MAP
|
||||
from frappe.query_builder import Case
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cstr, date_diff, flt, getdate
|
||||
from pypika.terms import LiteralValue
|
||||
from pypika.terms import Bracket, LiteralValue
|
||||
|
||||
from erpnext import get_company_currency
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
@@ -541,7 +541,7 @@ class FinancialQueryBuilder:
|
||||
.where(acb_table.period_closing_voucher == closing_voucher)
|
||||
)
|
||||
|
||||
query = self._apply_standard_filters(query, acb_table)
|
||||
query = self._apply_standard_filters(query, acb_table, "Account Closing Balance")
|
||||
results = self._execute_with_permissions(query, "Account Closing Balance")
|
||||
|
||||
for row in results:
|
||||
@@ -636,12 +636,15 @@ class FinancialQueryBuilder:
|
||||
return self._execute_with_permissions(query, "GL Entry")
|
||||
|
||||
def _calculate_running_balances(self, balances_data: dict, gl_data: list[dict]) -> dict:
|
||||
for row in gl_data:
|
||||
account = row["account"]
|
||||
gl_dict = {row["account"]: row for row in gl_data}
|
||||
accounts = set(balances_data.keys()) | set(gl_dict.keys())
|
||||
|
||||
for account in accounts:
|
||||
if account not in balances_data:
|
||||
balances_data[account] = AccountData(account=account, **self._get_account_meta(account))
|
||||
|
||||
account_data: AccountData = balances_data[account]
|
||||
gl_movement = gl_dict.get(account, {})
|
||||
|
||||
if account_data.has_periods():
|
||||
first_period = account_data.get_period(self.periods[0]["key"])
|
||||
@@ -651,20 +654,13 @@ class FinancialQueryBuilder:
|
||||
|
||||
for period in self.periods:
|
||||
period_key = period["key"]
|
||||
movement = row.get(period_key, 0.0)
|
||||
movement = gl_movement.get(period_key, 0.0)
|
||||
closing_balance = current_balance + movement
|
||||
|
||||
account_data.add_period(PeriodValue(period_key, current_balance, closing_balance, movement))
|
||||
|
||||
current_balance = closing_balance
|
||||
|
||||
# Accounts with no movements
|
||||
for account_data in balances_data.values():
|
||||
for period in self.periods:
|
||||
period_key = period["key"]
|
||||
if period_key not in account_data.period_values:
|
||||
account_data.add_period(PeriodValue(period_key, 0.0, 0.0, 0.0))
|
||||
|
||||
def _handle_balance_accumulation(self, balances_data):
|
||||
for account_data in balances_data.values():
|
||||
account_data: AccountData
|
||||
@@ -683,12 +679,12 @@ class FinancialQueryBuilder:
|
||||
else:
|
||||
account_data.unaccumulate_values()
|
||||
|
||||
def _apply_standard_filters(self, query, table):
|
||||
def _apply_standard_filters(self, query, table, doctype: str = "GL Entry"):
|
||||
if self.filters.get("ignore_closing_entries"):
|
||||
if hasattr(table, "is_period_closing_voucher_entry"):
|
||||
query = query.where(table.is_period_closing_voucher_entry == 0)
|
||||
else:
|
||||
if doctype == "GL Entry":
|
||||
query = query.where(table.voucher_type != "Period Closing Voucher")
|
||||
else:
|
||||
query = query.where(table.is_period_closing_voucher_entry == 0)
|
||||
|
||||
if self.filters.get("project"):
|
||||
projects = self.filters.get("project")
|
||||
@@ -736,7 +732,7 @@ class FinancialQueryBuilder:
|
||||
user_conditions = build_match_conditions(doctype)
|
||||
|
||||
if user_conditions:
|
||||
query = query.where(LiteralValue(user_conditions))
|
||||
query = query.where(Bracket(LiteralValue(user_conditions)))
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
|
||||
@@ -7,12 +7,14 @@ from frappe.utils import flt
|
||||
from erpnext.accounts.doctype.financial_report_template.financial_report_engine import (
|
||||
DependencyResolver,
|
||||
FilterExpressionParser,
|
||||
FinancialQueryBuilder,
|
||||
FormulaCalculator,
|
||||
)
|
||||
from erpnext.accounts.doctype.financial_report_template.test_financial_report_template import (
|
||||
FinancialReportTemplateTestCase,
|
||||
)
|
||||
from erpnext.accounts.utils import get_currency_precision
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
from erpnext.accounts.utils import get_currency_precision, get_fiscal_year
|
||||
|
||||
# On IntegrationTestCase, the doctype test records and all
|
||||
# link-field test record dependencies are recursively loaded
|
||||
@@ -1668,3 +1670,360 @@ class TestFilterExpressionParser(FinancialReportTemplateTestCase):
|
||||
mock_row_invalid = self._create_mock_report_row(invalid_formula)
|
||||
condition = parser.build_condition(mock_row_invalid, account_table)
|
||||
self.assertIsNone(condition)
|
||||
|
||||
|
||||
class TestFinancialQueryBuilder(FinancialReportTemplateTestCase):
|
||||
def test_fetch_balances_with_journal_entries(self):
|
||||
company = "_Test Company"
|
||||
cash_account = "_Test Cash - _TC"
|
||||
bank_account = "_Test Bank - _TC"
|
||||
|
||||
# Create journal entries in different periods
|
||||
# October: Transfer 1000 from Bank to Cash
|
||||
jv_oct = make_journal_entry(
|
||||
account1=cash_account,
|
||||
account2=bank_account,
|
||||
amount=1000,
|
||||
posting_date="2024-10-15",
|
||||
company=company,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
# November: Transfer 500 from Bank to Cash
|
||||
jv_nov = make_journal_entry(
|
||||
account1=cash_account,
|
||||
account2=bank_account,
|
||||
amount=500,
|
||||
posting_date="2024-11-20",
|
||||
company=company,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
# December: No transactions (test zero movement period)
|
||||
|
||||
try:
|
||||
# Set up filters and periods for Q4 2024
|
||||
filters = {
|
||||
"company": company,
|
||||
"from_fiscal_year": "2024",
|
||||
"to_fiscal_year": "2024",
|
||||
"period_start_date": "2024-10-01",
|
||||
"period_end_date": "2024-12-31",
|
||||
"filter_based_on": "Date Range",
|
||||
"periodicity": "Monthly",
|
||||
}
|
||||
|
||||
periods = [
|
||||
{"key": "2024_oct", "from_date": "2024-10-01", "to_date": "2024-10-31"},
|
||||
{"key": "2024_nov", "from_date": "2024-11-01", "to_date": "2024-11-30"},
|
||||
{"key": "2024_dec", "from_date": "2024-12-01", "to_date": "2024-12-31"},
|
||||
]
|
||||
|
||||
query_builder = FinancialQueryBuilder(filters, periods)
|
||||
|
||||
# Create account objects as expected by fetch_account_balances
|
||||
accounts = [
|
||||
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
|
||||
frappe._dict({"name": bank_account, "account_name": "Bank", "account_number": "1002"}),
|
||||
]
|
||||
|
||||
# Fetch balances using the full workflow
|
||||
balances_data = query_builder.fetch_account_balances(accounts)
|
||||
|
||||
# Verify Cash account balances
|
||||
cash_data = balances_data.get(cash_account)
|
||||
self.assertIsNotNone(cash_data, "Cash account should exist in results")
|
||||
|
||||
# October: movement = +1000 (debit)
|
||||
oct_cash = cash_data.get_period("2024_oct")
|
||||
self.assertIsNotNone(oct_cash, "October period should exist for cash")
|
||||
self.assertEqual(oct_cash.movement, 1000.0, "October cash movement should be 1000")
|
||||
|
||||
# November: movement = +500
|
||||
nov_cash = cash_data.get_period("2024_nov")
|
||||
self.assertIsNotNone(nov_cash, "November period should exist for cash")
|
||||
self.assertEqual(nov_cash.movement, 500.0, "November cash movement should be 500")
|
||||
self.assertEqual(
|
||||
nov_cash.opening, oct_cash.closing, "November opening should equal October closing"
|
||||
)
|
||||
|
||||
# December: movement = 0 (no transactions)
|
||||
dec_cash = cash_data.get_period("2024_dec")
|
||||
self.assertIsNotNone(dec_cash, "December period should exist for cash")
|
||||
self.assertEqual(dec_cash.movement, 0.0, "December cash movement should be 0")
|
||||
self.assertEqual(
|
||||
dec_cash.closing,
|
||||
nov_cash.closing,
|
||||
"December closing should equal November closing when no movement",
|
||||
)
|
||||
|
||||
# Verify Bank account balances (opposite direction)
|
||||
bank_data = balances_data.get(bank_account)
|
||||
self.assertIsNotNone(bank_data, "Bank account should exist in results")
|
||||
|
||||
oct_bank = bank_data.get_period("2024_oct")
|
||||
self.assertEqual(oct_bank.movement, -1000.0, "October bank movement should be -1000")
|
||||
|
||||
nov_bank = bank_data.get_period("2024_nov")
|
||||
self.assertEqual(nov_bank.movement, -500.0, "November bank movement should be -500")
|
||||
|
||||
finally:
|
||||
# Clean up: cancel journal entries
|
||||
jv_nov.cancel()
|
||||
jv_oct.cancel()
|
||||
|
||||
def test_opening_balance_from_previous_period_closing(self):
|
||||
company = "_Test Company"
|
||||
cash_account = "_Test Cash - _TC"
|
||||
sales_account = "Sales - _TC"
|
||||
posting_date_2023 = "2023-06-15"
|
||||
|
||||
# Create journal entry in prior period (2023)
|
||||
# Cash Dr 5000, Sales Cr 5000
|
||||
jv_2023 = make_journal_entry(
|
||||
account1=cash_account,
|
||||
account2=sales_account,
|
||||
amount=5000,
|
||||
posting_date=posting_date_2023,
|
||||
company=company,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
pcv = None
|
||||
jv_2024 = None
|
||||
original_pcv_setting = frappe.db.get_single_value(
|
||||
"Accounts Settings", "use_legacy_controller_for_pcv"
|
||||
)
|
||||
|
||||
try:
|
||||
# Create Period Closing Voucher for 2023
|
||||
# This will create Account Closing Balance entries
|
||||
closing_account = frappe.db.get_value(
|
||||
"Account",
|
||||
{
|
||||
"company": company,
|
||||
"root_type": "Liability",
|
||||
"is_group": 0,
|
||||
"account_type": ["not in", ["Payable", "Receivable"]],
|
||||
},
|
||||
"name",
|
||||
)
|
||||
|
||||
fy_2023 = get_fiscal_year(posting_date_2023, company=company)
|
||||
|
||||
frappe.db.set_single_value("Accounts Settings", "use_legacy_controller_for_pcv", 1)
|
||||
|
||||
pcv = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Period Closing Voucher",
|
||||
"transaction_date": "2023-12-31",
|
||||
"period_start_date": fy_2023[1],
|
||||
"period_end_date": fy_2023[2],
|
||||
"company": company,
|
||||
"fiscal_year": fy_2023[0],
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"closing_account_head": closing_account,
|
||||
"remarks": "Test Period Closing",
|
||||
}
|
||||
)
|
||||
pcv.insert()
|
||||
pcv.submit()
|
||||
pcv.reload()
|
||||
|
||||
# Now create a small transaction in 2024 to ensure the account appears
|
||||
jv_2024 = make_journal_entry(
|
||||
account1=cash_account,
|
||||
account2=sales_account,
|
||||
amount=100,
|
||||
posting_date="2024-01-15",
|
||||
company=company,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
# Set up filters for Q1 2024 (after the period closing)
|
||||
filters = {
|
||||
"company": company,
|
||||
"from_fiscal_year": "2024",
|
||||
"to_fiscal_year": "2024",
|
||||
"period_start_date": "2024-01-01",
|
||||
"period_end_date": "2024-03-31",
|
||||
"filter_based_on": "Date Range",
|
||||
"periodicity": "Monthly",
|
||||
"ignore_closing_entries": True, # Don't include PCV entries in movements
|
||||
}
|
||||
|
||||
periods = [
|
||||
{"key": "2024_jan", "from_date": "2024-01-01", "to_date": "2024-01-31"},
|
||||
{"key": "2024_feb", "from_date": "2024-02-01", "to_date": "2024-02-29"},
|
||||
{"key": "2024_mar", "from_date": "2024-03-01", "to_date": "2024-03-31"},
|
||||
]
|
||||
|
||||
query_builder = FinancialQueryBuilder(filters, periods)
|
||||
|
||||
accounts = [
|
||||
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
|
||||
]
|
||||
|
||||
balances_data = query_builder.fetch_account_balances(accounts)
|
||||
|
||||
# Verify Cash account has opening balance from 2023 transactions
|
||||
cash_data = balances_data.get(cash_account)
|
||||
self.assertIsNotNone(cash_data, "Cash account should exist in results")
|
||||
|
||||
jan_cash = cash_data.get_period("2024_jan")
|
||||
self.assertIsNotNone(jan_cash, "January period should exist")
|
||||
|
||||
# Opening balance should be from prior period
|
||||
# Cash had 5000 debit in 2023, so opening in 2024 should be >= 5000
|
||||
# (may be higher if there were other test transactions)
|
||||
self.assertEqual(
|
||||
jan_cash.opening,
|
||||
5000.0,
|
||||
"January opening should equal to balance from 2023 (5000)",
|
||||
)
|
||||
|
||||
# Verify running balance logic
|
||||
# Movement in January is 100 (from jv_2024)
|
||||
self.assertEqual(jan_cash.movement, 100.0, "January movement should be 100")
|
||||
self.assertEqual(
|
||||
jan_cash.closing, jan_cash.opening + jan_cash.movement, "Closing = Opening + Movement"
|
||||
)
|
||||
|
||||
# February and March should have no movement but carry the balance
|
||||
feb_cash = cash_data.get_period("2024_feb")
|
||||
self.assertEqual(feb_cash.opening, jan_cash.closing, "Feb opening = Jan closing")
|
||||
self.assertEqual(feb_cash.movement, 0.0, "February should have no movement")
|
||||
self.assertEqual(feb_cash.closing, feb_cash.opening, "Feb closing = opening when no movement")
|
||||
|
||||
mar_cash = cash_data.get_period("2024_mar")
|
||||
self.assertEqual(mar_cash.opening, feb_cash.closing, "Mar opening = Feb closing")
|
||||
self.assertEqual(mar_cash.movement, 0.0, "March should have no movement")
|
||||
self.assertEqual(mar_cash.closing, mar_cash.opening, "Mar closing = opening when no movement")
|
||||
|
||||
# Set up filters for Q2 2024
|
||||
filters_q2 = {
|
||||
"company": company,
|
||||
"from_fiscal_year": "2024",
|
||||
"to_fiscal_year": "2024",
|
||||
"period_start_date": "2024-04-01",
|
||||
"period_end_date": "2024-06-30",
|
||||
"filter_based_on": "Date Range",
|
||||
"periodicity": "Monthly",
|
||||
"ignore_closing_entries": True,
|
||||
}
|
||||
|
||||
periods_q2 = [
|
||||
{"key": "2024_apr", "from_date": "2024-04-01", "to_date": "2024-04-30"},
|
||||
{"key": "2024_may", "from_date": "2024-05-01", "to_date": "2024-05-31"},
|
||||
{"key": "2024_jun", "from_date": "2024-06-01", "to_date": "2024-06-30"},
|
||||
]
|
||||
|
||||
query_builder_q2 = FinancialQueryBuilder(filters_q2, periods_q2)
|
||||
|
||||
balances_data_q2 = query_builder_q2.fetch_account_balances(accounts)
|
||||
|
||||
# Verify Cash account in Q2
|
||||
cash_data_q2 = balances_data_q2.get(cash_account)
|
||||
self.assertIsNotNone(cash_data_q2, "Cash account should exist in Q2 results")
|
||||
|
||||
apr_cash = cash_data_q2.get_period("2024_apr")
|
||||
self.assertIsNotNone(apr_cash, "April period should exist")
|
||||
|
||||
# Opening balance in April should equal closing in March
|
||||
self.assertEqual(
|
||||
apr_cash.opening,
|
||||
mar_cash.closing,
|
||||
"April opening should equal March closing balance",
|
||||
)
|
||||
|
||||
self.assertEqual(apr_cash.closing, apr_cash.opening, "April closing = opening when no movement")
|
||||
|
||||
finally:
|
||||
# Clean up
|
||||
frappe.db.set_single_value(
|
||||
"Accounts Settings", "use_legacy_controller_for_pcv", original_pcv_setting or 0
|
||||
)
|
||||
|
||||
if jv_2024:
|
||||
jv_2024.cancel()
|
||||
|
||||
if pcv:
|
||||
pcv.reload()
|
||||
if pcv.docstatus == 1:
|
||||
pcv.cancel()
|
||||
|
||||
jv_2023.cancel()
|
||||
|
||||
def test_account_with_gl_entries_but_no_prior_closing_balance(self):
|
||||
company = "_Test Company"
|
||||
cash_account = "_Test Cash - _TC"
|
||||
bank_account = "_Test Bank - _TC"
|
||||
|
||||
# Create journal entries WITHOUT any prior Period Closing Voucher
|
||||
# This ensures the account exists in gl_dict but NOT in balances_data
|
||||
jv = make_journal_entry(
|
||||
account1=cash_account,
|
||||
account2=bank_account,
|
||||
amount=2500,
|
||||
posting_date="2024-07-15",
|
||||
company=company,
|
||||
submit=True,
|
||||
)
|
||||
|
||||
try:
|
||||
# Set up filters - use a period with no prior PCV
|
||||
filters = {
|
||||
"company": company,
|
||||
"from_fiscal_year": "2024",
|
||||
"to_fiscal_year": "2024",
|
||||
"period_start_date": "2024-07-01",
|
||||
"period_end_date": "2024-09-30",
|
||||
"filter_based_on": "Date Range",
|
||||
"periodicity": "Monthly",
|
||||
}
|
||||
|
||||
periods = [
|
||||
{"key": "2024_jul", "from_date": "2024-07-01", "to_date": "2024-07-31"},
|
||||
{"key": "2024_aug", "from_date": "2024-08-01", "to_date": "2024-08-31"},
|
||||
{"key": "2024_sep", "from_date": "2024-09-01", "to_date": "2024-09-30"},
|
||||
]
|
||||
|
||||
query_builder = FinancialQueryBuilder(filters, periods)
|
||||
|
||||
# Use accounts that have GL entries but may not have Account Closing Balance
|
||||
accounts = [
|
||||
frappe._dict({"name": cash_account, "account_name": "Cash", "account_number": "1001"}),
|
||||
frappe._dict({"name": bank_account, "account_name": "Bank", "account_number": "1002"}),
|
||||
]
|
||||
|
||||
balances_data = query_builder.fetch_account_balances(accounts)
|
||||
|
||||
# Verify accounts are present in results even without prior closing balance
|
||||
cash_data = balances_data.get(cash_account)
|
||||
self.assertIsNotNone(cash_data, "Cash account should exist in results")
|
||||
|
||||
bank_data = balances_data.get(bank_account)
|
||||
self.assertIsNotNone(bank_data, "Bank account should exist in results")
|
||||
|
||||
# Verify July has the movement from journal entry
|
||||
jul_cash = cash_data.get_period("2024_jul")
|
||||
self.assertIsNotNone(jul_cash, "July period should exist for cash")
|
||||
self.assertEqual(jul_cash.movement, 2500.0, "July cash movement should be 2500")
|
||||
|
||||
jul_bank = bank_data.get_period("2024_jul")
|
||||
self.assertIsNotNone(jul_bank, "July period should exist for bank")
|
||||
self.assertEqual(jul_bank.movement, -2500.0, "July bank movement should be -2500")
|
||||
|
||||
# Verify subsequent periods exist with zero movement
|
||||
aug_cash = cash_data.get_period("2024_aug")
|
||||
self.assertIsNotNone(aug_cash, "August period should exist for cash")
|
||||
self.assertEqual(aug_cash.movement, 0.0, "August cash movement should be 0")
|
||||
self.assertEqual(aug_cash.opening, jul_cash.closing, "August opening = July closing")
|
||||
|
||||
sep_cash = cash_data.get_period("2024_sep")
|
||||
self.assertIsNotNone(sep_cash, "September period should exist for cash")
|
||||
self.assertEqual(sep_cash.movement, 0.0, "September cash movement should be 0")
|
||||
self.assertEqual(sep_cash.opening, aug_cash.closing, "September opening = August closing")
|
||||
|
||||
finally:
|
||||
jv.cancel()
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe import _
|
||||
from frappe import _, cint
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_days, add_years, cstr, getdate
|
||||
|
||||
@@ -33,23 +33,11 @@ class FiscalYear(Document):
|
||||
self.validate_dates()
|
||||
self.validate_overlap()
|
||||
|
||||
if not self.is_new():
|
||||
year_start_end_dates = frappe.db.sql(
|
||||
"""select year_start_date, year_end_date
|
||||
from `tabFiscal Year` where name=%s""",
|
||||
(self.name),
|
||||
)
|
||||
def on_update(self):
|
||||
frappe.cache().delete_key("fiscal_years")
|
||||
|
||||
if year_start_end_dates:
|
||||
if (
|
||||
getdate(self.year_start_date) != year_start_end_dates[0][0]
|
||||
or getdate(self.year_end_date) != year_start_end_dates[0][1]
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."
|
||||
)
|
||||
)
|
||||
def on_trash(self):
|
||||
frappe.cache().delete_key("fiscal_years")
|
||||
|
||||
def validate_dates(self):
|
||||
self.validate_from_to_dates("year_start_date", "year_end_date")
|
||||
@@ -66,28 +54,20 @@ class FiscalYear(Document):
|
||||
frappe.exceptions.InvalidDates,
|
||||
)
|
||||
|
||||
def on_update(self):
|
||||
check_duplicate_fiscal_year(self)
|
||||
frappe.cache().delete_value("fiscal_years")
|
||||
|
||||
def on_trash(self):
|
||||
frappe.cache().delete_value("fiscal_years")
|
||||
|
||||
def validate_overlap(self):
|
||||
existing_fiscal_years = frappe.db.sql(
|
||||
"""select name from `tabFiscal Year`
|
||||
where (
|
||||
(%(year_start_date)s between year_start_date and year_end_date)
|
||||
or (%(year_end_date)s between year_start_date and year_end_date)
|
||||
or (year_start_date between %(year_start_date)s and %(year_end_date)s)
|
||||
or (year_end_date between %(year_start_date)s and %(year_end_date)s)
|
||||
) and name!=%(name)s""",
|
||||
{
|
||||
"year_start_date": self.year_start_date,
|
||||
"year_end_date": self.year_end_date,
|
||||
"name": self.name or "No Name",
|
||||
},
|
||||
as_dict=True,
|
||||
fy = frappe.qb.DocType("Fiscal Year")
|
||||
|
||||
name = self.name or self.year
|
||||
|
||||
existing_fiscal_years = (
|
||||
frappe.qb.from_(fy)
|
||||
.select(fy.name)
|
||||
.where(
|
||||
(fy.year_start_date <= self.year_end_date)
|
||||
& (fy.year_end_date >= self.year_start_date)
|
||||
& (fy.name != name)
|
||||
)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
if existing_fiscal_years:
|
||||
@@ -110,37 +90,30 @@ class FiscalYear(Document):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Year start date or end date is overlapping with {0}. To avoid please set company"
|
||||
).format(existing.name),
|
||||
).format(frappe.get_desk_link("Fiscal Year", existing.name, open_in_new_tab=True)),
|
||||
frappe.NameError,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def check_duplicate_fiscal_year(doc):
|
||||
year_start_end_dates = frappe.db.sql(
|
||||
"""select name, year_start_date, year_end_date from `tabFiscal Year` where name!=%s""",
|
||||
(doc.name),
|
||||
)
|
||||
for fiscal_year, ysd, yed in year_start_end_dates:
|
||||
if (getdate(doc.year_start_date) == ysd and getdate(doc.year_end_date) == yed) and (
|
||||
not frappe.in_test
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}"
|
||||
).format(fiscal_year)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def auto_create_fiscal_year():
|
||||
for d in frappe.db.sql(
|
||||
"""select name from `tabFiscal Year` where year_end_date = date_add(current_date, interval 3 day)"""
|
||||
):
|
||||
fy = frappe.qb.DocType("Fiscal Year")
|
||||
|
||||
# Skipped auto-creating Short Year, as it has very rare use case.
|
||||
# Reference: https://www.irs.gov/businesses/small-businesses-self-employed/tax-years (US)
|
||||
follow_up_date = add_days(getdate(), days=3)
|
||||
fiscal_year = (
|
||||
frappe.qb.from_(fy)
|
||||
.select(fy.name)
|
||||
.where((fy.year_end_date == follow_up_date) & (fy.is_short_year == 0))
|
||||
.run()
|
||||
)
|
||||
|
||||
for d in fiscal_year:
|
||||
try:
|
||||
current_fy = frappe.get_doc("Fiscal Year", d[0])
|
||||
|
||||
new_fy = frappe.copy_doc(current_fy, ignore_no_copy=False)
|
||||
new_fy = frappe.new_doc("Fiscal Year")
|
||||
new_fy.disabled = cint(current_fy.disabled)
|
||||
|
||||
new_fy.year_start_date = add_days(current_fy.year_end_date, 1)
|
||||
new_fy.year_end_date = add_years(current_fy.year_end_date, 1)
|
||||
@@ -148,6 +121,10 @@ def auto_create_fiscal_year():
|
||||
start_year = cstr(new_fy.year_start_date.year)
|
||||
end_year = cstr(new_fy.year_end_date.year)
|
||||
new_fy.year = start_year if start_year == end_year else (start_year + "-" + end_year)
|
||||
|
||||
for row in current_fy.companies:
|
||||
new_fy.append("companies", {"company": row.company})
|
||||
|
||||
new_fy.auto_created = 1
|
||||
|
||||
new_fy.insert(ignore_permissions=True)
|
||||
|
||||
@@ -15,20 +15,22 @@
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:09:44.659251",
|
||||
"modified": "2026-02-20 23:02:26.193606",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year Company",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class FiscalYearCompany(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
company: DF.Link | None
|
||||
company: DF.Link
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
|
||||
@@ -317,7 +317,7 @@ class InvoiceDiscounting(AccountsController):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoices(filters):
|
||||
def get_invoices(filters: str):
|
||||
filters = frappe._dict(json.loads(filters))
|
||||
cond = []
|
||||
if filters.customer:
|
||||
|
||||
@@ -277,7 +277,21 @@ frappe.ui.form.on("Journal Entry", {
|
||||
var update_jv_details = function (doc, r) {
|
||||
$.each(r, function (i, d) {
|
||||
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
|
||||
frappe.model.set_value(row.doctype, row.name, "account", d.account);
|
||||
const {
|
||||
idx,
|
||||
name,
|
||||
owner,
|
||||
parent,
|
||||
parenttype,
|
||||
parentfield,
|
||||
creation,
|
||||
modified,
|
||||
modified_by,
|
||||
doctype,
|
||||
docstatus,
|
||||
...fields
|
||||
} = d;
|
||||
frappe.model.set_value(row.doctype, row.name, fields);
|
||||
});
|
||||
refresh_field("accounts");
|
||||
};
|
||||
@@ -289,10 +303,6 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
}
|
||||
|
||||
onload_post_render() {
|
||||
this.frm.get_field("accounts").grid.set_multiple_add("account");
|
||||
}
|
||||
|
||||
load_defaults() {
|
||||
//this.frm.show_print_first = true;
|
||||
if (this.frm.doc.__islocal && this.frm.doc.company) {
|
||||
|
||||
@@ -9,19 +9,16 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"entry_type_and_date",
|
||||
"is_system_generated",
|
||||
"title",
|
||||
"voucher_type",
|
||||
"naming_series",
|
||||
"process_deferred_accounting",
|
||||
"reversal_of",
|
||||
"column_break1",
|
||||
"from_template",
|
||||
"company",
|
||||
"voucher_type",
|
||||
"column_break1",
|
||||
"naming_series",
|
||||
"posting_date",
|
||||
"finance_book",
|
||||
"multi_currency",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"is_system_generated",
|
||||
"amended_from",
|
||||
"section_break_tcvw",
|
||||
"for_all_stock_asset_accounts",
|
||||
"column_break_wpau",
|
||||
@@ -30,52 +27,60 @@
|
||||
"get_balance_for_periodic_accounting",
|
||||
"2_add_edit_gl_entries",
|
||||
"accounts",
|
||||
"section_break99",
|
||||
"cheque_no",
|
||||
"cheque_date",
|
||||
"user_remark",
|
||||
"column_break99",
|
||||
"section_break_ouaq",
|
||||
"total_debit",
|
||||
"column_break_cixu",
|
||||
"total_credit",
|
||||
"difference",
|
||||
"get_balance",
|
||||
"multi_currency",
|
||||
"total_amount_currency",
|
||||
"total_amount",
|
||||
"total_amount_in_words",
|
||||
"section_break99",
|
||||
"cheque_no",
|
||||
"cheque_date",
|
||||
"clearance_date",
|
||||
"column_break_oizh",
|
||||
"user_remark",
|
||||
"subscription_section",
|
||||
"auto_repeat",
|
||||
"tax_withholding_tab",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
"override_tax_withholding_entries",
|
||||
"tax_withholding_entries",
|
||||
"more_info_tab",
|
||||
"reference",
|
||||
"clearance_date",
|
||||
"remark",
|
||||
"inter_company_journal_entry_reference",
|
||||
"column_break98",
|
||||
"bill_no",
|
||||
"bill_date",
|
||||
"due_date",
|
||||
"column_break_isfa",
|
||||
"inter_company_journal_entry_reference",
|
||||
"process_deferred_accounting",
|
||||
"reversal_of",
|
||||
"payment_order",
|
||||
"stock_entry",
|
||||
"printing_settings",
|
||||
"pay_to_recd_from",
|
||||
"letter_head",
|
||||
"select_print_heading",
|
||||
"column_break_35",
|
||||
"total_amount_currency",
|
||||
"total_amount",
|
||||
"total_amount_in_words",
|
||||
"write_off",
|
||||
"write_off_based_on",
|
||||
"get_outstanding_invoices",
|
||||
"column_break_30",
|
||||
"write_off_amount",
|
||||
"printing_settings",
|
||||
"pay_to_recd_from",
|
||||
"column_break_35",
|
||||
"letter_head",
|
||||
"select_print_heading",
|
||||
"addtional_info",
|
||||
"mode_of_payment",
|
||||
"payment_order",
|
||||
"party_not_required",
|
||||
"column_break3",
|
||||
"is_opening",
|
||||
"stock_entry",
|
||||
"subscription_section",
|
||||
"auto_repeat",
|
||||
"amended_from"
|
||||
"finance_book",
|
||||
"from_template",
|
||||
"title",
|
||||
"column_break3",
|
||||
"remark",
|
||||
"mode_of_payment",
|
||||
"party_not_required"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -155,6 +160,7 @@
|
||||
{
|
||||
"fieldname": "2_add_edit_gl_entries",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1,
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-table"
|
||||
},
|
||||
@@ -202,10 +208,6 @@
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break99",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_debit",
|
||||
"fieldtype": "Currency",
|
||||
@@ -429,7 +431,7 @@
|
||||
"collapsible": 1,
|
||||
"fieldname": "addtional_info",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "More Information",
|
||||
"label": "Additional Info",
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-file-text"
|
||||
},
|
||||
@@ -476,7 +478,7 @@
|
||||
{
|
||||
"fieldname": "subscription_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Subscription Section"
|
||||
"label": "Subscription"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@@ -593,12 +595,10 @@
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.apply_tds && doc.docstatus == 0",
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "section_tax_withholding_entry",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withholding Entry"
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
@@ -624,6 +624,33 @@
|
||||
"label": "Tax Withholding Entries",
|
||||
"options": "Tax Withholding Entry",
|
||||
"read_only_depends_on": "eval: !doc.override_tax_withholding_entries"
|
||||
},
|
||||
{
|
||||
"fieldname": "more_info_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "More Info"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ouaq",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_cixu",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_oizh",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_isfa",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "tax_withholding_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Tax Withholding"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -638,7 +665,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2025-11-13 17:54:14.542903",
|
||||
"modified": "2026-02-16 16:06:10.468482",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.core.doctype.submission_queue.submission_queue import queue_submission
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
|
||||
|
||||
import erpnext
|
||||
@@ -74,8 +75,8 @@ class JournalEntry(AccountsController):
|
||||
mode_of_payment: DF.Link | None
|
||||
multi_currency: DF.Check
|
||||
naming_series: DF.Literal["ACC-JV-.YYYY.-"]
|
||||
party_not_required: DF.Check
|
||||
override_tax_withholding_entries: DF.Check
|
||||
party_not_required: DF.Check
|
||||
pay_to_recd_from: DF.Data | None
|
||||
payment_order: DF.Link | None
|
||||
periodic_entry_difference_account: DF.Link | None
|
||||
@@ -179,7 +180,7 @@ class JournalEntry(AccountsController):
|
||||
validate_docs_for_deferred_accounting([self.name], [])
|
||||
|
||||
def submit(self):
|
||||
if len(self.accounts) > 100:
|
||||
if len(self.accounts) > 100 and not self.meta.queue_in_background:
|
||||
queue_submission(self, "_submit")
|
||||
else:
|
||||
return self._submit()
|
||||
@@ -1215,7 +1216,7 @@ class JournalEntry(AccountsController):
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance(self, difference_account=None):
|
||||
def get_balance(self, difference_account: str | None = None):
|
||||
if not self.get("accounts"):
|
||||
msgprint(_("'Entries' cannot be empty"), raise_exception=True)
|
||||
else:
|
||||
@@ -1321,7 +1322,12 @@ class JournalEntry(AccountsController):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_default_bank_cash_account(
|
||||
company, account_type=None, mode_of_payment=None, account=None, *, fetch_balance=True
|
||||
company: str,
|
||||
account_type: str | None = None,
|
||||
mode_of_payment: str | None = None,
|
||||
account: str | None = None,
|
||||
*,
|
||||
fetch_balance: bool = True,
|
||||
):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
|
||||
|
||||
@@ -1370,7 +1376,12 @@ def get_default_bank_cash_account(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_against_order(
|
||||
dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None
|
||||
dt: str,
|
||||
dn: str,
|
||||
amount: float | None = None,
|
||||
debit_in_account_currency: str | float | None = None,
|
||||
journal_entry: bool = False,
|
||||
bank_account: str | None = None,
|
||||
):
|
||||
ref_doc = frappe.get_doc(dt, dn)
|
||||
|
||||
@@ -1415,7 +1426,12 @@ def get_payment_entry_against_order(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry_against_invoice(
|
||||
dt, dn, amount=None, debit_in_account_currency=None, journal_entry=False, bank_account=None
|
||||
dt: str,
|
||||
dn: str,
|
||||
amount: float | None = None,
|
||||
debit_in_account_currency: str | None = None,
|
||||
journal_entry: bool = False,
|
||||
bank_account: str | None = None,
|
||||
):
|
||||
ref_doc = frappe.get_doc(dt, dn)
|
||||
if dt == "Sales Invoice":
|
||||
@@ -1528,7 +1544,7 @@ def get_payment_entry(ref_doc, args):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_against_jv(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
if not frappe.db.has_column("Journal Entry", searchfield):
|
||||
return []
|
||||
|
||||
@@ -1559,7 +1575,7 @@ def get_against_jv(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding(args):
|
||||
def get_outstanding(args: str | dict):
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
@@ -1619,7 +1635,7 @@ def get_outstanding(args):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_account_and_currency(company, party_type, party):
|
||||
def get_party_account_and_currency(company: str, party_type: str, party: str):
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
@@ -1632,7 +1648,14 @@ def get_party_account_and_currency(company, party_type, party):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details_and_party_type(account, date, company, debit=None, credit=None, exchange_rate=None):
|
||||
def get_account_details_and_party_type(
|
||||
account: str,
|
||||
date: str,
|
||||
company: str,
|
||||
debit: float | str | None = None,
|
||||
credit: float | str | None = None,
|
||||
exchange_rate: float | str | None = None,
|
||||
):
|
||||
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
@@ -1681,16 +1704,20 @@ def get_account_details_and_party_type(account, date, company, debit=None, credi
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_exchange_rate(
|
||||
posting_date,
|
||||
account=None,
|
||||
account_currency=None,
|
||||
company=None,
|
||||
reference_type=None,
|
||||
reference_name=None,
|
||||
debit=None,
|
||||
credit=None,
|
||||
exchange_rate=None,
|
||||
posting_date: str | date,
|
||||
account: str | None = None,
|
||||
account_currency: str | None = None,
|
||||
company: str | None = None,
|
||||
reference_type: str | None = None,
|
||||
reference_name: str | None = None,
|
||||
debit: float | str | None = None,
|
||||
credit: float | str | None = None,
|
||||
exchange_rate: str | float | None = None,
|
||||
):
|
||||
# Ensure exchange_rate is always numeric to avoid calculation errors
|
||||
if isinstance(exchange_rate, str):
|
||||
exchange_rate = flt(exchange_rate) or 1
|
||||
|
||||
account_details = frappe.get_cached_value(
|
||||
"Account", account, ["account_type", "root_type", "account_currency", "company"], as_dict=1
|
||||
)
|
||||
@@ -1722,7 +1749,7 @@ def get_exchange_rate(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_average_exchange_rate(account):
|
||||
def get_average_exchange_rate(account: str):
|
||||
exchange_rate = 0
|
||||
bank_balance_in_account_currency = get_balance_on(account)
|
||||
if bank_balance_in_account_currency:
|
||||
@@ -1733,7 +1760,7 @@ def get_average_exchange_rate(account):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
def make_inter_company_journal_entry(name: str, voucher_type: str, company: str):
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.voucher_type = voucher_type
|
||||
journal_entry.company = company
|
||||
@@ -1743,7 +1770,7 @@ def make_inter_company_journal_entry(name, voucher_type, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_reverse_journal_entry(source_name, target_doc=None):
|
||||
def make_reverse_journal_entry(source_name: str, target_doc: str | Document | None = None):
|
||||
existing_reverse = frappe.db.exists("Journal Entry", {"reversal_of": source_name, "docstatus": 1})
|
||||
if existing_reverse:
|
||||
frappe.throw(
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"fields": [
|
||||
{
|
||||
"bold": 1,
|
||||
"columns": 2,
|
||||
"columns": 4,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
@@ -185,20 +185,19 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Reference Type",
|
||||
"no_copy": 1,
|
||||
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry",
|
||||
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry\nBank Transaction",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"no_copy": 1,
|
||||
"options": "reference_type",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
|
||||
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance', 'Bank Transaction'])",
|
||||
"fieldname": "reference_due_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Reference Due Date",
|
||||
@@ -294,7 +293,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-27 12:23:33.157655",
|
||||
"modified": "2026-02-19 17:01:22.642454",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
||||
@@ -55,6 +55,7 @@ class JournalEntryAccount(Document):
|
||||
"Fees",
|
||||
"Full and Final Statement",
|
||||
"Payment Entry",
|
||||
"Bank Transaction",
|
||||
]
|
||||
user_remark: DF.SmallText | None
|
||||
# end: auto-generated types
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
frappe.ui.form.on("Journal Entry Template", {
|
||||
onload: function (frm) {
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
|
||||
if (frm.is_new()) {
|
||||
frappe.call({
|
||||
type: "GET",
|
||||
@@ -37,6 +38,31 @@ frappe.ui.form.on("Journal Entry Template", {
|
||||
|
||||
return { filters: filters };
|
||||
});
|
||||
|
||||
frm.set_query("project", "accounts", function (doc, cdt, cdn) {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
let filters = {
|
||||
company: doc.company,
|
||||
};
|
||||
if (row.party_type == "Customer") {
|
||||
filters.customer = row.party;
|
||||
}
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_project_name",
|
||||
filters,
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("party_type", "accounts", function (doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
|
||||
return {
|
||||
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
|
||||
filters: {
|
||||
account: row.account,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
voucher_type: function (frm) {
|
||||
var add_accounts = function (doc, r) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
@@ -42,7 +43,29 @@ class JournalEntryTemplate(Document):
|
||||
]
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
def validate(self):
|
||||
self.validate_party()
|
||||
|
||||
def validate_party(self):
|
||||
"""
|
||||
Loop over all accounts and see if party and party type is set correctly
|
||||
"""
|
||||
for account in self.accounts:
|
||||
if account.party_type:
|
||||
account_type = frappe.get_cached_value("Account", account.account, "account_type")
|
||||
if account_type not in ["Receivable", "Payable"]:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Check row {0} for account {1}: Party Type is only allowed for Receivable or Payable accounts"
|
||||
).format(account.idx, account.account)
|
||||
)
|
||||
|
||||
if account.party and not account.party_type:
|
||||
frappe.throw(
|
||||
_("Check row {0} for account {1}: Party is only allowed if Party Type is set").format(
|
||||
account.idx, account.account
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"account"
|
||||
"account",
|
||||
"party_type",
|
||||
"party",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -15,18 +21,55 @@
|
||||
"label": "Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Party Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Party",
|
||||
"options": "party_type"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:09:58.986448",
|
||||
"modified": "2026-01-09 13:16:27.615083",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Template Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,9 +16,13 @@ class JournalEntryTemplateAccount(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
account: DF.Link
|
||||
cost_center: DF.Link | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
party: DF.DynamicLink | None
|
||||
party_type: DF.Link | None
|
||||
project: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
|
||||
@@ -55,7 +55,7 @@ class LedgerMerge(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def form_start_merge(docname):
|
||||
def form_start_merge(docname: str):
|
||||
return frappe.get_doc("Ledger Merge", docname).start_merge()
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -88,13 +89,13 @@ def get_loyalty_details(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_program_details_with_points(
|
||||
customer,
|
||||
loyalty_program=None,
|
||||
expiry_date=None,
|
||||
company=None,
|
||||
silent=False,
|
||||
include_expired_entry=False,
|
||||
current_transaction_amount=0,
|
||||
customer: str,
|
||||
loyalty_program: str | None = None,
|
||||
expiry_date: str | date | None = None,
|
||||
company: str | None = None,
|
||||
silent: bool = False,
|
||||
include_expired_entry: bool = False,
|
||||
current_transaction_amount: int | float = 0,
|
||||
):
|
||||
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
||||
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
|
||||
@@ -119,12 +120,12 @@ def get_loyalty_program_details_with_points(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_program_details(
|
||||
customer,
|
||||
loyalty_program=None,
|
||||
expiry_date=None,
|
||||
company=None,
|
||||
silent=False,
|
||||
include_expired_entry=False,
|
||||
customer: str,
|
||||
loyalty_program: str | None = None,
|
||||
expiry_date: str | date | None = None,
|
||||
company: str | None = None,
|
||||
silent: bool = False,
|
||||
include_expired_entry: bool = False,
|
||||
):
|
||||
lp_details = frappe._dict()
|
||||
|
||||
@@ -146,7 +147,7 @@ def get_loyalty_program_details(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_redeemption_factor(loyalty_program=None, customer=None):
|
||||
def get_redeemption_factor(loyalty_program: str | None = None, customer: str | None = None):
|
||||
customer_loyalty_program = None
|
||||
if not loyalty_program:
|
||||
customer_loyalty_program = frappe.db.get_value("Customer", customer, "loyalty_program")
|
||||
|
||||
@@ -293,7 +293,7 @@ def publish(index, total, doctype):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_temporary_opening_account(company=None):
|
||||
def get_temporary_opening_account(company: str | None = None):
|
||||
if not company:
|
||||
return
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class PartyLink(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_party_link(primary_role, primary_party, secondary_party):
|
||||
def create_party_link(primary_role: str, primary_party: str, secondary_party: str):
|
||||
party_link = frappe.new_doc("Party Link")
|
||||
party_link.primary_role = primary_role
|
||||
party_link.primary_party = primary_party
|
||||
|
||||
@@ -400,6 +400,16 @@ frappe.ui.form.on("Payment Entry", {
|
||||
);
|
||||
|
||||
frm.refresh_fields();
|
||||
|
||||
const party_currency =
|
||||
frm.doc.payment_type === "Receive" ? "paid_from_account_currency" : "paid_to_account_currency";
|
||||
|
||||
var reference_grid = frm.fields_dict["references"].grid;
|
||||
["total_amount", "outstanding_amount", "allocated_amount"].forEach((fieldname) => {
|
||||
reference_grid.update_docfield_property(fieldname, "options", party_currency);
|
||||
});
|
||||
|
||||
reference_grid.refresh();
|
||||
},
|
||||
|
||||
show_general_ledger: function (frm) {
|
||||
@@ -502,12 +512,16 @@ frappe.ui.form.on("Payment Entry", {
|
||||
frm.set_value("contact_email", "");
|
||||
frm.set_value("contact_person", "");
|
||||
}
|
||||
|
||||
if (frm.doc.payment_type && frm.doc.party_type && frm.doc.party && frm.doc.company) {
|
||||
if (!frm.doc.posting_date) {
|
||||
frappe.msgprint(__("Please select Posting Date before selecting Party"));
|
||||
frm.set_value("party", "");
|
||||
return;
|
||||
}
|
||||
|
||||
erpnext.utils.get_employee_contact_details(frm);
|
||||
|
||||
frm.set_party_account_based_on_party = true;
|
||||
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
@@ -1104,7 +1118,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
allocate_party_amount_against_ref_docs: async function (frm, paid_amount, paid_amount_change) {
|
||||
await frm.call("allocate_amount_to_references", {
|
||||
paid_amount: paid_amount,
|
||||
paid_amount: flt(paid_amount),
|
||||
paid_amount_change: paid_amount_change,
|
||||
allocate_payment_amount: frappe.flags.allocate_payment_amount ?? false,
|
||||
});
|
||||
@@ -1440,16 +1454,15 @@ frappe.ui.form.on("Payment Entry", {
|
||||
callback: function (r) {
|
||||
if (!r.exc && r.message) {
|
||||
// set taxes table
|
||||
if (r.message) {
|
||||
for (let tax of r.message) {
|
||||
if (tax.charge_type === "On Net Total") {
|
||||
tax.charge_type = "On Paid Amount";
|
||||
}
|
||||
frm.add_child("taxes", tax);
|
||||
let taxes = r.message;
|
||||
taxes.forEach((tax) => {
|
||||
if (tax.charge_type === "On Net Total") {
|
||||
tax.charge_type = "On Paid Amount";
|
||||
}
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
}
|
||||
});
|
||||
frm.set_value("taxes", taxes);
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -701,7 +701,6 @@
|
||||
"fetch_from": "company.book_advance_payments_in_separate_party_account",
|
||||
"fieldname": "book_advance_payments_in_separate_party_account",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Book Advance Payments in Separate Party Account",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
@@ -793,7 +792,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2025-12-18 13:56:40.206038",
|
||||
"modified": "2026-02-03 16:08:49.800381",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import json
|
||||
from datetime import date
|
||||
from functools import reduce
|
||||
|
||||
import frappe
|
||||
from frappe import ValidationError, _, qb, scrub, throw
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.query_builder import Tuple
|
||||
from frappe.query_builder.functions import Count
|
||||
@@ -1064,8 +1065,12 @@ class PaymentEntry(AccountsController):
|
||||
total_allocated_amount += flt(d.allocated_amount)
|
||||
base_total_allocated_amount += self.calculate_base_allocated_amount_for_reference(d)
|
||||
|
||||
self.total_allocated_amount = abs(total_allocated_amount)
|
||||
self.base_total_allocated_amount = abs(base_total_allocated_amount)
|
||||
self.total_allocated_amount = flt(
|
||||
abs(total_allocated_amount), self.precision("total_allocated_amount")
|
||||
)
|
||||
self.base_total_allocated_amount = flt(
|
||||
abs(base_total_allocated_amount), self.precision("base_total_allocated_amount")
|
||||
)
|
||||
|
||||
def set_unallocated_amount(self):
|
||||
self.unallocated_amount = 0
|
||||
@@ -1081,20 +1086,32 @@ class PaymentEntry(AccountsController):
|
||||
self.base_paid_amount + deductions_to_consider
|
||||
):
|
||||
self.unallocated_amount = (
|
||||
self.base_paid_amount
|
||||
+ deductions_to_consider
|
||||
- self.base_total_allocated_amount
|
||||
- included_taxes
|
||||
) / self.source_exchange_rate
|
||||
flt(
|
||||
(
|
||||
self.base_paid_amount
|
||||
+ deductions_to_consider
|
||||
- self.base_total_allocated_amount
|
||||
- included_taxes
|
||||
),
|
||||
self.precision("unallocated_amount"),
|
||||
)
|
||||
/ self.source_exchange_rate
|
||||
)
|
||||
elif self.payment_type == "Pay" and self.base_total_allocated_amount < (
|
||||
self.base_received_amount - deductions_to_consider
|
||||
):
|
||||
self.unallocated_amount = (
|
||||
self.base_received_amount
|
||||
- deductions_to_consider
|
||||
- self.base_total_allocated_amount
|
||||
- included_taxes
|
||||
) / self.target_exchange_rate
|
||||
flt(
|
||||
(
|
||||
self.base_received_amount
|
||||
- deductions_to_consider
|
||||
- self.base_total_allocated_amount
|
||||
- included_taxes
|
||||
),
|
||||
self.precision("unallocated_amount"),
|
||||
)
|
||||
/ self.target_exchange_rate
|
||||
)
|
||||
|
||||
def set_exchange_gain_loss(self):
|
||||
exchange_gain_loss = flt(
|
||||
@@ -1867,7 +1884,9 @@ class PaymentEntry(AccountsController):
|
||||
frappe.response["matched_payment_requests"] = matched_payment_requests
|
||||
|
||||
@frappe.whitelist()
|
||||
def allocate_amount_to_references(self, paid_amount, paid_amount_change, allocate_payment_amount):
|
||||
def allocate_amount_to_references(
|
||||
self, paid_amount: float, paid_amount_change: bool, allocate_payment_amount: bool
|
||||
):
|
||||
"""
|
||||
Allocate `Allocated Amount` and `Payment Request` against `Reference` based on `Paid Amount` and `Outstanding Amount`.\n
|
||||
:param paid_amount: Paid Amount / Received Amount.
|
||||
@@ -2039,7 +2058,7 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_matched_payment_requests(self, matched_payment_requests):
|
||||
def set_matched_payment_requests(self, matched_payment_requests: str | list | None):
|
||||
"""
|
||||
Set `Payment Request` against `Reference` based on `matched_payment_requests`.\n
|
||||
:param matched_payment_requests: List of tuple of matched Payment Requests.
|
||||
@@ -2255,7 +2274,7 @@ def validate_inclusive_tax(tax, doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding_reference_documents(args, validate=False):
|
||||
def get_outstanding_reference_documents(args: str | dict, validate: bool = False):
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
@@ -2670,7 +2689,7 @@ def get_negative_outstanding_invoices(
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
def get_party_details(company: str, party_type: str, party: str, date: str, cost_center: str | None = None):
|
||||
bank_account = ""
|
||||
party_bank_account = ""
|
||||
|
||||
@@ -2696,7 +2715,7 @@ def get_party_details(company, party_type, party, date, cost_center=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details(account, date, cost_center=None):
|
||||
def get_account_details(account: str, date: str | date, cost_center: str | None = None):
|
||||
frappe.has_permission("Payment Entry", throw=True)
|
||||
|
||||
# to check if the passed account is accessible under reference doctype Payment Entry
|
||||
@@ -2716,7 +2735,7 @@ def get_account_details(account, date, cost_center=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_company_defaults(company):
|
||||
def get_company_defaults(company: str):
|
||||
fields = ["write_off_account", "exchange_gain_loss_account", "cost_center"]
|
||||
return frappe.get_cached_value("Company", company, fields, as_dict=1)
|
||||
|
||||
@@ -2755,7 +2774,11 @@ def get_outstanding_on_journal_entry(voucher_no, party_type, party):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_reference_details(
|
||||
reference_doctype, reference_name, party_account_currency, party_type=None, party=None
|
||||
reference_doctype: str,
|
||||
reference_name: str,
|
||||
party_account_currency: str,
|
||||
party_type: str | None = None,
|
||||
party: str | None = None,
|
||||
):
|
||||
total_amount = outstanding_amount = exchange_rate = account = None
|
||||
|
||||
@@ -2846,15 +2869,15 @@ def get_reference_details(
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry(
|
||||
dt,
|
||||
dn,
|
||||
party_amount=None,
|
||||
bank_account=None,
|
||||
bank_amount=None,
|
||||
party_type=None,
|
||||
payment_type=None,
|
||||
reference_date=None,
|
||||
created_from_payment_request=False,
|
||||
dt: str,
|
||||
dn: str,
|
||||
party_amount: int | float | None = None,
|
||||
bank_account: str | None = None,
|
||||
bank_amount: int | float | None = None,
|
||||
party_type: str | None = None,
|
||||
payment_type: str | None = None,
|
||||
reference_date: str | date | None = None,
|
||||
created_from_payment_request: bool | None = None,
|
||||
):
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
over_billing_allowance = frappe.get_single_value("Accounts Settings", "over_billing_allowance")
|
||||
@@ -3520,7 +3543,7 @@ def get_paid_amount(dt, dn, party_type, party, account, due_date):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_payment_order(source_name, target_doc=None):
|
||||
def make_payment_order(source_name: str, target_doc: str | Document | None = None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def set_missing_values(source, target):
|
||||
|
||||
@@ -132,6 +132,12 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "due_date",
|
||||
|
||||
@@ -38,6 +38,7 @@ class PaymentLedgerEntry(Document):
|
||||
amount_in_account_currency: DF.Currency
|
||||
company: DF.Link | None
|
||||
cost_center: DF.Link | None
|
||||
project: DF.Link | None
|
||||
delinked: DF.Check
|
||||
due_date: DF.Date | None
|
||||
finance_book: DF.Link | None
|
||||
|
||||
@@ -59,7 +59,7 @@ class PaymentOrder(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_mop_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
return frappe.db.sql(
|
||||
""" select mode_of_payment from `tabPayment Order Reference`
|
||||
where parent = %(parent)s and mode_of_payment like %(txt)s
|
||||
@@ -70,7 +70,7 @@ def get_mop_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_supplier_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
return frappe.db.sql(
|
||||
""" select supplier from `tabPayment Order Reference`
|
||||
where parent = %(parent)s and supplier like %(txt)s and
|
||||
@@ -81,7 +81,7 @@ def get_supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_payment_records(name, supplier, mode_of_payment=None):
|
||||
def make_payment_records(name: str, supplier: str, mode_of_payment: str | None = None):
|
||||
doc = frappe.get_doc("Payment Order", name)
|
||||
make_journal_entry(doc, supplier, mode_of_payment)
|
||||
|
||||
|
||||
@@ -433,7 +433,9 @@ class PaymentReconciliation(Document):
|
||||
return frappe.get_single_value("Accounts Settings", "auto_reconcile_payments")
|
||||
|
||||
@frappe.whitelist()
|
||||
def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
|
||||
def calculate_difference_on_allocation_change(
|
||||
self, payment_entry: list, invoice: list, allocated_amount: float
|
||||
):
|
||||
invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry)
|
||||
invoice[0]["exchange_rate"] = invoice_exchange_map.get(invoice[0].get("invoice_number"))
|
||||
if payment_entry[0].get("reference_type") in ["Sales Invoice", "Purchase Invoice"]:
|
||||
@@ -445,7 +447,7 @@ class PaymentReconciliation(Document):
|
||||
return new_difference_amount
|
||||
|
||||
@frappe.whitelist()
|
||||
def allocate_entries(self, args):
|
||||
def allocate_entries(self, args: dict):
|
||||
self.validate_entries()
|
||||
|
||||
exc_gain_loss_posting_date = frappe.db.get_single_value(
|
||||
@@ -746,7 +748,7 @@ class PaymentReconciliation(Document):
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
for x in self.dimensions:
|
||||
dimension = x.fieldname
|
||||
if self.get(dimension):
|
||||
if self.get(dimension) and frappe.db.has_column("Payment Ledger Entry", dimension):
|
||||
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
|
||||
|
||||
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2025-12-02 17:50:08.648006",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"payment_term",
|
||||
"column_break_lnjp",
|
||||
"payment_schedule",
|
||||
"section_break_fjhh",
|
||||
"description",
|
||||
"section_break_mjlv",
|
||||
"due_date",
|
||||
"column_break_qghl",
|
||||
"amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "payment_term",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Payment Term",
|
||||
"options": "Payment Term"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_fjhh",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_mjlv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "due_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Due Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_qghl",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"precision": "2"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lnjp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "payment_schedule",
|
||||
"fieldtype": "Link",
|
||||
"label": "Payment Schedule",
|
||||
"options": "Payment Schedule",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2026-01-19 02:21:36.455830",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reference",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class PaymentReference(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
amount: DF.Currency
|
||||
description: DF.SmallText | None
|
||||
due_date: DF.Date | None
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
payment_schedule: DF.Link | None
|
||||
payment_term: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -105,3 +105,29 @@ frappe.ui.form.on("Payment Request", "is_a_subscription", function (frm) {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Payment Request", "calculate_total_amount_by_selected_rows", function (frm) {
|
||||
if (frm.doc.docstatus !== 0) {
|
||||
frappe.msgprint(__("Cannot fetch selected rows for submitted Payment Request"));
|
||||
return;
|
||||
}
|
||||
const selected = frm.get_selected()?.payment_reference || [];
|
||||
if (!selected.length) {
|
||||
frappe.throw(__("No rows selected"));
|
||||
}
|
||||
let total = 0;
|
||||
selected.forEach((name) => {
|
||||
const row = frm.doc.payment_reference.find((d) => d.name === name);
|
||||
if (row) {
|
||||
row.manually_selected = 1;
|
||||
|
||||
total += row.amount;
|
||||
}
|
||||
});
|
||||
frm.doc.payment_reference.forEach((row) => {
|
||||
row.auto_selected = 0;
|
||||
});
|
||||
frm.set_value("grand_total", total);
|
||||
frm.refresh_field("grand_total");
|
||||
frm.save();
|
||||
});
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
"column_break_4",
|
||||
"reference_doctype",
|
||||
"reference_name",
|
||||
"payment_reference_section",
|
||||
"payment_reference",
|
||||
"transaction_details",
|
||||
"grand_total",
|
||||
"currency",
|
||||
@@ -157,6 +159,7 @@
|
||||
"label": "Amount",
|
||||
"non_negative": 1,
|
||||
"options": "currency",
|
||||
"read_only_depends_on": "eval:doc.payment_reference.length>0",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -457,6 +460,17 @@
|
||||
"fieldname": "phone_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Phone Number"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_reference_section",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_reference",
|
||||
"fieldtype": "Table",
|
||||
"label": "Payment Reference",
|
||||
"options": "Payment Reference",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -464,7 +478,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-29 11:52:48.555415",
|
||||
"modified": "2026-01-13 12:53:00.963274",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
|
||||
@@ -45,6 +45,7 @@ class PaymentRequest(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.payment_reference.payment_reference import PaymentReference
|
||||
from erpnext.accounts.doctype.subscription_plan_detail.subscription_plan_detail import (
|
||||
SubscriptionPlanDetail,
|
||||
)
|
||||
@@ -78,6 +79,7 @@ class PaymentRequest(Document):
|
||||
payment_gateway: DF.ReadOnly | None
|
||||
payment_gateway_account: DF.Link | None
|
||||
payment_order: DF.Link | None
|
||||
payment_reference: DF.Table[PaymentReference]
|
||||
payment_request_type: DF.Literal["Outward", "Inward"]
|
||||
payment_url: DF.Data | None
|
||||
phone_number: DF.Data | None
|
||||
@@ -109,15 +111,36 @@ class PaymentRequest(Document):
|
||||
if self.get("__islocal"):
|
||||
self.status = "Draft"
|
||||
self.validate_reference_document()
|
||||
self.validate_against_payment_reference()
|
||||
self.validate_payment_request_amount()
|
||||
# self.validate_currency()
|
||||
self.validate_subscription_details()
|
||||
|
||||
def validate_against_payment_reference(self):
|
||||
if not self.payment_reference:
|
||||
return
|
||||
|
||||
expected = sum(flt(r.amount) for r in self.payment_reference)
|
||||
if flt(expected, self.precision("grand_total")) != flt(self.grand_total):
|
||||
frappe.throw(_("Grand Total must match sum of Payment References"))
|
||||
|
||||
seen = set()
|
||||
for r in self.payment_reference:
|
||||
if not r.payment_schedule:
|
||||
continue # legacy mode → skip
|
||||
|
||||
if r.payment_schedule in seen:
|
||||
frappe.throw(_("Duplicate Payment Schedule selected"))
|
||||
|
||||
seen.add(r.payment_schedule)
|
||||
|
||||
def validate_reference_document(self):
|
||||
if not self.reference_doctype or not self.reference_name:
|
||||
frappe.throw(_("To create a Payment Request reference document is required"))
|
||||
|
||||
def validate_payment_request_amount(self):
|
||||
if self.payment_reference:
|
||||
return
|
||||
if self.grand_total == 0:
|
||||
frappe.throw(
|
||||
_("{0} cannot be zero").format(self.get_label_from_fieldname("grand_total")),
|
||||
@@ -535,7 +558,7 @@ class PaymentRequest(Document):
|
||||
row_number += TO_SKIP_NEW_ROW
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@frappe.whitelist()
|
||||
def make_payment_request(**args):
|
||||
"""Make payment request"""
|
||||
|
||||
@@ -546,12 +569,69 @@ def make_payment_request(**args):
|
||||
if args.dn and not isinstance(args.dn, str):
|
||||
frappe.throw(_("Invalid parameter. 'dn' should be of type str"))
|
||||
|
||||
frappe.has_permission("Payment Request", "create", throw=True)
|
||||
frappe.has_permission(args.dt, "read", args.dn, throw=True)
|
||||
|
||||
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
|
||||
if not args.get("company"):
|
||||
args.company = ref_doc.company
|
||||
|
||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||
|
||||
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||
# Schedule-based PRs are allowed only if no Payment Entry exists for this document.
|
||||
# Any existing Payment Entry forces legacy (amount-based) flow.
|
||||
selected_payment_schedules = json.loads(args.get("schedules")) if args.get("schedules") else []
|
||||
|
||||
# Backend guard:
|
||||
# If any Payment Entry exists, schedule-based PRs are not allowed.
|
||||
if selected_payment_schedules and get_existing_payment_entry(ref_doc.name):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Payment Schedule based Payment Requests cannot be created because a Payment Entry already exists for this document."
|
||||
)
|
||||
)
|
||||
|
||||
has_payment_entry = bool(get_existing_payment_entry(ref_doc.name))
|
||||
|
||||
payment_reference = []
|
||||
|
||||
if selected_payment_schedules:
|
||||
existing_payment_references = get_existing_payment_references(ref_doc.name)
|
||||
|
||||
if existing_payment_references:
|
||||
existing_ids = {r["payment_schedule"] for r in existing_payment_references}
|
||||
selected_ids = {r["name"] for r in selected_payment_schedules}
|
||||
duplicate_ids = existing_ids & selected_ids
|
||||
|
||||
if duplicate_ids:
|
||||
duplicate_schedules = []
|
||||
for row in selected_payment_schedules:
|
||||
if row["name"] in duplicate_ids:
|
||||
existing_ref = next(
|
||||
(r for r in existing_payment_references if r["payment_schedule"] == row["name"]),
|
||||
{},
|
||||
)
|
||||
existing_pr = existing_ref.get("parent")
|
||||
duplicate_schedules.append(
|
||||
f"Payment Term: {row.get('payment_term')}, "
|
||||
f"Due Date: {row.get('due_date')}, "
|
||||
f"Amount: {row.get('payment_amount')} "
|
||||
f"(already requested in PR {existing_pr})"
|
||||
)
|
||||
frappe.throw(
|
||||
_("The following payment schedule(s) already exist:\n{0}").format(
|
||||
"\n".join(duplicate_schedules)
|
||||
)
|
||||
)
|
||||
|
||||
payment_reference = set_payment_references(args.get("schedules"))
|
||||
|
||||
# Determine grand_total
|
||||
if selected_payment_schedules and not has_payment_entry:
|
||||
grand_total = sum(row.get("payment_amount") for row in selected_payment_schedules)
|
||||
else:
|
||||
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||
|
||||
if not grand_total:
|
||||
frappe.throw(_("Payment Entry is already created"))
|
||||
|
||||
@@ -561,7 +641,6 @@ def make_payment_request(**args):
|
||||
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points)) # sets fields on ref_doc
|
||||
ref_doc.db_update()
|
||||
grand_total = grand_total - loyalty_amount
|
||||
|
||||
# fetches existing payment request `grand_total` amount
|
||||
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc)
|
||||
|
||||
@@ -581,19 +660,20 @@ def make_payment_request(**args):
|
||||
else:
|
||||
# If PR's are processed, cancel all of them.
|
||||
cancel_old_payment_requests(ref_doc.doctype, ref_doc.name)
|
||||
else:
|
||||
elif not selected_payment_schedules:
|
||||
grand_total = validate_and_calculate_grand_total(grand_total, existing_payment_request_amount)
|
||||
|
||||
draft_payment_request = frappe.db.get_value(
|
||||
"Payment Request",
|
||||
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
|
||||
)
|
||||
|
||||
if draft_payment_request:
|
||||
frappe.db.set_value(
|
||||
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
|
||||
)
|
||||
pr = frappe.get_doc("Payment Request", draft_payment_request)
|
||||
|
||||
if selected_payment_schedules:
|
||||
apply_payment_references(pr, payment_reference)
|
||||
pr.save()
|
||||
|
||||
else:
|
||||
bank_account = (
|
||||
get_party_bank_account(args.get("party_type"), args.get("party"))
|
||||
@@ -648,7 +728,10 @@ def make_payment_request(**args):
|
||||
}
|
||||
)
|
||||
|
||||
# Update dimensions
|
||||
if selected_payment_schedules:
|
||||
apply_payment_references(pr, payment_reference)
|
||||
|
||||
# Dimensions
|
||||
pr.update(
|
||||
{
|
||||
"cost_center": ref_doc.get("cost_center"),
|
||||
@@ -677,6 +760,51 @@ def make_payment_request(**args):
|
||||
return pr.as_dict()
|
||||
|
||||
|
||||
def apply_payment_references(pr, payment_reference):
|
||||
existing_refs = pr.get("payment_reference") or []
|
||||
|
||||
existing_ids = {r.get("payment_schedule") for r in existing_refs if r.get("payment_schedule")}
|
||||
new_refs = [r for r in (payment_reference or []) if r.get("payment_schedule") not in existing_ids]
|
||||
pr.set("payment_reference", existing_refs + new_refs)
|
||||
pr.set("grand_total", sum(flt(r.get("amount")) for r in pr.get("payment_reference")))
|
||||
|
||||
|
||||
def set_payment_references(payment_schedules):
|
||||
payment_schedules = json.loads(payment_schedules) if payment_schedules else []
|
||||
payment_reference = []
|
||||
|
||||
for row in payment_schedules:
|
||||
payment_reference.append(
|
||||
{
|
||||
"payment_term": row.get("payment_term"),
|
||||
"payment_schedule": row.get("name"),
|
||||
"description": row.get("description"),
|
||||
"due_date": row.get("due_date"),
|
||||
"amount": row.get("payment_amount"),
|
||||
}
|
||||
)
|
||||
|
||||
return payment_reference
|
||||
|
||||
|
||||
def get_existing_payment_entry(ref_docname):
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
per = frappe.qb.DocType("Payment Entry Reference")
|
||||
|
||||
existing_pe = (
|
||||
frappe.qb.from_(pe)
|
||||
.join(per)
|
||||
.on(per.parent == pe.name)
|
||||
.select(pe.name)
|
||||
.where(pe.docstatus < 2)
|
||||
.where(per.reference_name == ref_docname)
|
||||
.limit(1)
|
||||
.run()
|
||||
)
|
||||
|
||||
return existing_pe
|
||||
|
||||
|
||||
def get_amount(ref_doc, payment_account=None):
|
||||
"""get amount based on doctype"""
|
||||
grand_total = 0
|
||||
@@ -809,7 +937,7 @@ def get_payment_gateway_account(filter):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_print_format_list(ref_doctype):
|
||||
def get_print_format_list(ref_doctype: str):
|
||||
print_format_list = ["Standard"]
|
||||
|
||||
print_format_list.extend(
|
||||
@@ -819,13 +947,13 @@ def get_print_format_list(ref_doctype):
|
||||
return {"print_format": print_format_list}
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def resend_payment_email(docname):
|
||||
@frappe.whitelist()
|
||||
def resend_payment_email(docname: str):
|
||||
return frappe.get_doc("Payment Request", docname).send_email()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_payment_entry(docname):
|
||||
def make_payment_entry(docname: str):
|
||||
doc = frappe.get_doc("Payment Request", docname)
|
||||
return doc.create_payment_entry(submit=False).as_dict()
|
||||
|
||||
@@ -918,7 +1046,7 @@ def get_dummy_message(doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_subscription_details(reference_doctype, reference_name):
|
||||
def get_subscription_details(reference_doctype: str, reference_name: str):
|
||||
if reference_doctype == "Sales Invoice":
|
||||
subscriptions = frappe.db.sql(
|
||||
"""SELECT parent as sub_name FROM `tabSubscription Invoice` WHERE invoice=%s""",
|
||||
@@ -934,7 +1062,7 @@ def get_subscription_details(reference_doctype, reference_name):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_payment_order(source_name, target_doc=None):
|
||||
def make_payment_order(source_name: str, target_doc: str | Document | None = None):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def set_missing_values(source, target):
|
||||
@@ -982,7 +1110,9 @@ def validate_payment(doc, method=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_open_payment_requests_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_open_payment_requests_query(
|
||||
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
|
||||
):
|
||||
# permission checks in `get_list()`
|
||||
filters = frappe._dict(filters)
|
||||
|
||||
@@ -1021,3 +1151,44 @@ def get_irequests_of_payment_request(doc: str | None = None) -> list:
|
||||
},
|
||||
)
|
||||
return res
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_available_payment_schedules(reference_doctype: str, reference_name: str):
|
||||
ref_doc = frappe.get_doc(reference_doctype, reference_name)
|
||||
|
||||
if not hasattr(ref_doc, "payment_schedule") or not ref_doc.payment_schedule:
|
||||
return []
|
||||
|
||||
if get_existing_payment_entry(reference_name):
|
||||
return []
|
||||
|
||||
existing_refs = get_existing_payment_references(reference_name)
|
||||
existing_ids = {r["payment_schedule"] for r in existing_refs if r.get("payment_schedule")}
|
||||
|
||||
return [r for r in ref_doc.payment_schedule if r.name not in existing_ids]
|
||||
|
||||
|
||||
def get_existing_payment_references(reference_name):
|
||||
PR = frappe.qb.DocType("Payment Request")
|
||||
PRF = frappe.qb.DocType("Payment Reference")
|
||||
|
||||
result = (
|
||||
frappe.qb.from_(PR)
|
||||
.join(PRF)
|
||||
.on(PR.name == PRF.parent)
|
||||
.select(
|
||||
PRF.payment_term,
|
||||
PRF.due_date,
|
||||
PRF.amount.as_("payment_amount"),
|
||||
PRF.payment_schedule,
|
||||
PRF.parent,
|
||||
)
|
||||
.where(PR.reference_name == reference_name)
|
||||
.where(PR.docstatus < 2)
|
||||
.where(
|
||||
PR.status.isin(["Draft", "Requested", "Initiated", "Partially Paid", "Payment Ordered", "Paid"])
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
import re
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
from frappe.utils import add_days, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_terms_template
|
||||
@@ -850,3 +852,130 @@ class TestPaymentRequest(IntegrationTestCase):
|
||||
pr.load_from_db()
|
||||
|
||||
self.assertEqual(pr.grand_total, pi.outstanding_amount)
|
||||
|
||||
def test_payment_request_grand_total_from_selected_schedules(self):
|
||||
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
|
||||
po.payment_schedule = []
|
||||
|
||||
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 30})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 30})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 2), "payment_amount": 40})
|
||||
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
schedules = json.dumps(
|
||||
[
|
||||
{
|
||||
"payment_term": row.payment_term,
|
||||
"name": row.name,
|
||||
"due_date": row.due_date,
|
||||
"payment_amount": row.payment_amount,
|
||||
"description": row.description,
|
||||
}
|
||||
for row in [po.payment_schedule[0], po.payment_schedule[2]]
|
||||
]
|
||||
)
|
||||
pr = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
pr.submit()
|
||||
|
||||
self.assertEqual(pr.grand_total, 70)
|
||||
self.assertEqual(len(pr.payment_reference), 2)
|
||||
|
||||
def test_draft_pr_reuse_merges_payment_references(self):
|
||||
from frappe.utils import add_days, nowdate
|
||||
|
||||
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
|
||||
po.payment_schedule = []
|
||||
po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 50})
|
||||
po.append("payment_schedule", {"due_date": add_days(nowdate(), 1), "payment_amount": 50})
|
||||
po.save()
|
||||
po.submit()
|
||||
schedules = json.dumps(
|
||||
[
|
||||
{
|
||||
"payment_term": row.payment_term,
|
||||
"name": row.name,
|
||||
"due_date": row.due_date,
|
||||
"payment_amount": row.payment_amount,
|
||||
"description": row.description,
|
||||
}
|
||||
for row in [po.payment_schedule[0]]
|
||||
]
|
||||
)
|
||||
pr = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
pr.save()
|
||||
schedules = json.dumps(
|
||||
[
|
||||
{
|
||||
"payment_term": row.payment_term,
|
||||
"name": row.name,
|
||||
"due_date": row.due_date,
|
||||
"payment_amount": row.payment_amount,
|
||||
"description": row.description,
|
||||
}
|
||||
for row in [po.payment_schedule[1]]
|
||||
]
|
||||
)
|
||||
# call make_payment_request again → reuse draft
|
||||
pr_reused = make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
self.assertEqual(pr.name, pr_reused.name)
|
||||
self.assertEqual(pr_reused.grand_total, 100)
|
||||
self.assertEqual(len(pr_reused.payment_reference), 2)
|
||||
|
||||
def test_schedule_pr_not_allowed_if_payment_entry_exists(self):
|
||||
po = create_purchase_order(do_not_save=1, currency="INR", qty=1, rate=100)
|
||||
po.payment_schedule = []
|
||||
row = po.append("payment_schedule", {"due_date": nowdate(), "payment_amount": 100})
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
# create PE first
|
||||
pr = make_payment_request(dt="Purchase Order", dn=po.name, mute_email=1, submit_doc=1, return_doc=1)
|
||||
pr.create_payment_entry()
|
||||
|
||||
schedules = json.dumps(
|
||||
[
|
||||
{
|
||||
"name": row.name,
|
||||
"payment_term": row.payment_term,
|
||||
"due_date": row.due_date,
|
||||
"payment_amount": row.payment_amount,
|
||||
"description": row.description,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
make_payment_request(
|
||||
dt="Purchase Order",
|
||||
dn=po.name,
|
||||
mute_email=1,
|
||||
submit_doc=False,
|
||||
return_doc=True,
|
||||
schedules=schedules,
|
||||
)
|
||||
|
||||
@@ -515,7 +515,7 @@ def delete_closing_entries(voucher_no):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_period_start_end_date(fiscal_year, company):
|
||||
def get_period_start_end_date(fiscal_year: str, company: str):
|
||||
fy_start_date, fy_end_date = frappe.db.get_value(
|
||||
"Fiscal Year", fiscal_year, ["year_start_date", "year_end_date"]
|
||||
)
|
||||
|
||||
@@ -4,19 +4,6 @@
|
||||
frappe.ui.form.on("POS Closing Entry", {
|
||||
onload: async function (frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ["POS Invoice Merge Log", "Sales Invoice"];
|
||||
frm.set_query("pos_profile", function (doc) {
|
||||
return {
|
||||
filters: { user: doc.user },
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("user", function (doc) {
|
||||
return {
|
||||
query: "erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry.get_cashiers",
|
||||
filters: { parent: doc.pos_profile },
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("pos_opening_entry", function (doc) {
|
||||
return { filters: { status: "Open", docstatus: 1 } };
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import DocType
|
||||
@@ -252,13 +254,13 @@ class POSClosingEntry(StatusUpdater):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_cashiers(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_cashiers(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
cashiers_list = frappe.get_all("POS Profile User", filters=filters, fields=["user"], as_list=1)
|
||||
return [c for c in cashiers_list]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_invoices(start, end, pos_profile, user):
|
||||
def get_invoices(start: str | datetime, end: str | datetime, pos_profile: str, user: str):
|
||||
invoice_doctype = frappe.db.get_single_value("POS Settings", "invoice_type")
|
||||
|
||||
sales_inv_query = build_invoice_query("Sales Invoice", user, pos_profile, start, end)
|
||||
|
||||
@@ -159,15 +159,16 @@
|
||||
"language",
|
||||
"column_break_84",
|
||||
"select_print_heading",
|
||||
"utm_analytics_section",
|
||||
"utm_source",
|
||||
"utm_medium",
|
||||
"column_break_bhao",
|
||||
"utm_campaign",
|
||||
"more_information",
|
||||
"inter_company_invoice_reference",
|
||||
"customer_group",
|
||||
"is_discounted",
|
||||
"col_break23",
|
||||
"utm_source",
|
||||
"utm_campaign",
|
||||
"utm_medium",
|
||||
"column_break_gpiw",
|
||||
"status",
|
||||
"more_info",
|
||||
"debit_to",
|
||||
@@ -1541,10 +1542,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Delivery Note"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gpiw",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "utm_medium",
|
||||
"fieldtype": "Link",
|
||||
@@ -1610,13 +1607,24 @@
|
||||
"hidden": 1,
|
||||
"label": "Item Wise Tax Details",
|
||||
"no_copy": 1,
|
||||
"options": "Item Wise Tax Detail"
|
||||
"options": "Item Wise Tax Detail",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "utm_analytics_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "UTM Analytics"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bhao",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-04 22:22:31.471752",
|
||||
"modified": "2026-02-10 14:23:07.181782",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_child_doc, map_doc
|
||||
from frappe.query_builder.functions import IfNull, Sum
|
||||
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
|
||||
@@ -753,7 +754,7 @@ class POSInvoice(SalesInvoice):
|
||||
return profile
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
def set_missing_values(self, for_validate: bool = False):
|
||||
profile = self.set_pos_fields(for_validate)
|
||||
|
||||
if not self.debit_to:
|
||||
@@ -854,7 +855,7 @@ class POSInvoice(SalesInvoice):
|
||||
return frappe.get_doc("Payment Request", pr)
|
||||
|
||||
@frappe.whitelist()
|
||||
def update_payments(self, payments):
|
||||
def update_payments(self, payments: list):
|
||||
if self.status == "Consolidated":
|
||||
frappe.throw(_("Create Payment Entry for Consolidated POS Invoices."))
|
||||
|
||||
@@ -897,7 +898,7 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
def get_stock_availability(item_code: str | None, warehouse: str):
|
||||
if frappe.db.get_value("Item", item_code, "is_stock_item"):
|
||||
is_stock_item = True
|
||||
bin_qty = get_bin_qty(item_code, warehouse)
|
||||
@@ -1020,14 +1021,14 @@ def get_pos_reserved_qty_from_table(child_table, item_code, warehouse):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
def make_sales_return(source_name: str, target_doc: Document | None = None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
return make_return_doc("POS Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_merge_log(invoices):
|
||||
def make_merge_log(invoices: str | list):
|
||||
import json
|
||||
|
||||
if isinstance(invoices, str):
|
||||
@@ -1077,7 +1078,15 @@ def add_return_modes(doc, pos_profile):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
def item_query(
|
||||
doctype: str,
|
||||
txt: str,
|
||||
searchfield: str,
|
||||
start: int,
|
||||
page_len: int,
|
||||
filters: dict,
|
||||
as_dict: bool = False,
|
||||
):
|
||||
if pos_profile := filters.get("pos_profile")[1]:
|
||||
pos_profile = frappe.get_cached_doc("POS Profile", pos_profile)
|
||||
if item_groups := get_item_group(pos_profile):
|
||||
|
||||
@@ -897,6 +897,53 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
|
||||
self.assertEqual(batch.qty, 5)
|
||||
|
||||
def test_pos_batch_reservation_with_return_qty(self):
|
||||
"""
|
||||
Test POS Invoice reserved qty for batch without bundle with return invoices.
|
||||
"""
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||
get_auto_batch_nos,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_batch_item_with_batch,
|
||||
)
|
||||
|
||||
create_batch_item_with_batch("_Batch Item Reserve Return", "TestBatch-RR 01")
|
||||
se = make_stock_entry(
|
||||
target="_Test Warehouse - _TC",
|
||||
item_code="_Batch Item Reserve Return",
|
||||
qty=30,
|
||||
basic_rate=100,
|
||||
)
|
||||
|
||||
se.reload()
|
||||
|
||||
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
# POS Invoice for the batch without bundle
|
||||
pos_inv = create_pos_invoice(item="_Batch Item Reserve Return", rate=300, qty=15, do_not_save=1)
|
||||
pos_inv.append(
|
||||
"payments",
|
||||
{"mode_of_payment": "Cash", "amount": 4500},
|
||||
)
|
||||
pos_inv.items[0].batch_no = batch_no
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
# POS Invoice return
|
||||
pos_return = make_sales_return(pos_inv.name)
|
||||
|
||||
pos_return.insert()
|
||||
pos_return.submit()
|
||||
|
||||
batches = get_auto_batch_nos(
|
||||
frappe._dict({"item_code": "_Batch Item Reserve Return", "warehouse": "_Test Warehouse - _TC"})
|
||||
)
|
||||
|
||||
for batch in batches:
|
||||
if batch.batch_no == batch_no and batch.warehouse == "_Test Warehouse - _TC":
|
||||
self.assertEqual(batch.qty, 30)
|
||||
|
||||
def test_pos_batch_item_qty_validation(self):
|
||||
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||
BatchNegativeStockError,
|
||||
|
||||
@@ -12,56 +12,78 @@
|
||||
"disabled",
|
||||
"column_break_9",
|
||||
"warehouse",
|
||||
"utm_source",
|
||||
"utm_campaign",
|
||||
"utm_medium",
|
||||
"company_address",
|
||||
"section_break_15",
|
||||
"applicable_for_users",
|
||||
"accounting_tab",
|
||||
"section_break_11",
|
||||
"payments",
|
||||
"set_grand_total_to_default_mop",
|
||||
"price_list_and_currency_section",
|
||||
"currency",
|
||||
"column_break_bptt",
|
||||
"selling_price_list",
|
||||
"write_off_section",
|
||||
"write_off_account",
|
||||
"column_break_ukpz",
|
||||
"write_off_cost_center",
|
||||
"column_break_pkca",
|
||||
"write_off_limit",
|
||||
"income_and_expense_account",
|
||||
"income_account",
|
||||
"column_break_byzk",
|
||||
"expense_account",
|
||||
"taxes_section",
|
||||
"taxes_and_charges",
|
||||
"column_break_cjpp",
|
||||
"tax_category",
|
||||
"section_break_19",
|
||||
"account_for_change_amount",
|
||||
"disable_rounded_total",
|
||||
"column_break_23",
|
||||
"apply_discount_on",
|
||||
"allow_partial_payment",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project",
|
||||
"pos_configurations_tab",
|
||||
"section_break_14",
|
||||
"hide_images",
|
||||
"hide_unavailable_items",
|
||||
"auto_add_item_to_cart",
|
||||
"validate_stock_on_save",
|
||||
"print_receipt_on_order_complete",
|
||||
"action_on_new_invoice",
|
||||
"validate_stock_on_save",
|
||||
"column_break_16",
|
||||
"update_stock",
|
||||
"ignore_pricing_rule",
|
||||
"print_receipt_on_order_complete",
|
||||
"pos_item_selector_section",
|
||||
"hide_images",
|
||||
"column_break_rpny",
|
||||
"hide_unavailable_items",
|
||||
"column_break_stcl",
|
||||
"auto_add_item_to_cart",
|
||||
"pos_item_details_section",
|
||||
"allow_rate_change",
|
||||
"column_break_hwfg",
|
||||
"allow_discount_change",
|
||||
"set_grand_total_to_default_mop",
|
||||
"allow_partial_payment",
|
||||
"column_break_egpi",
|
||||
"allow_warehouse_change",
|
||||
"section_break_15",
|
||||
"applicable_for_users",
|
||||
"section_break_23",
|
||||
"item_groups",
|
||||
"column_break_25",
|
||||
"customer_groups",
|
||||
"more_info_tab",
|
||||
"section_break_16",
|
||||
"print_format",
|
||||
"letter_head",
|
||||
"column_break0",
|
||||
"tc_name",
|
||||
"select_print_heading",
|
||||
"section_break_19",
|
||||
"selling_price_list",
|
||||
"currency",
|
||||
"write_off_account",
|
||||
"write_off_cost_center",
|
||||
"write_off_limit",
|
||||
"account_for_change_amount",
|
||||
"disable_rounded_total",
|
||||
"column_break_23",
|
||||
"income_account",
|
||||
"expense_account",
|
||||
"taxes_and_charges",
|
||||
"tax_category",
|
||||
"apply_discount_on",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"project"
|
||||
"utm_analytics_section",
|
||||
"utm_source",
|
||||
"column_break_tvls",
|
||||
"utm_campaign",
|
||||
"column_break_xygw",
|
||||
"utm_medium"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -130,8 +152,7 @@
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Configuration"
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"description": "Only show Items from these Item Groups",
|
||||
@@ -152,6 +173,7 @@
|
||||
"options": "POS Customer Group"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Print Settings"
|
||||
@@ -191,7 +213,7 @@
|
||||
{
|
||||
"fieldname": "section_break_19",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting"
|
||||
"label": "Miscellaneous"
|
||||
},
|
||||
{
|
||||
"fieldname": "selling_price_list",
|
||||
@@ -427,9 +449,111 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Applicable only on Transactions made using POS",
|
||||
"fieldname": "allow_partial_payment",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Partial Payment"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_tvls",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_xygw",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Accounting"
|
||||
},
|
||||
{
|
||||
"fieldname": "more_info_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "More Info"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_configurations_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "POS Configurations"
|
||||
},
|
||||
{
|
||||
"fieldname": "price_list_and_currency_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Price List & Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_bptt",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "write_off_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Write Off"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ukpz",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_pkca",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "income_and_expense_account",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Income and Expense"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_byzk",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxes_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Taxes"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_cjpp",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_item_selector_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "POS Item Selector"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_rpny",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_stcl",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_item_details_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "POS Item Details"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_hwfg",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_egpi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "allow_warehouse_change",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow User to Edit Warehouse"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "utm_analytics_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Campaign"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -458,7 +582,7 @@
|
||||
"link_fieldname": "pos_profile"
|
||||
}
|
||||
],
|
||||
"modified": "2025-06-24 11:19:19.834905",
|
||||
"modified": "2026-02-10 14:24:48.597412",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
||||
@@ -34,6 +34,7 @@ class POSProfile(Document):
|
||||
allow_discount_change: DF.Check
|
||||
allow_partial_payment: DF.Check
|
||||
allow_rate_change: DF.Check
|
||||
allow_warehouse_change: DF.Check
|
||||
applicable_for_users: DF.Table[POSProfileUser]
|
||||
apply_discount_on: DF.Literal["Grand Total", "Net Total"]
|
||||
auto_add_item_to_cart: DF.Check
|
||||
@@ -274,7 +275,7 @@ def get_child_nodes(group_type, root):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
def pos_profile_query(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
user = frappe.session["user"]
|
||||
company = filters.get("company") or frappe.defaults.get_user_default("company")
|
||||
|
||||
@@ -318,7 +319,7 @@ def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_default_profile(pos_profile, company):
|
||||
def set_default_profile(pos_profile: str, company: str):
|
||||
modified = now()
|
||||
user = frappe.session.user
|
||||
|
||||
|
||||
@@ -98,8 +98,7 @@ def get_customers_list(pos_profile=None):
|
||||
|
||||
return (
|
||||
frappe.db.sql(
|
||||
f""" select name, customer_name, customer_group,
|
||||
territory, customer_pos_id from tabCustomer where disabled = 0
|
||||
f""" select name, customer_name, customer_group, territory from tabCustomer where disabled = 0
|
||||
and {cond}""",
|
||||
tuple(customer_groups),
|
||||
as_dict=1,
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Apply On",
|
||||
"options": "\nItem Code\nItem Group\nBrand\nTransaction",
|
||||
"options": "Item Code\nItem Group\nBrand\nTransaction",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -657,7 +657,7 @@
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2025-08-20 11:40:07.096854",
|
||||
"modified": "2026-02-17 12:24:07.553505",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
@@ -714,9 +714,10 @@
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class PricingRule(Document):
|
||||
apply_discount_on: DF.Literal["Grand Total", "Net Total"]
|
||||
apply_discount_on_rate: DF.Check
|
||||
apply_multiple_pricing_rules: DF.Check
|
||||
apply_on: DF.Literal["", "Item Code", "Item Group", "Brand", "Transaction"]
|
||||
apply_on: DF.Literal["Item Code", "Item Group", "Brand", "Transaction"]
|
||||
apply_recursion_over: DF.Float
|
||||
apply_rule_on_other: DF.Literal["", "Item Code", "Item Group", "Brand"]
|
||||
brands: DF.Table[PricingRuleBrand]
|
||||
@@ -320,7 +320,7 @@ class PricingRule(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def apply_pricing_rule(args, doc=None):
|
||||
def apply_pricing_rule(args: str | dict, doc: str | dict | Document | None = None):
|
||||
"""
|
||||
args = {
|
||||
"items": [{"doctype": "", "name": "", "item_code": "", "brand": "", "item_group": ""}, ...],
|
||||
@@ -346,8 +346,7 @@ def apply_pricing_rule(args, doc=None):
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
||||
if not args.transaction_type:
|
||||
set_transaction_type(args)
|
||||
set_transaction_type(args)
|
||||
|
||||
# list of dictionaries
|
||||
out = []
|
||||
@@ -618,7 +617,12 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None):
|
||||
def remove_pricing_rule_for_item(
|
||||
pricing_rules: str | None,
|
||||
item_details: str | frappe._dict,
|
||||
item_code: str | None = None,
|
||||
rate: float | None = None,
|
||||
):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (
|
||||
get_applied_pricing_rules,
|
||||
get_pricing_rule_items,
|
||||
@@ -666,7 +670,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_pricing_rules(item_list):
|
||||
def remove_pricing_rules(item_list: str | list):
|
||||
if isinstance(item_list, str):
|
||||
item_list = json.loads(item_list)
|
||||
|
||||
@@ -683,28 +687,28 @@ def remove_pricing_rules(item_list):
|
||||
return out
|
||||
|
||||
|
||||
def set_transaction_type(args):
|
||||
if args.transaction_type:
|
||||
def set_transaction_type(pricing_ctx: frappe._dict) -> None:
|
||||
if pricing_ctx.transaction_type in ["buying", "selling"]:
|
||||
return
|
||||
if args.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
|
||||
args.transaction_type = "selling"
|
||||
elif args.doctype in (
|
||||
if pricing_ctx.doctype in ("Opportunity", "Quotation", "Sales Order", "Delivery Note", "Sales Invoice"):
|
||||
pricing_ctx.transaction_type = "selling"
|
||||
elif pricing_ctx.doctype in (
|
||||
"Material Request",
|
||||
"Supplier Quotation",
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
):
|
||||
args.transaction_type = "buying"
|
||||
elif args.customer:
|
||||
args.transaction_type = "selling"
|
||||
pricing_ctx.transaction_type = "buying"
|
||||
elif pricing_ctx.customer:
|
||||
pricing_ctx.transaction_type = "selling"
|
||||
else:
|
||||
args.transaction_type = "buying"
|
||||
pricing_ctx.transaction_type = "buying"
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_item_uoms(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_item_uoms(doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict):
|
||||
items = [filters.get("value")]
|
||||
if filters.get("apply_on") != "Item Code":
|
||||
field = frappe.scrub(filters.get("apply_on"))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"depends_on": "eval:parent.apply_on == 'Item Code'",
|
||||
"depends_on": "eval:parent.apply_on == 'Brand'",
|
||||
"fieldname": "brand",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@@ -28,14 +28,15 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:17.857046",
|
||||
"modified": "2026-02-17 12:17:13.073587",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule Brand",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"depends_on": "eval:parent.apply_on == 'Item Code'",
|
||||
"depends_on": "eval:parent.apply_on == 'Item Group'",
|
||||
"fieldname": "item_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
@@ -28,14 +28,15 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:18.221095",
|
||||
"modified": "2026-02-17 12:16:57.778471",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule Item Group",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,8 +415,9 @@ def reconcile(doc: None | str = None) -> None:
|
||||
for x in allocations:
|
||||
pr.append("allocation", x)
|
||||
|
||||
skip_ref_details_update_for_pe = check_multi_currency(pr)
|
||||
# reconcile
|
||||
pr.reconcile_allocations(skip_ref_details_update_for_pe=True)
|
||||
pr.reconcile_allocations(skip_ref_details_update_for_pe=skip_ref_details_update_for_pe)
|
||||
|
||||
# If Payment Entry, update details only for newly linked references
|
||||
# This is for performance
|
||||
@@ -504,6 +505,37 @@ def reconcile(doc: None | str = None) -> None:
|
||||
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
|
||||
|
||||
|
||||
def check_multi_currency(pr_doc):
|
||||
GL = frappe.qb.DocType("GL Entry")
|
||||
Account = frappe.qb.DocType("Account")
|
||||
|
||||
def get_account_currency(voucher_type, voucher_no):
|
||||
currency = (
|
||||
frappe.qb.from_(GL)
|
||||
.join(Account)
|
||||
.on(GL.account == Account.name)
|
||||
.select(Account.account_currency)
|
||||
.where(
|
||||
(GL.voucher_type == voucher_type)
|
||||
& (GL.voucher_no == voucher_no)
|
||||
& (Account.account_type.isin(["Payable", "Receivable"]))
|
||||
)
|
||||
.limit(1)
|
||||
).run(as_dict=True)
|
||||
|
||||
return currency[0].account_currency if currency else None
|
||||
|
||||
for allocation in pr_doc.allocation:
|
||||
reference_currency = get_account_currency(allocation.reference_type, allocation.reference_name)
|
||||
|
||||
invoice_currency = get_account_currency(allocation.invoice_type, allocation.invoice_number)
|
||||
|
||||
if reference_currency != invoice_currency:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def is_any_doc_running(for_filter: str | dict | None = None) -> str | None:
|
||||
running_doc = None
|
||||
|
||||
@@ -420,7 +420,7 @@ def get_context(customer, doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
def fetch_customers(customer_collection: str, collection_name: str, primary_mandatory: str | int):
|
||||
customer_list = []
|
||||
customers = []
|
||||
|
||||
@@ -460,7 +460,7 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
||||
def get_customer_emails(customer_name: str, primary_mandatory: str | int, billing_and_primary: bool = True):
|
||||
"""Returns first email from Contact Email table as a Billing email
|
||||
when Is Billing Contact checked
|
||||
and Primary email- email with Is Primary checked"""
|
||||
@@ -506,7 +506,7 @@ def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=Tr
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_statements(document_name):
|
||||
def download_statements(document_name: str):
|
||||
doc = frappe.get_doc("Process Statement Of Accounts", document_name)
|
||||
report = get_report_pdf(doc)
|
||||
if report:
|
||||
@@ -516,7 +516,7 @@ def download_statements(document_name):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def send_emails(document_name, from_scheduler=False, posting_date=None):
|
||||
def send_emails(document_name: str, from_scheduler: bool = False, posting_date: str | None = None):
|
||||
doc = frappe.get_doc("Process Statement Of Accounts", document_name)
|
||||
report = get_report_pdf(doc, consolidated=False)
|
||||
|
||||
|
||||
@@ -18,8 +18,19 @@ class TestProcessStatementOfAccounts(AccountsTestMixin, IntegrationTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
letterhead = frappe.get_doc("Letter Head", "Company Letterhead - Grey")
|
||||
letterhead.is_default = 0
|
||||
letterhead.save()
|
||||
cls.enterClassContext(cls.change_settings("Selling Settings", validate_selling_price=0))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super().tearDownClass()
|
||||
letterhead = frappe.get_doc("Letter Head", "Company Letterhead - Grey")
|
||||
letterhead.is_default = 1
|
||||
letterhead.save()
|
||||
frappe.db.commit() # nosemgrep
|
||||
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
|
||||
@@ -134,7 +134,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request();
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
@@ -534,7 +534,7 @@ cur_frm.fields_dict["select_print_heading"].get_query = function (doc, cdt, cdn)
|
||||
|
||||
cur_frm.set_query("wip_composite_asset", "items", function () {
|
||||
return {
|
||||
filters: { is_composite_asset: 1, docstatus: 0 },
|
||||
filters: { asset_type: "Composite Asset", docstatus: 0 },
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -85,20 +85,24 @@
|
||||
"taxes_and_charges_added",
|
||||
"taxes_and_charges_deducted",
|
||||
"total_taxes_and_charges",
|
||||
"section_break_49",
|
||||
"totals_section",
|
||||
"use_company_roundoff_cost_center",
|
||||
"grand_total",
|
||||
"in_words",
|
||||
"column_break8",
|
||||
"disable_rounded_total",
|
||||
"rounding_adjustment",
|
||||
"rounded_total",
|
||||
"base_totals_section",
|
||||
"base_grand_total",
|
||||
"base_in_words",
|
||||
"column_break_hcca",
|
||||
"base_rounding_adjustment",
|
||||
"base_rounded_total",
|
||||
"base_in_words",
|
||||
"column_break8",
|
||||
"grand_total",
|
||||
"rounding_adjustment",
|
||||
"use_company_roundoff_cost_center",
|
||||
"rounded_total",
|
||||
"in_words",
|
||||
"section_break_ttrv",
|
||||
"total_advance",
|
||||
"column_break_peap",
|
||||
"outstanding_amount",
|
||||
"disable_rounded_total",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
@@ -606,6 +610,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.items.every((item) => !item.pr_detail)",
|
||||
"fieldname": "update_stock",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Stock",
|
||||
@@ -882,15 +887,10 @@
|
||||
"options": "currency",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_49",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Totals"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Grand Total (Company Currency)",
|
||||
"label": "Grand Total",
|
||||
"oldfieldname": "grand_total",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
@@ -901,7 +901,7 @@
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rounding Adjustment (Company Currency)",
|
||||
"label": "Rounding Adjustment",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
@@ -911,7 +911,7 @@
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rounded Total (Company Currency)",
|
||||
"label": "Rounded Total",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
@@ -920,7 +920,7 @@
|
||||
{
|
||||
"fieldname": "base_in_words",
|
||||
"fieldtype": "Data",
|
||||
"label": "In Words (Company Currency)",
|
||||
"label": "In Words",
|
||||
"length": 240,
|
||||
"oldfieldname": "in_words",
|
||||
"oldfieldtype": "Data",
|
||||
@@ -1660,6 +1660,28 @@
|
||||
"fieldname": "override_tax_withholding_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Tax Withholding Entries"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_hcca",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ttrv",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_peap",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_totals_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Totals (Company Currency)"
|
||||
},
|
||||
{
|
||||
"fieldname": "totals_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Totals"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1667,7 +1689,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-15 06:41:38.237728",
|
||||
"modified": "2026-02-23 13:23:57.269770",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb, throw
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
|
||||
@@ -36,7 +37,7 @@ from erpnext.accounts.utils import get_account_currency, get_fiscal_year, update
|
||||
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
from erpnext.controllers.accounts_controller import merge_taxes, validate_account_head
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
|
||||
update_billed_amount_based_on_po,
|
||||
@@ -1745,10 +1746,6 @@ class PurchaseInvoice(BuyingController):
|
||||
project_doc.db_update()
|
||||
|
||||
def validate_supplier_invoice(self):
|
||||
if self.bill_date:
|
||||
if getdate(self.bill_date) > getdate(self.posting_date):
|
||||
frappe.throw(_("Supplier Invoice Date cannot be greater than Posting Date"))
|
||||
|
||||
if self.bill_no:
|
||||
if cint(frappe.get_single_value("Accounts Settings", "check_supplier_invoice_uniqueness")):
|
||||
fiscal_year = get_fiscal_year(self.posting_date, company=self.company, as_dict=True)
|
||||
@@ -1945,14 +1942,14 @@ def make_regional_gl_entries(gl_entries, doc):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_debit_note(source_name, target_doc=None):
|
||||
def make_debit_note(source_name: str, target_doc: str | Document | None = None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
return make_return_doc("Purchase Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_stock_entry(source_name, target_doc=None):
|
||||
def make_stock_entry(source_name: str, target_doc: str | Document | None = None):
|
||||
doc = get_mapped_doc(
|
||||
"Purchase Invoice",
|
||||
source_name,
|
||||
@@ -1970,44 +1967,54 @@ def make_stock_entry(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def change_release_date(name, release_date=None):
|
||||
def change_release_date(name: str, release_date: str | None = None):
|
||||
if frappe.db.exists("Purchase Invoice", name):
|
||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
||||
pi.db_set("release_date", release_date)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def unblock_invoice(name):
|
||||
def unblock_invoice(name: str):
|
||||
if frappe.db.exists("Purchase Invoice", name):
|
||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
||||
pi.unblock_invoice()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def block_invoice(name, release_date, hold_comment=None):
|
||||
def block_invoice(name: str, release_date: str, hold_comment: str | None = None):
|
||||
if frappe.db.exists("Purchase Invoice", name):
|
||||
pi = frappe.get_lazy_doc("Purchase Invoice", name)
|
||||
pi.block_invoice(hold_comment, release_date)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||
def make_inter_company_sales_invoice(source_name: str, target_doc: Document | None = None):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_inter_company_transaction
|
||||
|
||||
return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None, args=None):
|
||||
def make_purchase_receipt(
|
||||
source_name: str, target_doc: str | Document | None = None, args: str | dict | None = None
|
||||
):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
def post_parent_process(source_parent, target_parent):
|
||||
for row in target_parent.get("items"):
|
||||
if row.get("qty") == 0:
|
||||
target_parent.remove(row)
|
||||
remove_items_with_zero_qty(target_parent)
|
||||
set_missing_values(source_parent, target_parent)
|
||||
|
||||
def remove_items_with_zero_qty(target_parent):
|
||||
target_parent.items = [row for row in target_parent.get("items") if row.get("qty") != 0]
|
||||
|
||||
def set_missing_values(source_parent, target_parent):
|
||||
target_parent.run_method("set_missing_values")
|
||||
if args and args.get("merge_taxes"):
|
||||
merge_taxes(source_parent, target_parent)
|
||||
target_parent.run_method("calculate_taxes_and_totals")
|
||||
|
||||
def update_item(obj, target, source_parent):
|
||||
from erpnext.controllers.sales_and_purchase_return import get_returned_qty_map_for_row
|
||||
@@ -2059,7 +2066,11 @@ def make_purchase_receipt(source_name, target_doc=None, args=None):
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and select_item(doc),
|
||||
},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"reset_value": not (args and args.get("merge_taxes")),
|
||||
"ignore": args.get("merge_taxes") if args else 0,
|
||||
},
|
||||
},
|
||||
target_doc,
|
||||
post_parent_process,
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"stock_uom_rate",
|
||||
"is_free_item",
|
||||
"apply_tds",
|
||||
"allow_zero_valuation_rate",
|
||||
"section_break_22",
|
||||
"net_rate",
|
||||
"net_amount",
|
||||
@@ -97,7 +98,6 @@
|
||||
"service_start_date",
|
||||
"service_end_date",
|
||||
"reference",
|
||||
"allow_zero_valuation_rate",
|
||||
"item_tax_rate",
|
||||
"bom",
|
||||
"include_exploded_items",
|
||||
@@ -420,6 +420,7 @@
|
||||
"options": "UOM"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock",
|
||||
"fieldname": "warehouse_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Warehouse"
|
||||
@@ -447,7 +448,6 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
@@ -459,14 +459,12 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Serial No",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "rejected_serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Rejected Serial No",
|
||||
@@ -577,6 +575,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.update_stock",
|
||||
"fieldname": "allow_zero_valuation_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Zero Valuation Rate",
|
||||
@@ -800,7 +799,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.is_internal_supplier && parent.update_stock",
|
||||
"depends_on": "eval:parent.is_internal_supplier",
|
||||
"fieldname": "from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
@@ -896,7 +895,7 @@
|
||||
"label": "Consider for Tax Withholding"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@@ -906,7 +905,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "rejected_serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Rejected Serial and Batch Bundle",
|
||||
@@ -922,7 +921,7 @@
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock === 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 && doc.docstatus === 0",
|
||||
"fieldname": "add_serial_batch_bundle",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No"
|
||||
@@ -992,7 +991,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-13 14:10:02.379392",
|
||||
"modified": "2026-02-15 21:07:49.455930",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -152,7 +152,7 @@ class RepostAccountingLedger(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def start_repost(account_repost_doc=str) -> None:
|
||||
def start_repost(account_repost_doc: str | None = None) -> None:
|
||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||
|
||||
frappe.flags.through_repost_accounting_ledger = True
|
||||
@@ -286,7 +286,9 @@ def validate_docs_for_voucher_types(doc_voucher_types):
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
|
||||
def get_repost_allowed_types(
|
||||
doctype: str, txt: str, searchfield: str, start: int, page_len: int, filters: dict
|
||||
):
|
||||
filters = {"allowed": True}
|
||||
|
||||
if txt:
|
||||
|
||||
@@ -21,7 +21,7 @@ def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def start_payment_ledger_repost(docname=None):
|
||||
def start_payment_ledger_repost(docname: str | None = None):
|
||||
"""
|
||||
Repost Payment Ledger Entries for Vouchers through Background Job
|
||||
"""
|
||||
@@ -119,7 +119,7 @@ class RepostPaymentLedger(Document):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def execute_repost_payment_ledger(docname):
|
||||
def execute_repost_payment_ledger(docname: str):
|
||||
"""Repost Payment Ledger Entries by background job."""
|
||||
|
||||
job_name = "payment_ledger_repost_" + docname
|
||||
|
||||
@@ -44,6 +44,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
"Unreconcile Payment Entries",
|
||||
"Serial and Batch Bundle",
|
||||
"Bank Transaction",
|
||||
"Packing Slip",
|
||||
];
|
||||
|
||||
if (!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||
@@ -137,7 +138,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
this.frm.add_custom_button(
|
||||
__("Payment Request"),
|
||||
function () {
|
||||
me.make_payment_request();
|
||||
me.make_payment_request_with_schedule();
|
||||
},
|
||||
__("Create")
|
||||
);
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"customer_section",
|
||||
"company",
|
||||
"company_tax_id",
|
||||
"naming_series",
|
||||
"customer",
|
||||
"customer_name",
|
||||
"tax_id",
|
||||
"company",
|
||||
"company_tax_id",
|
||||
"column_break1",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
@@ -77,34 +77,38 @@
|
||||
"base_total_taxes_and_charges",
|
||||
"column_break_47",
|
||||
"total_taxes_and_charges",
|
||||
"totals",
|
||||
"totals_section",
|
||||
"use_company_roundoff_cost_center",
|
||||
"grand_total",
|
||||
"in_words",
|
||||
"column_break5",
|
||||
"disable_rounded_total",
|
||||
"rounding_adjustment",
|
||||
"rounded_total",
|
||||
"base_totals_section",
|
||||
"base_grand_total",
|
||||
"base_in_words",
|
||||
"column_break_xjag",
|
||||
"base_rounding_adjustment",
|
||||
"base_rounded_total",
|
||||
"base_in_words",
|
||||
"column_break5",
|
||||
"grand_total",
|
||||
"rounding_adjustment",
|
||||
"use_company_roundoff_cost_center",
|
||||
"rounded_total",
|
||||
"in_words",
|
||||
"section_break_vacb",
|
||||
"total_advance",
|
||||
"column_break_rdks",
|
||||
"outstanding_amount",
|
||||
"disable_rounded_total",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
"override_tax_withholding_entries",
|
||||
"tax_withholding_entries",
|
||||
"section_break_49",
|
||||
"additional_discount_section",
|
||||
"apply_discount_on",
|
||||
"base_discount_amount",
|
||||
"coupon_code",
|
||||
"is_cash_or_non_trade_discount",
|
||||
"additional_discount_account",
|
||||
"column_break_51",
|
||||
"additional_discount_percentage",
|
||||
"discount_amount",
|
||||
"is_cash_or_non_trade_discount",
|
||||
"additional_discount_account",
|
||||
"sec_tax_breakup",
|
||||
"other_charges_calculation",
|
||||
"item_wise_tax_details",
|
||||
@@ -194,13 +198,13 @@
|
||||
"column_break8",
|
||||
"unrealized_profit_loss_account",
|
||||
"against_income_account",
|
||||
"sales_team_section_break",
|
||||
"commission_section",
|
||||
"sales_partner",
|
||||
"amount_eligible_for_commission",
|
||||
"column_break10",
|
||||
"commission_rate",
|
||||
"total_commission",
|
||||
"section_break2",
|
||||
"sales_team_section",
|
||||
"sales_team",
|
||||
"edit_printing_settings",
|
||||
"letter_head",
|
||||
@@ -215,20 +219,21 @@
|
||||
"column_break_140",
|
||||
"to_date",
|
||||
"update_auto_repeat_reference",
|
||||
"utm_analytics_section",
|
||||
"utm_source",
|
||||
"utm_medium",
|
||||
"column_break_ixxw",
|
||||
"utm_campaign",
|
||||
"utm_content",
|
||||
"more_information",
|
||||
"status",
|
||||
"inter_company_invoice_reference",
|
||||
"represents_company",
|
||||
"remarks",
|
||||
"customer_group",
|
||||
"column_break_imbx",
|
||||
"utm_source",
|
||||
"utm_campaign",
|
||||
"utm_medium",
|
||||
"utm_content",
|
||||
"col_break23",
|
||||
"is_internal_customer",
|
||||
"represents_company",
|
||||
"inter_company_invoice_reference",
|
||||
"is_discounted",
|
||||
"remarks",
|
||||
"connections_tab"
|
||||
],
|
||||
"fields": [
|
||||
@@ -266,6 +271,7 @@
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Customer",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@@ -703,6 +709,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.items.every((item) => !item.dn_detail)",
|
||||
"fieldname": "update_stock",
|
||||
"fieldtype": "Check",
|
||||
"hide_days": 1,
|
||||
@@ -1072,14 +1079,6 @@
|
||||
"no_copy": 1,
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "section_break_49",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Additional Discount"
|
||||
},
|
||||
{
|
||||
"default": "Grand Total",
|
||||
"fieldname": "apply_discount_on",
|
||||
@@ -1124,22 +1123,12 @@
|
||||
"options": "currency",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "totals",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Totals",
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-money",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Grand Total (Company Currency)",
|
||||
"label": "Grand Total",
|
||||
"oldfieldname": "grand_total",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
@@ -1153,9 +1142,8 @@
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Rounding Adjustment (Company Currency)",
|
||||
"label": "Rounding Adjustment",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1165,10 +1153,9 @@
|
||||
"fieldtype": "Currency",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Rounded Total (Company Currency)",
|
||||
"label": "Rounded Total",
|
||||
"oldfieldname": "rounded_total",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1178,7 +1165,7 @@
|
||||
"fieldtype": "Small Text",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "In Words (Company Currency)",
|
||||
"label": "In Words",
|
||||
"length": 240,
|
||||
"oldfieldname": "in_words",
|
||||
"oldfieldtype": "Data",
|
||||
@@ -1271,7 +1258,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "advances",
|
||||
"fieldname": "advances_section",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -1647,13 +1633,6 @@
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break23",
|
||||
"fieldtype": "Column Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"default": "Draft",
|
||||
"fieldname": "status",
|
||||
@@ -1705,10 +1684,10 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "No",
|
||||
"fieldname": "is_opening",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Is Opening Entry",
|
||||
@@ -1737,18 +1716,6 @@
|
||||
"oldfieldtype": "Text",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "sales_partner",
|
||||
"fieldname": "sales_team_section_break",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Commission",
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-group",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_partner",
|
||||
"fieldtype": "Link",
|
||||
@@ -1792,16 +1759,6 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "sales_team",
|
||||
"fieldname": "section_break2",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Sales Team",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "sales_team",
|
||||
@@ -2250,7 +2207,8 @@
|
||||
"hidden": 1,
|
||||
"label": "Item Wise Tax Details",
|
||||
"no_copy": 1,
|
||||
"options": "Item Wise Tax Detail"
|
||||
"options": "Item Wise Tax Detail",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -2291,6 +2249,74 @@
|
||||
"fieldname": "override_tax_withholding_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Tax Withholding Entries"
|
||||
},
|
||||
{
|
||||
"fieldname": "totals_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Totals",
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-money",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_totals_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Totals (Company Currency)",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_xjag",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "additional_discount_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Additional Discount"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "sales_team",
|
||||
"fieldname": "sales_team_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Sales Team",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "sales_partner",
|
||||
"fieldname": "commission_section",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Commission",
|
||||
"oldfieldtype": "Section Break",
|
||||
"options": "fa fa-group",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ixxw",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "utm_analytics_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "UTM Analytics"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_vacb",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_rdks",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -2304,7 +2330,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2025-12-24 18:29:50.242618",
|
||||
"modified": "2026-02-28 17:58:56.453076",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -6,6 +6,7 @@ import frappe
|
||||
import frappe.utils
|
||||
from frappe import _, msgprint, throw
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.query_builder import Case
|
||||
@@ -119,7 +120,7 @@ class SalesInvoice(SellingController):
|
||||
cost_center: DF.Link | None
|
||||
coupon_code: DF.Link | None
|
||||
currency: DF.Link
|
||||
customer: DF.Link | None
|
||||
customer: DF.Link
|
||||
customer_address: DF.Link | None
|
||||
customer_group: DF.Link | None
|
||||
customer_name: DF.SmallText | None
|
||||
@@ -741,7 +742,7 @@ class SalesInvoice(SellingController):
|
||||
pos_invoice_doc.cancel()
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
def set_missing_values(self, for_validate: bool = False):
|
||||
pos = self.set_pos_fields(for_validate)
|
||||
|
||||
if not self.debit_to:
|
||||
@@ -1451,6 +1452,9 @@ class SalesInvoice(SellingController):
|
||||
return asset_qty_map
|
||||
|
||||
def process_asset_depreciation(self):
|
||||
if self.is_internal_transfer():
|
||||
return
|
||||
|
||||
if (self.is_return and self.docstatus == 2) or (not self.is_return and self.docstatus == 1):
|
||||
self.depreciate_asset_on_sale()
|
||||
else:
|
||||
@@ -2409,7 +2413,7 @@ def get_list_context(context=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bank_cash_account(mode_of_payment, company):
|
||||
def get_bank_cash_account(mode_of_payment: str, company: str):
|
||||
account = frappe.db.get_value(
|
||||
"Mode of Payment Account", {"parent": mode_of_payment, "company": company}, "default_account"
|
||||
)
|
||||
@@ -2424,7 +2428,7 @@ def get_bank_cash_account(mode_of_payment, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_maintenance_schedule(source_name, target_doc=None):
|
||||
def make_maintenance_schedule(source_name: str, target_doc: str | Document | None = None):
|
||||
doclist = get_mapped_doc(
|
||||
"Sales Invoice",
|
||||
source_name,
|
||||
@@ -2441,7 +2445,7 @@ def make_maintenance_schedule(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_delivery_note(source_name, target_doc=None):
|
||||
def make_delivery_note(source_name: str, target_doc: Document | None = None):
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("set_po_nos")
|
||||
@@ -2490,7 +2494,7 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
def make_sales_return(source_name: str, target_doc: Document | None = None):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
return make_return_doc("Sales Invoice", source_name, target_doc)
|
||||
@@ -2584,7 +2588,7 @@ def validate_inter_company_transaction(doc, doctype):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_inter_company_purchase_invoice(source_name, target_doc=None):
|
||||
def make_inter_company_purchase_invoice(source_name: str, target_doc: Document | None = None):
|
||||
return make_inter_company_transaction("Sales Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
@@ -2962,7 +2966,7 @@ def update_address(doc, address_field, address_display_field, address_name):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_loyalty_programs(customer):
|
||||
def get_loyalty_programs(customer: str):
|
||||
"""sets applicable loyalty program to the customer or returns a list of applicable programs"""
|
||||
from erpnext.selling.doctype.customer.customer import get_loyalty_programs
|
||||
|
||||
@@ -2980,7 +2984,7 @@ def get_loyalty_programs(customer):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_invoice_discounting(source_name, target_doc=None):
|
||||
def create_invoice_discounting(source_name: str, target_doc: str | Document | None = None):
|
||||
invoice = frappe.get_doc("Sales Invoice", source_name)
|
||||
invoice_discounting = frappe.new_doc("Invoice Discounting")
|
||||
invoice_discounting.company = invoice.company
|
||||
@@ -3072,7 +3076,9 @@ def get_mode_of_payment_info(mode_of_payment, company):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_dunning(source_name, target_doc=None, ignore_permissions=False):
|
||||
def create_dunning(
|
||||
source_name: str, target_doc: str | Document | None = None, ignore_permissions: bool = False
|
||||
):
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
|
||||
def postprocess_dunning(source, target):
|
||||
|
||||
@@ -4739,6 +4739,66 @@ class TestSalesInvoice(ERPNextTestSuite):
|
||||
|
||||
doc.db_set("do_not_use_batchwise_valuation", original_value)
|
||||
|
||||
@change_settings("Selling Settings", {"set_zero_rate_for_expired_batch": True})
|
||||
def test_zero_valuation_for_standalone_credit_note_with_expired_batch(self):
|
||||
item_code = "_Test Item for Expiry Batch Zero Valuation"
|
||||
make_item_for_si(
|
||||
item_code,
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"has_expiry_date": 1,
|
||||
"shelf_life_in_days": 2,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TBATCH-EBZV.####",
|
||||
},
|
||||
)
|
||||
|
||||
se = make_stock_entry(
|
||||
item_code=item_code,
|
||||
qty=10,
|
||||
target="_Test Warehouse - _TC",
|
||||
rate=100,
|
||||
)
|
||||
|
||||
# fetch batch no from bundle
|
||||
batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
si = create_sales_invoice(
|
||||
posting_date=add_days(nowdate(), 3),
|
||||
item=item_code,
|
||||
qty=-10,
|
||||
rate=100,
|
||||
is_return=1,
|
||||
update_stock=1,
|
||||
use_serial_batch_fields=1,
|
||||
do_not_save=1,
|
||||
do_not_submit=1,
|
||||
)
|
||||
|
||||
si.items[0].batch_no = batch_no
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
si.reload()
|
||||
# check zero incoming rate in voucher
|
||||
self.assertEqual(si.items[0].incoming_rate, 0.0)
|
||||
|
||||
# chekc zero incoming rate in stock ledger
|
||||
stock_ledger_entry = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si.name,
|
||||
"item_code": item_code,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
},
|
||||
["incoming_rate", "valuation_rate"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
self.assertEqual(stock_ledger_entry.incoming_rate, 0.0)
|
||||
|
||||
|
||||
def make_item_for_si(item_code, properties=None):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"is_free_item",
|
||||
"apply_tds",
|
||||
"grant_commission",
|
||||
"allow_zero_valuation_rate",
|
||||
"section_break_21",
|
||||
"net_rate",
|
||||
"net_amount",
|
||||
@@ -88,7 +89,6 @@
|
||||
"serial_and_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"col_break5",
|
||||
"allow_zero_valuation_rate",
|
||||
"incoming_rate",
|
||||
"item_tax_rate",
|
||||
"actual_batch_qty",
|
||||
@@ -580,6 +580,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.serial_no || doc.batch_no",
|
||||
"depends_on": "eval:parent.update_stock",
|
||||
"fieldname": "warehouse_and_reference",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Stock Details"
|
||||
@@ -595,7 +596,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: parent.is_internal_customer && parent.update_stock",
|
||||
"depends_on": "eval: parent.is_internal_customer",
|
||||
"fieldname": "target_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
@@ -613,7 +614,6 @@
|
||||
"options": "Quality Inspection"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
@@ -626,6 +626,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.update_stock",
|
||||
"fieldname": "allow_zero_valuation_rate",
|
||||
"fieldtype": "Check",
|
||||
"label": "Allow Zero Valuation Rate",
|
||||
@@ -633,7 +634,6 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Serial No",
|
||||
@@ -843,6 +843,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Incoming Rate (Costing)",
|
||||
"no_copy": 1,
|
||||
"non_negative": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -906,7 +907,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 || doc.docstatus === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@@ -916,7 +917,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock === 1",
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 0 && doc.docstatus === 0",
|
||||
"fieldname": "pick_serial_and_batch",
|
||||
"fieldtype": "Button",
|
||||
"label": "Pick Serial / Batch No"
|
||||
@@ -1009,7 +1010,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-09-04 11:08:25.583561",
|
||||
"modified": "2026-02-23 14:37:14.853941",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:parent.doctype == 'Sales Invoice'",
|
||||
"depends_on": "eval: [\"POS Invoice\", \"Sales Invoice\"].includes(parent.doctype)",
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
@@ -85,14 +85,15 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:36.427565",
|
||||
"modified": "2026-02-16 20:46:34.592604",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Payment",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,14 +342,14 @@ class ShareTransfer(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_jv_entry(
|
||||
company,
|
||||
account,
|
||||
amount,
|
||||
payment_account,
|
||||
credit_applicant_type,
|
||||
credit_applicant,
|
||||
debit_applicant_type,
|
||||
debit_applicant,
|
||||
company: str,
|
||||
account: str,
|
||||
amount: float,
|
||||
payment_account: str,
|
||||
credit_applicant_type: str,
|
||||
credit_applicant: str,
|
||||
debit_applicant_type: str,
|
||||
debit_applicant: str,
|
||||
):
|
||||
journal_entry = frappe.new_doc("Journal Entry")
|
||||
journal_entry.voucher_type = "Journal Entry"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from dateutil import relativedelta
|
||||
@@ -43,7 +44,13 @@ class SubscriptionPlan(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_plan_rate(
|
||||
plan, quantity=1, customer=None, start_date=None, end_date=None, prorate_factor=1, party=None
|
||||
plan: str,
|
||||
quantity: int = 1,
|
||||
customer: str | None = None,
|
||||
start_date: str | date | None = None,
|
||||
end_date: str | date | None = None,
|
||||
prorate_factor: float = 1,
|
||||
party: str | None = None,
|
||||
):
|
||||
plan = frappe.get_doc("Subscription Plan", plan)
|
||||
if plan.price_determination == "Fixed Rate":
|
||||
|
||||
@@ -8,6 +8,8 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.contacts.doctype.address.address import get_default_address
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder import DocType
|
||||
from frappe.query_builder.functions import IfNull
|
||||
from frappe.utils import cstr
|
||||
from frappe.utils.nestedset import get_root_of
|
||||
|
||||
@@ -83,6 +85,8 @@ class TaxRule(Document):
|
||||
frappe.throw(_("Tax Template is mandatory."))
|
||||
|
||||
def validate_filters(self):
|
||||
TaxRule = DocType("Tax Rule")
|
||||
|
||||
filters = {
|
||||
"tax_type": self.tax_type,
|
||||
"customer": self.customer,
|
||||
@@ -105,37 +109,38 @@ class TaxRule(Document):
|
||||
"company": self.company,
|
||||
}
|
||||
|
||||
conds = ""
|
||||
for d in filters:
|
||||
if conds:
|
||||
conds += " and "
|
||||
conds += f"""ifnull({d}, '') = {frappe.db.escape(cstr(filters[d]))}"""
|
||||
|
||||
if self.from_date and self.to_date:
|
||||
conds += f""" and ((from_date > '{self.from_date}' and from_date < '{self.to_date}') or
|
||||
(to_date > '{self.from_date}' and to_date < '{self.to_date}') or
|
||||
('{self.from_date}' > from_date and '{self.from_date}' < to_date) or
|
||||
('{self.from_date}' = from_date and '{self.to_date}' = to_date))"""
|
||||
|
||||
elif self.from_date and not self.to_date:
|
||||
conds += f""" and to_date > '{self.from_date}'"""
|
||||
|
||||
elif self.to_date and not self.from_date:
|
||||
conds += f""" and from_date < '{self.to_date}'"""
|
||||
|
||||
tax_rule = frappe.db.sql(
|
||||
f"select name, priority \
|
||||
from `tabTax Rule` where {conds} and name != '{self.name}'",
|
||||
as_dict=1,
|
||||
query = (
|
||||
frappe.qb.from_(TaxRule).select(TaxRule.name, TaxRule.priority).where(TaxRule.name != self.name)
|
||||
)
|
||||
|
||||
if tax_rule:
|
||||
if tax_rule[0].priority == self.priority:
|
||||
frappe.throw(_("Tax Rule Conflicts with {0}").format(tax_rule[0].name), ConflictingTaxRule)
|
||||
for field, value in filters.items():
|
||||
query = query.where(IfNull(TaxRule[field], "") == cstr(value))
|
||||
|
||||
if self.from_date and self.to_date:
|
||||
query = query.where(
|
||||
((TaxRule.from_date > self.from_date) & (TaxRule.from_date < self.to_date))
|
||||
| ((TaxRule.to_date > self.from_date) & (TaxRule.to_date < self.to_date))
|
||||
| ((self.from_date > TaxRule.from_date) & (self.from_date < TaxRule.to_date))
|
||||
| ((TaxRule.from_date == self.from_date) & (TaxRule.to_date == self.to_date))
|
||||
)
|
||||
|
||||
elif self.from_date:
|
||||
query = query.where(TaxRule.to_date > self.from_date)
|
||||
|
||||
elif self.to_date:
|
||||
query = query.where(TaxRule.from_date < self.to_date)
|
||||
|
||||
tax_rule = query.run(as_dict=True)
|
||||
|
||||
if tax_rule and tax_rule[0].priority == self.priority:
|
||||
frappe.throw(
|
||||
_("Tax Rule Conflicts with {0}").format(tax_rule[0].name),
|
||||
ConflictingTaxRule,
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_details(party, party_type, args=None):
|
||||
def get_party_details(party: str | None, party_type: str, args: dict | None = None):
|
||||
out = {}
|
||||
billing_address, shipping_address = None, None
|
||||
if args:
|
||||
|
||||
@@ -43,16 +43,18 @@
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:55.008837",
|
||||
"modified": "2025-11-14 16:17:25.584675",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Transaction Deletion Record Details",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ def get_linked_advances(company, docname):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_unreconcile_doc_for_selection(selections=None):
|
||||
def create_unreconcile_doc_for_selection(selections: str | None = None):
|
||||
if selections:
|
||||
selections = json.loads(selections)
|
||||
# assuming each row is a unique voucher
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"allow_roles": [
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
}
|
||||
],
|
||||
"creation": "2026-02-22 18:26:42.015787",
|
||||
"docstatus": 0,
|
||||
"doctype": "Module Onboarding",
|
||||
"idx": 4,
|
||||
"is_complete": 0,
|
||||
"modified": "2026-02-23 22:51:34.267812",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Onboarding",
|
||||
"owner": "Administrator",
|
||||
"steps": [
|
||||
{
|
||||
"step": "Chart of Accounts"
|
||||
},
|
||||
{
|
||||
"step": "Setup Sales taxes"
|
||||
},
|
||||
{
|
||||
"step": "Create Sales Invoice"
|
||||
},
|
||||
{
|
||||
"step": "Create Payment Entry"
|
||||
},
|
||||
{
|
||||
"step": "View Balance Sheet"
|
||||
},
|
||||
{
|
||||
"step": "Review Accounts Settings"
|
||||
}
|
||||
],
|
||||
"title": "Accounting Onboarding"
|
||||
}
|
||||
@@ -1,3 +1,43 @@
|
||||
<h3>{{ _("Fiscal Year") }}</h3>
|
||||
<h4>{{ _("New Fiscal Year - {0}").format(doc.name) }}</h4>
|
||||
|
||||
<p>{{ _("New fiscal year created :- ") }} {{ doc.name }}</p>
|
||||
<p>{{ _("A new fiscal year has been automatically created.") }}</p>
|
||||
|
||||
<p>{{ _("Fiscal Year Details") }}</p>
|
||||
|
||||
<table style="margin-bottom: 1rem; width: 70%">
|
||||
<tr>
|
||||
<td style="font-weight:bold; width: 40%">{{ _("Year Name") }}</td>
|
||||
<td>{{ doc.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-weight:bold; width: 40%">{{ _("Start Date") }}</td>
|
||||
<td>{{ frappe.format_value(doc.year_start_date) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="font-weight:bold; width: 40%">{{ _("End Date") }}</td>
|
||||
<td>{{ frappe.format_value(doc.year_end_date) }}</td>
|
||||
</tr>
|
||||
{% if doc.companies|length > 0 %}
|
||||
<tr>
|
||||
<td style="vertical-align: top; font-weight: bold; width: 40%" rowspan="{{ doc.companies|length }}">
|
||||
{% if doc.companies|length < 2 %}
|
||||
{{ _("Company") }}
|
||||
{% else %}
|
||||
{{ _("Companies") }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ doc.companies[0].company }}</td>
|
||||
</tr>
|
||||
{% for idx in range(1, doc.companies|length) %}
|
||||
<tr>
|
||||
<td>{{ doc.companies[idx].company }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</table>
|
||||
|
||||
{% if doc.disabled %}
|
||||
<p>{{ _("The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status.") }}</p>
|
||||
{% endif %}
|
||||
|
||||
<p>{{ _("Please review the {0} configuration and complete any required financial setup activities.").format(frappe.utils.get_link_to_form("Fiscal Year", doc.name, frappe.bold("Fiscal Year"))) }}</p>
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"attach_print": 0,
|
||||
"channel": "Email",
|
||||
"condition": "doc.auto_created",
|
||||
"condition": "doc.auto_created == 1",
|
||||
"condition_type": "Python",
|
||||
"creation": "2018-04-25 14:19:05.440361",
|
||||
"days_in_advance": 0,
|
||||
"docstatus": 0,
|
||||
@@ -11,8 +12,10 @@
|
||||
"event": "New",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"message": "<h4>{{ _(\"New Fiscal Year - {0}\").format(doc.name) }}</h4>\n\n<p>{{ _(\"A new fiscal year has been automatically created.\") }}</p>\n\n<p>{{ _(\"Fiscal Year Details\") }}</p>\n\n<table style=\"margin-bottom: 1rem; width: 70%\">\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"Year Name\") }}</td>\n <td>{{ doc.name }}</td>\n </tr>\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"Start Date\") }}</td>\n <td>{{ frappe.format_value(doc.year_start_date) }}</td>\n </tr>\n <tr>\n <td style=\"font-weight:bold; width: 40%\">{{ _(\"End Date\") }}</td>\n <td>{{ frappe.format_value(doc.year_end_date) }}</td>\n </tr>\n {% if doc.companies|length > 0 %}\n <tr>\n <td style=\"vertical-align: top; font-weight: bold; width: 40%\" rowspan=\"{{ doc.companies|length }}\">\n {% if doc.companies|length < 2 %}\n {{ _(\"Company\") }}\n {% else %}\n {{ _(\"Companies\") }}\n {% endif %}\n </td>\n <td>{{ doc.companies[0].company }}</td>\n </tr>\n {% for idx in range(1, doc.companies|length) %}\n <tr>\n <td>{{ doc.companies[idx].company }}</td>\n </tr>\n {% endfor %}\n {% endif %}\n</table>\n\n{% if doc.disabled %}\n<p>{{ _(\"The fiscal year has been automatically created in a Disabled state to maintain consistency with the previous fiscal year's status.\") }}</p>\n{% endif %}\n\n<p>{{ _(\"Please review the {0} configuration and complete any required financial setup activities.\").format(frappe.utils.get_link_to_form(\"Fiscal Year\", doc.name, frappe.bold(\"Fiscal Year\"))) }}</p>",
|
||||
"message_type": "HTML",
|
||||
"modified": "2023-11-17 08:54:51.532104",
|
||||
"minutes_offset": 0,
|
||||
"modified": "2026-02-23 17:37:03.755394",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Notification for new fiscal year",
|
||||
@@ -27,5 +30,5 @@
|
||||
],
|
||||
"send_system_notification": 0,
|
||||
"send_to_all_assignees": 0,
|
||||
"subject": "Notification for new fiscal year {{ doc.name }}"
|
||||
"subject": "New Fiscal Year {{ doc.name }} - Review Required"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"action": "Go to Page",
|
||||
"action_label": "Configure Chart of Accounts",
|
||||
"creation": "2026-02-22 18:28:15.401383",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 1,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 22:44:45.540780",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Chart of Accounts",
|
||||
"owner": "Administrator",
|
||||
"path": "Tree/Account",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Review Chart of Accounts",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Create Payment Entry",
|
||||
"creation": "2026-02-23 19:22:12.005360",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2026-02-23 20:19:56.482245",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Payment Entry",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Payment Entry",
|
||||
"route_options": "{\n \"payment_type\": \"Receive\",\n \"party_type\": \"Customer\"\n}",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create Payment Entry",
|
||||
"validate_action": 1
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user