Compare commits
940 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e6f49209f | ||
|
|
e079a1bb33 | ||
|
|
e8abb201c1 | ||
|
|
0c6ca09e06 | ||
|
|
f6d6897f34 | ||
|
|
a0df79ee8c | ||
|
|
2f775ee53c | ||
|
|
a51e230f4a | ||
|
|
9997cce1ea | ||
|
|
cc73e7d6fa | ||
|
|
f225196028 | ||
|
|
f40da4ac4c | ||
|
|
683d18dd95 | ||
|
|
9ea5072c52 | ||
|
|
b24c0bfbf9 | ||
|
|
03f4db0606 | ||
|
|
72715956f1 | ||
|
|
59bf2df28e | ||
|
|
7ff6865817 | ||
|
|
c00d851a88 | ||
|
|
740d5c6c53 | ||
|
|
01c89eaad9 | ||
|
|
2f350bf450 | ||
|
|
9513d61a50 | ||
|
|
51ae46f0de | ||
|
|
2f89b5b38e | ||
|
|
41c5357602 | ||
|
|
bd8699b8a9 | ||
|
|
fea29ae8cb | ||
|
|
7be9f8dab1 | ||
|
|
0bad696faf | ||
|
|
07d9f3f74b | ||
|
|
4acbeecbbe | ||
|
|
621927d9f9 | ||
|
|
fad426fd5c | ||
|
|
74b3fc1e1c | ||
|
|
1e8f598ba5 | ||
|
|
dd0a8f20e2 | ||
|
|
fedee0e8da | ||
|
|
f07f7e9305 | ||
|
|
1cb2af00a8 | ||
|
|
9965af166e | ||
|
|
1ef1daf383 | ||
|
|
0003938f2b | ||
|
|
01aada6c90 | ||
|
|
ae41b53cee | ||
|
|
470c7e773f | ||
|
|
10473b1195 | ||
|
|
bf03671a33 | ||
|
|
f60c3f0655 | ||
|
|
432d8efa3d | ||
|
|
caacd0ad2c | ||
|
|
f7748e4109 | ||
|
|
c8a825c478 | ||
|
|
13d7043451 | ||
|
|
fe4f58d0f6 | ||
|
|
8e8434a78a | ||
|
|
9ac63da457 | ||
|
|
d53991857c | ||
|
|
eb0dea09bb | ||
|
|
01be96adb2 | ||
|
|
bf462abb00 | ||
|
|
afafe406c8 | ||
|
|
8f3c7ab402 | ||
|
|
e5a1bb7141 | ||
|
|
3888488b36 | ||
|
|
257cbd3b92 | ||
|
|
a82e9e42e1 | ||
|
|
cf28950327 | ||
|
|
8f945a9852 | ||
|
|
00f90c50c0 | ||
|
|
ea2470d174 | ||
|
|
a01264dae7 | ||
|
|
f130f8385e | ||
|
|
2bdc0e8993 | ||
|
|
0bd190b885 | ||
|
|
11d56903e7 | ||
|
|
67c7067778 | ||
|
|
422325bb74 | ||
|
|
3636efd7e6 | ||
|
|
5e99aa7f65 | ||
|
|
7f794cc0ea | ||
|
|
c8eca8a448 | ||
|
|
f0b62f70d5 | ||
|
|
842674ce79 | ||
|
|
dfe11aca6a | ||
|
|
7b781b1498 | ||
|
|
57d06a86f8 | ||
|
|
4f51b56928 | ||
|
|
fa9e67502c | ||
|
|
9b6d9a41f4 | ||
|
|
15b336df28 | ||
|
|
c69bc54297 | ||
|
|
5638fbb1aa | ||
|
|
db682d9e4c | ||
|
|
75fdf79376 | ||
|
|
a0599e5ac2 | ||
|
|
22c2d4f118 | ||
|
|
ddf87594a1 | ||
|
|
ea5b7d38bb | ||
|
|
cd036e115b | ||
|
|
3105332e3c | ||
|
|
63096014a7 | ||
|
|
311e277204 | ||
|
|
73db919c99 | ||
|
|
4503a38361 | ||
|
|
b6076f772d | ||
|
|
c0817838d9 | ||
|
|
877597bc16 | ||
|
|
18533e381a | ||
|
|
ad6f20c5c7 | ||
|
|
5173e74a04 | ||
|
|
d4146a91d6 | ||
|
|
ba2c3c776f | ||
|
|
752f099e9d | ||
|
|
d8bc514226 | ||
|
|
c2b5562a3f | ||
|
|
eee03fcbab | ||
|
|
117cf4eca6 | ||
|
|
d7c4818a5d | ||
|
|
9a4926bb0d | ||
|
|
f99f872946 | ||
|
|
87b4e6ea32 | ||
|
|
c14fc9530c | ||
|
|
a856624ccb | ||
|
|
0bfd56e615 | ||
|
|
68c697b354 | ||
|
|
cf4e29a604 | ||
|
|
4df54cc62c | ||
|
|
8492bf040d | ||
|
|
d482c41eeb | ||
|
|
815e6ec846 | ||
|
|
2a1d1e1cb1 | ||
|
|
713c92e6fd | ||
|
|
46b67b901b | ||
|
|
a287b22b55 | ||
|
|
61690775a8 | ||
|
|
9181dde86a | ||
|
|
1f10a99910 | ||
|
|
91dcc07e26 | ||
|
|
09b081ccad | ||
|
|
2d1c4fee1d | ||
|
|
9bcc402f41 | ||
|
|
7d7d797ffc | ||
|
|
ebc46c1e09 | ||
|
|
cf445eb7b4 | ||
|
|
1e5482cedd | ||
|
|
5abf905ff7 | ||
|
|
478360397d | ||
|
|
5d5dc56f94 | ||
|
|
c1853ddf73 | ||
|
|
170f2ad056 | ||
|
|
9f051a24db | ||
|
|
e5c47f8957 | ||
|
|
6588a936d5 | ||
|
|
9af3f12411 | ||
|
|
b4e7ee0e45 | ||
|
|
5474c09c70 | ||
|
|
9e43445f36 | ||
|
|
6ec8875434 | ||
|
|
ff4aadc657 | ||
|
|
bb0b1e61d6 | ||
|
|
6eb8d19cc9 | ||
|
|
cd36ba7e64 | ||
|
|
2f0298ee11 | ||
|
|
e955a4ee72 | ||
|
|
2ca9b765ef | ||
|
|
532a224c44 | ||
|
|
58daf5f916 | ||
|
|
43f85a2877 | ||
|
|
8958f55890 | ||
|
|
21e44b19f0 | ||
|
|
67768faaef | ||
|
|
b4b76b75cd | ||
|
|
970d2af7ad | ||
|
|
1564d39bf4 | ||
|
|
e3ca177828 | ||
|
|
bdba29bab5 | ||
|
|
5708b7140b | ||
|
|
b3a0a7b432 | ||
|
|
1658107a92 | ||
|
|
e21e435a0d | ||
|
|
a57976660a | ||
|
|
9382b1f154 | ||
|
|
11591b3e3e | ||
|
|
27797ffa46 | ||
|
|
2330c41cca | ||
|
|
57307443f0 | ||
|
|
c878389050 | ||
|
|
6a9798f305 | ||
|
|
fcab53b238 | ||
|
|
6e81489095 | ||
|
|
f6dce4df73 | ||
|
|
1ca8f6a51d | ||
|
|
81d164134d | ||
|
|
9dc625c1c9 | ||
|
|
79daed00c1 | ||
|
|
755ebdf582 | ||
|
|
da82bd4b51 | ||
|
|
dbdf2515cd | ||
|
|
4704003c94 | ||
|
|
b5d1a7731c | ||
|
|
c1954ec72a | ||
|
|
7a8a449ed4 | ||
|
|
3b12601461 | ||
|
|
e39c3f2e69 | ||
|
|
ea70f6f933 | ||
|
|
54cc1dedf2 | ||
|
|
bf3e191570 | ||
|
|
b5ffaff6a8 | ||
|
|
f79a72dbf3 | ||
|
|
7fd44907ba | ||
|
|
98e98d25e6 | ||
|
|
ca2e914715 | ||
|
|
820a579051 | ||
|
|
4f0e6cd911 | ||
|
|
f913838373 | ||
|
|
9f305e983c | ||
|
|
ea2408744a | ||
|
|
5884f1aeb0 | ||
|
|
26bec9d7b4 | ||
|
|
ce99701150 | ||
|
|
a4f5dcaa9a | ||
|
|
6b15a05de4 | ||
|
|
437e972108 | ||
|
|
9d8e8f8bdf | ||
|
|
bd9317956b | ||
|
|
bbe64b5604 | ||
|
|
a9b5dc6030 | ||
|
|
f5fa1ee4b6 | ||
|
|
5e49bee755 | ||
|
|
03b629c851 | ||
|
|
4cf0b02ddc | ||
|
|
3007c9900b | ||
|
|
7e006496dd | ||
|
|
c9c1d10435 | ||
|
|
1f7b95f390 | ||
|
|
078826d510 | ||
|
|
f913e0dd05 | ||
|
|
b4863bfa50 | ||
|
|
1b9b72d12e | ||
|
|
0bfffddac4 | ||
|
|
d802d73973 | ||
|
|
9ec0ded28f | ||
|
|
35e11fbea6 | ||
|
|
208b5f9e73 | ||
|
|
40a4330ec1 | ||
|
|
44815393b3 | ||
|
|
673470705b | ||
|
|
fc98abece9 | ||
|
|
ea2c02738d | ||
|
|
c05496a5a7 | ||
|
|
219a87d530 | ||
|
|
889140fd8c | ||
|
|
cfdda21dd2 | ||
|
|
703c30f5f8 | ||
|
|
9f15ebe711 | ||
|
|
f97206b3cb | ||
|
|
ea0dea46e0 | ||
|
|
49ec0e5ac3 | ||
|
|
d6eacf556d | ||
|
|
8347eb1dba | ||
|
|
773aabae44 | ||
|
|
3956f82e7c | ||
|
|
6495a4d2ed | ||
|
|
94484d7766 | ||
|
|
9cab26b7bf | ||
|
|
e78364c191 | ||
|
|
4b32ccb124 | ||
|
|
fb89008a13 | ||
|
|
99531a35e0 | ||
|
|
582f187726 | ||
|
|
ab3cd7e283 | ||
|
|
29a1a8d2d6 | ||
|
|
3d7f660bec | ||
|
|
f5db407461 | ||
|
|
5cc3f14506 | ||
|
|
ddb0ec261a | ||
|
|
2fb5291785 | ||
|
|
9a2db0b5b1 | ||
|
|
a94b89727c | ||
|
|
a2bb1b8acd | ||
|
|
8c844e4515 | ||
|
|
8520edc952 | ||
|
|
be881c743e | ||
|
|
81c97c13ce | ||
|
|
ef6e005c04 | ||
|
|
3d8f82459b | ||
|
|
b066fe9519 | ||
|
|
a58b571ccb | ||
|
|
3bb9f4a742 | ||
|
|
ef97269386 | ||
|
|
59e2e4989b | ||
|
|
ddef85ae97 | ||
|
|
f9390f596d | ||
|
|
d3ccb024f8 | ||
|
|
510077b3d4 | ||
|
|
b8442816ca | ||
|
|
913e92c29f | ||
|
|
5c19a9251f | ||
|
|
6f9de8c86f | ||
|
|
41b7c1aec0 | ||
|
|
3b1b4684ba | ||
|
|
8c73f6f19e | ||
|
|
60ce00531d | ||
|
|
86f689e54a | ||
|
|
b1c72da7d7 | ||
|
|
1fd80992d7 | ||
|
|
d70c6996f2 | ||
|
|
53a9ac4466 | ||
|
|
79dc0f0afc | ||
|
|
fb9cb78980 | ||
|
|
a97556fa8d | ||
|
|
24a88f6cf6 | ||
|
|
1e2df2c109 | ||
|
|
b2f2d0e749 | ||
|
|
b5a1491176 | ||
|
|
95bc141531 | ||
|
|
433815daba | ||
|
|
4afda3c89c | ||
|
|
a717b5ad6e | ||
|
|
5149101045 | ||
|
|
bbf6121bb5 | ||
|
|
ac52daa14f | ||
|
|
5ef9a62917 | ||
|
|
302855e160 | ||
|
|
4cd0f6ce23 | ||
|
|
cf1fc40b06 | ||
|
|
75b30efb05 | ||
|
|
27ec51f021 | ||
|
|
18833b5ea1 | ||
|
|
3b070d1b05 | ||
|
|
9eac4d0af6 | ||
|
|
8718013c96 | ||
|
|
0511ffcf30 | ||
|
|
ad58182395 | ||
|
|
c8ed863454 | ||
|
|
856484aeb0 | ||
|
|
580346360f | ||
|
|
28cdff10cf | ||
|
|
cf349aadf9 | ||
|
|
17550fb4bf | ||
|
|
efd7d584b2 | ||
|
|
400205cc8a | ||
|
|
d7d5c4c329 | ||
|
|
c4d851e45f | ||
|
|
a9c84f749a | ||
|
|
5bb89b0ead | ||
|
|
ec4a3723cc | ||
|
|
687ad9b942 | ||
|
|
42d72b55b8 | ||
|
|
d17217c4e2 | ||
|
|
27299cfcc5 | ||
|
|
002ec69b2f | ||
|
|
3b34fc4cce | ||
|
|
5434dd048b | ||
|
|
0d49b6c211 | ||
|
|
9a67141bfb | ||
|
|
d52539798c | ||
|
|
9a243ed469 | ||
|
|
a5c3427293 | ||
|
|
88176e65e4 | ||
|
|
137d08a9d7 | ||
|
|
96f8ebc308 | ||
|
|
43a24f2aa8 | ||
|
|
3dfbfe87e9 | ||
|
|
aedf25ba46 | ||
|
|
8975c5241e | ||
|
|
882913ca4e | ||
|
|
d063f9a5d1 | ||
|
|
076bd5eaab | ||
|
|
701cba7f03 | ||
|
|
edecd5b0c6 | ||
|
|
0b5fe1b5eb | ||
|
|
911818a9e2 | ||
|
|
42557c4ad3 | ||
|
|
26f0609390 | ||
|
|
9e648183db | ||
|
|
7dabb6be30 | ||
|
|
b57ebba3fd | ||
|
|
4c1dad8207 | ||
|
|
8090965162 | ||
|
|
758db793be | ||
|
|
bf5f87c2fe | ||
|
|
be631d1caf | ||
|
|
21b8e2f0d8 | ||
|
|
9a282aa593 | ||
|
|
99b583f688 | ||
|
|
18eed58fc5 | ||
|
|
5ed2ef493a | ||
|
|
7ace06ac21 | ||
|
|
0ea4d850e1 | ||
|
|
bbf07d9214 | ||
|
|
e4fcc5562f | ||
|
|
3f1231b77c | ||
|
|
062e247353 | ||
|
|
ca205be5ac | ||
|
|
0e5e1350b2 | ||
|
|
74818c7b62 | ||
|
|
447c978757 | ||
|
|
d4398fd84a | ||
|
|
cf7baeab16 | ||
|
|
6f2dacc60c | ||
|
|
7a20c3b92a | ||
|
|
18be767f75 | ||
|
|
d689068d82 | ||
|
|
3f32969bee | ||
|
|
bc46a9772d | ||
|
|
a85c2c16b4 | ||
|
|
3646c301ed | ||
|
|
e8a78bd43d | ||
|
|
8a7e283926 | ||
|
|
572b6df0f2 | ||
|
|
215516f819 | ||
|
|
3f45901f25 | ||
|
|
af4794b2d1 | ||
|
|
c6e016e545 | ||
|
|
4a1e270072 | ||
|
|
48036b4bab | ||
|
|
25b953ad16 | ||
|
|
cd503ce01d | ||
|
|
c59371ab0c | ||
|
|
cee0ddea78 | ||
|
|
8449a47048 | ||
|
|
c99eaf106e | ||
|
|
4e10ce1632 | ||
|
|
10558344b0 | ||
|
|
7437748a1d | ||
|
|
81b9a5ee47 | ||
|
|
311b378328 | ||
|
|
40b90e5b6e | ||
|
|
e7d9200e0f | ||
|
|
0385ae2b43 | ||
|
|
8821d71094 | ||
|
|
9affcd8f54 | ||
|
|
88d94dbf0a | ||
|
|
830e87a4dd | ||
|
|
f6627550d1 | ||
|
|
3c748efae3 | ||
|
|
b6f27a4cae | ||
|
|
f259bf48aa | ||
|
|
e3557ff131 | ||
|
|
a06ec03efc | ||
|
|
5dd92934ae | ||
|
|
11aff80dea | ||
|
|
721b4130db | ||
|
|
7c7c084159 | ||
|
|
ca34eec2d6 | ||
|
|
b3ed807b70 | ||
|
|
c6dcc9d94a | ||
|
|
6432aaa07a | ||
|
|
865663857c | ||
|
|
2c7fac0e76 | ||
|
|
2f5885627d | ||
|
|
1175e0686b | ||
|
|
c8b34798fe | ||
|
|
970f00922d | ||
|
|
022b5c973a | ||
|
|
908b0090e9 | ||
|
|
0ea6725baa | ||
|
|
0358b64743 | ||
|
|
80540d38ba | ||
|
|
d0ba0217e4 | ||
|
|
c63e233bc7 | ||
|
|
4c94ccc8d8 | ||
|
|
89f621baa4 | ||
|
|
3be28054de | ||
|
|
42b2ea556f | ||
|
|
48b1a82fa1 | ||
|
|
ea4f8102e8 | ||
|
|
d067e25dad | ||
|
|
c3d8115ca1 | ||
|
|
5483f21fe7 | ||
|
|
431d3295b4 | ||
|
|
2df7f474fa | ||
|
|
fb9cb0fd23 | ||
|
|
d4dc76c3ee | ||
|
|
b72b4c0bf9 | ||
|
|
6fb218e033 | ||
|
|
05386ff12f | ||
|
|
b084f1d320 | ||
|
|
20be7fb93d | ||
|
|
bbce2e91a3 | ||
|
|
d24eccd623 | ||
|
|
faa25166a9 | ||
|
|
0b02f1335f | ||
|
|
d071586589 | ||
|
|
7fa045c1c9 | ||
|
|
3bca90dbe6 | ||
|
|
68b050de97 | ||
|
|
17736afab5 | ||
|
|
8a776a63cd | ||
|
|
24f2d58f53 | ||
|
|
f4db3139b7 | ||
|
|
c4d4be3265 | ||
|
|
bcf116422a | ||
|
|
85aeca1443 | ||
|
|
6da2212e2d | ||
|
|
113a6f3d80 | ||
|
|
77f2686142 | ||
|
|
b9a8afc234 | ||
|
|
96542c3d04 | ||
|
|
273589e835 | ||
|
|
54354a84e1 | ||
|
|
d48ea663d9 | ||
|
|
d1a13ec050 | ||
|
|
a891d6eed2 | ||
|
|
b3e647feca | ||
|
|
19c5fd72d6 | ||
|
|
a70e11450e | ||
|
|
ab8816e11d | ||
|
|
c79f5d7514 | ||
|
|
175cb27bcb | ||
|
|
8696580254 | ||
|
|
6ca8989013 | ||
|
|
5e88b14a01 | ||
|
|
b4c958caf0 | ||
|
|
3b69aa80fc | ||
|
|
c26d41acf9 | ||
|
|
232cd28d67 | ||
|
|
7b74985a54 | ||
|
|
0f3d862ba9 | ||
|
|
85dd5d2252 | ||
|
|
34e620fb5b | ||
|
|
dd15304921 | ||
|
|
591f3f0bb9 | ||
|
|
a150645b57 | ||
|
|
2e83cb77ca | ||
|
|
4311fdc3a1 | ||
|
|
50794407b4 | ||
|
|
86ee3ebb09 | ||
|
|
46d39d27aa | ||
|
|
04dfaf3b2a | ||
|
|
740c68d075 | ||
|
|
5269f1537d | ||
|
|
fbc8694291 | ||
|
|
bd783e8072 | ||
|
|
753e5894de | ||
|
|
9e4c28852e | ||
|
|
5dad72260f | ||
|
|
bb0a40b995 | ||
|
|
ea18230274 | ||
|
|
3382655b5e | ||
|
|
45a89a7c67 | ||
|
|
be3671fe7b | ||
|
|
e2059fb9f6 | ||
|
|
5e4128e70c | ||
|
|
dc51388666 | ||
|
|
9857d63523 | ||
|
|
1e3a3b27c6 | ||
|
|
1cdf5a0dba | ||
|
|
c5b074269a | ||
|
|
477a90e2ac | ||
|
|
7342eb64e9 | ||
|
|
349ef8274b | ||
|
|
36a11bf9ce | ||
|
|
be157e7467 | ||
|
|
2e0e4a7861 | ||
|
|
db5217e48e | ||
|
|
0e804e292a | ||
|
|
f3f87886a9 | ||
|
|
5e27c7dae2 | ||
|
|
8fddd0f0c1 | ||
|
|
ff96bdf0c1 | ||
|
|
e5df60287e | ||
|
|
ba2cbf8ec6 | ||
|
|
c262705143 | ||
|
|
81376ea44f | ||
|
|
18cfced032 | ||
|
|
db95db892c | ||
|
|
99636c6aca | ||
|
|
4d61fa2497 | ||
|
|
6a62ad325f | ||
|
|
507a211c81 | ||
|
|
84f270e732 | ||
|
|
73e41c0bd6 | ||
|
|
55a3fb57dd | ||
|
|
36d47f97f4 | ||
|
|
6f84cee6fe | ||
|
|
b07f7d1b70 | ||
|
|
c9da1fc568 | ||
|
|
a23aaf43f4 | ||
|
|
e2f83ffaa4 | ||
|
|
77dcee9d67 | ||
|
|
fd380d34f9 | ||
|
|
a87e3fcb7c | ||
|
|
79b422c0a9 | ||
|
|
d18dde7757 | ||
|
|
c26de28613 | ||
|
|
61c5e478af | ||
|
|
c9aa726818 | ||
|
|
3efd411ddb | ||
|
|
073dcf7e42 | ||
|
|
09d9bd19ac | ||
|
|
bc7c5bfbe2 | ||
|
|
b4f0347c02 | ||
|
|
7c0383a9a6 | ||
|
|
5670cf4386 | ||
|
|
25112244ed | ||
|
|
c348215f61 | ||
|
|
63e4ab4f2d | ||
|
|
dd5b31f8c4 | ||
|
|
b6d061fa8c | ||
|
|
15f8a0fb22 | ||
|
|
3aed662f46 | ||
|
|
c15fef571f | ||
|
|
76dd6e9046 | ||
|
|
42e057d079 | ||
|
|
e971b4592e | ||
|
|
57266a7343 | ||
|
|
fc54cf68ac | ||
|
|
4e360f805f | ||
|
|
a11a8e8ab2 | ||
|
|
eb17732876 | ||
|
|
f5a937bc45 | ||
|
|
b9fa12d572 | ||
|
|
019be66b7b | ||
|
|
0d7f54c6c2 | ||
|
|
297dc5e345 | ||
|
|
958d485ba4 | ||
|
|
ccbde0efa0 | ||
|
|
44c489223b | ||
|
|
dc7afa743c | ||
|
|
bf1b3b89d1 | ||
|
|
6809ff4f64 | ||
|
|
8f057b4bac | ||
|
|
10085580c8 | ||
|
|
6368c976c7 | ||
|
|
c12264f6bc | ||
|
|
ce88c945cd | ||
|
|
5457db0601 | ||
|
|
7b4a38c71e | ||
|
|
a2d6cf3125 | ||
|
|
c229ac9322 | ||
|
|
4b484d741d | ||
|
|
931c886f92 | ||
|
|
83e6e2e68a | ||
|
|
0b4858d8e5 | ||
|
|
ad58a8164a | ||
|
|
4a2dbd4885 | ||
|
|
330353a5ce | ||
|
|
e1ab290911 | ||
|
|
eaf0465c44 | ||
|
|
b0019bcf5b | ||
|
|
ab34f5b485 | ||
|
|
579a1b7028 | ||
|
|
0b5e340b6e | ||
|
|
e930456be2 | ||
|
|
1097dc89c5 | ||
|
|
4dcac4ae81 | ||
|
|
9979cf5fcc | ||
|
|
651e934415 | ||
|
|
795909fdcd | ||
|
|
605ea044f3 | ||
|
|
84730aa4a5 | ||
|
|
722cfdb7e5 | ||
|
|
a7d0dbb085 | ||
|
|
8a407f1ec3 | ||
|
|
be247ec3de | ||
|
|
0d8b9a9d0a | ||
|
|
f0e6a16910 | ||
|
|
181c5d240b | ||
|
|
21e662f678 | ||
|
|
4427390ab3 | ||
|
|
0169cd845a | ||
|
|
1270febfff | ||
|
|
aa516e5d17 | ||
|
|
aeb88385bb | ||
|
|
08598238d7 | ||
|
|
8bc90ffcf5 | ||
|
|
b63cc307cc | ||
|
|
b9ad385232 | ||
|
|
7fb385a89f | ||
|
|
4bd641367b | ||
|
|
516c789127 | ||
|
|
4e73c8a79f | ||
|
|
bf7f0530e6 | ||
|
|
8e34c49ac9 | ||
|
|
3768216dca | ||
|
|
f79ef5d8cf | ||
|
|
5456873641 | ||
|
|
9bd779401d | ||
|
|
90f7ec840c | ||
|
|
8d7d4b0ba7 | ||
|
|
0b29bc0eeb | ||
|
|
426b04003c | ||
|
|
814fd19424 | ||
|
|
ecbb8cbc84 | ||
|
|
042b8524cc | ||
|
|
5da34bddd6 | ||
|
|
bdba064fa6 | ||
|
|
0ab0fcdd51 | ||
|
|
ca2fb47d44 | ||
|
|
b6783b158f | ||
|
|
f3b3d81e0b | ||
|
|
41ac8be6f2 | ||
|
|
6bae78f410 | ||
|
|
bc92ecb10f | ||
|
|
42d2f663fa | ||
|
|
9ec0f11800 | ||
|
|
2f403f1bcd | ||
|
|
ad0b8fdd1e | ||
|
|
87baa646e7 | ||
|
|
35137ba9e0 | ||
|
|
cab998c293 | ||
|
|
fd4743cc31 | ||
|
|
d8de7fccc2 | ||
|
|
55c2fec683 | ||
|
|
2aa401826e | ||
|
|
e9f6c8cdb1 | ||
|
|
98fc4195b3 | ||
|
|
eca86290bc | ||
|
|
be3cde9313 | ||
|
|
55fe85d850 | ||
|
|
ba940bb9e1 | ||
|
|
0048418c46 | ||
|
|
fc44478810 | ||
|
|
a0a88a710e | ||
|
|
af1376c1df | ||
|
|
e85770fe3f | ||
|
|
7c6de1a8ac | ||
|
|
d42dd5d868 | ||
|
|
27f50d5852 | ||
|
|
6084baa9ae | ||
|
|
347fcedb2e | ||
|
|
8e748f8451 | ||
|
|
95e05fbdac | ||
|
|
0c482fde5f | ||
|
|
c29c6ff9a7 | ||
|
|
fe68a0ff80 | ||
|
|
0612300631 | ||
|
|
c3c54fe058 | ||
|
|
3c79ee3a2b | ||
|
|
dc205e805f | ||
|
|
6578c045ca | ||
|
|
4fbabb4132 | ||
|
|
4ec0656f64 | ||
|
|
aaca8335f0 | ||
|
|
9f017a351b | ||
|
|
e7a2fdd81a | ||
|
|
dd1822ef58 | ||
|
|
d984be0ccd | ||
|
|
b1f8c80be3 | ||
|
|
958c96ee3f | ||
|
|
a665f14620 | ||
|
|
a60c3081cf | ||
|
|
e38192cb6d | ||
|
|
d2520680bc | ||
|
|
83f98f6992 | ||
|
|
9f0823a164 | ||
|
|
55d47a2baa | ||
|
|
13dfb9734c | ||
|
|
1a48eb49cf | ||
|
|
f2eb8dd1d5 | ||
|
|
6e179c3092 | ||
|
|
9226cd3932 | ||
|
|
50f52dfbcb | ||
|
|
7715441842 | ||
|
|
aa9e172091 | ||
|
|
64bd4f27b8 | ||
|
|
42fe07bd19 | ||
|
|
1726ce547c | ||
|
|
c6dc9eaf2e | ||
|
|
90e671905a | ||
|
|
e28165ea87 | ||
|
|
da7fefe29d | ||
|
|
27cf19a19f | ||
|
|
5618ce3852 | ||
|
|
062d30146f | ||
|
|
7f79d463f6 | ||
|
|
735fbdc350 | ||
|
|
00e00e4e90 | ||
|
|
f648d2d7c4 | ||
|
|
996f7e53a1 | ||
|
|
f132ed4335 | ||
|
|
0e0de6baa1 | ||
|
|
4ecae62194 | ||
|
|
134eaa5786 | ||
|
|
695becdd05 | ||
|
|
dc6233b703 | ||
|
|
900a8fb21a | ||
|
|
f43a86d90f | ||
|
|
d502f76319 | ||
|
|
8a1f86db16 | ||
|
|
9dd0a26e92 | ||
|
|
7ed531611b | ||
|
|
180ba1dfc9 | ||
|
|
bb3e5d00f4 | ||
|
|
85b675a554 | ||
|
|
ffea9d4126 | ||
|
|
136eb30081 | ||
|
|
1bb7bb74ad | ||
|
|
2a20a03c28 | ||
|
|
8be414873b | ||
|
|
04923d6a65 | ||
|
|
18ad15ed16 | ||
|
|
eebc6e9277 | ||
|
|
5fc4f1e37d | ||
|
|
ba8dc1ffbd | ||
|
|
384f4b5b7e | ||
|
|
1e554378c7 | ||
|
|
076020643d | ||
|
|
308905b1be | ||
|
|
e36f303042 | ||
|
|
24e2cc9107 | ||
|
|
ca37380d2e | ||
|
|
f1bdfac7a8 | ||
|
|
1f4df80565 | ||
|
|
88734eea14 | ||
|
|
8f34ca4ac6 | ||
|
|
cdc99cdd49 | ||
|
|
9cc7c294e7 | ||
|
|
0ae702aab9 | ||
|
|
84ec295b43 | ||
|
|
1bac72b74d | ||
|
|
72e602ae54 | ||
|
|
6a5a380c07 | ||
|
|
35d4829383 | ||
|
|
5f177aaeaa | ||
|
|
c571141c1f | ||
|
|
f6610c96dd | ||
|
|
a66184fe3c | ||
|
|
824f089569 | ||
|
|
1efafb3ce8 | ||
|
|
2fb573781d | ||
|
|
0c7448fc41 | ||
|
|
00ea336b52 | ||
|
|
e91b0021ac | ||
|
|
da0ba15cbf | ||
|
|
204ea1027f | ||
|
|
07a081d8ac | ||
|
|
a90c81626f | ||
|
|
c4231c72a6 | ||
|
|
c0352010d3 | ||
|
|
8aa57953ee | ||
|
|
cd9fa61659 | ||
|
|
26920b439a | ||
|
|
7ad66caf7f | ||
|
|
0dc411951e | ||
|
|
338a006e28 | ||
|
|
7783a56c08 | ||
|
|
76e1e68cf4 | ||
|
|
07fb98b91d | ||
|
|
dd1e7624bf | ||
|
|
7bf25b4cef | ||
|
|
0dc00219e8 | ||
|
|
6fb417590f | ||
|
|
90c667205a | ||
|
|
93c22ebbb9 | ||
|
|
3386d4948e | ||
|
|
83e3820575 | ||
|
|
dc086dd52f | ||
|
|
ab052599c0 | ||
|
|
56f697052c | ||
|
|
b7ca913904 | ||
|
|
3b1ae4eb90 | ||
|
|
64a38f52cf | ||
|
|
d75b4c37a3 | ||
|
|
a949480acf | ||
|
|
44b07e4ef5 | ||
|
|
da5b55c4f5 | ||
|
|
3f7ec95af8 | ||
|
|
aa4f750d8c | ||
|
|
3f53b87379 | ||
|
|
c9187b00af | ||
|
|
82905166d9 | ||
|
|
b7fea12cdf | ||
|
|
ad4365eb0e | ||
|
|
8a1e5e189c | ||
|
|
5b9d3f15a2 | ||
|
|
b2be91e731 | ||
|
|
b9078a0c86 | ||
|
|
ebc6c51abe | ||
|
|
df06e49e0c | ||
|
|
7f1b2de74d | ||
|
|
1e912db3bb | ||
|
|
0b9aa58b81 | ||
|
|
968ec110f3 | ||
|
|
c5c9f9a941 | ||
|
|
d552fe6778 | ||
|
|
52ea6b126b | ||
|
|
242242c43f | ||
|
|
8787ebf83d | ||
|
|
b0e160ff78 | ||
|
|
66a71bdd1a | ||
|
|
50997709d5 | ||
|
|
090177494d | ||
|
|
e5e20c50a9 | ||
|
|
6d3305c446 | ||
|
|
48b2fc9b12 | ||
|
|
2acc66db2c | ||
|
|
fa9629c1e1 | ||
|
|
66250351d2 | ||
|
|
3130ed52ff | ||
|
|
e379f083bb | ||
|
|
48cd4de0d2 | ||
|
|
57f487a16b | ||
|
|
de69a1186c | ||
|
|
4a1159408a | ||
|
|
ebbcc90d89 | ||
|
|
5d7d338d2a | ||
|
|
135b852cf0 | ||
|
|
9a0a561ec6 | ||
|
|
0e949adea5 | ||
|
|
840c921229 | ||
|
|
bb746fcbe4 | ||
|
|
5ebc6abfad | ||
|
|
6daae681bd | ||
|
|
e6fd3b86bd | ||
|
|
2c802720c3 | ||
|
|
2da6ab7f2a | ||
|
|
1ac471e04f | ||
|
|
4e25aa77dd | ||
|
|
03f711e3a2 | ||
|
|
7f8b95efe8 | ||
|
|
1bc65ddbe4 | ||
|
|
f68205468c | ||
|
|
82ebc47ba1 | ||
|
|
faca478317 | ||
|
|
a5d062453e | ||
|
|
cb6494876f | ||
|
|
23f645e7da | ||
|
|
1f08d8e3de | ||
|
|
c42318ec24 | ||
|
|
31b5dfe9ee | ||
|
|
e66cf0aa44 | ||
|
|
0a45fc8c58 | ||
|
|
3a12f1f1ae | ||
|
|
ea0fd31f60 | ||
|
|
3b4b17476a | ||
|
|
c6e13ac218 | ||
|
|
ed36fb2073 | ||
|
|
03425071e7 | ||
|
|
368a6541e9 | ||
|
|
94f293940c | ||
|
|
4fe2d35b2e | ||
|
|
b39608a02e | ||
|
|
6b2e4f2b5d | ||
|
|
09c7598a67 | ||
|
|
a93b514b2f |
3
.flake8
3
.flake8
@@ -29,4 +29,5 @@ ignore =
|
||||
B950,
|
||||
W191,
|
||||
|
||||
max-line-length = 200
|
||||
max-line-length = 200
|
||||
exclude=.github/helper/semgrep_rules
|
||||
|
||||
12
.git-blame-ignore-revs
Normal file
12
.git-blame-ignore-revs
Normal file
@@ -0,0 +1,12 @@
|
||||
# Since version 2.23 (released in August 2019), git-blame has a feature
|
||||
# to ignore or bypass certain commits.
|
||||
#
|
||||
# This file contains a list of commits that are not likely what you
|
||||
# are looking for in a blame, such as mass reformatting or renaming.
|
||||
# You can set this file as a default ignore file for blame by running
|
||||
# the following command.
|
||||
#
|
||||
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
|
||||
# This commit just changes spaces to tabs for indentation in some files
|
||||
5f473611bd6ed57703716244a054d3fb5ba9cd23
|
||||
@@ -4,25 +4,61 @@ from frappe import _, flt
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
# ruleid: frappe-modifying-but-not-comitting
|
||||
def on_submit(self):
|
||||
if self.value_of_goods == 0:
|
||||
frappe.throw(_('Value of goods cannot be 0'))
|
||||
# ruleid: frappe-modifying-after-submit
|
||||
self.status = 'Submitted'
|
||||
|
||||
|
||||
# ok: frappe-modifying-but-not-comitting
|
||||
def on_submit(self):
|
||||
if flt(self.per_billed) < 100:
|
||||
self.update_billing_status()
|
||||
else:
|
||||
# todook: frappe-modifying-after-submit
|
||||
self.status = "Completed"
|
||||
self.db_set("status", "Completed")
|
||||
if self.value_of_goods == 0:
|
||||
frappe.throw(_('Value of goods cannot be 0'))
|
||||
self.status = 'Submitted'
|
||||
self.db_set('status', 'Submitted')
|
||||
|
||||
class TestDoc(Document):
|
||||
pass
|
||||
# ok: frappe-modifying-but-not-comitting
|
||||
def on_submit(self):
|
||||
if self.value_of_goods == 0:
|
||||
frappe.throw(_('Value of goods cannot be 0'))
|
||||
x = "y"
|
||||
self.status = x
|
||||
self.db_set('status', x)
|
||||
|
||||
def validate(self):
|
||||
#ruleid: frappe-modifying-child-tables-while-iterating
|
||||
for item in self.child_table:
|
||||
if item.value < 0:
|
||||
self.remove(item)
|
||||
|
||||
# ok: frappe-modifying-but-not-comitting
|
||||
def on_submit(self):
|
||||
x = "y"
|
||||
self.status = x
|
||||
self.save()
|
||||
|
||||
# ruleid: frappe-modifying-but-not-comitting-other-method
|
||||
class DoctypeClass(Document):
|
||||
def on_submit(self):
|
||||
self.good_method()
|
||||
self.tainted_method()
|
||||
|
||||
def tainted_method(self):
|
||||
self.status = "uptate"
|
||||
|
||||
|
||||
# ok: frappe-modifying-but-not-comitting-other-method
|
||||
class DoctypeClass(Document):
|
||||
def on_submit(self):
|
||||
self.good_method()
|
||||
self.tainted_method()
|
||||
|
||||
def tainted_method(self):
|
||||
self.status = "update"
|
||||
self.db_set("status", "update")
|
||||
|
||||
# ok: frappe-modifying-but-not-comitting-other-method
|
||||
class DoctypeClass(Document):
|
||||
def on_submit(self):
|
||||
self.good_method()
|
||||
self.tainted_method()
|
||||
self.save()
|
||||
|
||||
def tainted_method(self):
|
||||
self.status = "uptate"
|
||||
|
||||
@@ -1,32 +1,93 @@
|
||||
# This file specifies rules for correctness according to how frappe doctype data model works.
|
||||
|
||||
rules:
|
||||
- id: frappe-modifying-after-submit
|
||||
- id: frappe-modifying-but-not-comitting
|
||||
patterns:
|
||||
- pattern: self.$ATTR = ...
|
||||
- pattern-inside: |
|
||||
def on_submit(self, ...):
|
||||
- pattern: |
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
- pattern-not: |
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
...
|
||||
self.db_set(..., self.$ATTR, ...)
|
||||
- pattern-not: |
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = $SOME_VAR
|
||||
...
|
||||
self.db_set(..., $SOME_VAR, ...)
|
||||
- pattern-not: |
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = $SOME_VAR
|
||||
...
|
||||
self.save()
|
||||
- metavariable-regex:
|
||||
metavariable: '$ATTR'
|
||||
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
||||
regex: '^(?!status_updater)(.*)$'
|
||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||
- metavariable-regex:
|
||||
metavariable: "$METHOD"
|
||||
regex: "(on_submit|on_cancel)"
|
||||
message: |
|
||||
Doctype modified after submission. Please check if modification of self.$ATTR is commited to database.
|
||||
DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-modifying-after-cancel
|
||||
- id: frappe-modifying-but-not-comitting-other-method
|
||||
patterns:
|
||||
- pattern: self.$ATTR = ...
|
||||
- pattern-inside: |
|
||||
def on_cancel(self, ...):
|
||||
- pattern: |
|
||||
class $DOCTYPE(...):
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
- metavariable-regex:
|
||||
metavariable: '$ATTR'
|
||||
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||
self.$ANOTHER_METHOD()
|
||||
...
|
||||
|
||||
def $ANOTHER_METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
- pattern-not: |
|
||||
class $DOCTYPE(...):
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ANOTHER_METHOD()
|
||||
...
|
||||
|
||||
def $ANOTHER_METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
...
|
||||
self.db_set(..., self.$ATTR, ...)
|
||||
- pattern-not: |
|
||||
class $DOCTYPE(...):
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ANOTHER_METHOD()
|
||||
...
|
||||
|
||||
def $ANOTHER_METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = $SOME_VAR
|
||||
...
|
||||
self.db_set(..., $SOME_VAR, ...)
|
||||
- pattern-not: |
|
||||
class $DOCTYPE(...):
|
||||
def $METHOD(self, ...):
|
||||
...
|
||||
self.$ANOTHER_METHOD()
|
||||
...
|
||||
self.save()
|
||||
def $ANOTHER_METHOD(self, ...):
|
||||
...
|
||||
self.$ATTR = ...
|
||||
- metavariable-regex:
|
||||
metavariable: "$METHOD"
|
||||
regex: "(on_submit|on_cancel)"
|
||||
message: |
|
||||
Doctype modified after cancellation. Please check if modification of self.$ATTR is commited to database.
|
||||
self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database.
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
|
||||
7
.github/helper/semgrep_rules/translate.js
vendored
7
.github/helper/semgrep_rules/translate.js
vendored
@@ -35,3 +35,10 @@ __('You have' + 'subscribers in your mailing list.')
|
||||
// ruleid: frappe-translation-js-splitting
|
||||
__('You have {0} subscribers' +
|
||||
'in your mailing list', [subscribers.length])
|
||||
|
||||
// ok: frappe-translation-js-splitting
|
||||
__("Ctrl+Enter to add comment")
|
||||
|
||||
// ruleid: frappe-translation-js-splitting
|
||||
__('You have {0} subscribers \
|
||||
in your mailing list', [subscribers.length])
|
||||
|
||||
8
.github/helper/semgrep_rules/translate.py
vendored
8
.github/helper/semgrep_rules/translate.py
vendored
@@ -51,3 +51,11 @@ _(f"what" + f"this is also not cool")
|
||||
_("")
|
||||
# ruleid: frappe-translation-empty-string
|
||||
_('')
|
||||
|
||||
|
||||
class Test:
|
||||
# ok: frappe-translation-python-splitting
|
||||
def __init__(
|
||||
args
|
||||
):
|
||||
pass
|
||||
|
||||
9
.github/helper/semgrep_rules/translate.yml
vendored
9
.github/helper/semgrep_rules/translate.yml
vendored
@@ -42,9 +42,10 @@ rules:
|
||||
|
||||
- id: frappe-translation-python-splitting
|
||||
pattern-either:
|
||||
- pattern: _(...) + ... + _(...)
|
||||
- pattern: _(...) + _(...)
|
||||
- pattern: _("..." + "...")
|
||||
- pattern-regex: '_\([^\)]*\\\s*'
|
||||
- pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\`
|
||||
- pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( )
|
||||
message: |
|
||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||
@@ -53,8 +54,8 @@ rules:
|
||||
|
||||
- id: frappe-translation-js-splitting
|
||||
pattern-either:
|
||||
- pattern-regex: '__\([^\)]*[\+\\]\s*'
|
||||
- pattern: __('...' + '...')
|
||||
- pattern-regex: '__\([^\)]*[\\]\s+'
|
||||
- pattern: __('...' + '...', ...)
|
||||
- pattern: __('...') + __('...')
|
||||
message: |
|
||||
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||
|
||||
9
.github/helper/semgrep_rules/ux.js
vendored
Normal file
9
.github/helper/semgrep_rules/ux.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
// ok: frappe-missing-translate-function-js
|
||||
frappe.msgprint('{{ _("Both login and password required") }}');
|
||||
|
||||
// ruleid: frappe-missing-translate-function-js
|
||||
frappe.msgprint('What');
|
||||
|
||||
// ok: frappe-missing-translate-function-js
|
||||
frappe.throw(' {{ _("Both login and password required") }}. ');
|
||||
18
.github/helper/semgrep_rules/ux.py
vendored
18
.github/helper/semgrep_rules/ux.py
vendored
@@ -2,30 +2,30 @@ import frappe
|
||||
from frappe import msgprint, throw, _
|
||||
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
# ruleid: frappe-missing-translate-function-python
|
||||
throw("Error Occured")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
# ruleid: frappe-missing-translate-function-python
|
||||
frappe.throw("Error Occured")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
# ruleid: frappe-missing-translate-function-python
|
||||
frappe.msgprint("Useful message")
|
||||
|
||||
# ruleid: frappe-missing-translate-function
|
||||
# ruleid: frappe-missing-translate-function-python
|
||||
msgprint("Useful message")
|
||||
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
# ok: frappe-missing-translate-function-python
|
||||
translatedmessage = _("Hello")
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
# ok: frappe-missing-translate-function-python
|
||||
throw(translatedmessage)
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
# ok: frappe-missing-translate-function-python
|
||||
msgprint(translatedmessage)
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
# ok: frappe-missing-translate-function-python
|
||||
msgprint(_("Helpful message"))
|
||||
|
||||
# ok: frappe-missing-translate-function
|
||||
# ok: frappe-missing-translate-function-python
|
||||
frappe.throw(_("Error occured"))
|
||||
|
||||
23
.github/helper/semgrep_rules/ux.yml
vendored
23
.github/helper/semgrep_rules/ux.yml
vendored
@@ -1,15 +1,30 @@
|
||||
rules:
|
||||
- id: frappe-missing-translate-function
|
||||
- id: frappe-missing-translate-function-python
|
||||
pattern-either:
|
||||
- patterns:
|
||||
- pattern: frappe.msgprint("...", ...)
|
||||
- pattern-not: frappe.msgprint(_("..."), ...)
|
||||
- pattern-not: frappe.msgprint(__("..."), ...)
|
||||
- patterns:
|
||||
- pattern: frappe.throw("...", ...)
|
||||
- pattern-not: frappe.throw(_("..."), ...)
|
||||
- pattern-not: frappe.throw(__("..."), ...)
|
||||
message: |
|
||||
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
|
||||
languages: [python, javascript, json]
|
||||
languages: [python]
|
||||
severity: ERROR
|
||||
|
||||
- id: frappe-missing-translate-function-js
|
||||
pattern-either:
|
||||
- patterns:
|
||||
- pattern: frappe.msgprint("...", ...)
|
||||
- pattern-not: frappe.msgprint(__("..."), ...)
|
||||
# ignore microtemplating e.g. msgprint("{{ _("server side translation") }}")
|
||||
- pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...)
|
||||
- patterns:
|
||||
- pattern: frappe.throw("...", ...)
|
||||
- pattern-not: frappe.throw(__("..."), ...)
|
||||
# ignore microtemplating
|
||||
- pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...)
|
||||
message: |
|
||||
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
|
||||
languages: [javascript]
|
||||
severity: ERROR
|
||||
|
||||
73
.github/workflows/patch.yml
vendored
Normal file
73
.github/workflows/patch.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: Patch
|
||||
|
||||
on: [pull_request, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-18.04
|
||||
|
||||
name: Patch Test
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mariadb:10.3
|
||||
env:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: YES
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.6
|
||||
|
||||
- name: Add to Hosts
|
||||
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||
|
||||
- name: Cache pip
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-node-modules
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install
|
||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
|
||||
- name: Run Patch Tests
|
||||
run: |
|
||||
cd ~/frappe-bench/
|
||||
wget https://erpnext.com/files/v10-erpnext.sql.gz
|
||||
bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
|
||||
bench --site test_site migrate
|
||||
12
.github/workflows/semgrep.yml
vendored
12
.github/workflows/semgrep.yml
vendored
@@ -4,6 +4,8 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- version-13-hotfix
|
||||
- version-13-pre-release
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Frappe Linter
|
||||
@@ -14,11 +16,19 @@ jobs:
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.8
|
||||
- name: Run semgrep
|
||||
|
||||
- name: Setup semgrep
|
||||
run: |
|
||||
python -m pip install -q semgrep
|
||||
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||
|
||||
- name: Semgrep errors
|
||||
run: |
|
||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity ERROR --config=.github/helper/semgrep_rules --quiet --error $files
|
||||
semgrep --config="r/python.lang.correctness" --quiet --error $files
|
||||
|
||||
- name: Semgrep warnings
|
||||
run: |
|
||||
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||
[[ -d .github/helper/semgrep_rules ]] && semgrep --severity WARNING --severity INFO --config=.github/helper/semgrep_rules --quiet $files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: CI
|
||||
name: Server
|
||||
|
||||
on: [pull_request, workflow_dispatch, push]
|
||||
on: [pull_request, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -10,15 +10,9 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- TYPE: "server"
|
||||
JOB_NAME: "Server"
|
||||
RUN_COMMAND: cd ~/frappe-bench/ && bench --site test_site run-tests --app erpnext --coverage
|
||||
- TYPE: "patch"
|
||||
JOB_NAME: "Patch"
|
||||
RUN_COMMAND: cd ~/frappe-bench/ && wget http://build.erpnext.com/20171108_190013_955977f8_database.sql.gz && bench --site test_site --force restore ~/frappe-bench/20171108_190013_955977f8_database.sql.gz && bench --site test_site migrate
|
||||
container: [1, 2, 3]
|
||||
|
||||
name: ${{ matrix.JOB_NAME }}
|
||||
name: Python Unit Tests
|
||||
|
||||
services:
|
||||
mysql:
|
||||
@@ -36,7 +30,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.6
|
||||
python-version: 3.7
|
||||
|
||||
- name: Add to Hosts
|
||||
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||
@@ -49,6 +43,7 @@ jobs:
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Cache node modules
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
@@ -60,6 +55,7 @@ jobs:
|
||||
${{ runner.os }}-build-${{ env.cache-name }}-
|
||||
${{ runner.os }}-build-
|
||||
${{ runner.os }}-
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
@@ -76,18 +72,39 @@ jobs:
|
||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
|
||||
- name: Run Tests
|
||||
run: ${{ matrix.RUN_COMMAND }}
|
||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
|
||||
env:
|
||||
TYPE: ${{ matrix.TYPE }}
|
||||
TYPE: server
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
|
||||
|
||||
- name: Coverage
|
||||
if: matrix.TYPE == 'server'
|
||||
- name: Upload Coverage Data
|
||||
run: |
|
||||
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||
cd ${GITHUB_WORKSPACE}
|
||||
pip install coveralls==3.0.1
|
||||
pip install coverage==5.5
|
||||
coveralls --service=github
|
||||
pip3 install coverage==5.5
|
||||
pip3 install coveralls==3.0.1
|
||||
coveralls
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COVERALLS_FLAG_NAME: run-${{ matrix.container }}
|
||||
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
|
||||
COVERALLS_PARALLEL: true
|
||||
|
||||
coveralls:
|
||||
name: Coverage Wrap Up
|
||||
needs: test
|
||||
container: python:3-slim
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Coveralls Finished
|
||||
run: |
|
||||
cd ${GITHUB_WORKSPACE}
|
||||
pip3 install coverage==5.5
|
||||
pip3 install coveralls==3.0.1
|
||||
coveralls --finish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
|
||||
@@ -5,7 +5,7 @@ import frappe
|
||||
from erpnext.hooks import regional_overrides
|
||||
from frappe.utils import getdate
|
||||
|
||||
__version__ = '13.2.1'
|
||||
__version__ = '13.7.0'
|
||||
|
||||
def get_default_company(user=None):
|
||||
'''Get default company for user'''
|
||||
|
||||
@@ -33,6 +33,8 @@ def get_shipping_address(company, address = None):
|
||||
if address and frappe.db.get_value('Dynamic Link',
|
||||
{'parent': address, 'link_name': company}):
|
||||
filters.append(["Address", "name", "=", address])
|
||||
if not address:
|
||||
filters.append(["Address", "is_shipping_address", "=", 1])
|
||||
|
||||
address = frappe.get_all("Address", filters=filters, fields=fields) or {}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ def build_conditions(process_type, account, company):
|
||||
if account:
|
||||
conditions += "AND %s='%s'"%(deferred_account, account)
|
||||
elif company:
|
||||
conditions += "AND p.company='%s'"%(company)
|
||||
conditions += f"AND p.company = {frappe.db.escape(company)}"
|
||||
|
||||
return conditions
|
||||
|
||||
@@ -263,6 +263,9 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
|
||||
total_days, total_booking_days, account_currency)
|
||||
|
||||
if not amount:
|
||||
return
|
||||
|
||||
if via_journal_entry:
|
||||
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
|
||||
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
|
||||
@@ -298,17 +301,21 @@ def process_deferred_accounting(posting_date=None):
|
||||
start_date = add_months(today(), -1)
|
||||
end_date = add_days(today(), -1)
|
||||
|
||||
for record_type in ('Income', 'Expense'):
|
||||
doc = frappe.get_doc(dict(
|
||||
doctype='Process Deferred Accounting',
|
||||
posting_date=posting_date,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
type=record_type
|
||||
))
|
||||
companies = frappe.get_all('Company')
|
||||
|
||||
doc.insert()
|
||||
doc.submit()
|
||||
for company in companies:
|
||||
for record_type in ('Income', 'Expense'):
|
||||
doc = frappe.get_doc(dict(
|
||||
doctype='Process Deferred Accounting',
|
||||
company=company.name,
|
||||
posting_date=posting_date,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
type=record_type
|
||||
))
|
||||
|
||||
doc.insert()
|
||||
doc.submit()
|
||||
|
||||
def make_gl_entries(doc, credit_account, debit_account, against,
|
||||
amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
|
||||
@@ -360,12 +367,10 @@ def make_gl_entries(doc, credit_account, debit_account, against,
|
||||
frappe.flags.deferred_accounting_error = True
|
||||
|
||||
def send_mail(deferred_process):
|
||||
title = _("Error while processing deferred accounting for {0}".format(deferred_process))
|
||||
content = _("""
|
||||
Deferred accounting failed for some invoices:
|
||||
Please check Process Deferred Accounting {0}
|
||||
and submit manually after resolving errors
|
||||
""").format(get_link_to_form('Process Deferred Accounting', deferred_process))
|
||||
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
|
||||
link = get_link_to_form('Process Deferred Accounting', deferred_process)
|
||||
content = _("Deferred accounting failed for some invoices:") + "\n"
|
||||
content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link)
|
||||
sendmail_to_system_managers(title, content)
|
||||
|
||||
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
|
||||
|
||||
@@ -19,7 +19,7 @@ class AccountingDimension(Document):
|
||||
|
||||
def validate(self):
|
||||
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
||||
'Cost Center', 'Accounting Dimension Detail', 'Company') :
|
||||
'Cost Center', 'Accounting Dimension Detail', 'Company', 'Account') :
|
||||
|
||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||
frappe.throw(msg)
|
||||
@@ -27,7 +27,7 @@ class AccountingDimension(Document):
|
||||
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
|
||||
|
||||
if exists and self.is_new():
|
||||
frappe.throw("Document Type already used as a dimension")
|
||||
frappe.throw(_("Document Type already used as a dimension"))
|
||||
|
||||
if not self.is_new():
|
||||
self.validate_document_type_change()
|
||||
|
||||
@@ -7,7 +7,8 @@ import frappe
|
||||
import unittest
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import delete_accounting_dimension
|
||||
|
||||
test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department']
|
||||
|
||||
class TestAccountingDimension(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -9,6 +9,8 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
||||
from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import create_dimension, disable_dimension
|
||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||
|
||||
test_dependencies = ['Location', 'Cost Center', 'Department']
|
||||
|
||||
class TestAccountingDimensionFilter(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_dimension()
|
||||
|
||||
@@ -10,6 +10,8 @@ from erpnext.accounts.general_ledger import ClosedAccountingPeriod
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import OverlapError
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
test_dependencies = ['Item']
|
||||
|
||||
class TestAccountingPeriod(unittest.TestCase):
|
||||
def test_overlap(self):
|
||||
ap1 = create_accounting_period(start_date = "2018-04-01",
|
||||
@@ -38,7 +40,7 @@ def create_accounting_period(**args):
|
||||
accounting_period.start_date = args.start_date or nowdate()
|
||||
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
||||
accounting_period.company = args.company or "_Test Company"
|
||||
accounting_period.period_name =args.period_name or "_Test_Period_Name_1"
|
||||
accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
|
||||
accounting_period.append("closed_documents", {
|
||||
"document_type": 'Sales Invoice', "closed": 1
|
||||
})
|
||||
|
||||
@@ -7,26 +7,31 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"auto_accounting_for_stock",
|
||||
"acc_frozen_upto",
|
||||
"frozen_accounts_modifier",
|
||||
"determine_address_tax_category_from",
|
||||
"accounts_transactions_settings_section",
|
||||
"over_billing_allowance",
|
||||
"role_allowed_to_over_bill",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
"make_payment_via_journal_entry",
|
||||
"column_break_11",
|
||||
"check_supplier_invoice_uniqueness",
|
||||
"unlink_payment_on_cancellation_of_invoice",
|
||||
"unlink_advance_payment_on_cancelation_of_order",
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"automatically_fetch_payment_terms",
|
||||
"delete_linked_ledger_entries",
|
||||
"book_asset_depreciation_entry_automatically",
|
||||
"unlink_advance_payment_on_cancelation_of_order",
|
||||
"post_change_gl_entries",
|
||||
"tax_settings_section",
|
||||
"determine_address_tax_category_from",
|
||||
"column_break_19",
|
||||
"add_taxes_from_item_tax_template",
|
||||
"period_closing_settings_section",
|
||||
"acc_frozen_upto",
|
||||
"frozen_accounts_modifier",
|
||||
"column_break_4",
|
||||
"credit_controller",
|
||||
"deferred_accounting_settings_section",
|
||||
"automatically_process_deferred_accounting_entry",
|
||||
"book_deferred_entries_based_on",
|
||||
"column_break_18",
|
||||
"automatically_process_deferred_accounting_entry",
|
||||
"book_deferred_entries_via_journal_entry",
|
||||
"submit_journal_entries",
|
||||
"print_settings",
|
||||
@@ -40,15 +45,6 @@
|
||||
"use_custom_cash_flow"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, the system will post accounting entries for inventory automatically",
|
||||
"fieldname": "auto_accounting_for_stock",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Make Accounting Entry For Every Stock Movement"
|
||||
},
|
||||
{
|
||||
"description": "Accounting entries are frozen up to this date. Nobody can create or modify entries except users with the role specified below",
|
||||
"fieldname": "acc_frozen_upto",
|
||||
@@ -94,6 +90,7 @@
|
||||
"default": "0",
|
||||
"fieldname": "make_payment_via_journal_entry",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Make Payment via Journal Entry"
|
||||
},
|
||||
{
|
||||
@@ -234,6 +231,36 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Role Allowed to Over Bill ",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_closing_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Period Closing Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounts_transactions_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Transactions Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_settings_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Settings"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, ledger entries will be posted for change amount in POS transactions",
|
||||
"fieldname": "post_change_gl_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create Ledger Entries for Change Amount"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -241,7 +268,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-11 18:52:05.601996",
|
||||
"modified": "2021-06-17 20:26:03.721202",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint
|
||||
from frappe.model.document import Document
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
@@ -24,7 +25,7 @@ class AccountsSettings(Document):
|
||||
def validate_stale_days(self):
|
||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||
frappe.msgprint(
|
||||
"Stale Days should start from 1.", title='Error', indicator='red',
|
||||
_("Stale Days should start from 1."), title='Error', indicator='red',
|
||||
raise_exception=1)
|
||||
|
||||
def enable_payment_schedule_in_print(self):
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2020-09-12 22:26:19.594367",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"add_deduct_tax",
|
||||
"charge_type",
|
||||
"row_id",
|
||||
"account_head",
|
||||
"col_break_1",
|
||||
"description",
|
||||
"included_in_paid_amount",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"section_break_8",
|
||||
"rate",
|
||||
"section_break_9",
|
||||
"currency",
|
||||
"tax_amount",
|
||||
"total",
|
||||
"allocated_amount",
|
||||
"column_break_13",
|
||||
"base_tax_amount",
|
||||
"base_total",
|
||||
"base_allocated_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "charge_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Type",
|
||||
"oldfieldname": "charge_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "\nActual\nOn Paid Amount\nOn Previous Row Amount\nOn Previous Row Total",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:[\"On Previous Row Amount\", \"On Previous Row Total\"].indexOf(doc.charge_type)!==-1",
|
||||
"fieldname": "row_id",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference Row #",
|
||||
"oldfieldname": "row_id",
|
||||
"oldfieldtype": "Data"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "account_head",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account Head",
|
||||
"oldfieldname": "account_head",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Account",
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break_1",
|
||||
"fieldtype": "Column Break",
|
||||
"width": "50%"
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description",
|
||||
"oldfieldname": "description",
|
||||
"oldfieldtype": "Small Text",
|
||||
"print_width": "300px",
|
||||
"reqd": 1,
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"default": ":Company",
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"oldfieldname": "cost_center_other_charges",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"oldfieldname": "rate",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_9",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "tax_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "total",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Total",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_tax_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount (Company Currency)",
|
||||
"oldfieldname": "tax_amount",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total (Company Currency)",
|
||||
"oldfieldname": "total",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "add_deduct_tax",
|
||||
"fieldtype": "Select",
|
||||
"label": "Add Or Deduct",
|
||||
"options": "Add\nDeduct",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "included_in_paid_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Considered In Paid Amount"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount (Company Currency)",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"fetch_from": "account_head.account_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-09 11:46:58.373170",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Taxes and Charges",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class AdvanceTaxesandCharges(Document):
|
||||
pass
|
||||
@@ -120,4 +120,4 @@ erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
|
||||
plaid_success(token, response) {
|
||||
frappe.show_alert({ message: __('Plaid Link Updated'), indicator: 'green' });
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -239,6 +239,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
"withdrawal",
|
||||
"description",
|
||||
"reference_number",
|
||||
"bank_account"
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -146,7 +146,7 @@
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal && !doc.import_file\n",
|
||||
"description": "Must be a publicly accessible Google Sheets URL",
|
||||
"description": "Must be a publicly accessible Google Sheets URL and adding Bank Account column is necessary for importing via Google Sheets",
|
||||
"fieldname": "google_sheets_url",
|
||||
"fieldtype": "Data",
|
||||
"label": "Import from Google Sheets"
|
||||
@@ -202,7 +202,7 @@
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-10 19:29:59.027325",
|
||||
"modified": "2021-05-12 14:17:37.777246",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Statement Import",
|
||||
@@ -224,4 +224,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,13 @@ class BankStatementImport(DataImport):
|
||||
|
||||
def start_import(self):
|
||||
|
||||
preview = frappe.get_doc("Bank Statement Import", self.name).get_preview_from_template(
|
||||
self.import_file, self.google_sheets_url
|
||||
)
|
||||
|
||||
if 'Bank Account' not in json.dumps(preview['columns']):
|
||||
frappe.throw(_("Please add the Bank Account column"))
|
||||
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
@@ -67,6 +74,7 @@ class BankStatementImport(DataImport):
|
||||
data_import=self.name,
|
||||
bank_account=self.bank_account,
|
||||
import_file_path=self.import_file,
|
||||
google_sheets_url=self.google_sheets_url,
|
||||
bank=self.bank,
|
||||
template_options=self.template_options,
|
||||
now=frappe.conf.developer_mode or frappe.flags.in_test,
|
||||
@@ -90,18 +98,20 @@ def download_errored_template(data_import_name):
|
||||
data_import = frappe.get_doc("Bank Statement Import", data_import_name)
|
||||
data_import.export_errored_rows()
|
||||
|
||||
def start_import(data_import, bank_account, import_file_path, bank, template_options):
|
||||
def start_import(data_import, bank_account, import_file_path, google_sheets_url, bank, template_options):
|
||||
"""This method runs in background job"""
|
||||
|
||||
update_mapping_db(bank, template_options)
|
||||
|
||||
data_import = frappe.get_doc("Bank Statement Import", data_import)
|
||||
file = import_file_path if import_file_path else google_sheets_url
|
||||
|
||||
import_file = ImportFile("Bank Transaction", file = import_file_path, import_type="Insert New Records")
|
||||
import_file = ImportFile("Bank Transaction", file = file, import_type="Insert New Records")
|
||||
data = import_file.raw_data
|
||||
|
||||
add_bank_account(data, bank_account)
|
||||
write_files(import_file, data)
|
||||
if import_file_path:
|
||||
add_bank_account(data, bank_account)
|
||||
write_files(import_file, data)
|
||||
|
||||
try:
|
||||
i = Importer(data_import.reference_doctype, data_import=data_import)
|
||||
|
||||
@@ -11,6 +11,8 @@ from erpnext.buying.doctype.purchase_order.test_purchase_order import create_pur
|
||||
from erpnext.accounts.doctype.budget.budget import get_actual_expense, BudgetError
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
|
||||
test_dependencies = ['Monthly Distribution']
|
||||
|
||||
class TestBudget(unittest.TestCase):
|
||||
def test_monthly_budget_crossed_ignore(self):
|
||||
set_total_expense_zero(nowdate(), "cost_center")
|
||||
|
||||
@@ -54,7 +54,7 @@ class CForm(Document):
|
||||
frappe.throw(_("Please enter atleast 1 invoice in the table"))
|
||||
|
||||
def set_total_invoiced_amount(self):
|
||||
total = sum([flt(d.grand_total) for d in self.get('invoices')])
|
||||
total = sum(flt(d.grand_total) for d in self.get('invoices'))
|
||||
frappe.db.set(self, 'total_invoiced_amount', total)
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -13,7 +13,8 @@ from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import
|
||||
from frappe.utils.xlsxutils import read_xlsx_file_from_attached_file, read_xls_file_from_attached_file
|
||||
|
||||
class ChartofAccountsImporter(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
validate_accounts(self.import_file)
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_company(company):
|
||||
@@ -22,7 +23,7 @@ def validate_company(company):
|
||||
'allow_account_creation_against_child_company'])
|
||||
|
||||
if parent_company and (not allow_account_creation_against_child_company):
|
||||
msg = _("{} is a child company. ").format(frappe.bold(company))
|
||||
msg = _("{} is a child company.").format(frappe.bold(company)) + " "
|
||||
msg += _("Please import accounts against parent company or enable {} in company master.").format(
|
||||
frappe.bold('Allow Account Creation Against Child Company'))
|
||||
frappe.throw(msg, title=_('Wrong Company'))
|
||||
@@ -56,7 +57,7 @@ def get_file(file_name):
|
||||
extension = extension.lstrip(".")
|
||||
|
||||
if extension not in ('csv', 'xlsx', 'xls'):
|
||||
frappe.throw("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload")
|
||||
frappe.throw(_("Only CSV and Excel files can be used to for importing data. Please check the file format you are trying to upload"))
|
||||
|
||||
return file_doc, extension
|
||||
|
||||
@@ -293,7 +294,7 @@ def validate_accounts(file_name):
|
||||
accounts_dict = {}
|
||||
for account in accounts:
|
||||
accounts_dict.setdefault(account["account_name"], account)
|
||||
if not hasattr(account, "parent_account"):
|
||||
if "parent_account" not in account:
|
||||
msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
|
||||
msg += "<br><br>"
|
||||
msg += _("Alternatively, you can download the template and fill your data in.")
|
||||
@@ -301,28 +302,27 @@ def validate_accounts(file_name):
|
||||
if account["parent_account"] and accounts_dict.get(account["parent_account"]):
|
||||
accounts_dict[account["parent_account"]]["is_group"] = 1
|
||||
|
||||
message = validate_root(accounts_dict)
|
||||
if message: return message
|
||||
message = validate_account_types(accounts_dict)
|
||||
if message: return message
|
||||
validate_root(accounts_dict)
|
||||
|
||||
validate_account_types(accounts_dict)
|
||||
|
||||
return [True, len(accounts)]
|
||||
|
||||
def validate_root(accounts):
|
||||
roots = [accounts[d] for d in accounts if not accounts[d].get('parent_account')]
|
||||
if len(roots) < 4:
|
||||
return _("Number of root accounts cannot be less than 4")
|
||||
frappe.throw(_("Number of root accounts cannot be less than 4"))
|
||||
|
||||
error_messages = []
|
||||
|
||||
for account in roots:
|
||||
if not account.get("root_type") and account.get("account_name"):
|
||||
error_messages.append("Please enter Root Type for account- {0}".format(account.get("account_name")))
|
||||
error_messages.append(_("Please enter Root Type for account- {0}").format(account.get("account_name")))
|
||||
elif account.get("root_type") not in get_root_types() and account.get("account_name"):
|
||||
error_messages.append("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity".format(account.get("account_name")))
|
||||
error_messages.append(_("Root Type for {0} must be one of the Asset, Liability, Income, Expense and Equity").format(account.get("account_name")))
|
||||
|
||||
if error_messages:
|
||||
return "<br>".join(error_messages)
|
||||
frappe.throw("<br>".join(error_messages))
|
||||
|
||||
def get_root_types():
|
||||
return ('Asset', 'Liability', 'Expense', 'Income', 'Equity')
|
||||
@@ -356,7 +356,7 @@ def validate_account_types(accounts):
|
||||
|
||||
missing = list(set(account_types_for_ledger) - set(account_types))
|
||||
if missing:
|
||||
return _("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing))
|
||||
frappe.throw(_("Please identify/create Account (Ledger) for type - {0}").format(' , '.join(missing)))
|
||||
|
||||
account_types_for_group = ["Bank", "Cash", "Stock"]
|
||||
# fix logic bug
|
||||
@@ -364,7 +364,7 @@ def validate_account_types(accounts):
|
||||
|
||||
missing = list(set(account_types_for_group) - set(account_groups))
|
||||
if missing:
|
||||
return _("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing))
|
||||
frappe.throw(_("Please identify/create Account (Group) for type - {0}").format(' , '.join(missing)))
|
||||
|
||||
def unset_existing_data(company):
|
||||
linked = frappe.db.sql('''select fieldname from tabDocField
|
||||
|
||||
@@ -14,7 +14,7 @@ class CouponCode(Document):
|
||||
|
||||
if not self.coupon_code:
|
||||
if self.coupon_type == "Promotional":
|
||||
self.coupon_code =''.join([i for i in self.coupon_name if not i.isdigit()])[0:8].upper()
|
||||
self.coupon_code =''.join(i for i in self.coupon_name if not i.isdigit())[0:8].upper()
|
||||
elif self.coupon_type == "Gift Card":
|
||||
self.coupon_code = frappe.generate_hash()[:10].upper()
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class Dunning(AccountsController):
|
||||
|
||||
def validate_amount(self):
|
||||
amounts = calculate_interest_and_amount(
|
||||
self.posting_date, self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
|
||||
self.outstanding_amount, self.rate_of_interest, self.dunning_fee, self.overdue_days)
|
||||
if self.interest_amount != amounts.get('interest_amount'):
|
||||
self.interest_amount = flt(amounts.get('interest_amount'), self.precision('interest_amount'))
|
||||
if self.dunning_amount != amounts.get('dunning_amount'):
|
||||
@@ -86,18 +86,18 @@ def resolve_dunning(doc, state):
|
||||
for reference in doc.references:
|
||||
if reference.reference_doctype == 'Sales Invoice' and reference.outstanding_amount <= 0:
|
||||
dunnings = frappe.get_list('Dunning', filters={
|
||||
'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')})
|
||||
'sales_invoice': reference.reference_name, 'status': ('!=', 'Resolved')}, ignore_permissions=True)
|
||||
|
||||
for dunning in dunnings:
|
||||
frappe.db.set_value("Dunning", dunning.name, "status", 'Resolved')
|
||||
|
||||
def calculate_interest_and_amount(posting_date, outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
||||
def calculate_interest_and_amount(outstanding_amount, rate_of_interest, dunning_fee, overdue_days):
|
||||
interest_amount = 0
|
||||
grand_total = 0
|
||||
grand_total = flt(outstanding_amount) + flt(dunning_fee)
|
||||
if rate_of_interest:
|
||||
interest_per_year = flt(outstanding_amount) * flt(rate_of_interest) / 100
|
||||
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
||||
grand_total = flt(outstanding_amount) + flt(interest_amount) + flt(dunning_fee)
|
||||
interest_amount = (interest_per_year * cint(overdue_days)) / 365
|
||||
grand_total += flt(interest_amount)
|
||||
dunning_amount = flt(interest_amount) + flt(dunning_fee)
|
||||
return {
|
||||
'interest_amount': interest_amount,
|
||||
|
||||
@@ -16,6 +16,7 @@ class TestDunning(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
create_dunning_type()
|
||||
create_dunning_type_with_zero_interest_rate()
|
||||
unlink_payment_on_cancel_of_invoice()
|
||||
|
||||
@classmethod
|
||||
@@ -25,11 +26,20 @@ class TestDunning(unittest.TestCase):
|
||||
def test_dunning(self):
|
||||
dunning = create_dunning()
|
||||
amounts = calculate_interest_and_amount(
|
||||
dunning.posting_date, dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
||||
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
||||
self.assertEqual(round(amounts.get('interest_amount'), 2), 0.44)
|
||||
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20.44)
|
||||
self.assertEqual(round(amounts.get('grand_total'), 2), 120.44)
|
||||
|
||||
|
||||
def test_dunning_with_zero_interest_rate(self):
|
||||
dunning = create_dunning_with_zero_interest_rate()
|
||||
amounts = calculate_interest_and_amount(
|
||||
dunning.outstanding_amount, dunning.rate_of_interest, dunning.dunning_fee, dunning.overdue_days)
|
||||
self.assertEqual(round(amounts.get('interest_amount'), 2), 0)
|
||||
self.assertEqual(round(amounts.get('dunning_amount'), 2), 20)
|
||||
self.assertEqual(round(amounts.get('grand_total'), 2), 120)
|
||||
|
||||
|
||||
def test_gl_entries(self):
|
||||
dunning = create_dunning()
|
||||
dunning.submit()
|
||||
@@ -42,9 +52,9 @@ class TestDunning(unittest.TestCase):
|
||||
['Sales - _TC', 0.0, 20.44]
|
||||
])
|
||||
for gle in gl_entries:
|
||||
self.assertEquals(expected_values[gle.account][0], gle.account)
|
||||
self.assertEquals(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEquals(expected_values[gle.account][2], gle.credit)
|
||||
self.assertEqual(expected_values[gle.account][0], gle.account)
|
||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_payment_entry(self):
|
||||
dunning = create_dunning()
|
||||
@@ -83,6 +93,27 @@ def create_dunning():
|
||||
dunning.save()
|
||||
return dunning
|
||||
|
||||
def create_dunning_with_zero_interest_rate():
|
||||
posting_date = add_days(today(), -20)
|
||||
due_date = add_days(today(), -15)
|
||||
sales_invoice = create_sales_invoice_against_cost_center(
|
||||
posting_date=posting_date, due_date=due_date, status='Overdue')
|
||||
dunning_type = frappe.get_doc("Dunning Type", 'First Notice with 0% Rate of Interest')
|
||||
dunning = frappe.new_doc("Dunning")
|
||||
dunning.sales_invoice = sales_invoice.name
|
||||
dunning.customer_name = sales_invoice.customer_name
|
||||
dunning.outstanding_amount = sales_invoice.outstanding_amount
|
||||
dunning.debit_to = sales_invoice.debit_to
|
||||
dunning.currency = sales_invoice.currency
|
||||
dunning.company = sales_invoice.company
|
||||
dunning.posting_date = nowdate()
|
||||
dunning.due_date = sales_invoice.due_date
|
||||
dunning.dunning_type = 'First Notice with 0% Rate of Interest'
|
||||
dunning.rate_of_interest = dunning_type.rate_of_interest
|
||||
dunning.dunning_fee = dunning_type.dunning_fee
|
||||
dunning.save()
|
||||
return dunning
|
||||
|
||||
def create_dunning_type():
|
||||
dunning_type = frappe.new_doc("Dunning Type")
|
||||
dunning_type.dunning_type = 'First Notice'
|
||||
@@ -98,3 +129,19 @@ def create_dunning_type():
|
||||
}
|
||||
)
|
||||
dunning_type.save()
|
||||
|
||||
def create_dunning_type_with_zero_interest_rate():
|
||||
dunning_type = frappe.new_doc("Dunning Type")
|
||||
dunning_type.dunning_type = 'First Notice with 0% Rate of Interest'
|
||||
dunning_type.start_day = 10
|
||||
dunning_type.end_day = 20
|
||||
dunning_type.dunning_fee = 20
|
||||
dunning_type.rate_of_interest = 0
|
||||
dunning_type.append(
|
||||
"dunning_letter_text", {
|
||||
'language': 'en',
|
||||
'body_text': 'We have still not received payment for our invoice ',
|
||||
'closing_text': 'We kindly request that you pay the outstanding amount immediately, and late fees.'
|
||||
}
|
||||
)
|
||||
dunning_type.save()
|
||||
@@ -75,8 +75,13 @@ class GLEntry(Document):
|
||||
def pl_must_have_cost_center(self):
|
||||
if frappe.get_cached_value("Account", self.account, "report_type") == "Profit and Loss":
|
||||
if not self.cost_center and self.voucher_type != 'Period Closing Voucher':
|
||||
frappe.throw(_("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}. Please set up a default Cost Center for the Company.")
|
||||
.format(self.voucher_type, self.voucher_no, self.account))
|
||||
msg = _("{0} {1}: Cost Center is required for 'Profit and Loss' account {2}.").format(
|
||||
self.voucher_type, self.voucher_no, self.account)
|
||||
msg += " "
|
||||
msg += _("Please set the cost center field in {0} or setup a default Cost Center for the Company.").format(
|
||||
self.voucher_type)
|
||||
|
||||
frappe.throw(msg, title=_("Missing Cost Center"))
|
||||
|
||||
def validate_dimensions_for_pl_and_bs(self):
|
||||
account_type = frappe.db.get_value("Account", self.account, "report_type")
|
||||
@@ -116,8 +121,7 @@ class GLEntry(Document):
|
||||
|
||||
def check_pl_account(self):
|
||||
if self.is_opening=='Yes' and \
|
||||
frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss" and \
|
||||
self.voucher_type not in ['Purchase Invoice', 'Sales Invoice']:
|
||||
frappe.db.get_value("Account", self.account, "report_type")=="Profit and Loss":
|
||||
frappe.throw(_("{0} {1}: 'Profit and Loss' type account {2} not allowed in Opening Entry")
|
||||
.format(self.voucher_type, self.voucher_no, self.account))
|
||||
|
||||
|
||||
@@ -54,4 +54,4 @@ class TestGLEntry(unittest.TestCase):
|
||||
self.assertTrue(all(new.name != old.name for new, old in zip(gl_entries, new_gl_entries)))
|
||||
|
||||
new_naming_series_current_value = frappe.db.sql("SELECT current from tabSeries where name = %s", naming_series)[0][0]
|
||||
self.assertEquals(old_naming_series_current_value + 2, new_naming_series_current_value)
|
||||
self.assertEqual(old_naming_series_current_value + 2, new_naming_series_current_value)
|
||||
|
||||
@@ -1,196 +1,82 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2018-01-02 15:48:58.768352",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2018-01-02 15:48:58.768352",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"company",
|
||||
"cgst_account",
|
||||
"sgst_account",
|
||||
"igst_account",
|
||||
"cess_account",
|
||||
"is_reverse_charge_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 1,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "cgst_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "CGST Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "cgst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "CGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "sgst_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "SGST Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "sgst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "SGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "igst_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "IGST Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"columns": 2,
|
||||
"fieldname": "igst_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "IGST Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "cess_account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "CESS Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"columns": 2,
|
||||
"fieldname": "cess_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "CESS Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"default": "0",
|
||||
"fieldname": "is_reverse_charge_account",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Is Reverse Charge Account"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-01-02 15:52:22.335988",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-09 12:30:25.889993",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "GST Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -42,18 +42,18 @@ class InvoiceDiscounting(AccountsController):
|
||||
record.idx, frappe.bold(actual_outstanding), frappe.bold(record.sales_invoice)))
|
||||
|
||||
def calculate_total_amount(self):
|
||||
self.total_amount = sum([flt(d.outstanding_amount) for d in self.invoices])
|
||||
self.total_amount = sum(flt(d.outstanding_amount) for d in self.invoices)
|
||||
|
||||
def on_submit(self):
|
||||
self.update_sales_invoice()
|
||||
self.make_gl_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
self.set_status()
|
||||
self.set_status(cancel=1)
|
||||
self.update_sales_invoice()
|
||||
self.make_gl_entries()
|
||||
|
||||
def set_status(self, status=None):
|
||||
def set_status(self, status=None, cancel=0):
|
||||
if status:
|
||||
self.status = status
|
||||
self.db_set("status", status)
|
||||
@@ -66,6 +66,9 @@ class InvoiceDiscounting(AccountsController):
|
||||
elif self.docstatus == 2:
|
||||
self.status = "Cancelled"
|
||||
|
||||
if cancel:
|
||||
self.db_set('status', self.status, update_modified = True)
|
||||
|
||||
def update_sales_invoice(self):
|
||||
for d in self.invoices:
|
||||
if self.docstatus == 1:
|
||||
|
||||
@@ -39,7 +39,11 @@ class JournalEntry(AccountsController):
|
||||
self.validate_multi_currency()
|
||||
self.set_amounts_in_company_currency()
|
||||
self.validate_debit_credit_amount()
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
# Do not validate while importing via data import
|
||||
if not frappe.flags.in_import:
|
||||
self.validate_total_debit_and_credit()
|
||||
|
||||
self.validate_against_jv()
|
||||
self.validate_reference_doc()
|
||||
self.set_against_account()
|
||||
@@ -192,8 +196,8 @@ class JournalEntry(AccountsController):
|
||||
frappe.throw(_("Row {0}: Party Type and Party is required for Receivable / Payable account {1}").format(d.idx, d.account))
|
||||
|
||||
def check_credit_limit(self):
|
||||
customers = list(set([d.party for d in self.get("accounts")
|
||||
if d.party_type=="Customer" and d.party and flt(d.debit) > 0]))
|
||||
customers = list(set(d.party for d in self.get("accounts")
|
||||
if d.party_type=="Customer" and d.party and flt(d.debit) > 0))
|
||||
if customers:
|
||||
from erpnext.selling.doctype.customer.customer import check_credit_limit
|
||||
for customer in customers:
|
||||
|
||||
17
erpnext/accounts/doctype/journal_entry/regional/india.js
Normal file
17
erpnext/accounts/doctype/journal_entry/regional/india.js
Normal file
@@ -0,0 +1,17 @@
|
||||
frappe.ui.form.on("Journal Entry", {
|
||||
refresh: function(frm) {
|
||||
frm.set_query('company_address', function(doc) {
|
||||
if(!doc.company) {
|
||||
frappe.throw(__('Please set Company'));
|
||||
}
|
||||
|
||||
return {
|
||||
query: 'frappe.contacts.doctype.address.address.address_query',
|
||||
filters: {
|
||||
link_doctype: 'Company',
|
||||
link_name: doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -21,7 +21,7 @@ class MonthlyDistribution(Document):
|
||||
idx += 1
|
||||
|
||||
def validate(self):
|
||||
total = sum([flt(d.percentage_allocation) for d in self.get("percentages")])
|
||||
total = sum(flt(d.percentage_allocation) for d in self.get("percentages"))
|
||||
|
||||
if flt(total, 2) != 100.0:
|
||||
frappe.throw(_("Percentage Allocation should be equal to 100%") + \
|
||||
|
||||
@@ -1,87 +1,39 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2014-08-29 16:02:39.740505",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2014-08-29 16:02:39.740505",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"field_order": [
|
||||
"company",
|
||||
"account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"options": "Account"
|
||||
}
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-07-11 03:28:03.348246",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Party Account",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-07 18:13:08.833822",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Party Account",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
{% include "erpnext/public/js/controllers/accounts.js" %}
|
||||
frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
cur_frm.cscript.tax_table = "Advance Taxes and Charges";
|
||||
|
||||
frappe.ui.form.on('Payment Entry', {
|
||||
onload: function(frm) {
|
||||
if(frm.doc.__islocal) {
|
||||
@@ -91,6 +93,16 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("advance_tax_account", function() {
|
||||
return {
|
||||
filters: {
|
||||
"company": frm.doc.company,
|
||||
"root_type": ["in", ["Asset", "Liability"]],
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("reference_doctype", "references", function() {
|
||||
if (frm.doc.party_type == "Customer") {
|
||||
var doctypes = ["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"];
|
||||
@@ -182,6 +194,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.doc.paid_from_account_currency != frm.doc.paid_to_account_currency));
|
||||
|
||||
frm.toggle_display("base_paid_amount", frm.doc.paid_from_account_currency != company_currency);
|
||||
frm.toggle_display("base_total_taxes_and_charges", frm.doc.total_taxes_and_charges &&
|
||||
(frm.doc.paid_from_account_currency != company_currency));
|
||||
|
||||
frm.toggle_display("base_received_amount", (
|
||||
frm.doc.paid_to_account_currency != company_currency
|
||||
@@ -216,7 +230,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
var company_currency = frm.doc.company? frappe.get_doc(":Company", frm.doc.company).default_currency: "";
|
||||
|
||||
frm.set_currency_labels(["base_paid_amount", "base_received_amount", "base_total_allocated_amount",
|
||||
"difference_amount"], company_currency);
|
||||
"difference_amount", "base_paid_amount_after_tax", "base_received_amount_after_tax"], company_currency);
|
||||
|
||||
frm.set_currency_labels(["paid_amount"], frm.doc.paid_from_account_currency);
|
||||
frm.set_currency_labels(["received_amount"], frm.doc.paid_to_account_currency);
|
||||
@@ -224,11 +238,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
var party_account_currency = frm.doc.payment_type=="Receive" ?
|
||||
frm.doc.paid_from_account_currency : frm.doc.paid_to_account_currency;
|
||||
|
||||
frm.set_currency_labels(["total_allocated_amount", "unallocated_amount"], party_account_currency);
|
||||
frm.set_currency_labels(["total_allocated_amount", "unallocated_amount",
|
||||
"total_taxes_and_charges"], party_account_currency);
|
||||
|
||||
var currency_field = (frm.doc.payment_type=="Receive") ? "paid_from_account_currency" : "paid_to_account_currency"
|
||||
frm.set_df_property("total_allocated_amount", "options", currency_field);
|
||||
frm.set_df_property("unallocated_amount", "options", currency_field);
|
||||
frm.set_df_property("total_taxes_and_charges", "options", currency_field);
|
||||
frm.set_df_property("party_balance", "options", currency_field);
|
||||
|
||||
frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
|
||||
@@ -364,6 +380,16 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
},
|
||||
|
||||
apply_tax_withholding_amount: function(frm) {
|
||||
if (!frm.doc.apply_tax_withholding_amount) {
|
||||
frm.set_value("tax_withholding_category", '');
|
||||
} else {
|
||||
frappe.db.get_value('Supplier', frm.doc.party, 'tax_withholding_category', (values) => {
|
||||
frm.set_value("tax_withholding_category", values.tax_withholding_category);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
paid_from: function(frm) {
|
||||
if(frm.set_party_account_based_on_party) return;
|
||||
|
||||
@@ -843,12 +869,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
if(frm.doc.payment_type == "Receive"
|
||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
|
||||
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
|
||||
unallocated_amount = (frm.doc.base_received_amount + total_deductions
|
||||
- frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
|
||||
unallocated_amount = (frm.doc.base_received_amount + total_deductions + frm.doc.base_total_taxes_and_charges
|
||||
+ frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
|
||||
} else if (frm.doc.payment_type == "Pay"
|
||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
|
||||
&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
|
||||
unallocated_amount = (frm.doc.base_paid_amount - (total_deductions
|
||||
unallocated_amount = (frm.doc.base_paid_amount + frm.doc.base_total_taxes_and_charges - (total_deductions
|
||||
+ frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate;
|
||||
}
|
||||
}
|
||||
@@ -874,7 +900,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
var total_deductions = frappe.utils.sum($.map(frm.doc.deductions || [],
|
||||
function(d) { return flt(d.amount) }));
|
||||
|
||||
frm.set_value("difference_amount", difference_amount - total_deductions);
|
||||
frm.set_value("difference_amount", difference_amount - total_deductions +
|
||||
frm.doc.base_total_taxes_and_charges);
|
||||
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
},
|
||||
@@ -1002,7 +1029,266 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
sales_taxes_and_charges_template: function(frm) {
|
||||
frm.trigger('fetch_taxes_from_template');
|
||||
},
|
||||
|
||||
purchase_taxes_and_charges_template: function(frm) {
|
||||
frm.trigger('fetch_taxes_from_template');
|
||||
},
|
||||
|
||||
fetch_taxes_from_template: function(frm) {
|
||||
let master_doctype = '';
|
||||
let taxes_and_charges = '';
|
||||
|
||||
if (frm.doc.party_type == 'Supplier') {
|
||||
master_doctype = 'Purchase Taxes and Charges Template';
|
||||
taxes_and_charges = frm.doc.purchase_taxes_and_charges_template;
|
||||
} else if (frm.doc.party_type == 'Customer') {
|
||||
master_doctype = 'Sales Taxes and Charges Template';
|
||||
taxes_and_charges = frm.doc.sales_taxes_and_charges_template;
|
||||
}
|
||||
|
||||
if (!taxes_and_charges) {
|
||||
return;
|
||||
}
|
||||
|
||||
frappe.call({
|
||||
method: "erpnext.controllers.accounts_controller.get_taxes_and_charges",
|
||||
args: {
|
||||
"master_doctype": master_doctype,
|
||||
"master_name": taxes_and_charges
|
||||
},
|
||||
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';
|
||||
}
|
||||
me.frm.add_child("taxes", tax);
|
||||
}
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
apply_taxes: function(frm) {
|
||||
frm.events.initialize_taxes(frm);
|
||||
frm.events.determine_exclusive_rate(frm);
|
||||
frm.events.calculate_taxes(frm);
|
||||
},
|
||||
|
||||
initialize_taxes: function(frm) {
|
||||
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||
frm.events.validate_taxes_and_charges(tax);
|
||||
frm.events.validate_inclusive_tax(tax);
|
||||
tax.item_wise_tax_detail = {};
|
||||
let tax_fields = ["total", "tax_fraction_for_current_item",
|
||||
"grand_total_fraction_for_current_item"];
|
||||
|
||||
if (cstr(tax.charge_type) != "Actual") {
|
||||
tax_fields.push("tax_amount");
|
||||
}
|
||||
|
||||
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
|
||||
|
||||
frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
|
||||
});
|
||||
},
|
||||
|
||||
validate_taxes_and_charges: function(d) {
|
||||
let msg = "";
|
||||
|
||||
if (d.account_head && !d.description) {
|
||||
// set description from account head
|
||||
d.description = d.account_head.split(' - ').slice(0, -1).join(' - ');
|
||||
}
|
||||
|
||||
if (!d.charge_type && (d.row_id || d.rate || d.tax_amount)) {
|
||||
msg = __("Please select Charge Type first");
|
||||
d.row_id = "";
|
||||
d.rate = d.tax_amount = 0.0;
|
||||
} else if ((d.charge_type == 'Actual' || d.charge_type == 'On Net Total' || d.charge_type == 'On Paid Amount') && d.row_id) {
|
||||
msg = __("Can refer row only if the charge type is 'On Previous Row Amount' or 'Previous Row Total'");
|
||||
d.row_id = "";
|
||||
} else if ((d.charge_type == 'On Previous Row Amount' || d.charge_type == 'On Previous Row Total') && d.row_id) {
|
||||
if (d.idx == 1) {
|
||||
msg = __("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row");
|
||||
d.charge_type = '';
|
||||
} else if (!d.row_id) {
|
||||
msg = __("Please specify a valid Row ID for row {0} in table {1}", [d.idx, __(d.doctype)]);
|
||||
d.row_id = "";
|
||||
} else if (d.row_id && d.row_id >= d.idx) {
|
||||
msg = __("Cannot refer row number greater than or equal to current row number for this Charge type");
|
||||
d.row_id = "";
|
||||
}
|
||||
}
|
||||
if (msg) {
|
||||
frappe.validated = false;
|
||||
refresh_field("taxes");
|
||||
frappe.throw(msg);
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
validate_inclusive_tax: function(tax) {
|
||||
let actual_type_error = function() {
|
||||
let msg = __("Actual type tax cannot be included in Item rate in row {0}", [tax.idx])
|
||||
frappe.throw(msg);
|
||||
};
|
||||
|
||||
let on_previous_row_error = function(row_range) {
|
||||
let msg = __("For row {0} in {1}. To include {2} in Item rate, rows {3} must also be included",
|
||||
[tax.idx, __(tax.doctype), tax.charge_type, row_range])
|
||||
frappe.throw(msg);
|
||||
};
|
||||
|
||||
if(cint(tax.included_in_paid_amount)) {
|
||||
if(tax.charge_type == "Actual") {
|
||||
// inclusive tax cannot be of type Actual
|
||||
actual_type_error();
|
||||
} else if(tax.charge_type == "On Previous Row Amount" &&
|
||||
!cint(this.frm.doc["taxes"][tax.row_id - 1].included_in_paid_amount)
|
||||
) {
|
||||
// referred row should also be an inclusive tax
|
||||
on_previous_row_error(tax.row_id);
|
||||
} else if(tax.charge_type == "On Previous Row Total") {
|
||||
let taxes_not_included = $.map(this.frm.doc["taxes"].slice(0, tax.row_id),
|
||||
function(t) { return cint(t.included_in_paid_amount) ? null : t; });
|
||||
if(taxes_not_included.length > 0) {
|
||||
// all rows above this tax should be inclusive
|
||||
on_previous_row_error(tax.row_id == 1 ? "1" : "1 - " + tax.row_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
determine_exclusive_rate: function(frm) {
|
||||
let has_inclusive_tax = false;
|
||||
$.each(frm.doc["taxes"] || [], function(i, row) {
|
||||
if(cint(row.included_in_paid_amount)) has_inclusive_tax = true;
|
||||
});
|
||||
if(has_inclusive_tax==false) return;
|
||||
|
||||
let cumulated_tax_fraction = 0.0;
|
||||
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||
tax.tax_fraction_for_current_item = frm.events.get_current_tax_fraction(frm, tax);
|
||||
|
||||
if(i==0) {
|
||||
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item;
|
||||
} else {
|
||||
tax.grand_total_fraction_for_current_item =
|
||||
me.frm.doc["taxes"][i-1].grand_total_fraction_for_current_item +
|
||||
tax.tax_fraction_for_current_item;
|
||||
}
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item;
|
||||
frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
|
||||
});
|
||||
},
|
||||
|
||||
get_current_tax_fraction: function(frm, tax) {
|
||||
let current_tax_fraction = 0.0;
|
||||
|
||||
if(cint(tax.included_in_paid_amount)) {
|
||||
let tax_rate = tax.rate;
|
||||
|
||||
if(tax.charge_type == "On Paid Amount") {
|
||||
current_tax_fraction = (tax_rate / 100.0);
|
||||
} else if(tax.charge_type == "On Previous Row Amount") {
|
||||
current_tax_fraction = (tax_rate / 100.0) *
|
||||
frm.doc["taxes"][cint(tax.row_id) - 1].tax_fraction_for_current_item;
|
||||
} else if(tax.charge_type == "On Previous Row Total") {
|
||||
current_tax_fraction = (tax_rate / 100.0) *
|
||||
frm.doc["taxes"][cint(tax.row_id) - 1].grand_total_fraction_for_current_item;
|
||||
}
|
||||
}
|
||||
|
||||
if(tax.add_deduct_tax && tax.add_deduct_tax == "Deduct") {
|
||||
current_tax_fraction *= -1;
|
||||
}
|
||||
return current_tax_fraction;
|
||||
},
|
||||
|
||||
|
||||
calculate_taxes: function(frm) {
|
||||
frm.doc.total_taxes_and_charges = 0.0;
|
||||
frm.doc.base_total_taxes_and_charges = 0.0;
|
||||
|
||||
let actual_tax_dict = {};
|
||||
|
||||
// maintain actual tax rate based on idx
|
||||
$.each(frm.doc["taxes"] || [], function(i, tax) {
|
||||
if (tax.charge_type == "Actual") {
|
||||
actual_tax_dict[tax.idx] = flt(tax.tax_amount, precision("tax_amount", tax));
|
||||
}
|
||||
});
|
||||
|
||||
$.each(me.frm.doc["taxes"] || [], function(i, tax) {
|
||||
let current_tax_amount = frm.events.get_current_tax_amount(frm, tax);
|
||||
|
||||
// Adjust divisional loss to the last item
|
||||
if (tax.charge_type == "Actual") {
|
||||
actual_tax_dict[tax.idx] -= current_tax_amount;
|
||||
if (i == frm.doc["taxes"].length - 1) {
|
||||
current_tax_amount += actual_tax_dict[tax.idx];
|
||||
}
|
||||
}
|
||||
|
||||
tax.tax_amount = current_tax_amount;
|
||||
tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
|
||||
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
|
||||
|
||||
if(i==0) {
|
||||
tax.total = flt(frm.doc.paid_amount_after_tax + current_tax_amount, precision("total", tax));
|
||||
} else {
|
||||
tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
|
||||
}
|
||||
|
||||
tax.base_total = tax.total * frm.doc.source_exchange_rate;
|
||||
frm.doc.total_taxes_and_charges += current_tax_amount;
|
||||
frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate;
|
||||
|
||||
frm.refresh_field('taxes');
|
||||
frm.refresh_field('total_taxes_and_charges');
|
||||
frm.refresh_field('base_total_taxes_and_charges');
|
||||
});
|
||||
},
|
||||
|
||||
get_current_tax_amount: function(frm, tax) {
|
||||
let tax_rate = tax.rate;
|
||||
let current_tax_amount = 0.0;
|
||||
|
||||
// To set row_id by default as previous row.
|
||||
if(["On Previous Row Amount", "On Previous Row Total"].includes(tax.charge_type)) {
|
||||
if (tax.idx === 1) {
|
||||
frappe.throw(
|
||||
__("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"));
|
||||
}
|
||||
}
|
||||
|
||||
if(tax.charge_type == "Actual") {
|
||||
current_tax_amount = flt(tax.tax_amount, precision("tax_amount", tax))
|
||||
} else if(tax.charge_type == "On Paid Amount") {
|
||||
current_tax_amount = flt((tax_rate / 100.0) * frm.doc.paid_amount_after_tax);
|
||||
} else if(tax.charge_type == "On Previous Row Amount") {
|
||||
current_tax_amount = flt((tax_rate / 100.0) *
|
||||
frm.doc["taxes"][cint(tax.row_id) - 1].tax_amount);
|
||||
|
||||
} else if(tax.charge_type == "On Previous Row Total") {
|
||||
current_tax_amount = flt((tax_rate / 100.0) *
|
||||
frm.doc["taxes"][cint(tax.row_id) - 1].total);
|
||||
}
|
||||
|
||||
return current_tax_amount;
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1049,6 +1335,38 @@ frappe.ui.form.on('Payment Entry Reference', {
|
||||
}
|
||||
})
|
||||
|
||||
frappe.ui.form.on('Advance Taxes and Charges', {
|
||||
rate: function(frm) {
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
|
||||
tax_amount : function(frm) {
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
|
||||
row_id: function(frm) {
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
|
||||
taxes_remove: function(frm) {
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
|
||||
included_in_paid_amount: function(frm) {
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
|
||||
charge_type: function(frm) {
|
||||
frm.events.apply_taxes(frm);
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
}
|
||||
})
|
||||
|
||||
frappe.ui.form.on('Payment Entry Deduction', {
|
||||
amount: function(frm) {
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
|
||||
@@ -35,12 +35,16 @@
|
||||
"paid_to_account_balance",
|
||||
"payment_amounts_section",
|
||||
"paid_amount",
|
||||
"paid_amount_after_tax",
|
||||
"source_exchange_rate",
|
||||
"base_paid_amount",
|
||||
"base_paid_amount_after_tax",
|
||||
"column_break_21",
|
||||
"received_amount",
|
||||
"received_amount_after_tax",
|
||||
"target_exchange_rate",
|
||||
"base_received_amount",
|
||||
"base_received_amount_after_tax",
|
||||
"section_break_14",
|
||||
"get_outstanding_invoice",
|
||||
"references",
|
||||
@@ -52,6 +56,17 @@
|
||||
"unallocated_amount",
|
||||
"difference_amount",
|
||||
"write_off_difference_amount",
|
||||
"taxes_and_charges_section",
|
||||
"purchase_taxes_and_charges_template",
|
||||
"sales_taxes_and_charges_template",
|
||||
"advance_tax_account",
|
||||
"column_break_55",
|
||||
"apply_tax_withholding_amount",
|
||||
"tax_withholding_category",
|
||||
"section_break_56",
|
||||
"taxes",
|
||||
"base_total_taxes_and_charges",
|
||||
"total_taxes_and_charges",
|
||||
"deductions_or_loss_section",
|
||||
"deductions",
|
||||
"transaction_references",
|
||||
@@ -320,6 +335,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "doc.received_amount",
|
||||
"fieldname": "base_received_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Received Amount (Company Currency)",
|
||||
@@ -584,12 +600,119 @@
|
||||
"fieldname": "custom_remarks",
|
||||
"fieldtype": "Check",
|
||||
"label": "Custom Remarks"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Category",
|
||||
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"options": "Tax Withholding Category"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||
"fieldname": "apply_tax_withholding_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax Withholding Amount"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "taxes_and_charges_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Taxes and Charges"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||
"fieldname": "purchase_taxes_and_charges_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Taxes and Charges Template",
|
||||
"options": "Purchase Taxes and Charges Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.party_type == 'Customer'",
|
||||
"fieldname": "sales_taxes_and_charges_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Taxes and Charges Template",
|
||||
"options": "Sales Taxes and Charges Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.party_type == 'Supplier' || doc.party_type == 'Customer'",
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"label": "Advance Taxes and Charges",
|
||||
"options": "Advance Taxes and Charges"
|
||||
},
|
||||
{
|
||||
"fieldname": "base_total_taxes_and_charges",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Taxes and Charges (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "total_taxes_and_charges",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Taxes and Charges",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "paid_amount_after_tax",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Paid Amount After Tax",
|
||||
"options": "paid_from_account_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_paid_amount_after_tax",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Paid Amount After Tax (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_55",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_56",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"description": "Provisional tax account for advance tax. Taxes are parked in this account until payments are allocated to invoices",
|
||||
"fieldname": "advance_tax_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Advance Tax Account",
|
||||
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
|
||||
"fieldname": "received_amount_after_tax",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Received Amount After Tax",
|
||||
"options": "paid_to_account_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "doc.received_amount",
|
||||
"fieldname": "base_received_amount_after_tax",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 1,
|
||||
"label": "Received Amount After Tax (Company Currency)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-08 13:05:16.958866",
|
||||
"modified": "2021-07-09 08:58:15.008761",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
@@ -633,4 +756,4 @@
|
||||
"sort_order": "DESC",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import frappe, erpnext, json
|
||||
from frappe import _, scrub, ValidationError
|
||||
from frappe.utils import flt, comma_or, nowdate, getdate
|
||||
from frappe import _, scrub, ValidationError, throw
|
||||
from frappe.utils import flt, comma_or, nowdate, getdate, cint
|
||||
from erpnext.accounts.utils import get_outstanding_invoices, get_account_currency, get_balance_on
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import get_default_bank_cash_account
|
||||
@@ -15,9 +15,11 @@ from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amo
|
||||
from erpnext.accounts.doctype.bank_account.bank_account import get_party_bank_account, get_bank_account_details
|
||||
from erpnext.controllers.accounts_controller import AccountsController, get_supplier_block_status
|
||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
|
||||
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import get_party_tax_withholding_details
|
||||
from six import string_types, iteritems
|
||||
|
||||
from erpnext.controllers.accounts_controller import validate_taxes_and_charges
|
||||
|
||||
class InvalidPaymentEntry(ValidationError):
|
||||
pass
|
||||
|
||||
@@ -52,6 +54,8 @@ class PaymentEntry(AccountsController):
|
||||
self.set_exchange_rate()
|
||||
self.validate_mandatory()
|
||||
self.validate_reference_documents()
|
||||
self.set_tax_withholding()
|
||||
self.apply_taxes()
|
||||
self.set_amounts()
|
||||
self.clear_unallocated_reference_document_rows()
|
||||
self.validate_payment_against_negative_invoice()
|
||||
@@ -65,7 +69,6 @@ class PaymentEntry(AccountsController):
|
||||
self.set_status()
|
||||
|
||||
def on_submit(self):
|
||||
self.setup_party_account_field()
|
||||
if self.difference_amount:
|
||||
frappe.throw(_("Difference Amount must be zero"))
|
||||
self.make_gl_entries()
|
||||
@@ -78,7 +81,6 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry')
|
||||
self.setup_party_account_field()
|
||||
self.make_gl_entries(cancel=1)
|
||||
self.update_outstanding_amounts()
|
||||
self.update_advance_paid()
|
||||
@@ -122,6 +124,11 @@ class PaymentEntry(AccountsController):
|
||||
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
|
||||
|
||||
# Check for negative outstanding invoices as well
|
||||
if flt(d.allocated_amount) < 0:
|
||||
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||
frappe.throw(_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx))
|
||||
|
||||
def delink_advance_entry_references(self):
|
||||
for reference in self.references:
|
||||
if reference.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
@@ -177,7 +184,7 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
for field, value in iteritems(ref_details):
|
||||
if field == 'exchange_rate' or not d.get(field) or force:
|
||||
d.set(field, value)
|
||||
d.db_set(field, value)
|
||||
|
||||
def validate_payment_type(self):
|
||||
if self.payment_type not in ("Receive", "Pay", "Internal Transfer"):
|
||||
@@ -303,11 +310,10 @@ class PaymentEntry(AccountsController):
|
||||
for k, v in no_oustanding_refs.items():
|
||||
frappe.msgprint(
|
||||
_("{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry.")
|
||||
.format(k, frappe.bold(", ".join([d.reference_name for d in v])), frappe.bold("negative outstanding amount"))
|
||||
.format(k, frappe.bold(", ".join(d.reference_name for d in v)), frappe.bold("negative outstanding amount"))
|
||||
+ "<br><br>" + _("If this is undesirable please cancel the corresponding Payment Entry."),
|
||||
title=_("Warning"), indicator="orange")
|
||||
|
||||
|
||||
def validate_journal_entry(self):
|
||||
for d in self.get("references"):
|
||||
if d.allocated_amount and d.reference_doctype == "Journal Entry":
|
||||
@@ -386,12 +392,104 @@ class PaymentEntry(AccountsController):
|
||||
else:
|
||||
self.status = 'Draft'
|
||||
|
||||
self.db_set('status', self.status, update_modified = True)
|
||||
|
||||
def set_tax_withholding(self):
|
||||
if not self.party_type == 'Supplier':
|
||||
return
|
||||
|
||||
if not self.apply_tax_withholding_amount:
|
||||
return
|
||||
|
||||
if not self.advance_tax_account:
|
||||
frappe.throw(_("Advance TDS account is mandatory for advance TDS deduction"))
|
||||
|
||||
net_total = self.paid_amount
|
||||
|
||||
for reference in self.get("references"):
|
||||
net_total_for_tds = 0
|
||||
if reference.reference_doctype == 'Purchase Order':
|
||||
net_total_for_tds += flt(frappe.db.get_value('Purchase Order', reference.reference_name, 'net_total'))
|
||||
|
||||
if net_total_for_tds:
|
||||
net_total = net_total_for_tds
|
||||
|
||||
# Adding args as purchase invoice to get TDS amount
|
||||
args = frappe._dict({
|
||||
'company': self.company,
|
||||
'doctype': 'Purchase Invoice',
|
||||
'supplier': self.party,
|
||||
'posting_date': self.posting_date,
|
||||
'net_total': net_total
|
||||
})
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
tax_withholding_details.update({
|
||||
'add_deduct_tax': 'Add',
|
||||
'cost_center': self.cost_center or erpnext.get_default_cost_center(self.company)
|
||||
})
|
||||
|
||||
accounts = []
|
||||
for d in self.taxes:
|
||||
if d.account_head == tax_withholding_details.get("account_head"):
|
||||
|
||||
# Preserve user updated included in paid amount
|
||||
if d.included_in_paid_amount:
|
||||
tax_withholding_details.update({'included_in_paid_amount': d.included_in_paid_amount})
|
||||
|
||||
d.update(tax_withholding_details)
|
||||
accounts.append(d.account_head)
|
||||
|
||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||
self.append("taxes", tax_withholding_details)
|
||||
|
||||
to_remove = [d for d in self.taxes
|
||||
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")]
|
||||
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
def apply_taxes(self):
|
||||
self.initialize_taxes()
|
||||
self.determine_exclusive_rate()
|
||||
self.calculate_taxes()
|
||||
|
||||
def set_amounts(self):
|
||||
self.set_received_amount()
|
||||
self.set_amounts_in_company_currency()
|
||||
self.set_amounts_after_tax()
|
||||
self.set_total_allocated_amount()
|
||||
self.set_unallocated_amount()
|
||||
self.set_difference_amount()
|
||||
|
||||
def set_received_amount(self):
|
||||
self.base_received_amount = self.base_paid_amount
|
||||
|
||||
def set_amounts_after_tax(self):
|
||||
applicable_tax = 0
|
||||
base_applicable_tax = 0
|
||||
for tax in self.get('taxes'):
|
||||
if not tax.included_in_paid_amount:
|
||||
amount = -1 * tax.tax_amount if tax.add_deduct_tax == 'Deduct' else tax.tax_amount
|
||||
base_amount = -1 * tax.base_tax_amount if tax.add_deduct_tax == 'Deduct' else tax.base_tax_amount
|
||||
|
||||
applicable_tax += amount
|
||||
base_applicable_tax += base_amount
|
||||
|
||||
self.paid_amount_after_tax = flt(flt(self.paid_amount) + flt(applicable_tax),
|
||||
self.precision("paid_amount_after_tax"))
|
||||
self.base_paid_amount_after_tax = flt(flt(self.paid_amount_after_tax) * flt(self.source_exchange_rate),
|
||||
self.precision("base_paid_amount_after_tax"))
|
||||
|
||||
self.received_amount_after_tax = flt(flt(self.received_amount) + flt(applicable_tax),
|
||||
self.precision("paid_amount_after_tax"))
|
||||
self.base_received_amount_after_tax = flt(flt(self.received_amount_after_tax) * flt(self.target_exchange_rate),
|
||||
self.precision("base_paid_amount_after_tax"))
|
||||
|
||||
def set_amounts_in_company_currency(self):
|
||||
self.base_paid_amount, self.base_received_amount, self.difference_amount = 0, 0, 0
|
||||
if self.paid_amount:
|
||||
@@ -419,17 +517,20 @@ class PaymentEntry(AccountsController):
|
||||
def set_unallocated_amount(self):
|
||||
self.unallocated_amount = 0
|
||||
if self.party:
|
||||
total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
|
||||
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
||||
included_taxes = self.get_included_taxes()
|
||||
if self.payment_type == "Receive" \
|
||||
and self.base_total_allocated_amount < self.base_received_amount + total_deductions \
|
||||
and self.total_allocated_amount < self.paid_amount + (total_deductions / self.source_exchange_rate):
|
||||
self.unallocated_amount = (self.base_received_amount + total_deductions -
|
||||
self.base_total_allocated_amount) / self.source_exchange_rate
|
||||
self.unallocated_amount = (self.received_amount + total_deductions -
|
||||
self.base_total_allocated_amount) / self.source_exchange_rate
|
||||
self.unallocated_amount -= included_taxes
|
||||
elif self.payment_type == "Pay" \
|
||||
and self.base_total_allocated_amount < (self.base_paid_amount - total_deductions) \
|
||||
and self.total_allocated_amount < self.received_amount + (total_deductions / self.target_exchange_rate):
|
||||
self.unallocated_amount = (self.base_paid_amount - (total_deductions +
|
||||
self.base_total_allocated_amount)) / self.target_exchange_rate
|
||||
self.unallocated_amount = (self.base_paid_amount - (total_deductions +
|
||||
self.base_total_allocated_amount)) / self.target_exchange_rate
|
||||
self.unallocated_amount -= included_taxes
|
||||
|
||||
def set_difference_amount(self):
|
||||
base_unallocated_amount = flt(self.unallocated_amount) * (flt(self.source_exchange_rate)
|
||||
@@ -444,11 +545,23 @@ class PaymentEntry(AccountsController):
|
||||
else:
|
||||
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
||||
|
||||
total_deductions = sum([flt(d.amount) for d in self.get("deductions")])
|
||||
total_deductions = sum(flt(d.amount) for d in self.get("deductions"))
|
||||
included_taxes = self.get_included_taxes()
|
||||
|
||||
self.difference_amount = flt(self.difference_amount - total_deductions,
|
||||
self.difference_amount = flt(self.difference_amount - total_deductions - included_taxes,
|
||||
self.precision("difference_amount"))
|
||||
|
||||
def get_included_taxes(self):
|
||||
included_taxes = 0
|
||||
for tax in self.get('taxes'):
|
||||
if tax.included_in_paid_amount:
|
||||
if tax.add_deduct_tax == 'Add':
|
||||
included_taxes += tax.base_tax_amount
|
||||
else:
|
||||
included_taxes -= tax.base_tax_amount
|
||||
|
||||
return included_taxes
|
||||
|
||||
# Paid amount is auto allocated in the reference document by default.
|
||||
# Clear the reference document which doesn't have allocated amount on validate so that form can be loaded fast
|
||||
def clear_unallocated_reference_document_rows(self):
|
||||
@@ -460,8 +573,8 @@ class PaymentEntry(AccountsController):
|
||||
if ((self.payment_type=="Pay" and self.party_type=="Customer")
|
||||
or (self.payment_type=="Receive" and self.party_type=="Supplier")):
|
||||
|
||||
total_negative_outstanding = sum([abs(flt(d.outstanding_amount))
|
||||
for d in self.get("references") if flt(d.outstanding_amount) < 0])
|
||||
total_negative_outstanding = sum(abs(flt(d.outstanding_amount))
|
||||
for d in self.get("references") if flt(d.outstanding_amount) < 0)
|
||||
|
||||
paid_amount = self.paid_amount if self.payment_type=="Receive" else self.received_amount
|
||||
additional_charges = sum([flt(d.amount) for d in self.deductions])
|
||||
@@ -532,6 +645,7 @@ class PaymentEntry(AccountsController):
|
||||
self.add_party_gl_entries(gl_entries)
|
||||
self.add_bank_gl_entries(gl_entries)
|
||||
self.add_deductions_gl_entries(gl_entries)
|
||||
self.add_tax_gl_entries(gl_entries)
|
||||
|
||||
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
||||
|
||||
@@ -571,7 +685,7 @@ class PaymentEntry(AccountsController):
|
||||
gl_entries.append(gle)
|
||||
|
||||
if self.unallocated_amount:
|
||||
base_unallocated_amount = base_unallocated_amount = self.unallocated_amount * \
|
||||
base_unallocated_amount = self.unallocated_amount * \
|
||||
(self.source_exchange_rate if self.payment_type=="Receive" else self.target_exchange_rate)
|
||||
|
||||
gle = party_gl_dict.copy()
|
||||
@@ -607,6 +721,51 @@ class PaymentEntry(AccountsController):
|
||||
}, item=self)
|
||||
)
|
||||
|
||||
def add_tax_gl_entries(self, gl_entries):
|
||||
for d in self.get('taxes'):
|
||||
account_currency = get_account_currency(d.account_head)
|
||||
if account_currency != self.company_currency:
|
||||
frappe.throw(_("Currency for {0} must be {1}").format(d.account_head, self.company_currency))
|
||||
|
||||
if self.payment_type in ('Pay', 'Internal Transfer'):
|
||||
dr_or_cr = "debit" if d.add_deduct_tax == "Add" else "credit"
|
||||
against = self.party or self.paid_from
|
||||
elif self.payment_type == 'Receive':
|
||||
dr_or_cr = "credit" if d.add_deduct_tax == "Add" else "debit"
|
||||
against = self.party or self.paid_to
|
||||
|
||||
payment_or_advance_account = self.get_party_account_for_taxes()
|
||||
tax_amount = d.tax_amount
|
||||
base_tax_amount = d.base_tax_amount
|
||||
|
||||
if self.advance_tax_account:
|
||||
tax_amount = -1 * tax_amount
|
||||
base_tax_amount = -1 * base_tax_amount
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": d.account_head,
|
||||
"against": against,
|
||||
dr_or_cr: tax_amount,
|
||||
dr_or_cr + "_in_account_currency": base_tax_amount
|
||||
if account_currency==self.company_currency
|
||||
else d.tax_amount,
|
||||
"cost_center": d.cost_center
|
||||
}, account_currency, item=d))
|
||||
|
||||
#Intentionally use -1 to get net values in party account
|
||||
if not d.included_in_paid_amount or self.advance_tax_account:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
"account": payment_or_advance_account,
|
||||
"against": against,
|
||||
dr_or_cr: -1 * tax_amount,
|
||||
dr_or_cr + "_in_account_currency": -1 * base_tax_amount
|
||||
if account_currency==self.company_currency
|
||||
else d.tax_amount,
|
||||
"cost_center": self.cost_center,
|
||||
}, account_currency, item=d))
|
||||
|
||||
def add_deductions_gl_entries(self, gl_entries):
|
||||
for d in self.get("deductions"):
|
||||
if d.amount:
|
||||
@@ -625,6 +784,14 @@ class PaymentEntry(AccountsController):
|
||||
}, item=d)
|
||||
)
|
||||
|
||||
def get_party_account_for_taxes(self):
|
||||
if self.advance_tax_account:
|
||||
return self.advance_tax_account
|
||||
elif self.payment_type == 'Receive':
|
||||
return self.paid_to
|
||||
elif self.payment_type in ('Pay', 'Internal Transfer'):
|
||||
return self.paid_from
|
||||
|
||||
def update_advance_paid(self):
|
||||
if self.payment_type in ("Receive", "Pay") and self.party:
|
||||
for d in self.get("references"):
|
||||
@@ -671,6 +838,139 @@ class PaymentEntry(AccountsController):
|
||||
self.append('deductions', row)
|
||||
self.set_unallocated_amount()
|
||||
|
||||
def initialize_taxes(self):
|
||||
for tax in self.get("taxes"):
|
||||
validate_taxes_and_charges(tax)
|
||||
validate_inclusive_tax(tax, self)
|
||||
|
||||
tax_fields = ["total", "tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
|
||||
|
||||
if tax.charge_type != "Actual":
|
||||
tax_fields.append("tax_amount")
|
||||
|
||||
for fieldname in tax_fields:
|
||||
tax.set(fieldname, 0.0)
|
||||
|
||||
self.paid_amount_after_tax = self.paid_amount
|
||||
|
||||
def determine_exclusive_rate(self):
|
||||
if not any((cint(tax.included_in_paid_amount) for tax in self.get("taxes"))):
|
||||
return
|
||||
|
||||
cumulated_tax_fraction = 0
|
||||
for i, tax in enumerate(self.get("taxes")):
|
||||
tax.tax_fraction_for_current_item = self.get_current_tax_fraction(tax)
|
||||
if i==0:
|
||||
tax.grand_total_fraction_for_current_item = 1 + tax.tax_fraction_for_current_item
|
||||
else:
|
||||
tax.grand_total_fraction_for_current_item = \
|
||||
self.get("taxes")[i-1].grand_total_fraction_for_current_item \
|
||||
+ tax.tax_fraction_for_current_item
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||
|
||||
self.paid_amount_after_tax = flt(self.paid_amount/(1+cumulated_tax_fraction))
|
||||
|
||||
def calculate_taxes(self):
|
||||
self.total_taxes_and_charges = 0.0
|
||||
self.base_total_taxes_and_charges = 0.0
|
||||
|
||||
actual_tax_dict = dict([[tax.idx, flt(tax.tax_amount, tax.precision("tax_amount"))]
|
||||
for tax in self.get("taxes") if tax.charge_type == "Actual"])
|
||||
|
||||
for i, tax in enumerate(self.get('taxes')):
|
||||
current_tax_amount = self.get_current_tax_amount(tax)
|
||||
|
||||
if tax.charge_type == "Actual":
|
||||
actual_tax_dict[tax.idx] -= current_tax_amount
|
||||
if i == len(self.get("taxes")) - 1:
|
||||
current_tax_amount += actual_tax_dict[tax.idx]
|
||||
|
||||
tax.tax_amount = current_tax_amount
|
||||
tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
|
||||
|
||||
if tax.add_deduct_tax == "Deduct":
|
||||
current_tax_amount *= -1.0
|
||||
else:
|
||||
current_tax_amount *= 1.0
|
||||
|
||||
if i == 0:
|
||||
tax.total = flt(self.paid_amount_after_tax + current_tax_amount, self.precision("total", tax))
|
||||
else:
|
||||
tax.total = flt(self.get('taxes')[i-1].total + current_tax_amount, self.precision("total", tax))
|
||||
|
||||
tax.base_total = tax.total * self.source_exchange_rate
|
||||
|
||||
self.total_taxes_and_charges += current_tax_amount
|
||||
self.base_total_taxes_and_charges += current_tax_amount * self.source_exchange_rate
|
||||
|
||||
if self.get('taxes'):
|
||||
self.paid_amount_after_tax = self.get('taxes')[-1].base_total
|
||||
|
||||
def get_current_tax_amount(self, tax):
|
||||
tax_rate = tax.rate
|
||||
|
||||
# To set row_id by default as previous row.
|
||||
if tax.charge_type in ["On Previous Row Amount", "On Previous Row Total"]:
|
||||
if tax.idx == 1:
|
||||
frappe.throw(_("Cannot select charge type as 'On Previous Row Amount' or 'On Previous Row Total' for first row"))
|
||||
|
||||
if not tax.row_id:
|
||||
tax.row_id = tax.idx - 1
|
||||
|
||||
if tax.charge_type == "Actual":
|
||||
current_tax_amount = flt(tax.tax_amount, self.precision("tax_amount", tax))
|
||||
elif tax.charge_type == "On Paid Amount":
|
||||
current_tax_amount = (tax_rate / 100.0) * self.paid_amount_after_tax
|
||||
elif tax.charge_type == "On Previous Row Amount":
|
||||
current_tax_amount = (tax_rate / 100.0) * \
|
||||
self.get('taxes')[cint(tax.row_id) - 1].tax_amount
|
||||
|
||||
elif tax.charge_type == "On Previous Row Total":
|
||||
current_tax_amount = (tax_rate / 100.0) * \
|
||||
self.get('taxes')[cint(tax.row_id) - 1].total
|
||||
|
||||
return current_tax_amount
|
||||
|
||||
def get_current_tax_fraction(self, tax):
|
||||
current_tax_fraction = 0
|
||||
|
||||
if cint(tax.included_in_paid_amount):
|
||||
tax_rate = tax.rate
|
||||
|
||||
if tax.charge_type == "On Paid Amount":
|
||||
current_tax_fraction = tax_rate / 100.0
|
||||
elif tax.charge_type == "On Previous Row Amount":
|
||||
current_tax_fraction = (tax_rate / 100.0) * \
|
||||
self.get("taxes")[cint(tax.row_id) - 1].tax_fraction_for_current_item
|
||||
elif tax.charge_type == "On Previous Row Total":
|
||||
current_tax_fraction = (tax_rate / 100.0) * \
|
||||
self.get("taxes")[cint(tax.row_id) - 1].grand_total_fraction_for_current_item
|
||||
|
||||
if getattr(tax, "add_deduct_tax", None) and tax.add_deduct_tax == "Deduct":
|
||||
current_tax_fraction *= -1.0
|
||||
|
||||
return current_tax_fraction
|
||||
|
||||
def validate_inclusive_tax(tax, doc):
|
||||
def _on_previous_row_error(row_range):
|
||||
throw(_("To include tax in row {0} in Item rate, taxes in rows {1} must also be included").format(tax.idx, row_range))
|
||||
|
||||
if cint(getattr(tax, "included_in_paid_amount", None)):
|
||||
if tax.charge_type == "Actual":
|
||||
# inclusive tax cannot be of type Actual
|
||||
throw(_("Charge of type 'Actual' in row {0} cannot be included in Item Rate or Paid Amount").format(tax.idx))
|
||||
elif tax.charge_type == "On Previous Row Amount" and \
|
||||
not cint(doc.get("taxes")[cint(tax.row_id) - 1].included_in_paid_amount):
|
||||
# referred row should also be inclusive
|
||||
_on_previous_row_error(tax.row_id)
|
||||
elif tax.charge_type == "On Previous Row Total" and \
|
||||
not all([cint(t.included_in_paid_amount for t in doc.get("taxes")[:cint(tax.row_id) - 1])]):
|
||||
# all rows about the referred tax should be inclusive
|
||||
_on_previous_row_error("1 - %d" % (cint(tax.row_id),))
|
||||
elif tax.get("category") == "Valuation":
|
||||
frappe.throw(_("Valuation type charges can not be marked as Inclusive"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_outstanding_reference_documents(args):
|
||||
|
||||
@@ -791,7 +1091,7 @@ def split_invoices_based_on_payment_terms(outstanding_invoices):
|
||||
|
||||
outstanding_invoices.pop(idx - 1)
|
||||
outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
|
||||
|
||||
|
||||
return outstanding_invoices
|
||||
|
||||
def get_orders_to_be_billed(posting_date, party_type, party,
|
||||
@@ -989,6 +1289,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
elif reference_doctype == "Donation":
|
||||
total_amount = ref_doc.get("amount")
|
||||
outstanding_amount = total_amount
|
||||
exchange_rate = 1
|
||||
elif reference_doctype == "Dunning":
|
||||
total_amount = ref_doc.get("dunning_amount")
|
||||
@@ -1045,9 +1346,9 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
|
||||
return frappe._dict({
|
||||
"due_date": ref_doc.get("due_date"),
|
||||
"total_amount": total_amount,
|
||||
"outstanding_amount": outstanding_amount,
|
||||
"exchange_rate": exchange_rate,
|
||||
"total_amount": flt(total_amount),
|
||||
"outstanding_amount": flt(outstanding_amount),
|
||||
"exchange_rate": flt(exchange_rate),
|
||||
"bill_no": bill_no
|
||||
})
|
||||
|
||||
@@ -1235,6 +1536,13 @@ def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=
|
||||
})
|
||||
pe.set_difference_amount()
|
||||
|
||||
if doc.doctype == 'Purchase Order' and doc.apply_tds:
|
||||
pe.apply_tax_withholding_amount = 1
|
||||
pe.tax_withholding_category = doc.tax_withholding_category
|
||||
|
||||
if not pe.advance_tax_account:
|
||||
pe.advance_tax_account = frappe.db.get_value('Company', pe.company, 'unrealized_profit_loss_account')
|
||||
|
||||
return pe
|
||||
|
||||
def get_bank_cash_account(doc, bank_account):
|
||||
@@ -1353,6 +1661,7 @@ def set_paid_amount_and_received_amount(dt, party_account_currency, bank, outsta
|
||||
paid_amount = received_amount * doc.get('conversion_rate', 1)
|
||||
if dt == "Employee Advance":
|
||||
paid_amount = received_amount * doc.get('exchange_rate', 1)
|
||||
|
||||
return paid_amount, received_amount
|
||||
|
||||
def apply_early_payment_discount(paid_amount, received_amount, doc):
|
||||
|
||||
@@ -589,9 +589,9 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
party_account_balance = get_balance_on(account=pe.paid_from, cost_center=pe.cost_center)
|
||||
|
||||
self.assertEqual(pe.cost_center, si.cost_center)
|
||||
self.assertEqual(expected_account_balance, account_balance)
|
||||
self.assertEqual(expected_party_balance, party_balance)
|
||||
self.assertEqual(expected_party_account_balance, party_account_balance)
|
||||
self.assertEqual(flt(expected_account_balance), account_balance)
|
||||
self.assertEqual(flt(expected_party_balance), party_balance)
|
||||
self.assertEqual(flt(expected_party_account_balance), party_account_balance)
|
||||
|
||||
def create_payment_terms_template():
|
||||
|
||||
|
||||
@@ -1,140 +1,70 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-06-15 15:56:30.815503",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"actions": [],
|
||||
"creation": "2016-06-15 15:56:30.815503",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"field_order": [
|
||||
"account",
|
||||
"cost_center",
|
||||
"amount",
|
||||
"column_break_2",
|
||||
"description"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Account",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Account",
|
||||
"options": "Account",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Cost Center",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Cost Center",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center",
|
||||
"print_hide": 1,
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Description",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-01-07 16:52:07.040146",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Deduction",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-12 20:38:08.110674",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Deduction",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -31,10 +31,10 @@ class TestPaymentOrder(unittest.TestCase):
|
||||
|
||||
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
|
||||
reference_doc = doc.get("references")[0]
|
||||
self.assertEquals(reference_doc.reference_name, payment_entry.name)
|
||||
self.assertEquals(reference_doc.reference_doctype, "Payment Entry")
|
||||
self.assertEquals(reference_doc.supplier, "_Test Supplier")
|
||||
self.assertEquals(reference_doc.amount, 250)
|
||||
self.assertEqual(reference_doc.reference_name, payment_entry.name)
|
||||
self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
|
||||
self.assertEqual(reference_doc.supplier, "_Test Supplier")
|
||||
self.assertEqual(reference_doc.amount, 250)
|
||||
|
||||
def create_payment_order_against_payment_entry(ref_doc, order_type):
|
||||
payment_order = frappe.get_doc(dict(
|
||||
|
||||
@@ -114,7 +114,7 @@ class PaymentReconciliation(Document):
|
||||
'party_type': self.party_type,
|
||||
'voucher_type': voucher_type,
|
||||
'account': self.receivable_payable_account
|
||||
}, as_dict=1, debug=1)
|
||||
}, as_dict=1)
|
||||
|
||||
def add_payment_entries(self, entries):
|
||||
self.set('payments', [])
|
||||
|
||||
@@ -101,7 +101,7 @@ class PaymentRequest(Document):
|
||||
|
||||
controller.validate_transaction_currency(self.currency)
|
||||
controller.request_for_payment(**payment_record)
|
||||
|
||||
|
||||
def get_request_amount(self):
|
||||
data_of_completed_requests = frappe.get_all("Integration Request", filters={
|
||||
'reference_doctype': self.doctype,
|
||||
@@ -112,7 +112,7 @@ class PaymentRequest(Document):
|
||||
if not data_of_completed_requests:
|
||||
return self.grand_total
|
||||
|
||||
request_amounts = sum([json.loads(d).get('request_amount') for d in data_of_completed_requests])
|
||||
request_amounts = sum(json.loads(d).get('request_amount') for d in data_of_completed_requests)
|
||||
return request_amounts
|
||||
|
||||
def on_cancel(self):
|
||||
@@ -492,7 +492,6 @@ def update_payment_req_status(doc, method):
|
||||
status = 'Requested'
|
||||
|
||||
pay_req_doc.db_set('status', status)
|
||||
frappe.db.commit()
|
||||
|
||||
def get_dummy_message(doc):
|
||||
return frappe.render_template("""{% if doc.contact_person -%}
|
||||
|
||||
@@ -26,7 +26,7 @@ class PaymentTermsTemplate(Document):
|
||||
def check_duplicate_terms(self):
|
||||
terms = []
|
||||
for term in self.terms:
|
||||
term_info = (term.credit_days, term.credit_months, term.due_date_based_on)
|
||||
term_info = (term.payment_term, term.credit_days, term.credit_months, term.due_date_based_on)
|
||||
if term_info in terms:
|
||||
frappe.msgprint(
|
||||
_('The Payment Term at row {0} is possibly a duplicate.').format(term.idx),
|
||||
|
||||
@@ -1,350 +1,138 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "ACC-PCV-.YYYY.-.#####",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-10 16:34:07",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"autoname": "ACC-PCV-.YYYY.-.#####",
|
||||
"creation": "2013-01-10 16:34:07",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"transaction_date",
|
||||
"posting_date",
|
||||
"fiscal_year",
|
||||
"amended_from",
|
||||
"company",
|
||||
"cost_center_wise_pnl",
|
||||
"column_break1",
|
||||
"closing_account_head",
|
||||
"remarks"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Transaction Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "transaction_date",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Transaction Date",
|
||||
"oldfieldname": "transaction_date",
|
||||
"oldfieldtype": "Date"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Posting Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Posting Date",
|
||||
"oldfieldname": "posting_date",
|
||||
"oldfieldtype": "Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "fiscal_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Closing Fiscal Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "fiscal_year",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Fiscal Year",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "fiscal_year",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Closing Fiscal Year",
|
||||
"oldfieldname": "fiscal_year",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Fiscal Year",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 1,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "amended_from",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Period Closing Voucher",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "amended_from",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "Period Closing Voucher",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Company",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Company",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"oldfieldname": "company",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldtype": "Column Break",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break1",
|
||||
"fieldtype": "Column Break",
|
||||
"oldfieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "The account head under Liability or Equity, in which Profit/Loss will be booked",
|
||||
"fieldname": "closing_account_head",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Closing Account Head",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "closing_account_head",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Account",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"description": "The account head under Liability or Equity, in which Profit/Loss will be booked",
|
||||
"fieldname": "closing_account_head",
|
||||
"fieldtype": "Link",
|
||||
"label": "Closing Account Head",
|
||||
"oldfieldname": "closing_account_head",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Remarks",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"permlevel": 0,
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Remarks",
|
||||
"oldfieldname": "remarks",
|
||||
"oldfieldtype": "Small Text",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "cost_center_wise_pnl",
|
||||
"fieldtype": "Check",
|
||||
"label": "Book Cost Center Wise Profit/Loss"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Period Closing Voucher",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-20 15:27:37.210458",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Period Closing Voucher",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"search_fields": "posting_date, fiscal_year",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "closing_account_head",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"search_fields": "posting_date, fiscal_year",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"title_field": "closing_account_head"
|
||||
}
|
||||
@@ -51,63 +51,96 @@ class PeriodClosingVoucher(AccountsController):
|
||||
|
||||
def make_gl_entries(self):
|
||||
gl_entries = []
|
||||
net_pl_balance = 0
|
||||
dimension_fields = ['t1.cost_center']
|
||||
net_pl_balance = 0
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions()
|
||||
for dimension in accounting_dimensions:
|
||||
dimension_fields.append('t1.{0}'.format(dimension))
|
||||
|
||||
dimension_filters, default_dimensions = get_dimensions()
|
||||
|
||||
pl_accounts = self.get_pl_balances(dimension_fields)
|
||||
pl_accounts = self.get_pl_balances()
|
||||
|
||||
for acc in pl_accounts:
|
||||
if flt(acc.balance_in_company_currency):
|
||||
if flt(acc.bal_in_company_currency):
|
||||
gl_entries.append(self.get_gl_dict({
|
||||
"account": acc.account,
|
||||
"cost_center": acc.cost_center,
|
||||
"account_currency": acc.account_currency,
|
||||
"debit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
|
||||
if flt(acc.balance_in_account_currency) < 0 else 0,
|
||||
"debit": abs(flt(acc.balance_in_company_currency)) \
|
||||
if flt(acc.balance_in_company_currency) < 0 else 0,
|
||||
"credit_in_account_currency": abs(flt(acc.balance_in_account_currency)) \
|
||||
if flt(acc.balance_in_account_currency) > 0 else 0,
|
||||
"credit": abs(flt(acc.balance_in_company_currency)) \
|
||||
if flt(acc.balance_in_company_currency) > 0 else 0
|
||||
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
|
||||
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
|
||||
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0
|
||||
}, item=acc))
|
||||
|
||||
net_pl_balance += flt(acc.balance_in_company_currency)
|
||||
net_pl_balance += flt(acc.bal_in_company_currency)
|
||||
|
||||
if net_pl_balance:
|
||||
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
gl_entry = self.get_gl_dict({
|
||||
"account": self.closing_account_head,
|
||||
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||
"cost_center": cost_center
|
||||
})
|
||||
|
||||
for dimension in accounting_dimensions:
|
||||
gl_entry.update({
|
||||
dimension: default_dimensions.get(self.company, {}).get(dimension)
|
||||
})
|
||||
|
||||
gl_entries.append(gl_entry)
|
||||
if self.cost_center_wise_pnl:
|
||||
costcenter_wise_gl_entries = self.get_costcenter_wise_pnl_gl_entries(pl_accounts)
|
||||
gl_entries += costcenter_wise_gl_entries
|
||||
else:
|
||||
gl_entry = self.get_pnl_gl_entry(net_pl_balance)
|
||||
gl_entries.append(gl_entry)
|
||||
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
make_gl_entries(gl_entries)
|
||||
|
||||
def get_pnl_gl_entry(self, net_pl_balance):
|
||||
cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
gl_entry = self.get_gl_dict({
|
||||
"account": self.closing_account_head,
|
||||
"debit_in_account_currency": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||
"debit": abs(net_pl_balance) if net_pl_balance > 0 else 0,
|
||||
"credit_in_account_currency": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||
"credit": abs(net_pl_balance) if net_pl_balance < 0 else 0,
|
||||
"cost_center": cost_center
|
||||
})
|
||||
|
||||
self.update_default_dimensions(gl_entry)
|
||||
|
||||
return gl_entry
|
||||
|
||||
def get_costcenter_wise_pnl_gl_entries(self, pl_accounts):
|
||||
company_cost_center = frappe.db.get_value("Company", self.company, "cost_center")
|
||||
gl_entries = []
|
||||
|
||||
for acc in pl_accounts:
|
||||
if flt(acc.bal_in_company_currency):
|
||||
gl_entry = self.get_gl_dict({
|
||||
"account": self.closing_account_head,
|
||||
"cost_center": acc.cost_center or company_cost_center,
|
||||
"account_currency": acc.account_currency,
|
||||
"debit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) > 0 else 0,
|
||||
"debit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
|
||||
"credit_in_account_currency": abs(flt(acc.bal_in_account_currency)) if flt(acc.bal_in_account_currency) < 0 else 0,
|
||||
"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0
|
||||
}, item=acc)
|
||||
|
||||
self.update_default_dimensions(gl_entry)
|
||||
|
||||
gl_entries.append(gl_entry)
|
||||
|
||||
return gl_entries
|
||||
|
||||
def update_default_dimensions(self, gl_entry):
|
||||
if not self.accounting_dimensions:
|
||||
self.accounting_dimensions = get_accounting_dimensions()
|
||||
|
||||
_, default_dimensions = get_dimensions()
|
||||
for dimension in self.accounting_dimensions:
|
||||
gl_entry.update({
|
||||
dimension: default_dimensions.get(self.company, {}).get(dimension)
|
||||
})
|
||||
|
||||
def get_pl_balances(self):
|
||||
"""Get balance for dimension-wise pl accounts"""
|
||||
|
||||
dimension_fields = ['t1.cost_center']
|
||||
|
||||
self.accounting_dimensions = get_accounting_dimensions()
|
||||
for dimension in self.accounting_dimensions:
|
||||
dimension_fields.append('t1.{0}'.format(dimension))
|
||||
|
||||
def get_pl_balances(self, dimension_fields):
|
||||
"""Get balance for pl accounts"""
|
||||
return frappe.db.sql("""
|
||||
select
|
||||
t1.account, t2.account_currency, {dimension_fields},
|
||||
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as balance_in_account_currency,
|
||||
sum(t1.debit) - sum(t1.credit) as balance_in_company_currency
|
||||
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
|
||||
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
|
||||
from `tabGL Entry` t1, `tabAccount` t2
|
||||
where t1.account = t2.name and t2.report_type = 'Profit and Loss'
|
||||
and t2.docstatus < 2 and t2.company = %s
|
||||
|
||||
@@ -8,6 +8,7 @@ import frappe
|
||||
from frappe.utils import flt, today
|
||||
from erpnext.accounts.utils import get_fiscal_year, now
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
def test_closing_entry(self):
|
||||
@@ -65,6 +66,58 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
self.assertEqual(gle_for_random_expense_account[0].amount_in_account_currency,
|
||||
-1*random_expense_account[0].balance_in_account_currency)
|
||||
|
||||
def test_cost_center_wise_posting(self):
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
|
||||
|
||||
company = create_company()
|
||||
surplus_account = create_account()
|
||||
|
||||
cost_center1 = create_cost_center("Test Cost Center 1")
|
||||
cost_center2 = create_cost_center("Test Cost Center 2")
|
||||
|
||||
create_sales_invoice(
|
||||
company=company,
|
||||
cost_center=cost_center1,
|
||||
income_account="Sales - TPC",
|
||||
expense_account="Cost of Goods Sold - TPC",
|
||||
rate=400,
|
||||
debit_to="Debtors - TPC"
|
||||
)
|
||||
create_sales_invoice(
|
||||
company=company,
|
||||
cost_center=cost_center2,
|
||||
income_account="Sales - TPC",
|
||||
expense_account="Cost of Goods Sold - TPC",
|
||||
rate=200,
|
||||
debit_to="Debtors - TPC"
|
||||
)
|
||||
|
||||
pcv = frappe.get_doc({
|
||||
"transaction_date": today(),
|
||||
"posting_date": today(),
|
||||
"fiscal_year": get_fiscal_year(today())[0],
|
||||
"company": "Test PCV Company",
|
||||
"cost_center_wise_pnl": 1,
|
||||
"closing_account_head": surplus_account,
|
||||
"remarks": "Test",
|
||||
"doctype": "Period Closing Voucher"
|
||||
})
|
||||
pcv.insert()
|
||||
pcv.submit()
|
||||
|
||||
expected_gle = (
|
||||
('Sales - TPC', 200.0, 0.0, cost_center2),
|
||||
(surplus_account, 0.0, 200.0, cost_center2),
|
||||
('Sales - TPC', 400.0, 0.0, cost_center1),
|
||||
(surplus_account, 0.0, 400.0, cost_center1)
|
||||
)
|
||||
|
||||
pcv_gle = frappe.db.sql("""
|
||||
select account, debit, credit, cost_center from `tabGL Entry` where voucher_no=%s
|
||||
""", (pcv.name))
|
||||
|
||||
self.assertTrue(pcv_gle, expected_gle)
|
||||
|
||||
def make_period_closing_voucher(self):
|
||||
pcv = frappe.get_doc({
|
||||
"doctype": "Period Closing Voucher",
|
||||
@@ -80,6 +133,38 @@ class TestPeriodClosingVoucher(unittest.TestCase):
|
||||
|
||||
return pcv
|
||||
|
||||
def create_company():
|
||||
company = frappe.get_doc({
|
||||
'doctype': 'Company',
|
||||
'company_name': "Test PCV Company",
|
||||
'country': 'United States',
|
||||
'default_currency': 'USD'
|
||||
})
|
||||
company.insert(ignore_if_duplicate = True)
|
||||
return company.name
|
||||
|
||||
def create_account():
|
||||
account = frappe.get_doc({
|
||||
"account_name": "Reserve and Surplus",
|
||||
"is_group": 0,
|
||||
"company": "Test PCV Company",
|
||||
"root_type": "Liability",
|
||||
"report_type": "Balance Sheet",
|
||||
"account_currency": "USD",
|
||||
"parent_account": "Current Liabilities - TPC",
|
||||
"doctype": "Account"
|
||||
}).insert(ignore_if_duplicate = True)
|
||||
return account.name
|
||||
|
||||
def create_cost_center(cc_name):
|
||||
costcenter = frappe.get_doc({
|
||||
"company": "Test PCV Company",
|
||||
"cost_center_name": cc_name,
|
||||
"doctype": "Cost Center",
|
||||
"parent_cost_center": "Test PCV Company - TPC"
|
||||
})
|
||||
costcenter.insert(ignore_if_duplicate = True)
|
||||
return costcenter.name
|
||||
|
||||
test_dependencies = ["Customer", "Cost Center"]
|
||||
test_records = frappe.get_test_records("Period Closing Voucher")
|
||||
|
||||
@@ -101,15 +101,24 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
},
|
||||
|
||||
before_save: function(frm) {
|
||||
frm.set_value("grand_total", 0);
|
||||
frm.set_value("net_total", 0);
|
||||
frm.set_value("total_quantity", 0);
|
||||
frm.set_value("taxes", []);
|
||||
|
||||
for (let row of frm.doc.payment_reconciliation) {
|
||||
row.expected_amount = row.opening_amount;
|
||||
}
|
||||
|
||||
for (let row of frm.doc.pos_transactions) {
|
||||
frappe.db.get_doc("POS Invoice", row.pos_invoice).then(doc => {
|
||||
cur_frm.doc.grand_total -= flt(doc.grand_total);
|
||||
cur_frm.doc.net_total -= flt(doc.net_total);
|
||||
cur_frm.doc.total_quantity -= flt(doc.total_qty);
|
||||
refresh_payments(doc, cur_frm, 1);
|
||||
refresh_taxes(doc, cur_frm, 1);
|
||||
refresh_fields(cur_frm);
|
||||
set_html_data(cur_frm);
|
||||
frm.doc.grand_total += flt(doc.grand_total);
|
||||
frm.doc.net_total += flt(doc.net_total);
|
||||
frm.doc.total_quantity += flt(doc.total_qty);
|
||||
refresh_payments(doc, frm);
|
||||
refresh_taxes(doc, frm);
|
||||
refresh_fields(frm);
|
||||
set_html_data(frm);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -118,7 +127,7 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
frappe.ui.form.on('POS Closing Entry Detail', {
|
||||
closing_amount: (frm, cdt, cdn) => {
|
||||
const row = locals[cdt][cdn];
|
||||
frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount))
|
||||
frappe.model.set_value(cdt, cdn, "difference", flt(row.expected_amount - row.closing_amount));
|
||||
}
|
||||
})
|
||||
|
||||
@@ -142,28 +151,31 @@ function add_to_pos_transaction(d, frm) {
|
||||
})
|
||||
}
|
||||
|
||||
function refresh_payments(d, frm, remove) {
|
||||
function refresh_payments(d, frm) {
|
||||
d.payments.forEach(p => {
|
||||
const payment = frm.doc.payment_reconciliation.find(pay => pay.mode_of_payment === p.mode_of_payment);
|
||||
if (p.account == d.account_for_change_amount) {
|
||||
p.amount -= flt(d.change_amount);
|
||||
}
|
||||
if (payment) {
|
||||
if (!remove) payment.expected_amount += flt(p.amount);
|
||||
else payment.expected_amount -= flt(p.amount);
|
||||
payment.expected_amount += flt(p.amount);
|
||||
payment.difference = payment.closing_amount - payment.expected_amount;
|
||||
} else {
|
||||
frm.add_child("payment_reconciliation", {
|
||||
mode_of_payment: p.mode_of_payment,
|
||||
opening_amount: 0,
|
||||
expected_amount: p.amount
|
||||
expected_amount: p.amount,
|
||||
closing_amount: 0
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function refresh_taxes(d, frm, remove) {
|
||||
function refresh_taxes(d, frm) {
|
||||
d.taxes.forEach(t => {
|
||||
const tax = frm.doc.taxes.find(tx => tx.account_head === t.account_head && tx.rate === t.rate);
|
||||
if (tax) {
|
||||
if (!remove) tax.amount += flt(t.tax_amount);
|
||||
else tax.amount -= flt(t.tax_amount);
|
||||
tax.amount += flt(t.tax_amount);
|
||||
} else {
|
||||
frm.add_child("taxes", {
|
||||
account_head: t.account_head,
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "closing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
@@ -57,7 +58,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-23 16:45:43.662034",
|
||||
"modified": "2021-05-19 20:08:44.523861",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry Detail",
|
||||
|
||||
@@ -140,6 +140,7 @@ class POSInvoice(SalesInvoice):
|
||||
return
|
||||
|
||||
available_stock = get_stock_availability(d.item_code, d.warehouse)
|
||||
|
||||
item_code, warehouse, qty = frappe.bold(d.item_code), frappe.bold(d.warehouse), frappe.bold(d.qty)
|
||||
if flt(available_stock) <= 0:
|
||||
frappe.throw(_('Row #{}: Item Code: {} is not available under warehouse {}.')
|
||||
@@ -213,8 +214,9 @@ class POSInvoice(SalesInvoice):
|
||||
for d in self.get("items"):
|
||||
is_stock_item = frappe.get_cached_value("Item", d.get("item_code"), "is_stock_item")
|
||||
if not is_stock_item:
|
||||
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice. ")
|
||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
if not frappe.db.exists('Product Bundle', d.item_code):
|
||||
frappe.throw(_("Row #{}: Item {} is a non stock item. You can only include stock items in a POS Invoice.")
|
||||
.format(d.idx, frappe.bold(d.item_code)), title=_("Invalid Item"))
|
||||
|
||||
def validate_mode_of_payment(self):
|
||||
if len(self.payments) == 0:
|
||||
@@ -455,29 +457,48 @@ class POSInvoice(SalesInvoice):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_stock_availability(item_code, warehouse):
|
||||
latest_sle = frappe.db.sql("""select qty_after_transaction
|
||||
from `tabStock Ledger Entry`
|
||||
if frappe.db.get_value('Item', item_code, 'is_stock_item'):
|
||||
bin_qty = get_bin_qty(item_code, warehouse)
|
||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||
return bin_qty - pos_sales_qty
|
||||
else:
|
||||
if frappe.db.exists('Product Bundle', item_code):
|
||||
return get_bundle_availability(item_code, warehouse)
|
||||
|
||||
def get_bundle_availability(bundle_item_code, warehouse):
|
||||
product_bundle = frappe.get_doc('Product Bundle', bundle_item_code)
|
||||
|
||||
bundle_bin_qty = 1000000
|
||||
for item in product_bundle.items:
|
||||
item_bin_qty = get_bin_qty(item.item_code, warehouse)
|
||||
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
|
||||
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||
|
||||
max_available_bundles = available_qty / item.qty
|
||||
if bundle_bin_qty > max_available_bundles:
|
||||
bundle_bin_qty = max_available_bundles
|
||||
|
||||
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
|
||||
return bundle_bin_qty - pos_sales_qty
|
||||
|
||||
def get_bin_qty(item_code, warehouse):
|
||||
bin_qty = frappe.db.sql("""select actual_qty from `tabBin`
|
||||
where item_code = %s and warehouse = %s
|
||||
order by posting_date desc, posting_time desc
|
||||
limit 1""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
pos_sales_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||
return bin_qty[0].actual_qty or 0 if bin_qty else 0
|
||||
|
||||
def get_pos_reserved_qty(item_code, warehouse):
|
||||
reserved_qty = frappe.db.sql("""select sum(p_item.qty) as qty
|
||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
|
||||
where p.name = p_item.parent
|
||||
and p.consolidated_invoice is NULL
|
||||
and p.docstatus = 1
|
||||
and ifnull(p.consolidated_invoice, '') = ''
|
||||
and p_item.docstatus = 1
|
||||
and p_item.item_code = %s
|
||||
and p_item.warehouse = %s
|
||||
""", (item_code, warehouse), as_dict=1)
|
||||
|
||||
sle_qty = latest_sle[0].qty_after_transaction or 0 if latest_sle else 0
|
||||
pos_sales_qty = pos_sales_qty[0].qty or 0 if pos_sales_qty else 0
|
||||
|
||||
if sle_qty and pos_sales_qty:
|
||||
return sle_qty - pos_sales_qty
|
||||
else:
|
||||
return sle_qty
|
||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_sales_return(source_name, target_doc=None):
|
||||
@@ -524,4 +545,4 @@ def add_return_modes(doc, pos_profile):
|
||||
mode_of_payment = pos_payment_method.mode_of_payment
|
||||
if pos_payment_method.allow_in_returns and not [d for d in doc.get('payments') if d.mode_of_payment == mode_of_payment]:
|
||||
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
||||
append_payment(payment_mode[0])
|
||||
append_payment(payment_mode[0])
|
||||
|
||||
@@ -42,8 +42,9 @@ class POSInvoiceMergeLog(Document):
|
||||
if return_against_status != "Consolidated":
|
||||
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||
bold_unconsolidated = frappe.bold("not Consolidated")
|
||||
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}. ")
|
||||
msg = (_("Row #{}: Original Invoice {} of return invoice {} is {}.")
|
||||
.format(d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated))
|
||||
msg += " "
|
||||
msg += _("Original invoice should be consolidated before or along with the return invoice.")
|
||||
msg += "<br><br>"
|
||||
msg += _("You can add original invoice {} manually to proceed.").format(bold_return_against)
|
||||
@@ -56,12 +57,12 @@ class POSInvoiceMergeLog(Document):
|
||||
sales = [d for d in pos_invoice_docs if d.get('is_return') == 0]
|
||||
|
||||
sales_invoice, credit_note = "", ""
|
||||
if sales:
|
||||
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
||||
|
||||
if returns:
|
||||
credit_note = self.process_merging_into_credit_note(returns)
|
||||
|
||||
if sales:
|
||||
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
||||
|
||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||
|
||||
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
||||
@@ -274,9 +275,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
closing_entry.db_set('error_message', '')
|
||||
closing_entry.update_opening_entry()
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
message_log = frappe.message_log.pop()
|
||||
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||
error_message = safe_load_json(message_log)
|
||||
|
||||
if closing_entry:
|
||||
@@ -300,9 +301,9 @@ def cancel_merge_logs(merge_logs, closing_entry=None):
|
||||
closing_entry.db_set('error_message', '')
|
||||
closing_entry.update_opening_entry(for_cancel=True)
|
||||
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
message_log = frappe.message_log.pop()
|
||||
message_log = frappe.message_log.pop() if frappe.message_log else str(e)
|
||||
error_message = safe_load_json(message_log)
|
||||
|
||||
if closing_entry:
|
||||
@@ -348,11 +349,9 @@ def job_already_enqueued(job_name):
|
||||
return True
|
||||
|
||||
def safe_load_json(message):
|
||||
JSONDecodeError = ValueError if six.PY2 else json.JSONDecodeError
|
||||
|
||||
try:
|
||||
json_message = json.loads(message).get('message')
|
||||
except JSONDecodeError:
|
||||
except Exception:
|
||||
json_message = message
|
||||
|
||||
return json_message
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-04-19 14:56:06.652327",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"field",
|
||||
"fieldname"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Fieldname"
|
||||
},
|
||||
{
|
||||
"fieldname": "field",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Field"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-21 11:12:54.632093",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Search Fields",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
from __future__ import unicode_literals
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
class POSSearchFields(Document):
|
||||
pass
|
||||
@@ -1,9 +1,17 @@
|
||||
// Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
let search_fields_datatypes = ['Data', 'Link', 'Dynamic Link', 'Long Text', 'Select', 'Small Text', 'Text', 'Text Editor'];
|
||||
let do_not_include_fields = ["naming_series", "item_code", "item_name", "stock_uom", "hub_sync_id", "asset_naming_series",
|
||||
"default_material_request_type", "valuation_method", "warranty_period", "weight_uom", "batch_number_series",
|
||||
"serial_no_series", "purchase_uom", "customs_tariff_number", "sales_uom", "deferred_revenue_account",
|
||||
"deferred_expense_account", "quality_inspection_template", "route", "slideshow", "website_image_alt", "thumbnail",
|
||||
"web_long_description", "hub_sync_id"]
|
||||
|
||||
frappe.ui.form.on('POS Settings', {
|
||||
onload: function(frm) {
|
||||
frm.trigger("get_invoice_fields");
|
||||
frm.trigger("add_search_options");
|
||||
},
|
||||
|
||||
get_invoice_fields: function(frm) {
|
||||
@@ -21,6 +29,38 @@ frappe.ui.form.on('POS Settings', {
|
||||
);
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
add_search_options: function(frm) {
|
||||
frappe.model.with_doctype("Item", () => {
|
||||
var fields = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
|
||||
if (search_fields_datatypes.includes(d.fieldtype) && !(do_not_include_fields.includes(d.fieldname))) {
|
||||
return [d.label];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
fields.unshift('');
|
||||
frm.fields_dict.pos_search_fields.grid.update_docfield_property('field', 'options', fields);
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
frappe.ui.form.on("POS Search Fields", {
|
||||
field: function(frm, doctype, name) {
|
||||
var doc = frappe.get_doc(doctype, name);
|
||||
var df = $.map(frappe.get_doc("DocType", "Item").fields, function(d) {
|
||||
if (doc.field == d.label && search_fields_datatypes.includes(d.fieldtype)) {
|
||||
return d;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
})[0];
|
||||
|
||||
doc.fieldname = df.fieldname;
|
||||
frm.refresh_field("fields");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"invoice_fields"
|
||||
"invoice_fields",
|
||||
"pos_search_fields"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -13,11 +14,17 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Field",
|
||||
"options": "POS Field"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_search_fields",
|
||||
"fieldtype": "Table",
|
||||
"label": "POS Search Fields",
|
||||
"options": "POS Search Fields"
|
||||
}
|
||||
],
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2020-06-01 15:46:41.478928",
|
||||
"modified": "2021-04-19 14:56:24.465218",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Settings",
|
||||
|
||||
@@ -152,7 +152,7 @@ class PricingRule(Document):
|
||||
frappe.throw(_("Valid from date must be less than valid upto date"))
|
||||
|
||||
def validate_condition(self):
|
||||
if self.condition and ("=" in self.condition) and re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", self.condition):
|
||||
if self.condition and ("=" in self.condition) and re.match(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+', self.condition):
|
||||
frappe.throw(_("Invalid condition expression"))
|
||||
|
||||
#--------------------------------------------------------------------------------
|
||||
|
||||
@@ -99,7 +99,7 @@ class TestPricingRule(unittest.TestCase):
|
||||
|
||||
args.item_code = "_Test Item 2"
|
||||
details = get_item_details(args)
|
||||
self.assertEquals(details.get("discount_percentage"), 15)
|
||||
self.assertEqual(details.get("discount_percentage"), 15)
|
||||
|
||||
def test_pricing_rule_for_margin(self):
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
@@ -145,8 +145,8 @@ class TestPricingRule(unittest.TestCase):
|
||||
"name": None
|
||||
})
|
||||
details = get_item_details(args)
|
||||
self.assertEquals(details.get("margin_type"), "Percentage")
|
||||
self.assertEquals(details.get("margin_rate_or_amount"), 10)
|
||||
self.assertEqual(details.get("margin_type"), "Percentage")
|
||||
self.assertEqual(details.get("margin_rate_or_amount"), 10)
|
||||
|
||||
def test_mixed_conditions_for_item_group(self):
|
||||
for item in ["Mixed Cond Item 1", "Mixed Cond Item 2"]:
|
||||
@@ -192,7 +192,7 @@ class TestPricingRule(unittest.TestCase):
|
||||
"name": None
|
||||
})
|
||||
details = get_item_details(args)
|
||||
self.assertEquals(details.get("discount_percentage"), 10)
|
||||
self.assertEqual(details.get("discount_percentage"), 10)
|
||||
|
||||
def test_pricing_rule_for_variants(self):
|
||||
from erpnext.stock.get_item_details import get_item_details
|
||||
@@ -322,11 +322,11 @@ class TestPricingRule(unittest.TestCase):
|
||||
si.insert(ignore_permissions=True)
|
||||
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.margin_rate_or_amount, 10)
|
||||
self.assertEquals(item.rate_with_margin, 1100)
|
||||
self.assertEqual(item.margin_rate_or_amount, 10)
|
||||
self.assertEqual(item.rate_with_margin, 1100)
|
||||
self.assertEqual(item.discount_percentage, 10)
|
||||
self.assertEquals(item.discount_amount, 110)
|
||||
self.assertEquals(item.rate, 990)
|
||||
self.assertEqual(item.discount_amount, 110)
|
||||
self.assertEqual(item.rate, 990)
|
||||
|
||||
def test_pricing_rule_with_margin_and_discount_amount(self):
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
@@ -338,10 +338,10 @@ class TestPricingRule(unittest.TestCase):
|
||||
si.insert(ignore_permissions=True)
|
||||
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.margin_rate_or_amount, 10)
|
||||
self.assertEquals(item.rate_with_margin, 1100)
|
||||
self.assertEquals(item.discount_amount, 110)
|
||||
self.assertEquals(item.rate, 990)
|
||||
self.assertEqual(item.margin_rate_or_amount, 10)
|
||||
self.assertEqual(item.rate_with_margin, 1100)
|
||||
self.assertEqual(item.discount_amount, 110)
|
||||
self.assertEqual(item.rate, 990)
|
||||
|
||||
def test_pricing_rule_for_product_discount_on_same_item(self):
|
||||
frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
|
||||
@@ -458,21 +458,21 @@ class TestPricingRule(unittest.TestCase):
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.submit()
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.rate, 100)
|
||||
self.assertEqual(item.rate, 100)
|
||||
|
||||
# Correct Customer and Incorrect is_return value
|
||||
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=1, qty=-1)
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.submit()
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.rate, 100)
|
||||
self.assertEqual(item.rate, 100)
|
||||
|
||||
# Correct Customer and correct is_return value
|
||||
si = create_sales_invoice(do_not_submit=True, customer="_Test Customer 1", is_return=0)
|
||||
si.items[0].price_list_rate = 1000
|
||||
si.submit()
|
||||
item = si.items[0]
|
||||
self.assertEquals(item.rate, 900)
|
||||
self.assertEqual(item.rate, 900)
|
||||
|
||||
def test_multiple_pricing_rules(self):
|
||||
make_pricing_rule(discount_percentage=20, selling=1, priority=1, apply_multiple_pricing_rules=1,
|
||||
@@ -545,11 +545,11 @@ class TestPricingRule(unittest.TestCase):
|
||||
apply_on="Transaction", free_item="Water Flask 1", free_qty=1, free_item_rate=10)
|
||||
|
||||
si = create_sales_invoice(qty=5, do_not_submit=True)
|
||||
self.assertEquals(len(si.items), 2)
|
||||
self.assertEquals(si.items[1].rate, 10)
|
||||
self.assertEqual(len(si.items), 2)
|
||||
self.assertEqual(si.items[1].rate, 10)
|
||||
|
||||
si1 = create_sales_invoice(qty=2, do_not_submit=True)
|
||||
self.assertEquals(len(si1.items), 1)
|
||||
self.assertEqual(len(si1.items), 1)
|
||||
|
||||
for doc in [si, si1]:
|
||||
doc.delete()
|
||||
|
||||
@@ -20,9 +20,9 @@ from frappe.utils import cint, flt, get_link_to_form, getdate, today, fmt_money
|
||||
class MultiplePricingRuleConflict(frappe.ValidationError): pass
|
||||
|
||||
apply_on_table = {
|
||||
'Item Code': 'items',
|
||||
'Item Group': 'item_groups',
|
||||
'Brand': 'brands'
|
||||
'Item Code': 'items',
|
||||
'Item Group': 'item_groups',
|
||||
'Brand': 'brands'
|
||||
}
|
||||
|
||||
def get_pricing_rules(args, doc=None):
|
||||
@@ -183,7 +183,7 @@ def _get_tree_conditions(args, parenttype, table, allow_blank=True):
|
||||
condition = "ifnull({table}.{field}, '') in ({parent_groups})".format(
|
||||
table=table,
|
||||
field=field,
|
||||
parent_groups=", ".join([frappe.db.escape(d) for d in parent_groups])
|
||||
parent_groups=", ".join(frappe.db.escape(d) for d in parent_groups)
|
||||
)
|
||||
|
||||
frappe.flags.tree_conditions[key] = condition
|
||||
@@ -264,7 +264,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
||||
|
||||
# find pricing rule with highest priority
|
||||
if pricing_rules:
|
||||
max_priority = max([cint(p.priority) for p in pricing_rules])
|
||||
max_priority = max(cint(p.priority) for p in pricing_rules)
|
||||
if max_priority:
|
||||
pricing_rules = list(filter(lambda x: cint(x.priority)==max_priority, pricing_rules))
|
||||
|
||||
@@ -272,14 +272,14 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
||||
pricing_rules = list(pricing_rules)
|
||||
|
||||
if len(pricing_rules) > 1:
|
||||
rate_or_discount = list(set([d.rate_or_discount for d in pricing_rules]))
|
||||
rate_or_discount = list(set(d.rate_or_discount for d in pricing_rules))
|
||||
if len(rate_or_discount) == 1 and rate_or_discount[0] == "Discount Percentage":
|
||||
pricing_rules = list(filter(lambda x: x.for_price_list==args.price_list, pricing_rules)) \
|
||||
or pricing_rules
|
||||
|
||||
if len(pricing_rules) > 1 and not args.for_shopping_cart:
|
||||
frappe.throw(_("Multiple Price Rules exists with same criteria, please resolve conflict by assigning priority. Price Rules: {0}")
|
||||
.format("\n".join([d.name for d in pricing_rules])), MultiplePricingRuleConflict)
|
||||
.format("\n".join(d.name for d in pricing_rules)), MultiplePricingRuleConflict)
|
||||
elif pricing_rules:
|
||||
return pricing_rules[0]
|
||||
|
||||
@@ -541,7 +541,7 @@ def get_product_discount_rule(pricing_rule, item_details, args=None, doc=None):
|
||||
|
||||
def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
|
||||
if pricing_rule_args:
|
||||
items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item])
|
||||
items = tuple((d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item)
|
||||
|
||||
for args in pricing_rule_args:
|
||||
if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
|
||||
@@ -589,4 +589,4 @@ def update_coupon_code_count(coupon_name,transaction_type):
|
||||
elif transaction_type=='cancelled':
|
||||
if coupon.used>0:
|
||||
coupon.used=coupon.used-1
|
||||
coupon.save(ignore_permissions=True)
|
||||
coupon.save(ignore_permissions=True)
|
||||
|
||||
@@ -1,24 +1,42 @@
|
||||
<h1 class="text-center" style="page-break-before:always">{{ filters.party[0] }}</h1>
|
||||
<h3 class="text-center">{{ _("Statement of Accounts") }}</h3>
|
||||
<div class="page-break">
|
||||
<div id="header-html" class="hidden-pdf">
|
||||
{% if letter_head %}
|
||||
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
||||
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="footer-html" class="visible-pdf">
|
||||
{% if letter_head.footer %}
|
||||
<div class="letter-head-footer">
|
||||
<hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
|
||||
{{ letter_head.footer }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
|
||||
<div>
|
||||
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>
|
||||
<h5 style="float: right;">
|
||||
{{ _("Date: ") }}
|
||||
<b>{{ frappe.format(filters.from_date, 'Date')}}
|
||||
{{ _("to") }}
|
||||
{{ frappe.format(filters.to_date, 'Date')}}</b>
|
||||
</h5>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<h5 class="text-center">
|
||||
{{ frappe.format(filters.from_date, 'Date')}}
|
||||
{{ _("to") }}
|
||||
{{ frappe.format(filters.to_date, 'Date')}}
|
||||
</h5>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%">{{ _("Date") }}</th>
|
||||
<th style="width: 15%">{{ _("Ref") }}</th>
|
||||
<th style="width: 25%">{{ _("Party") }}</th>
|
||||
<th style="width: 15%">{{ _("Debit") }}</th>
|
||||
<th style="width: 15%">{{ _("Credit") }}</th>
|
||||
<th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%">{{ _("Date") }}</th>
|
||||
<th style="width: 15%">{{ _("Reference") }}</th>
|
||||
<th style="width: 25%">{{ _("Remarks") }}</th>
|
||||
<th style="width: 15%">{{ _("Debit") }}</th>
|
||||
<th style="width: 15%">{{ _("Credit") }}</th>
|
||||
<th style="width: 18%">{{ _("Balance (Dr - Cr)") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in data %}
|
||||
<tr>
|
||||
{% if(row.posting_date) %}
|
||||
@@ -58,32 +76,34 @@
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<br><br>
|
||||
{% if ageing %}
|
||||
<h3 class="text-center">{{ _("Ageing Report Based On ") }} {{ ageing.ageing_based_on }}</h3>
|
||||
<h5 class="text-center">
|
||||
{{ _("Up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
|
||||
</h5>
|
||||
<br>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%">30 Days</th>
|
||||
<th style="width: 15%">60 Days</th>
|
||||
<th style="width: 25%">90 Days</th>
|
||||
<th style="width: 15%">120 Days</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<p class="text-right text-muted">Printed On {{ frappe.format(frappe.utils.get_datetime(), 'Datetime') }}</p>
|
||||
</table>
|
||||
<br>
|
||||
{% if ageing %}
|
||||
<h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
|
||||
{{ _("up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
|
||||
</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25%">30 Days</th>
|
||||
<th style="width: 25%">60 Days</th>
|
||||
<th style="width: 25%">90 Days</th>
|
||||
<th style="width: 25%">120 Days</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if terms_and_conditions %}
|
||||
<div>
|
||||
{{ terms_and_conditions }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -19,7 +19,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
frappe.show_alert({message: __('Emails Queued'), indicator: 'blue'});
|
||||
}
|
||||
else{
|
||||
frappe.msgprint('No Records for these settings.')
|
||||
frappe.msgprint(__('No Records for these settings.'))
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -33,7 +33,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
type: 'GET',
|
||||
success: function(result) {
|
||||
if(jQuery.isEmptyObject(result)){
|
||||
frappe.msgprint('No Records for these settings.');
|
||||
frappe.msgprint(__('No Records for these settings.'));
|
||||
}
|
||||
else{
|
||||
window.location = url;
|
||||
@@ -92,7 +92,7 @@ frappe.ui.form.on('Process Statement Of Accounts', {
|
||||
frm.refresh_field('customers');
|
||||
}
|
||||
else{
|
||||
frappe.throw('No Customers found with selected options.');
|
||||
frappe.throw(__('No Customers found with selected options.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,4 +129,4 @@ frappe.ui.form.on('Process Statement Of Accounts Customer', {
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_workflow": 1,
|
||||
"autoname": "Prompt",
|
||||
"creation": "2020-05-22 16:46:18.712954",
|
||||
"doctype": "DocType",
|
||||
@@ -28,9 +27,11 @@
|
||||
"customers",
|
||||
"preferences",
|
||||
"orientation",
|
||||
"section_break_14",
|
||||
"include_ageing",
|
||||
"ageing_based_on",
|
||||
"section_break_14",
|
||||
"letter_head",
|
||||
"terms_and_conditions",
|
||||
"section_break_1",
|
||||
"enable_auto_email",
|
||||
"section_break_18",
|
||||
@@ -270,10 +271,22 @@
|
||||
"fieldname": "body",
|
||||
"fieldtype": "Text Editor",
|
||||
"label": "Body"
|
||||
},
|
||||
{
|
||||
"fieldname": "letter_head",
|
||||
"fieldtype": "Link",
|
||||
"label": "Letter Head",
|
||||
"options": "Letter Head"
|
||||
},
|
||||
{
|
||||
"fieldname": "terms_and_conditions",
|
||||
"fieldtype": "Link",
|
||||
"label": "Terms and Conditions",
|
||||
"options": "Terms and Conditions"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-08-08 08:47:09.185728",
|
||||
"modified": "2021-05-21 10:14:22.426672",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -64,6 +64,9 @@ def get_report_pdf(doc, consolidated=True):
|
||||
tax_id = frappe.get_doc('Customer', entry.customer).tax_id
|
||||
presentation_currency = get_party_account_currency('Customer', entry.customer, doc.company) \
|
||||
or doc.currency or get_company_currency(doc.company)
|
||||
if doc.letter_head:
|
||||
from frappe.www.printview import get_letter_head
|
||||
letter_head = get_letter_head(doc, 0)
|
||||
|
||||
filters= frappe._dict({
|
||||
'from_date': doc.from_date,
|
||||
@@ -91,7 +94,10 @@ def get_report_pdf(doc, consolidated=True):
|
||||
continue
|
||||
|
||||
html = frappe.render_template(template_path, \
|
||||
{"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None})
|
||||
{"filters": filters, "data": res, "ageing": ageing[0] if (doc.include_ageing and ageing) else None,
|
||||
"letter_head": letter_head if doc.letter_head else None,
|
||||
"terms_and_conditions": frappe.db.get_value('Terms and Conditions', doc.terms_and_conditions, 'terms')
|
||||
if doc.terms_and_conditions else None})
|
||||
|
||||
html = frappe.render_template(base_template_path, {"body": html, \
|
||||
"css": get_print_style(), "title": "Statement For " + entry.customer})
|
||||
@@ -201,10 +207,9 @@ def fetch_customers(customer_collection, collection_name, primary_mandatory):
|
||||
@frappe.whitelist()
|
||||
def get_customer_emails(customer_name, primary_mandatory, billing_and_primary=True):
|
||||
billing_email = frappe.db.sql("""
|
||||
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent \
|
||||
WHERE l.link_doctype='Customer' and l.link_name='""" + customer_name + """' and \
|
||||
c.is_billing_contact=1 \
|
||||
order by c.creation desc""")
|
||||
SELECT c.email_id FROM `tabContact` AS c JOIN `tabDynamic Link` AS l ON c.name=l.parent
|
||||
WHERE l.link_doctype='Customer' and l.link_name=%s and c.is_billing_contact=1
|
||||
order by c.creation desc""", customer_name)
|
||||
|
||||
if len(billing_email) == 0 or (billing_email[0][0] is None):
|
||||
if billing_and_primary:
|
||||
|
||||
@@ -27,10 +27,6 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
});
|
||||
},
|
||||
|
||||
company: function() {
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
},
|
||||
|
||||
onload: function() {
|
||||
this._super();
|
||||
|
||||
@@ -514,6 +510,28 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
frm.events.add_custom_buttons(frm);
|
||||
},
|
||||
|
||||
add_custom_buttons: function(frm) {
|
||||
if (frm.doc.per_received < 100) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frm.events.make_purchase_receipt(frm);
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus == 1 && frm.doc.per_received > 0) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frappe.route_options = {
|
||||
'purchase_invoice': frm.doc.name
|
||||
}
|
||||
|
||||
frappe.set_route("List", "Purchase Receipt", "List")
|
||||
}, __('View'));
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
if(frm.doc.__onload && frm.is_new()) {
|
||||
if(frm.doc.supplier) {
|
||||
@@ -539,5 +557,17 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
update_stock: function(frm) {
|
||||
hide_fields(frm.doc);
|
||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock? true: false);
|
||||
}
|
||||
},
|
||||
|
||||
make_purchase_receipt: function(frm) {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_purchase_receipt",
|
||||
frm: frm,
|
||||
freeze_message: __("Creating Purchase Receipt ...")
|
||||
})
|
||||
},
|
||||
|
||||
company: function(frm) {
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
},
|
||||
})
|
||||
|
||||
@@ -163,7 +163,8 @@
|
||||
"to_date",
|
||||
"column_break_114",
|
||||
"auto_repeat",
|
||||
"update_auto_repeat_reference"
|
||||
"update_auto_repeat_reference",
|
||||
"per_received"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -591,11 +592,12 @@
|
||||
"label": "Raw Materials Supplied"
|
||||
},
|
||||
{
|
||||
"depends_on": "update_stock",
|
||||
"fieldname": "supplied_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Supplied Items",
|
||||
"options": "Purchase Receipt Item Supplied",
|
||||
"read_only": 1
|
||||
"no_copy": 1,
|
||||
"options": "Purchase Receipt Item Supplied"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_26",
|
||||
@@ -836,6 +838,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "base_rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rounding Adjustment (Company Currency)",
|
||||
@@ -882,6 +885,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.disable_rounded_total",
|
||||
"fieldname": "rounding_adjustment",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Rounding Adjustment",
|
||||
@@ -1364,13 +1368,22 @@
|
||||
"print_hide": 1,
|
||||
"print_width": "50px",
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"fieldname": "per_received",
|
||||
"fieldtype": "Percent",
|
||||
"hidden": 1,
|
||||
"label": "Per Received",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-30 22:45:58.334107",
|
||||
"modified": "2021-06-15 18:20:56.806195",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -68,9 +68,6 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
super(PurchaseInvoice, self).validate()
|
||||
|
||||
# apply tax withholding only if checked and applicable
|
||||
self.set_tax_withholding()
|
||||
|
||||
if not self.is_return:
|
||||
self.po_required()
|
||||
self.pr_required()
|
||||
@@ -251,11 +248,9 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if self.update_stock and (not item.from_warehouse):
|
||||
if for_validate and item.expense_account and item.expense_account != warehouse_account[item.warehouse]["account"]:
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]))
|
||||
msg += _("because account {} is not linked to warehouse {} ").format(frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||
msg += _("or it is not the default inventory account")
|
||||
msg = _("Row {0}: Expense Head changed to {1} because account {2} is not linked to warehouse {3} or it is not the default inventory account").format(
|
||||
item.idx, frappe.bold(warehouse_account[item.warehouse]["account"]), frappe.bold(item.expense_account), frappe.bold(item.warehouse))
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = warehouse_account[item.warehouse]["account"]
|
||||
else:
|
||||
# check if 'Stock Received But Not Billed' account is credited in Purchase receipt or not
|
||||
@@ -266,8 +261,8 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if negative_expense_booked_in_pr:
|
||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||
msg += _("because expense is booked against this account in Purchase Receipt {}").format(frappe.bold(item.purchase_receipt))
|
||||
msg = _("Row {0}: Expense Head changed to {1} because expense is booked against this account in Purchase Receipt {2}").format(
|
||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.purchase_receipt))
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
item.expense_account = stock_not_billed_account
|
||||
@@ -275,8 +270,9 @@ class PurchaseInvoice(BuyingController):
|
||||
# If no purchase receipt present then book expense in 'Stock Received But Not Billed'
|
||||
# This is done in cases when Purchase Invoice is created before Purchase Receipt
|
||||
if for_validate and item.expense_account and item.expense_account != stock_not_billed_account:
|
||||
msg = _("Row {}: Expense Head changed to {} ").format(item.idx, frappe.bold(stock_not_billed_account))
|
||||
msg += _("as no Purchase Receipt is created against Item {}. ").format(frappe.bold(item.item_code))
|
||||
msg = _("Row {0}: Expense Head changed to {1} as no Purchase Receipt is created against Item {2}.").format(
|
||||
item.idx, frappe.bold(stock_not_billed_account), frappe.bold(item.item_code))
|
||||
msg += "<br>"
|
||||
msg += _("This is done to handle accounting for cases when Purchase Receipt is created after Purchase Invoice")
|
||||
frappe.msgprint(msg, title=_("Expense Head Changed"))
|
||||
|
||||
@@ -308,8 +304,8 @@ class PurchaseInvoice(BuyingController):
|
||||
if not d.purchase_order:
|
||||
msg = _("Purchase Order Required for item {}").format(frappe.bold(d.item_code))
|
||||
msg += "<br><br>"
|
||||
msg += _("To submit the invoice without purchase order please set {} ").format(frappe.bold(_('Purchase Order Required')))
|
||||
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||
msg += _("To submit the invoice without purchase order please set {0} as {1} in {2}").format(
|
||||
frappe.bold(_('Purchase Order Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||
throw(msg, title=_("Mandatory Purchase Order"))
|
||||
|
||||
def pr_required(self):
|
||||
@@ -323,8 +319,8 @@ class PurchaseInvoice(BuyingController):
|
||||
if not d.purchase_receipt and d.item_code in stock_items:
|
||||
msg = _("Purchase Receipt Required for item {}").format(frappe.bold(d.item_code))
|
||||
msg += "<br><br>"
|
||||
msg += _("To submit the invoice without purchase receipt please set {} ").format(frappe.bold(_('Purchase Receipt Required')))
|
||||
msg += _("as {} in {}").format(frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||
msg += _("To submit the invoice without purchase receipt please set {0} as {1} in {2}").format(
|
||||
frappe.bold(_('Purchase Receipt Required')), frappe.bold('No'), get_link_to_form('Buying Settings', 'Buying Settings', 'Buying Settings'))
|
||||
throw(msg, title=_("Mandatory Purchase Receipt"))
|
||||
|
||||
def validate_write_off_account(self):
|
||||
@@ -404,6 +400,7 @@ class PurchaseInvoice(BuyingController):
|
||||
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
||||
if self.update_stock == 1:
|
||||
self.update_stock_ledger()
|
||||
self.set_consumed_qty_in_po()
|
||||
from erpnext.stock.doctype.serial_no.serial_no import update_serial_nos_after_submit
|
||||
update_serial_nos_after_submit(self, "items")
|
||||
|
||||
@@ -456,6 +453,8 @@ class PurchaseInvoice(BuyingController):
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
self.allocate_advance_taxes(gl_entries)
|
||||
|
||||
gl_entries = make_regional_gl_entries(gl_entries, self)
|
||||
|
||||
gl_entries = merge_similar_entries(gl_entries)
|
||||
@@ -1000,6 +999,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.update_stock == 1:
|
||||
self.update_stock_ledger()
|
||||
self.delete_auto_created_batches()
|
||||
self.set_consumed_qty_in_po()
|
||||
|
||||
self.make_gl_entries_on_cancel()
|
||||
|
||||
@@ -1090,6 +1090,7 @@ class PurchaseInvoice(BuyingController):
|
||||
for d in self.taxes:
|
||||
if d.account_head == tax_withholding_details.get("account_head"):
|
||||
d.update(tax_withholding_details)
|
||||
|
||||
accounts.append(d.account_head)
|
||||
|
||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||
@@ -1207,3 +1208,41 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.stock_qty = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.conversion_factor)
|
||||
target.amount = (flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate)
|
||||
target.base_amount = (flt(obj.qty) - flt(obj.received_qty)) * \
|
||||
flt(obj.rate) * flt(source_parent.conversion_rate)
|
||||
|
||||
doc = get_mapped_doc("Purchase Invoice", source_name, {
|
||||
"Purchase Invoice": {
|
||||
"doctype": "Purchase Receipt",
|
||||
"validation": {
|
||||
"docstatus": ["=", 1],
|
||||
}
|
||||
},
|
||||
"Purchase Invoice Item": {
|
||||
"doctype": "Purchase Receipt Item",
|
||||
"field_map": {
|
||||
"name": "purchase_invoice_item",
|
||||
"parent": "purchase_invoice",
|
||||
"bom": "bom",
|
||||
"purchase_order": "purchase_order",
|
||||
"po_detail": "purchase_order_item",
|
||||
"material_request": "material_request",
|
||||
"material_request_item": "material_request_item"
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty)
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges"
|
||||
}
|
||||
}, target_doc)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -16,6 +16,7 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import get_qty_after_tra
|
||||
from erpnext.projects.doctype.project.test_project import make_project
|
||||
from erpnext.accounts.doctype.account.test_account import get_inventory_account, create_account
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
|
||||
test_dependencies = ["Item", "Cost Center", "Payment Term", "Payment Terms Template"]
|
||||
test_ignore = ["Serial No"]
|
||||
@@ -620,8 +621,10 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
self.assertEqual(actual_qty_0, get_qty_after_transaction())
|
||||
|
||||
def test_subcontracting_via_purchase_invoice(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import update_backflush_based_on
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
update_backflush_based_on('BOM')
|
||||
make_stock_entry(item_code="_Test Item", target="_Test Warehouse 1 - _TC", qty=100, basic_rate=100)
|
||||
make_stock_entry(item_code="_Test Item Home Desktop 100", target="_Test Warehouse 1 - _TC",
|
||||
qty=100, basic_rate=100)
|
||||
@@ -631,13 +634,13 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
|
||||
self.assertEqual(len(pi.get("supplied_items")), 2)
|
||||
|
||||
rm_supp_cost = sum([d.amount for d in pi.get("supplied_items")])
|
||||
rm_supp_cost = sum(d.amount for d in pi.get("supplied_items"))
|
||||
self.assertEqual(flt(pi.get("items")[0].rm_supp_cost, 2), flt(rm_supp_cost, 2))
|
||||
|
||||
def test_rejected_serial_no(self):
|
||||
pi = make_purchase_invoice(item_code="_Test Serialized Item With Series", received_qty=2, qty=1,
|
||||
rejected_qty=1, rate=500, update_stock=1,
|
||||
rejected_warehouse = "_Test Rejected Warehouse - _TC")
|
||||
rejected_qty=1, rate=500, update_stock=1, rejected_warehouse = "_Test Rejected Warehouse - _TC",
|
||||
allow_zero_valuation_rate=1)
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Serial No", pi.get("items")[0].serial_no, "warehouse"),
|
||||
pi.get("items")[0].warehouse)
|
||||
@@ -950,6 +953,103 @@ class TestPurchaseInvoice(unittest.TestCase):
|
||||
acc_settings.submit_journal_entriessubmit_journal_entries = 0
|
||||
acc_settings.save()
|
||||
|
||||
def test_purchase_invoice_advance_taxes(self):
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
|
||||
|
||||
# create a new supplier to test
|
||||
supplier = create_supplier(supplier_name = '_Test TDS Advance Supplier',
|
||||
tax_withholding_category = 'TDS - 194 - Dividends - Individual')
|
||||
|
||||
# Update tax withholding category with current fiscal year and rate details
|
||||
update_tax_witholding_category('_Test Company', 'TDS Payable - _TC', nowdate())
|
||||
|
||||
# Create Purchase Order with TDS applied
|
||||
po = create_purchase_order(do_not_save=1, supplier=supplier.name, rate=3000, item='_Test Non Stock Item')
|
||||
po.apply_tds = 1
|
||||
po.tax_withholding_category = 'TDS - 194 - Dividends - Individual'
|
||||
po.save()
|
||||
po.submit()
|
||||
|
||||
# Update Unrealized Profit / Loss Account which is used as default advance tax account
|
||||
frappe.db.set_value('Company', '_Test Company', 'unrealized_profit_loss_account', '_Test Account Excise Duty - _TC')
|
||||
|
||||
# Create Payment Entry Against the order
|
||||
payment_entry = get_payment_entry(dt='Purchase Order', dn=po.name)
|
||||
payment_entry.paid_from = 'Cash - _TC'
|
||||
payment_entry.save()
|
||||
payment_entry.submit()
|
||||
|
||||
# Check GLE for Payment Entry
|
||||
expected_gle = [
|
||||
['_Test Account Excise Duty - _TC', 3000, 0],
|
||||
['Cash - _TC', 0, 27000],
|
||||
['Creditors - _TC', 27000, 0],
|
||||
['TDS Payable - _TC', 0, 3000],
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, debit, credit
|
||||
from `tabGL Entry`
|
||||
where voucher_type='Payment Entry' and voucher_no=%s
|
||||
order by account asc""", (payment_entry.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.debit)
|
||||
self.assertEqual(expected_gle[i][2], gle.credit)
|
||||
|
||||
# Create Purchase Invoice against Purchase Order
|
||||
purchase_invoice = get_mapped_purchase_invoice(po.name)
|
||||
purchase_invoice.allocate_advances_automatically = 1
|
||||
purchase_invoice.items[0].item_code = '_Test Non Stock Item'
|
||||
purchase_invoice.items[0].expense_account = '_Test Account Cost for Goods Sold - _TC'
|
||||
purchase_invoice.save()
|
||||
purchase_invoice.submit()
|
||||
|
||||
# Check GLE for Purchase Invoice
|
||||
# Zero net effect on final TDS Payable on invoice
|
||||
expected_gle = [
|
||||
['_Test Account Cost for Goods Sold - _TC', 30000],
|
||||
['_Test Account Excise Duty - _TC', -3000],
|
||||
['Creditors - _TC', -27000],
|
||||
['TDS Payable - _TC', 0]
|
||||
]
|
||||
|
||||
gl_entries = frappe.db.sql("""select account, sum(debit - credit) as amount
|
||||
from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no=%s
|
||||
group by account
|
||||
order by account asc""", (purchase_invoice.name), as_dict=1)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_gle[i][0], gle.account)
|
||||
self.assertEqual(expected_gle[i][1], gle.amount)
|
||||
|
||||
def update_tax_witholding_category(company, account, date):
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
fiscal_year = get_fiscal_year(date=date, company=company)
|
||||
|
||||
if not frappe.db.get_value('Tax Withholding Rate',
|
||||
{'parent': 'TDS - 194 - Dividends - Individual', 'fiscal_year': fiscal_year[0]}):
|
||||
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
|
||||
tds_category.append('rates', {
|
||||
'fiscal_year': fiscal_year[0],
|
||||
'tax_withholding_rate': 10,
|
||||
'single_threshold': 2500,
|
||||
'cumulative_threshold': 0
|
||||
})
|
||||
tds_category.save()
|
||||
|
||||
if not frappe.db.get_value('Tax Withholding Account',
|
||||
{'parent': 'TDS - 194 - Dividends - Individual', 'account': account}):
|
||||
tds_category = frappe.get_doc('Tax Withholding Category', 'TDS - 194 - Dividends - Individual')
|
||||
tds_category.append('accounts', {
|
||||
'company': company,
|
||||
'account': account
|
||||
})
|
||||
tds_category.save()
|
||||
|
||||
def unlink_payment_on_cancel_of_invoice(enable=1):
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
@@ -994,7 +1094,8 @@ def make_purchase_invoice(**args):
|
||||
"project": args.project,
|
||||
"rejected_warehouse": args.rejected_warehouse or "",
|
||||
"rejected_serial_no": args.rejected_serial_no or "",
|
||||
"asset_location": args.location or ""
|
||||
"asset_location": args.location or "",
|
||||
"allow_zero_valuation_rate": args.get("allow_zero_valuation_rate") or 0
|
||||
})
|
||||
|
||||
if args.get_taxes_and_charges:
|
||||
|
||||
@@ -272,7 +272,7 @@
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate ",
|
||||
"label": "Rate",
|
||||
"oldfieldname": "import_rate",
|
||||
"oldfieldtype": "Currency",
|
||||
"options": "currency",
|
||||
@@ -607,6 +607,7 @@
|
||||
"oldfieldname": "purchase_order",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
@@ -853,7 +854,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-23 00:59:52.614805",
|
||||
"modified": "2021-06-16 19:57:03.101571",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"charge_type",
|
||||
"row_id",
|
||||
"included_in_print_rate",
|
||||
"included_in_paid_amount",
|
||||
"col_break1",
|
||||
"account_head",
|
||||
"description",
|
||||
@@ -21,6 +22,7 @@
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"section_break_9",
|
||||
"currency",
|
||||
"tax_amount",
|
||||
"tax_amount_after_discount_amount",
|
||||
"total",
|
||||
@@ -205,12 +207,28 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "account_head.account_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:['Purchase Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||
"description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
|
||||
"fieldname": "included_in_paid_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Considered In Paid Amount"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 17:26:09.703215",
|
||||
"modified": "2021-06-14 01:43:50.750455",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges",
|
||||
|
||||
@@ -17,7 +17,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
var me = this;
|
||||
this._super();
|
||||
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice'];
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log', 'POS Closing Entry'];
|
||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||
// show debit_to in print format
|
||||
this.frm.set_df_property("debit_to", "print_hide", 0);
|
||||
@@ -356,11 +356,11 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
},
|
||||
|
||||
items_on_form_rendered: function() {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
packed_items_on_form_rendered: function(doc, grid_row) {
|
||||
erpnext.setup_serial_no();
|
||||
erpnext.setup_serial_or_batch_no();
|
||||
},
|
||||
|
||||
make_sales_return: function() {
|
||||
@@ -582,6 +582,16 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("adjustment_against", function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
customer: frm.doc.customer,
|
||||
docstatus: 1
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
@@ -685,14 +695,16 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
},
|
||||
|
||||
project: function(frm){
|
||||
frm.call({
|
||||
method: "add_timesheet_data",
|
||||
doc: frm.doc,
|
||||
callback: function(r, rt) {
|
||||
refresh_field(['timesheets'])
|
||||
}
|
||||
})
|
||||
frm.refresh();
|
||||
if (!frm.doc.is_return) {
|
||||
frm.call({
|
||||
method: "add_timesheet_data",
|
||||
doc: frm.doc,
|
||||
callback: function(r, rt) {
|
||||
refresh_field(['timesheets'])
|
||||
}
|
||||
})
|
||||
frm.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
onload: function(frm) {
|
||||
@@ -807,14 +819,27 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
}
|
||||
},
|
||||
|
||||
add_timesheet_row: function(frm, row, exchange_rate) {
|
||||
frm.add_child('timesheets', {
|
||||
'activity_type': row.activity_type,
|
||||
'description': row.description,
|
||||
'time_sheet': row.parent,
|
||||
'billing_hours': row.billing_hours,
|
||||
'billing_amount': flt(row.billing_amount) * flt(exchange_rate),
|
||||
'timesheet_detail': row.name
|
||||
});
|
||||
frm.refresh_field('timesheets');
|
||||
calculate_total_billing_amount(frm);
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
if (frm.doc.project) {
|
||||
if (frm.doc.docstatus===0 && !frm.doc.is_return) {
|
||||
frm.add_custom_button(__('Fetch Timesheet'), function() {
|
||||
let d = new frappe.ui.Dialog({
|
||||
title: __('Fetch Timesheet'),
|
||||
fields: [
|
||||
{
|
||||
"label" : "From",
|
||||
"label" : __("From"),
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
@@ -824,11 +849,18 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
fieldname: 'col_break_1',
|
||||
},
|
||||
{
|
||||
"label" : "To",
|
||||
"label" : __("To"),
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Date",
|
||||
"reqd": 1,
|
||||
}
|
||||
},
|
||||
{
|
||||
"label" : __("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"default": frm.doc.project
|
||||
},
|
||||
],
|
||||
primary_action: function() {
|
||||
let data = d.get_values();
|
||||
@@ -837,27 +869,35 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
args: {
|
||||
from_time: data.from_time,
|
||||
to_time: data.to_time,
|
||||
project: frm.doc.project
|
||||
project: data.project
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
if(r.message.length > 0) {
|
||||
frm.clear_table('timesheets')
|
||||
r.message.forEach((d) => {
|
||||
frm.add_child('timesheets',{
|
||||
'time_sheet': d.parent,
|
||||
'billing_hours': d.billing_hours,
|
||||
'billing_amount': d.billing_amt,
|
||||
'timesheet_detail': d.name
|
||||
if (!r.exc && r.message.length > 0) {
|
||||
frm.clear_table('timesheets')
|
||||
r.message.forEach((d) => {
|
||||
let exchange_rate = 1.0;
|
||||
if (frm.doc.currency != d.currency) {
|
||||
frappe.call({
|
||||
method: 'erpnext.setup.utils.get_exchange_rate',
|
||||
args: {
|
||||
from_currency: d.currency,
|
||||
to_currency: frm.doc.currency
|
||||
},
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
exchange_rate = r.message;
|
||||
frm.events.add_timesheet_row(frm, d, exchange_rate);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
frm.refresh_field('timesheets')
|
||||
}
|
||||
else {
|
||||
frappe.msgprint(__('No Timesheet Found.'))
|
||||
}
|
||||
d.hide();
|
||||
} else {
|
||||
frm.events.add_timesheet_row(frm, d, exchange_rate);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
frappe.msgprint(__('No Timesheets found with the selected filters.'))
|
||||
}
|
||||
d.hide();
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -867,6 +907,10 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
})
|
||||
}
|
||||
|
||||
if (frm.doc.is_debit_note) {
|
||||
frm.set_df_property('return_against', 'label', 'Adjustment Against');
|
||||
}
|
||||
|
||||
if (frappe.boot.active_domains.includes("Healthcare")) {
|
||||
frm.set_df_property("patient", "hidden", 0);
|
||||
frm.set_df_property("patient_name", "hidden", 0);
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"is_pos",
|
||||
"is_consolidated",
|
||||
"is_return",
|
||||
"is_debit_note",
|
||||
"update_billed_amount_in_sales_order",
|
||||
"column_break1",
|
||||
"company",
|
||||
@@ -392,7 +393,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "return_against",
|
||||
"depends_on": "eval:doc.return_against || doc.is_debit_note",
|
||||
"fieldname": "return_against",
|
||||
"fieldtype": "Link",
|
||||
"hide_days": 1,
|
||||
@@ -401,7 +402,7 @@
|
||||
"no_copy": 1,
|
||||
"options": "Sales Invoice",
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:doc.is_return",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@@ -748,6 +749,7 @@
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval:doc.total_billing_amount > 0",
|
||||
"depends_on": "eval: !doc.is_return",
|
||||
"fieldname": "time_sheet_list",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_days": 1,
|
||||
@@ -770,6 +772,7 @@
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Total Billing Amount",
|
||||
"options": "currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -1951,6 +1954,12 @@
|
||||
"label": "Set Target Warehouse",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_debit_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Debit Note"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "grand_total",
|
||||
@@ -1969,7 +1978,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2021-04-15 23:57:58.766651",
|
||||
"modified": "2021-05-20 22:48:33.988881",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -125,6 +125,8 @@ class SalesInvoice(SellingController):
|
||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
|
||||
if not self.is_return:
|
||||
self.validate_serial_numbers()
|
||||
else:
|
||||
self.timesheets = []
|
||||
self.update_packing_list()
|
||||
self.set_billing_hours_and_amount()
|
||||
self.update_timesheet_billing_for_project()
|
||||
@@ -337,7 +339,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if "Healthcare" in active_domains:
|
||||
manage_invoice_submit_cancel(self, "on_cancel")
|
||||
|
||||
self.unlink_sales_invoice_from_timesheets()
|
||||
self.ignore_linked_doctypes = ('GL Entry', 'Stock Ledger Entry', 'Repost Item Valuation')
|
||||
|
||||
def update_status_updater_args(self):
|
||||
@@ -393,6 +395,18 @@ class SalesInvoice(SellingController):
|
||||
if validate_against_credit_limit:
|
||||
check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
|
||||
|
||||
def unlink_sales_invoice_from_timesheets(self):
|
||||
for row in self.timesheets:
|
||||
timesheet = frappe.get_doc('Timesheet', row.time_sheet)
|
||||
for time_log in timesheet.time_logs:
|
||||
if time_log.sales_invoice == self.name:
|
||||
time_log.sales_invoice = None
|
||||
timesheet.calculate_total_amounts()
|
||||
timesheet.calculate_percentage_billed()
|
||||
timesheet.flags.ignore_validate_update_after_submit = True
|
||||
timesheet.set_status()
|
||||
timesheet.db_update_all()
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_missing_values(self, for_validate=False):
|
||||
pos = self.set_pos_fields(for_validate)
|
||||
@@ -427,7 +441,7 @@ class SalesInvoice(SellingController):
|
||||
timesheet.calculate_percentage_billed()
|
||||
timesheet.flags.ignore_validate_update_after_submit = True
|
||||
timesheet.set_status()
|
||||
timesheet.save()
|
||||
timesheet.db_update_all()
|
||||
|
||||
def update_time_sheet_detail(self, timesheet, args, sales_invoice):
|
||||
for data in timesheet.time_logs:
|
||||
@@ -517,7 +531,7 @@ class SalesInvoice(SellingController):
|
||||
# set pos values in items
|
||||
for item in self.get("items"):
|
||||
if item.get('item_code'):
|
||||
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos)
|
||||
profile_details = get_pos_profile_item_details(pos, frappe._dict(item.as_dict()), pos, update_data=True)
|
||||
for fname, val in iteritems(profile_details):
|
||||
if (not for_validate) or (for_validate and not item.get(fname)):
|
||||
item.set(fname, val)
|
||||
@@ -741,8 +755,10 @@ class SalesInvoice(SellingController):
|
||||
self.append('timesheets', {
|
||||
'time_sheet': data.parent,
|
||||
'billing_hours': data.billing_hours,
|
||||
'billing_amount': data.billing_amt,
|
||||
'timesheet_detail': data.name
|
||||
'billing_amount': data.billing_amount,
|
||||
'timesheet_detail': data.name,
|
||||
'activity_type': data.activity_type,
|
||||
'description': data.description
|
||||
})
|
||||
|
||||
self.calculate_billing_amount_for_timesheet()
|
||||
@@ -826,6 +842,8 @@ class SalesInvoice(SellingController):
|
||||
self.make_tax_gl_entries(gl_entries)
|
||||
self.make_internal_transfer_gl_entries(gl_entries)
|
||||
|
||||
self.allocate_advance_taxes(gl_entries)
|
||||
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
|
||||
# merge gl entries before adding pos entries
|
||||
@@ -833,7 +851,6 @@ class SalesInvoice(SellingController):
|
||||
|
||||
self.make_loyalty_point_redemption_gle(gl_entries)
|
||||
self.make_pos_gl_entries(gl_entries)
|
||||
self.make_gle_for_change_amount(gl_entries)
|
||||
|
||||
self.make_write_off_gl_entry(gl_entries)
|
||||
self.make_gle_for_rounding_adjustment(gl_entries)
|
||||
@@ -967,7 +984,13 @@ class SalesInvoice(SellingController):
|
||||
|
||||
def make_pos_gl_entries(self, gl_entries):
|
||||
if cint(self.is_pos):
|
||||
|
||||
skip_change_gl_entries = not cint(frappe.db.get_single_value('Accounts Settings', 'post_change_gl_entries'))
|
||||
|
||||
for payment_mode in self.payments:
|
||||
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
||||
payment_mode.base_amount -= flt(self.change_amount)
|
||||
|
||||
if payment_mode.amount:
|
||||
# POS, make payment entries
|
||||
gl_entries.append(
|
||||
@@ -999,8 +1022,11 @@ class SalesInvoice(SellingController):
|
||||
}, payment_mode_account_currency, item=self)
|
||||
)
|
||||
|
||||
if not skip_change_gl_entries:
|
||||
self.make_gle_for_change_amount(gl_entries)
|
||||
|
||||
def make_gle_for_change_amount(self, gl_entries):
|
||||
if cint(self.is_pos) and self.change_amount:
|
||||
if self.change_amount:
|
||||
if self.account_for_change_amount:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict({
|
||||
@@ -1111,7 +1137,7 @@ class SalesInvoice(SellingController):
|
||||
if not item.serial_no:
|
||||
continue
|
||||
|
||||
for serial_no in item.serial_no.split("\n"):
|
||||
for serial_no in get_serial_nos(item.serial_no):
|
||||
if serial_no and frappe.db.get_value('Serial No', serial_no, 'item_code') == item.item_code:
|
||||
frappe.db.set_value('Serial No', serial_no, 'sales_invoice', invoice)
|
||||
|
||||
@@ -1121,7 +1147,6 @@ class SalesInvoice(SellingController):
|
||||
"""
|
||||
self.set_serial_no_against_delivery_note()
|
||||
self.validate_serial_against_delivery_note()
|
||||
self.validate_serial_against_sales_invoice()
|
||||
|
||||
def set_serial_no_against_delivery_note(self):
|
||||
for item in self.items:
|
||||
@@ -1152,26 +1177,6 @@ class SalesInvoice(SellingController):
|
||||
frappe.throw(_("Row {0}: {1} Serial numbers required for Item {2}. You have provided {3}.").format(
|
||||
item.idx, item.qty, item.item_code, len(si_serial_nos)))
|
||||
|
||||
def validate_serial_against_sales_invoice(self):
|
||||
""" check if serial number is already used in other sales invoice """
|
||||
for item in self.items:
|
||||
if not item.serial_no:
|
||||
continue
|
||||
|
||||
for serial_no in item.serial_no.split("\n"):
|
||||
serial_no_details = frappe.db.get_value("Serial No", serial_no,
|
||||
["sales_invoice", "item_code"], as_dict=1)
|
||||
|
||||
if not serial_no_details:
|
||||
continue
|
||||
|
||||
if serial_no_details.sales_invoice and serial_no_details.item_code == item.item_code \
|
||||
and self.name != serial_no_details.sales_invoice:
|
||||
sales_invoice_company = frappe.db.get_value("Sales Invoice", serial_no_details.sales_invoice, "company")
|
||||
if sales_invoice_company == self.company:
|
||||
frappe.throw(_("Serial Number: {0} is already referenced in Sales Invoice: {1}")
|
||||
.format(serial_no, serial_no_details.sales_invoice))
|
||||
|
||||
def update_project(self):
|
||||
if self.project:
|
||||
project = frappe.get_doc("Project", self.project)
|
||||
@@ -1755,15 +1760,10 @@ def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, wa
|
||||
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
|
||||
def get_delivery_note_details(internal_reference):
|
||||
so_item_map = {}
|
||||
|
||||
si_item_details = frappe.get_all('Delivery Note Item', fields=['name', 'so_detail'],
|
||||
filters={'parent': internal_reference})
|
||||
|
||||
for d in si_item_details:
|
||||
so_item_map.setdefault(d.name, d.so_detail)
|
||||
|
||||
return so_item_map
|
||||
return {d.name: d.so_detail for d in si_item_details if d.so_detail}
|
||||
|
||||
def get_sales_invoice_details(internal_reference):
|
||||
dn_item_map = {}
|
||||
|
||||
@@ -713,7 +713,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.submit()
|
||||
self.assertEqual(si.paid_amount, 100.0)
|
||||
|
||||
self.pos_gl_entry(si, pos, 50)
|
||||
self.validate_pos_gl_entry(si, pos, 50)
|
||||
|
||||
def test_pos_returns_with_repayment(self):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
||||
@@ -749,7 +749,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
pr = make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||
make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
||||
|
||||
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
|
||||
@@ -770,7 +770,45 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(pos.grand_total, 100.0)
|
||||
self.assertEqual(pos.write_off_amount, -5)
|
||||
|
||||
def pos_gl_entry(self, si, pos, cash_amount):
|
||||
def test_pos_with_no_gl_entry_for_change_amount(self):
|
||||
frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 0)
|
||||
|
||||
make_pos_profile(company="_Test Company with perpetual inventory", income_account = "Sales - TCP1",
|
||||
expense_account = "Cost of Goods Sold - TCP1", warehouse="Stores - TCP1", cost_center = "Main - TCP1", write_off_account="_Test Write Off - TCP1")
|
||||
|
||||
make_purchase_receipt(company= "_Test Company with perpetual inventory",
|
||||
item_code= "_Test FG Item",warehouse= "Stores - TCP1", cost_center= "Main - TCP1")
|
||||
|
||||
pos = create_sales_invoice(company= "_Test Company with perpetual inventory",
|
||||
debit_to="Debtors - TCP1", item_code= "_Test FG Item", warehouse="Stores - TCP1",
|
||||
income_account = "Sales - TCP1", expense_account = "Cost of Goods Sold - TCP1",
|
||||
cost_center = "Main - TCP1", do_not_save=True)
|
||||
|
||||
pos.is_pos = 1
|
||||
pos.update_stock = 1
|
||||
|
||||
taxes = get_taxes_and_charges()
|
||||
pos.taxes = []
|
||||
for tax in taxes:
|
||||
pos.append("taxes", tax)
|
||||
|
||||
pos.append("payments", {'mode_of_payment': 'Bank Draft', 'account': '_Test Bank - TCP1', 'amount': 50})
|
||||
pos.append("payments", {'mode_of_payment': 'Cash', 'account': 'Cash - TCP1', 'amount': 60})
|
||||
|
||||
pos.insert()
|
||||
pos.submit()
|
||||
|
||||
self.assertEqual(pos.grand_total, 100.0)
|
||||
self.assertEqual(pos.change_amount, 10)
|
||||
|
||||
self.validate_pos_gl_entry(pos, pos, 60, validate_without_change_gle=True)
|
||||
|
||||
frappe.db.set_value('Accounts Settings', None, 'post_change_gl_entries', 1)
|
||||
|
||||
def validate_pos_gl_entry(self, si, pos, cash_amount, validate_without_change_gle=False):
|
||||
if validate_without_change_gle:
|
||||
cash_amount -= pos.change_amount
|
||||
|
||||
# check stock ledger entries
|
||||
sle = frappe.db.sql("""select * from `tabStock Ledger Entry`
|
||||
where voucher_type = 'Sales Invoice' and voucher_no = %s""",
|
||||
@@ -933,12 +971,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertFalse(frappe.db.get_value("Serial No", serial_nos[0], "warehouse"))
|
||||
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0],
|
||||
"delivery_document_no"), si.name)
|
||||
self.assertEqual(frappe.db.get_value("Serial No", serial_nos[0], "sales_invoice"),
|
||||
si.name)
|
||||
|
||||
# check if the serial number is already linked with any other Sales Invoice
|
||||
_si = frappe.copy_doc(si.as_dict())
|
||||
self.assertRaises(frappe.ValidationError, _si.insert)
|
||||
|
||||
return si
|
||||
|
||||
@@ -1905,69 +1937,81 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
frappe.flags.country = country
|
||||
|
||||
def test_einvoice_json(self):
|
||||
from erpnext.regional.india.e_invoice.utils import make_einvoice
|
||||
from erpnext.regional.india.e_invoice.utils import make_einvoice, validate_totals
|
||||
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
si.naming_series = 'INV-2020-.#####'
|
||||
si.items = []
|
||||
si.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"uom": "Nos",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 2000,
|
||||
"rate": 12,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
})
|
||||
si.append("items", {
|
||||
"item_code": "_Test Item 2",
|
||||
"uom": "Nos",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 420,
|
||||
"rate": 15,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
})
|
||||
si = get_sales_invoice_for_e_invoice()
|
||||
si.discount_amount = 100
|
||||
si.save()
|
||||
|
||||
einvoice = make_einvoice(si)
|
||||
|
||||
total_item_ass_value = 0
|
||||
total_item_cgst_value = 0
|
||||
total_item_sgst_value = 0
|
||||
total_item_igst_value = 0
|
||||
total_item_value = 0
|
||||
|
||||
for item in einvoice['ItemList']:
|
||||
total_item_ass_value += item['AssAmt']
|
||||
total_item_cgst_value += item['CgstAmt']
|
||||
total_item_sgst_value += item['SgstAmt']
|
||||
total_item_igst_value += item['IgstAmt']
|
||||
total_item_value += item['TotItemVal']
|
||||
|
||||
self.assertTrue(item['AssAmt'], item['TotAmt'] - item['Discount'])
|
||||
self.assertTrue(item['TotItemVal'], item['AssAmt'] + item['CgstAmt'] + item['SgstAmt'] + item['IgstAmt'])
|
||||
|
||||
value_details = einvoice['ValDtls']
|
||||
|
||||
self.assertEqual(einvoice['Version'], '1.1')
|
||||
self.assertEqual(value_details['AssVal'], total_item_ass_value)
|
||||
self.assertEqual(value_details['CgstVal'], total_item_cgst_value)
|
||||
self.assertEqual(value_details['SgstVal'], total_item_sgst_value)
|
||||
self.assertEqual(value_details['IgstVal'], total_item_igst_value)
|
||||
|
||||
calculated_invoice_value = \
|
||||
value_details['AssVal'] + value_details['CgstVal'] \
|
||||
+ value_details['SgstVal'] + value_details['IgstVal'] \
|
||||
+ value_details['OthChrg'] - value_details['Discount']
|
||||
|
||||
self.assertTrue(value_details['TotInvVal'] - calculated_invoice_value < 0.1)
|
||||
|
||||
self.assertEqual(value_details['TotInvVal'], si.base_grand_total)
|
||||
self.assertTrue(einvoice['EwbDtls'])
|
||||
validate_totals(einvoice)
|
||||
|
||||
si.apply_discount_on = 'Net Total'
|
||||
si.save()
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
[d.set('included_in_print_rate', 1) for d in si.taxes]
|
||||
si.save()
|
||||
einvoice = make_einvoice(si)
|
||||
validate_totals(einvoice)
|
||||
|
||||
def test_item_tax_net_range(self):
|
||||
item = create_item("T Shirt")
|
||||
|
||||
item.set('taxes', [])
|
||||
item.append("taxes", {
|
||||
"item_tax_template": "_Test Account Excise Duty @ 10 - _TC",
|
||||
"minimum_net_rate": 0,
|
||||
"maximum_net_rate": 500
|
||||
})
|
||||
|
||||
item.append("taxes", {
|
||||
"item_tax_template": "_Test Account Excise Duty @ 12 - _TC",
|
||||
"minimum_net_rate": 501,
|
||||
"maximum_net_rate": 1000
|
||||
})
|
||||
|
||||
item.save()
|
||||
|
||||
sales_invoice = create_sales_invoice(item = "T Shirt", rate=700, do_not_submit=True)
|
||||
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 12 - _TC")
|
||||
|
||||
# Apply discount
|
||||
sales_invoice.apply_discount_on = 'Net Total'
|
||||
sales_invoice.discount_amount = 300
|
||||
sales_invoice.save()
|
||||
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
si.naming_series = 'INV-2020-.#####'
|
||||
si.items = []
|
||||
si.append("items", {
|
||||
"item_code": "_Test Item",
|
||||
"uom": "Nos",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 2000,
|
||||
"rate": 12,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
})
|
||||
|
||||
si.append("items", {
|
||||
"item_code": "_Test Item 2",
|
||||
"uom": "Nos",
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"qty": 420,
|
||||
"rate": 15,
|
||||
"income_account": "Sales - _TC",
|
||||
"expense_account": "Cost of Goods Sold - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
})
|
||||
|
||||
return si
|
||||
|
||||
|
||||
def make_test_address_for_ewaybill():
|
||||
if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
|
||||
@@ -2044,9 +2088,9 @@ def make_sales_invoice_for_ewaybill():
|
||||
if not gst_account:
|
||||
gst_settings.append("gst_accounts", {
|
||||
"company": "_Test Company",
|
||||
"cgst_account": "CGST - _TC",
|
||||
"sgst_account": "SGST - _TC",
|
||||
"igst_account": "IGST - _TC",
|
||||
"cgst_account": "Output Tax CGST - _TC",
|
||||
"sgst_account": "Output Tax SGST - _TC",
|
||||
"igst_account": "Output Tax IGST - _TC",
|
||||
})
|
||||
|
||||
gst_settings.save()
|
||||
@@ -2063,7 +2107,7 @@ def make_sales_invoice_for_ewaybill():
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "CGST - _TC",
|
||||
"account_head": "Output Tax CGST - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "CGST @ 9.0",
|
||||
"rate": 9
|
||||
@@ -2071,7 +2115,7 @@ def make_sales_invoice_for_ewaybill():
|
||||
|
||||
si.append("taxes", {
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "SGST - _TC",
|
||||
"account_head": "Output Tax SGST - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "SGST @ 9.0",
|
||||
"rate": 9
|
||||
@@ -2091,27 +2135,6 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
doc.assertEqual(expected_gle[i][2], gle.credit)
|
||||
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||
|
||||
def test_item_tax_validity(self):
|
||||
item = frappe.get_doc("Item", "_Test Item 2")
|
||||
|
||||
if item.taxes:
|
||||
item.taxes = []
|
||||
item.save()
|
||||
|
||||
item.append("taxes", {
|
||||
"item_tax_template": "_Test Item Tax Template 1 - _TC",
|
||||
"valid_from": add_days(nowdate(), 1)
|
||||
})
|
||||
|
||||
item.save()
|
||||
|
||||
sales_invoice = create_sales_invoice(item = "_Test Item 2", do_not_save=1)
|
||||
sales_invoice.items[0].item_tax_template = "_Test Item Tax Template 1 - _TC"
|
||||
self.assertRaises(frappe.ValidationError, sales_invoice.save)
|
||||
|
||||
item.taxes = []
|
||||
item.save()
|
||||
|
||||
def create_sales_invoice(**args):
|
||||
si = frappe.new_doc("Sales Invoice")
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -1,172 +1,78 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2016-06-14 19:21:34.321662",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"creation": "2016-06-14 19:21:34.321662",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"activity_type",
|
||||
"description",
|
||||
"billing_hours",
|
||||
"billing_amount",
|
||||
"time_sheet",
|
||||
"timesheet_detail"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "time_sheet",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Time Sheet",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Timesheet",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "time_sheet",
|
||||
"fieldtype": "Link",
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Time Sheet",
|
||||
"options": "Timesheet",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "billing_hours",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Billing Hours",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "billing_hours",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Hours",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Billing Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "billing_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Billing Amount",
|
||||
"options": "currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "timesheet_detail",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Timesheet Detail",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "timesheet_detail",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Timesheet Detail",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "activity_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Activity Type",
|
||||
"options": "Activity Type",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Description",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-18 18:50:44.770361",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Timesheet",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-20 22:33:57.234846",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Timesheet",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2013-04-24 11:39:32",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"charge_type",
|
||||
"row_id",
|
||||
@@ -10,12 +12,14 @@
|
||||
"col_break_1",
|
||||
"description",
|
||||
"included_in_print_rate",
|
||||
"included_in_paid_amount",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"section_break_8",
|
||||
"rate",
|
||||
"section_break_9",
|
||||
"currency",
|
||||
"tax_amount",
|
||||
"total",
|
||||
"tax_amount_after_discount_amount",
|
||||
@@ -23,8 +27,7 @@
|
||||
"base_tax_amount",
|
||||
"base_total",
|
||||
"base_tax_amount_after_discount_amount",
|
||||
"item_wise_tax_detail",
|
||||
"parenttype"
|
||||
"item_wise_tax_detail"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -173,17 +176,6 @@
|
||||
"oldfieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "parenttype",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"in_filter": 1,
|
||||
"label": "Parenttype",
|
||||
"oldfieldname": "parenttype",
|
||||
"oldfieldtype": "Data",
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -192,15 +184,34 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "account_head.account_currency",
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Account Currency",
|
||||
"options": "Currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:['Sales Taxes and Charges Template', 'Payment Entry'].includes(parent.doctype)",
|
||||
"description": "If checked, the tax amount will be considered as already included in the Paid Amount in Payment Entry",
|
||||
"fieldname": "included_in_paid_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Considered In Paid Amount"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"modified": "2019-05-25 22:59:38.740883",
|
||||
"links": [],
|
||||
"modified": "2021-06-14 01:44:36.899147",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Taxes and Charges",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC"
|
||||
}
|
||||
@@ -36,6 +36,7 @@
|
||||
"additional_discount_percentage",
|
||||
"additional_discount_amount",
|
||||
"sb_3",
|
||||
"submit_invoice",
|
||||
"invoices",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
@@ -45,9 +46,7 @@
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "cb_1",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
@@ -55,97 +54,73 @@
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "\nTrialling\nActive\nPast Due Date\nCancelled\nUnpaid\nCompleted",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "subscription_period",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Subscription Period",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Subscription Period"
|
||||
},
|
||||
{
|
||||
"fieldname": "cancelation_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Cancelation Date",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "trial_period_start",
|
||||
"fieldtype": "Date",
|
||||
"label": "Trial Period Start Date",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.trial_period_start",
|
||||
"fieldname": "trial_period_end",
|
||||
"fieldtype": "Date",
|
||||
"label": "Trial Period End Date",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_invoice_start",
|
||||
"fieldtype": "Date",
|
||||
"label": "Current Invoice Start Date",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_invoice_end",
|
||||
"fieldtype": "Date",
|
||||
"label": "Current Invoice End Date",
|
||||
"read_only": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Number of days that the subscriber has to pay invoices generated by this subscription",
|
||||
"fieldname": "days_until_due",
|
||||
"fieldtype": "Int",
|
||||
"label": "Days Until Due",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Days Until Due"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "cancel_at_period_end",
|
||||
"fieldtype": "Check",
|
||||
"label": "Cancel At End Of Period",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Cancel At End Of Period"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "generate_invoice_at_period_start",
|
||||
"fieldtype": "Check",
|
||||
"label": "Generate Invoice At Beginning Of Period",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Generate Invoice At Beginning Of Period"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "sb_4",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Plans",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Plans"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@@ -153,84 +128,62 @@
|
||||
"fieldtype": "Table",
|
||||
"label": "Plans",
|
||||
"options": "Subscription Plan Detail",
|
||||
"reqd": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:['Customer', 'Supplier'].includes(doc.party_type)",
|
||||
"fieldname": "sb_1",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Taxes",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Taxes"
|
||||
},
|
||||
{
|
||||
"fieldname": "sb_2",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discounts",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Discounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "apply_additional_discount",
|
||||
"fieldtype": "Select",
|
||||
"label": "Apply Additional Discount On",
|
||||
"options": "\nGrand Total\nNet Total",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "\nGrand Total\nNet Total"
|
||||
},
|
||||
{
|
||||
"fieldname": "cb_2",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_discount_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Additional DIscount Percentage",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Additional DIscount Percentage"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "additional_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Additional DIscount Amount",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Additional DIscount Amount"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.invoices",
|
||||
"fieldname": "sb_3",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Invoices",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Invoices"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "invoices",
|
||||
"fieldtype": "Table",
|
||||
"label": "Invoices",
|
||||
"options": "Subscription Invoice",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Subscription Invoice"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "party_type",
|
||||
@@ -238,9 +191,7 @@
|
||||
"label": "Party Type",
|
||||
"options": "DocType",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "party",
|
||||
@@ -249,27 +200,21 @@
|
||||
"label": "Party",
|
||||
"options": "party_type",
|
||||
"reqd": 1,
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.party_type === 'Customer'",
|
||||
"fieldname": "sales_tax_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Sales Taxes and Charges Template",
|
||||
"options": "Sales Taxes and Charges Template",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Sales Taxes and Charges Template"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.party_type === 'Supplier'",
|
||||
"fieldname": "purchase_tax_template",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Taxes and Charges Template",
|
||||
"options": "Purchase Taxes and Charges Template",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Purchase Taxes and Charges Template"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -277,55 +222,49 @@
|
||||
"fieldname": "follow_calendar_months",
|
||||
"fieldtype": "Check",
|
||||
"label": "Follow Calendar Months",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "New invoices will be generated as per schedule even if current invoices are unpaid or past due date",
|
||||
"fieldname": "generate_new_invoices_past_due_date",
|
||||
"fieldtype": "Check",
|
||||
"label": "Generate New Invoices Past Due Date",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"label": "Generate New Invoices Past Due Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Subscription End Date",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Subscription Start Date",
|
||||
"set_only_once": 1,
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"set_only_once": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"show_days": 1,
|
||||
"show_seconds": 1
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "submit_invoice",
|
||||
"fieldtype": "Check",
|
||||
"label": "Submit Invoice Automatically"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-09 15:44:20.024789",
|
||||
"modified": "2021-04-19 15:24:27.550797",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Subscription",
|
||||
|
||||
@@ -276,7 +276,7 @@ class Subscription(Document):
|
||||
frappe.throw(_('Subscription End Date is mandatory to follow calendar months'))
|
||||
|
||||
if billing_info[0]['billing_interval'] != 'Month':
|
||||
frappe.throw('Billing Interval in Subscription Plan must be Month to follow calendar months')
|
||||
frappe.throw(_('Billing Interval in Subscription Plan must be Month to follow calendar months'))
|
||||
|
||||
def after_insert(self):
|
||||
# todo: deal with users who collect prepayments. Maybe a new Subscription Invoice doctype?
|
||||
@@ -383,7 +383,9 @@ class Subscription(Document):
|
||||
|
||||
invoice.flags.ignore_mandatory = True
|
||||
invoice.save()
|
||||
invoice.submit()
|
||||
|
||||
if self.submit_invoice:
|
||||
invoice.submit()
|
||||
|
||||
return invoice
|
||||
|
||||
|
||||
@@ -1,24 +1,6 @@
|
||||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
cur_frm.add_fetch("customer", "customer_group", "customer_group" );
|
||||
cur_frm.add_fetch("supplier", "supplier_group_name", "supplier_group" );
|
||||
|
||||
frappe.ui.form.on("Tax Rule", "tax_type", function(frm) {
|
||||
frm.toggle_reqd("sales_tax_template", frm.doc.tax_type=="Sales");
|
||||
frm.toggle_reqd("purchase_tax_template", frm.doc.tax_type=="Purchase");
|
||||
})
|
||||
|
||||
frappe.ui.form.on("Tax Rule", "onload", function(frm) {
|
||||
if(frm.doc.__islocal) {
|
||||
frm.set_value("use_for_shopping_cart", 1);
|
||||
}
|
||||
})
|
||||
|
||||
frappe.ui.form.on("Tax Rule", "refresh", function(frm) {
|
||||
frappe.ui.form.trigger("Tax Rule", "tax_type");
|
||||
})
|
||||
|
||||
frappe.ui.form.on("Tax Rule", "customer", function(frm) {
|
||||
if(frm.doc.customer) {
|
||||
frappe.call({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,7 @@ class TestTaxRule(unittest.TestCase):
|
||||
tax_rule1 = make_tax_rule(customer_group= "All Customer Groups",
|
||||
sales_tax_template = "_Test Sales Taxes and Charges Template - _TC", priority = 1, from_date = "2015-01-01")
|
||||
tax_rule1.save()
|
||||
self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":0}),
|
||||
self.assertEqual(get_tax_template("2015-01-01", {"customer_group" : "Commercial", "use_for_shopping_cart":1}),
|
||||
"_Test Sales Taxes and Charges Template - _TC")
|
||||
|
||||
def test_conflict_with_overlapping_dates(self):
|
||||
|
||||
@@ -21,7 +21,10 @@ def get_party_details(inv):
|
||||
else:
|
||||
party_type = 'Supplier'
|
||||
party = inv.supplier
|
||||
|
||||
|
||||
if not party:
|
||||
frappe.throw(_("Please select {0} first").format(party_type))
|
||||
|
||||
return party_type, party
|
||||
|
||||
def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
@@ -46,7 +49,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
if not parties:
|
||||
parties.append(party)
|
||||
|
||||
fiscal_year = get_fiscal_year(inv.posting_date, company=inv.company)
|
||||
fiscal_year = get_fiscal_year(inv.get('posting_date') or inv.get('transaction_date'), company=inv.company)
|
||||
tax_details = get_tax_withholding_details(tax_withholding_category, fiscal_year[0], inv.company)
|
||||
|
||||
if not tax_details:
|
||||
@@ -151,7 +154,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, fiscal_year_details, p
|
||||
tax_deducted = get_deducted_tax(taxable_vouchers, fiscal_year, tax_details)
|
||||
|
||||
tax_amount = 0
|
||||
posting_date = inv.posting_date
|
||||
posting_date = inv.get('posting_date') or inv.get('transaction_date')
|
||||
if party_type == 'Supplier':
|
||||
ldc = get_lower_deduction_certificate(fiscal_year, pan_no)
|
||||
if tax_deducted:
|
||||
@@ -254,7 +257,7 @@ def get_tds_amount(ldc, parties, inv, tax_details, fiscal_year_details, tax_dedu
|
||||
if ((threshold and inv.net_total >= threshold) or (cumulative_threshold and supp_credit_amt >= cumulative_threshold)):
|
||||
if ldc and is_valid_certificate(
|
||||
ldc.valid_from, ldc.valid_upto,
|
||||
inv.posting_date, tax_deducted,
|
||||
inv.get('posting_date') or inv.get('transaction_date'), tax_deducted,
|
||||
inv.net_total, ldc.certificate_limit
|
||||
):
|
||||
tds_amount = get_ltds_amount(supp_credit_amt, 0, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
@@ -324,7 +327,7 @@ def get_tds_amount_from_ldc(ldc, parties, fiscal_year, pan_no, tax_details, post
|
||||
net_total, ldc.certificate_limit
|
||||
):
|
||||
tds_amount = get_ltds_amount(net_total, limit_consumed, ldc.certificate_limit, ldc.rate, tax_details)
|
||||
|
||||
|
||||
return tds_amount
|
||||
|
||||
def get_debit_note_amount(suppliers, fiscal_year_details, company=None):
|
||||
|
||||
@@ -112,7 +112,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
si = create_sales_invoice(customer = "Test TCS Customer", rate=5000)
|
||||
si.submit()
|
||||
|
||||
tcs_charged = sum([d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC'])
|
||||
tcs_charged = sum(d.base_tax_amount for d in si.taxes if d.account_head == 'TCS - _TC')
|
||||
self.assertEqual(tcs_charged, 500)
|
||||
invoices.append(si)
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ def merge_similar_entries(gl_map, precision=None):
|
||||
|
||||
def check_if_in_list(gle, gl_map, dimensions=None):
|
||||
account_head_fieldnames = ['party_type', 'party', 'against_voucher', 'against_voucher_type',
|
||||
'cost_center', 'project']
|
||||
'cost_center', 'project', 'voucher_detail_no']
|
||||
|
||||
if dimensions:
|
||||
account_head_fieldnames = account_head_fieldnames + dimensions
|
||||
@@ -143,7 +143,7 @@ def make_entry(args, adv_adj, update_outstanding, from_repost=False):
|
||||
validate_expense_against_budget(args)
|
||||
|
||||
def validate_cwip_accounts(gl_map):
|
||||
cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
|
||||
cwip_enabled = any(cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting"))
|
||||
|
||||
if cwip_enabled and gl_map[0].voucher_type == "Journal Entry":
|
||||
cwip_accounts = [d[0] for d in frappe.db.sql("""select name from tabAccount
|
||||
@@ -171,7 +171,7 @@ def round_off_debit_credit(gl_map):
|
||||
else:
|
||||
allowance = .5
|
||||
|
||||
if abs(debit_credit_diff) >= allowance:
|
||||
if abs(debit_credit_diff) > allowance:
|
||||
frappe.throw(_("Debit and Credit not equal for {0} #{1}. Difference is {2}.")
|
||||
.format(gl_map[0].voucher_type, gl_map[0].voucher_no, debit_credit_diff))
|
||||
|
||||
@@ -185,10 +185,10 @@ def make_round_off_gle(gl_map, debit_credit_diff, precision):
|
||||
for d in gl_map:
|
||||
if d.account == round_off_account:
|
||||
round_off_gle = d
|
||||
if d.debit_in_account_currency:
|
||||
debit_credit_diff -= flt(d.debit_in_account_currency)
|
||||
if d.debit:
|
||||
debit_credit_diff -= flt(d.debit)
|
||||
else:
|
||||
debit_credit_diff += flt(d.credit_in_account_currency)
|
||||
debit_credit_diff += flt(d.credit)
|
||||
round_off_account_exists = True
|
||||
|
||||
if round_off_account_exists and abs(debit_credit_diff) <= (1.0 / (10**precision)):
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"attach_print": 0,
|
||||
"channel": "Email",
|
||||
"condition": "doc.auto_created",
|
||||
"creation": "2018-04-25 14:19:05.440361",
|
||||
"days_in_advance": 0,
|
||||
|
||||
@@ -457,7 +457,7 @@ def validate_party_frozen_disabled(party_type, party_name):
|
||||
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
|
||||
|
||||
elif party_type == "Employee":
|
||||
if frappe.db.get_value("Employee", party_name, "status") == "Left":
|
||||
if frappe.db.get_value("Employee", party_name, "status") != "Active":
|
||||
frappe.msgprint(_("{0} {1} is not active").format(party_type, party_name), alert=True)
|
||||
|
||||
def get_timeline_data(doctype, name):
|
||||
@@ -542,6 +542,7 @@ def get_dashboard_info(party_type, party, loyalty_program=None):
|
||||
select company, sum(debit_in_account_currency) - sum(credit_in_account_currency)
|
||||
from `tabGL Entry`
|
||||
where party_type = %s and party=%s
|
||||
and is_cancelled = 0
|
||||
group by company""", (party_type, party)))
|
||||
|
||||
for d in companies:
|
||||
|
||||
@@ -58,11 +58,9 @@ def get_conditions(filters):
|
||||
def get_data(filters):
|
||||
|
||||
data = []
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
accounts = frappe.db.get_all("Account", fields=["name", "account_currency"],
|
||||
filters=conditions)
|
||||
filters=conditions, order_by='name')
|
||||
|
||||
for d in accounts:
|
||||
balance = get_balance_on(d.name, date=filters.report_date)
|
||||
|
||||
@@ -23,7 +23,7 @@ class TestAccountBalance(unittest.TestCase):
|
||||
|
||||
expected_data = [
|
||||
{
|
||||
"account": 'Sales - _TC2',
|
||||
"account": 'Direct Income - _TC2',
|
||||
"currency": 'EUR',
|
||||
"balance": -100.0,
|
||||
},
|
||||
@@ -32,21 +32,21 @@ class TestAccountBalance(unittest.TestCase):
|
||||
"currency": 'EUR',
|
||||
"balance": -100.0,
|
||||
},
|
||||
{
|
||||
"account": 'Service - _TC2',
|
||||
"currency": 'EUR',
|
||||
"balance": 0.0,
|
||||
},
|
||||
{
|
||||
"account": 'Direct Income - _TC2',
|
||||
"currency": 'EUR',
|
||||
"balance": -100.0,
|
||||
},
|
||||
{
|
||||
"account": 'Indirect Income - _TC2',
|
||||
"currency": 'EUR',
|
||||
"balance": 0.0,
|
||||
},
|
||||
{
|
||||
"account": 'Sales - _TC2',
|
||||
"currency": 'EUR',
|
||||
"balance": -100.0,
|
||||
},
|
||||
{
|
||||
"account": 'Service - _TC2',
|
||||
"currency": 'EUR',
|
||||
"balance": 0.0,
|
||||
}
|
||||
]
|
||||
|
||||
self.assertEqual(expected_data, report[1])
|
||||
|
||||
@@ -364,7 +364,7 @@ class ReceivablePayableReport(object):
|
||||
payment_terms_details = frappe.db.sql("""
|
||||
select
|
||||
si.name, si.party_account_currency, si.currency, si.conversion_rate,
|
||||
ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
ps.due_date, ps.payment_term, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
|
||||
from `tab{0}` si, `tabPayment Schedule` ps
|
||||
where
|
||||
si.name = ps.parent and
|
||||
@@ -394,7 +394,7 @@ class ReceivablePayableReport(object):
|
||||
"due_date": d.due_date,
|
||||
"invoiced": invoiced,
|
||||
"invoice_grand_total": row.invoiced,
|
||||
"payment_term": d.description,
|
||||
"payment_term": d.description or d.payment_term,
|
||||
"paid": d.paid_amount + d.discounted_amount,
|
||||
"credit_note": 0.0,
|
||||
"outstanding": invoiced - d.paid_amount - d.discounted_amount
|
||||
@@ -584,6 +584,7 @@ class ReceivablePayableReport(object):
|
||||
`tabGL Entry`
|
||||
where
|
||||
docstatus < 2
|
||||
and is_cancelled = 0
|
||||
and party_type=%s
|
||||
and (party is not null and party != '')
|
||||
{1} {2} {3}"""
|
||||
|
||||
@@ -5,7 +5,8 @@ from __future__ import unicode_literals
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import flt, cint
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data)
|
||||
from erpnext.accounts.report.financial_statements import (get_period_list, get_columns, get_data,
|
||||
get_filtered_list_for_consolidated_report)
|
||||
|
||||
def execute(filters=None):
|
||||
period_list = get_period_list(filters.from_fiscal_year, filters.to_fiscal_year,
|
||||
@@ -132,6 +133,10 @@ def get_report_summary(period_list, asset, liability, equity, provisional_profit
|
||||
if filters.get('accumulated_values'):
|
||||
period_list = [period_list[-1]]
|
||||
|
||||
# from consolidated financial statement
|
||||
if filters.get('accumulated_in_group_company'):
|
||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
if asset:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user