mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-13 02:08:47 +00:00
Compare commits
883 Commits
v15.16.0
...
fix-get_si
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0255642deb | ||
|
|
2178fdc65a | ||
|
|
eabf706f37 | ||
|
|
d4d9e6c6fa | ||
|
|
2eb318a5ce | ||
|
|
b897225136 | ||
|
|
bb18ae82cf | ||
|
|
74ceb6da5e | ||
|
|
e0ad52b500 | ||
|
|
8252d92e6a | ||
|
|
b15795392b | ||
|
|
274c65c451 | ||
|
|
1b4fbbb115 | ||
|
|
f95403d1dc | ||
|
|
2d2ff7cf52 | ||
|
|
d32a407e08 | ||
|
|
5e0d017497 | ||
|
|
ec07b42ea2 | ||
|
|
3b861798e6 | ||
|
|
b498094a97 | ||
|
|
62bbcbc7ef | ||
|
|
96530b9c0b | ||
|
|
0cbcc55d9a | ||
|
|
6b5c54bcbc | ||
|
|
c2dde04aa2 | ||
|
|
c14986f9e6 | ||
|
|
62a6945bd2 | ||
|
|
be8bb235dc | ||
|
|
498bf15ecd | ||
|
|
210d7711b4 | ||
|
|
cf988434e9 | ||
|
|
466625213b | ||
|
|
9ba6ff67d5 | ||
|
|
eab22eb282 | ||
|
|
7e198ccb21 | ||
|
|
3d54fd8389 | ||
|
|
d0ea598cdf | ||
|
|
6de8c18f98 | ||
|
|
5923b48ede | ||
|
|
7d264696f3 | ||
|
|
b96c063c93 | ||
|
|
2bad706dcf | ||
|
|
7fa3e82ac7 | ||
|
|
7ddbfa10c9 | ||
|
|
4bd437b59d | ||
|
|
fca8028e3c | ||
|
|
cd293a5173 | ||
|
|
07b605a287 | ||
|
|
54383cfb55 | ||
|
|
4cc3c1b765 | ||
|
|
4d56f725fe | ||
|
|
c34f09c503 | ||
|
|
fe43dab4d7 | ||
|
|
b69cdeb4a6 | ||
|
|
15dc5c7e99 | ||
|
|
e05bf9d32a | ||
|
|
3fb7886418 | ||
|
|
bae7c64964 | ||
|
|
68c0e188e8 | ||
|
|
e912e9597d | ||
|
|
610d4f5cb6 | ||
|
|
cdd5441435 | ||
|
|
f09e2130a1 | ||
|
|
0819675fce | ||
|
|
784b6dcfea | ||
|
|
245effcccd | ||
|
|
0d01bd8a5a | ||
|
|
57b6a98703 | ||
|
|
e1b0fffd0c | ||
|
|
c2f88f29dc | ||
|
|
277dade9f9 | ||
|
|
e2cf1ea73e | ||
|
|
df409d80e0 | ||
|
|
5b1571879c | ||
|
|
bb839b2924 | ||
|
|
5bc2035bd0 | ||
|
|
9e973476b2 | ||
|
|
5e2669f4b6 | ||
|
|
0f1be03faf | ||
|
|
351ee5b8fe | ||
|
|
e84c9f7c51 | ||
|
|
7da9ffa3bd | ||
|
|
4415212a2d | ||
|
|
6401908f41 | ||
|
|
beee98da6d | ||
|
|
60c33ac3e6 | ||
|
|
1bc74bde29 | ||
|
|
12c40ef2e4 | ||
|
|
b250a21a2b | ||
|
|
4e28b5a199 | ||
|
|
739434b727 | ||
|
|
9ea963bfe9 | ||
|
|
6e5484ea03 | ||
|
|
d048644327 | ||
|
|
1a9e091d12 | ||
|
|
1b0d9643cd | ||
|
|
92bc962f60 | ||
|
|
a9576f0cf6 | ||
|
|
4f8a16848f | ||
|
|
bacf2b7431 | ||
|
|
34d8bc4701 | ||
|
|
510fdf7bf6 | ||
|
|
0346f47c1d | ||
|
|
3f9693b31f | ||
|
|
877cc7255d | ||
|
|
70abedc57a | ||
|
|
0c6de4ecb2 | ||
|
|
a517125d64 | ||
|
|
b7f283b2f0 | ||
|
|
cd37fd790b | ||
|
|
f983e09f92 | ||
|
|
3b4b2275de | ||
|
|
b5340c5ec0 | ||
|
|
10074e9980 | ||
|
|
bbee9b5637 | ||
|
|
915d864166 | ||
|
|
8555617295 | ||
|
|
b71b0d5997 | ||
|
|
a117ef3cb8 | ||
|
|
33b631e395 | ||
|
|
9b1c22250f | ||
|
|
4feecb69d8 | ||
|
|
12560e2407 | ||
|
|
0890b414b1 | ||
|
|
d54f8318fb | ||
|
|
fe77b9d633 | ||
|
|
47ee801d37 | ||
|
|
705ae7da14 | ||
|
|
7223106417 | ||
|
|
5a5758423e | ||
|
|
b09c9354fb | ||
|
|
d00f6672a8 | ||
|
|
06d6220a2a | ||
|
|
3011322b22 | ||
|
|
e560029736 | ||
|
|
b1ba210332 | ||
|
|
cb9114442b | ||
|
|
eb5bb9f9a9 | ||
|
|
87ba3b64f7 | ||
|
|
47f7b65058 | ||
|
|
161ae1edd1 | ||
|
|
d097ad6c19 | ||
|
|
a5d5223c0e | ||
|
|
1a1629196d | ||
|
|
d0ed8ef83b | ||
|
|
0773f66feb | ||
|
|
2dc49c834a | ||
|
|
787333896c | ||
|
|
283763dfb2 | ||
|
|
07175367d8 | ||
|
|
61219ca4ce | ||
|
|
c20995ec2f | ||
|
|
6a0a08b59c | ||
|
|
d370c60a6c | ||
|
|
a6ab53236e | ||
|
|
9a00edb031 | ||
|
|
ae353398d9 | ||
|
|
9983283f95 | ||
|
|
6d5bdc6c68 | ||
|
|
80e69210db | ||
|
|
793e3ad78e | ||
|
|
4057682c87 | ||
|
|
5e68b7e3a6 | ||
|
|
495b47db16 | ||
|
|
4aa960b744 | ||
|
|
dd6c192695 | ||
|
|
8772628912 | ||
|
|
a99d0a65b0 | ||
|
|
a09241e3c7 | ||
|
|
c7b961ffa2 | ||
|
|
32a608f948 | ||
|
|
8d79365e0d | ||
|
|
71e833c3f2 | ||
|
|
b5f3013005 | ||
|
|
39ef75e2d0 | ||
|
|
42813d38c3 | ||
|
|
55147781f3 | ||
|
|
c9fd182268 | ||
|
|
0743289925 | ||
|
|
4d75159247 | ||
|
|
baa1978128 | ||
|
|
259f313af7 | ||
|
|
863116f1cd | ||
|
|
5cb5e09dbb | ||
|
|
fa1c7b663c | ||
|
|
a9a84cc7d4 | ||
|
|
ff967c45f7 | ||
|
|
9a5c422074 | ||
|
|
5a83a16e60 | ||
|
|
1704180f38 | ||
|
|
0cf9ff0a04 | ||
|
|
41ae2a2dc5 | ||
|
|
f7b2380ec1 | ||
|
|
5eeb650dfd | ||
|
|
a1b95606b1 | ||
|
|
726ac6bda1 | ||
|
|
d6201ce5c7 | ||
|
|
e7e23fbc96 | ||
|
|
e7544e9fc1 | ||
|
|
eaf86a6461 | ||
|
|
f53ba178a8 | ||
|
|
c68ad73c6e | ||
|
|
6851c5042f | ||
|
|
da96578afb | ||
|
|
27f05145ae | ||
|
|
1b3ba25220 | ||
|
|
13cba5068b | ||
|
|
db24e24882 | ||
|
|
e7984b3ef9 | ||
|
|
b562b4cf99 | ||
|
|
69d7a640ee | ||
|
|
fa2d33cb50 | ||
|
|
2588970d55 | ||
|
|
d4ac57704c | ||
|
|
15c90551b6 | ||
|
|
0acd0f50c5 | ||
|
|
acb6e8e120 | ||
|
|
ccff588563 | ||
|
|
780c4278e6 | ||
|
|
2d6506ecec | ||
|
|
a97b3db749 | ||
|
|
cc15f695b4 | ||
|
|
c41cbb3e29 | ||
|
|
6ad298adfc | ||
|
|
db9829e83f | ||
|
|
956c3c50a0 | ||
|
|
40c1acc961 | ||
|
|
7babfd4ac4 | ||
|
|
65df4b6aa8 | ||
|
|
2721ee3a8d | ||
|
|
137b5a6108 | ||
|
|
b023e5d6b3 | ||
|
|
89a0e9c245 | ||
|
|
89326bd657 | ||
|
|
8ddc26eb2e | ||
|
|
ca0c3eb184 | ||
|
|
0465c9aabb | ||
|
|
a045916aca | ||
|
|
be312cea4c | ||
|
|
9611e9bd7f | ||
|
|
08ed3cd313 | ||
|
|
231ab83562 | ||
|
|
f45dd740c5 | ||
|
|
0156339f34 | ||
|
|
f99bb61181 | ||
|
|
6a47a2ceaf | ||
|
|
525f656cc1 | ||
|
|
d34787cf6d | ||
|
|
9424bbc01c | ||
|
|
16c297c2ec | ||
|
|
8beec58670 | ||
|
|
bac811bd5e | ||
|
|
eb9ee3f79b | ||
|
|
5da3e532c9 | ||
|
|
b1d9f3132d | ||
|
|
aaa9036eca | ||
|
|
005c5a587f | ||
|
|
47c78a5a73 | ||
|
|
24ccb3eb78 | ||
|
|
a56b79cc72 | ||
|
|
84ee50e492 | ||
|
|
16b7401d4c | ||
|
|
1a5d56977e | ||
|
|
6812e91893 | ||
|
|
4ca84eadb6 | ||
|
|
27aba02d16 | ||
|
|
3d7ad71b22 | ||
|
|
8d5045ef4c | ||
|
|
00261094c8 | ||
|
|
9471d8fff9 | ||
|
|
ab6e92aae1 | ||
|
|
fb3421fcce | ||
|
|
c8693cdf37 | ||
|
|
3df1d75bdd | ||
|
|
fc12238fcc | ||
|
|
15fff84bb5 | ||
|
|
01aadbef85 | ||
|
|
ccdcb7dfcc | ||
|
|
94fabe0321 | ||
|
|
dd39da0b77 | ||
|
|
3688d9412e | ||
|
|
4918aeb4c6 | ||
|
|
d659d407a0 | ||
|
|
262cafc430 | ||
|
|
11190aac4c | ||
|
|
6d31563920 | ||
|
|
b6957ddac2 | ||
|
|
b2d8a44199 | ||
|
|
2815d196de | ||
|
|
64266c4d38 | ||
|
|
f4d418ea6d | ||
|
|
a008f5f611 | ||
|
|
74eab91042 | ||
|
|
9aeb3932d0 | ||
|
|
b6a7549407 | ||
|
|
9087e1443e | ||
|
|
5952cfa673 | ||
|
|
a1e0197a8b | ||
|
|
af7bc4f178 | ||
|
|
023bc36592 | ||
|
|
ceeb724acc | ||
|
|
5e9016ffab | ||
|
|
6bc40373f2 | ||
|
|
14c8c8c33d | ||
|
|
0925706d5e | ||
|
|
ca14ae8f1b | ||
|
|
90c6d4dc85 | ||
|
|
228aa1a244 | ||
|
|
993e2bfbf9 | ||
|
|
ea3071db66 | ||
|
|
395299803f | ||
|
|
5e2d21c033 | ||
|
|
c4c3090f46 | ||
|
|
6492019383 | ||
|
|
f6831fba13 | ||
|
|
16db6c2f47 | ||
|
|
bd3dc6482e | ||
|
|
f7b7b2b438 | ||
|
|
99fbd8ad18 | ||
|
|
9d20256366 | ||
|
|
85f2a6dd54 | ||
|
|
de2eba0d98 | ||
|
|
705ef4f5a3 | ||
|
|
b2dde55f2c | ||
|
|
d53b34c0ce | ||
|
|
a427029151 | ||
|
|
2de3e6ce6d | ||
|
|
03a38ed025 | ||
|
|
26503a205f | ||
|
|
5a25c80f2e | ||
|
|
4c8a8c3bcd | ||
|
|
decdbd2782 | ||
|
|
eb4c476490 | ||
|
|
68f5dd3e7b | ||
|
|
68aee8c144 | ||
|
|
b24e28953b | ||
|
|
9656412bba | ||
|
|
61280117eb | ||
|
|
961bdf0d24 | ||
|
|
080aa30407 | ||
|
|
eecf9cd1d8 | ||
|
|
e2a519464b | ||
|
|
60a81a563e | ||
|
|
3a66aefd2c | ||
|
|
54f7cf59fd | ||
|
|
ba10f7d04c | ||
|
|
691e3bb24f | ||
|
|
832734ff4c | ||
|
|
3d4156cc7d | ||
|
|
cc053ad894 | ||
|
|
ae294ee470 | ||
|
|
e4bdd3a28d | ||
|
|
63313eef6f | ||
|
|
1da9087cc4 | ||
|
|
de3795a7d4 | ||
|
|
cfd3230c75 | ||
|
|
e38b06bf2d | ||
|
|
7cb0b1b7c3 | ||
|
|
62b4a263f8 | ||
|
|
96b13c59c1 | ||
|
|
1423b38d50 | ||
|
|
ff27cccff4 | ||
|
|
abc7d30024 | ||
|
|
591f4ebdca | ||
|
|
ef8e4191cd | ||
|
|
cf97e3c21f | ||
|
|
2e8739fff7 | ||
|
|
3da0aa6a0b | ||
|
|
97be527ee9 | ||
|
|
cac3b4a0d8 | ||
|
|
96f31847b2 | ||
|
|
85f9f8d176 | ||
|
|
7b0cd03f88 | ||
|
|
68585f6f2b | ||
|
|
857f2b5a01 | ||
|
|
258148b615 | ||
|
|
7145b040f1 | ||
|
|
874766a82f | ||
|
|
096a2c8cd0 | ||
|
|
4a047fefb8 | ||
|
|
fddf341f44 | ||
|
|
fe5fc5bd3a | ||
|
|
0255e09285 | ||
|
|
2b0b15f4d1 | ||
|
|
8e4b591ea2 | ||
|
|
663bb8726c | ||
|
|
2add802d0d | ||
|
|
adfcdb3b65 | ||
|
|
9fadf5f426 | ||
|
|
3e6306348a | ||
|
|
458dd51af7 | ||
|
|
23b0b8ba36 | ||
|
|
592fc81260 | ||
|
|
c232acbe9b | ||
|
|
640dfab827 | ||
|
|
01044ca8e9 | ||
|
|
2633d7dca3 | ||
|
|
ecb533c4d1 | ||
|
|
5fc19dab54 | ||
|
|
58114e7b24 | ||
|
|
1763824e5f | ||
|
|
aee2e12f39 | ||
|
|
500573067a | ||
|
|
8052103197 | ||
|
|
78ab11f991 | ||
|
|
3263f2023c | ||
|
|
8f00481c5f | ||
|
|
9872e371a2 | ||
|
|
37b3ac7952 | ||
|
|
d891bd7fac | ||
|
|
d366a91d9e | ||
|
|
8c3713b649 | ||
|
|
729fc738af | ||
|
|
add238c892 | ||
|
|
ad3634be7c | ||
|
|
5a53a4b044 | ||
|
|
5a97fa6336 | ||
|
|
657bb9d682 | ||
|
|
b9f5a1c85d | ||
|
|
592ce45da7 | ||
|
|
d9b3b95854 | ||
|
|
5426b93387 | ||
|
|
284a40aa63 | ||
|
|
2989e36b1d | ||
|
|
d8245cef72 | ||
|
|
edf4514d8b | ||
|
|
477d9fa87e | ||
|
|
01f133f8c8 | ||
|
|
d32147f8fe | ||
|
|
86aeacf393 | ||
|
|
dd80d3b9b9 | ||
|
|
98cba5ed30 | ||
|
|
ca8a5b45ba | ||
|
|
9d6a06aec5 | ||
|
|
dd016e6ced | ||
|
|
0fe6dcd742 | ||
|
|
bcbe6c4a53 | ||
|
|
f09c5f32cf | ||
|
|
3ca7e442bc | ||
|
|
8ac647ece7 | ||
|
|
4307d3b5c9 | ||
|
|
aa17110bde | ||
|
|
3f6d805033 | ||
|
|
64b44a360a | ||
|
|
040cc8d22f | ||
|
|
3be345e605 | ||
|
|
e1cea25781 | ||
|
|
a2ede7d6d5 | ||
|
|
7e4dd33ab0 | ||
|
|
45d5cff47d | ||
|
|
745e3bfb73 | ||
|
|
762906f240 | ||
|
|
9ec6f1e1d6 | ||
|
|
628ea42b63 | ||
|
|
1efff268b0 | ||
|
|
b206b0583b | ||
|
|
816b1b6bd5 | ||
|
|
24fcd67f8b | ||
|
|
c99c805743 | ||
|
|
f258ab5e98 | ||
|
|
880a85d2af | ||
|
|
efd31a429c | ||
|
|
728cc9f725 | ||
|
|
52305e3000 | ||
|
|
1657337887 | ||
|
|
362f377f61 | ||
|
|
8bdb61cb87 | ||
|
|
67f43d37df | ||
|
|
3543f86c63 | ||
|
|
627165dc7c | ||
|
|
874774fe6c | ||
|
|
ee76af7681 | ||
|
|
f9713eeb56 | ||
|
|
0ca7527f7a | ||
|
|
2a41da94d4 | ||
|
|
73586fd9b2 | ||
|
|
9c889b37fb | ||
|
|
b11ae4b54c | ||
|
|
e3af1dc864 | ||
|
|
8b04c1d4f6 | ||
|
|
74f9e34182 | ||
|
|
1fddc30350 | ||
|
|
e2bb4e2baa | ||
|
|
9006c9b747 | ||
|
|
6518582ed3 | ||
|
|
5c308a4f9a | ||
|
|
ac91030b31 | ||
|
|
9680edfcc3 | ||
|
|
56e991b7f4 | ||
|
|
9903049c7a | ||
|
|
f34ffc2062 | ||
|
|
f50d933a25 | ||
|
|
ce092bf23b | ||
|
|
fb06ad7330 | ||
|
|
80ab4eea8c | ||
|
|
83a13e22b7 | ||
|
|
ee0c64215d | ||
|
|
5ac8bd7f08 | ||
|
|
2f3fc12c08 | ||
|
|
331ad62f3c | ||
|
|
f6e93f084a | ||
|
|
7b2eacd4d8 | ||
|
|
a9bf906545 | ||
|
|
97090ff367 | ||
|
|
6c8b6de4c9 | ||
|
|
f91752cad2 | ||
|
|
cc60c328fe | ||
|
|
ff5b1b7ded | ||
|
|
a1ff7cab7e | ||
|
|
32f622ef80 | ||
|
|
969616ed09 | ||
|
|
ca62cde9aa | ||
|
|
3d1e3a9cde | ||
|
|
184848edf9 | ||
|
|
65ba5b3000 | ||
|
|
3a487bd33a | ||
|
|
56e92b702c | ||
|
|
45299fe4b3 | ||
|
|
434c2a1815 | ||
|
|
f31002636b | ||
|
|
b5dd0c8630 | ||
|
|
9c7b19e0b7 | ||
|
|
3a51a3f37e | ||
|
|
a5a5341643 | ||
|
|
20c6e9fca2 | ||
|
|
5b3eba7bee | ||
|
|
8c6e341a71 | ||
|
|
545ef3c234 | ||
|
|
7842c9fba8 | ||
|
|
7e43d7b131 | ||
|
|
089da459f7 | ||
|
|
b27af6b5c8 | ||
|
|
c69fb80222 | ||
|
|
134201794a | ||
|
|
2df767f596 | ||
|
|
da80e4dbce | ||
|
|
5cc8603cff | ||
|
|
426c245032 | ||
|
|
9731b74ad3 | ||
|
|
ee29526bbe | ||
|
|
3e884d347a | ||
|
|
6e0362dee8 | ||
|
|
2499675ad1 | ||
|
|
908b21f7fd | ||
|
|
e93a19ffb5 | ||
|
|
3e77c0b564 | ||
|
|
e769e750ec | ||
|
|
c31ee8ea33 | ||
|
|
894ae1fe0f | ||
|
|
5fae2f6d57 | ||
|
|
ad5edbb1de | ||
|
|
780b827adc | ||
|
|
8e010ef063 | ||
|
|
09f9764bbd | ||
|
|
a59c942cd4 | ||
|
|
b097bb20d9 | ||
|
|
2f9e96e324 | ||
|
|
56b8d1b927 | ||
|
|
5b446d4575 | ||
|
|
9ae5c979e8 | ||
|
|
ea7565889f | ||
|
|
4a111f7362 | ||
|
|
ffd171a26b | ||
|
|
95edd82638 | ||
|
|
e07c3aad6b | ||
|
|
a393a6b76c | ||
|
|
d380bf8179 | ||
|
|
8062d2be3b | ||
|
|
d17e37c581 | ||
|
|
6f432b8e45 | ||
|
|
94faa44697 | ||
|
|
9fde782403 | ||
|
|
ecc305dd59 | ||
|
|
6f6d5cb4cf | ||
|
|
8634abc021 | ||
|
|
a74e1f1600 | ||
|
|
59438ee8d4 | ||
|
|
4ed9927a30 | ||
|
|
ae508144cd | ||
|
|
a52a1b49af | ||
|
|
93295bf25b | ||
|
|
73639db910 | ||
|
|
c320288690 | ||
|
|
0aa1636d04 | ||
|
|
194d70f8a0 | ||
|
|
af35590549 | ||
|
|
a248b13cc3 | ||
|
|
b0dfc936a1 | ||
|
|
73090fa130 | ||
|
|
ade09bc709 | ||
|
|
8d9f391309 | ||
|
|
ebd74a4e5b | ||
|
|
c4f8f3613f | ||
|
|
e5a8ad54e2 | ||
|
|
815c616f18 | ||
|
|
1d8fcd66e6 | ||
|
|
6ca3b26820 | ||
|
|
18a2e6ecb2 | ||
|
|
922fffda1f | ||
|
|
1fc5844025 | ||
|
|
45b4bfc947 | ||
|
|
92f6d2c87c | ||
|
|
c2bda2c705 | ||
|
|
4b4b176fcf | ||
|
|
a27374fd8f | ||
|
|
56ac3424d2 | ||
|
|
696e2108ac | ||
|
|
89612f2605 | ||
|
|
a8216b9727 | ||
|
|
779260fb49 | ||
|
|
860b67e9c0 | ||
|
|
47ba357bea | ||
|
|
3502c01aa4 | ||
|
|
6e3e094c95 | ||
|
|
a89afb65d7 | ||
|
|
9a171db97f | ||
|
|
48567ef755 | ||
|
|
b57eba6eaf | ||
|
|
70d99eebc0 | ||
|
|
4783e4beee | ||
|
|
450c2470e9 | ||
|
|
09439334ca | ||
|
|
f9c88ea7bc | ||
|
|
33eedb97dc | ||
|
|
2984a86f37 | ||
|
|
5f5d75a0bb | ||
|
|
6f231e4c83 | ||
|
|
c750e4d7ef | ||
|
|
291a499124 | ||
|
|
67b36a0823 | ||
|
|
162c0497d1 | ||
|
|
416bd400bb | ||
|
|
ee60fa940c | ||
|
|
6210b24c64 | ||
|
|
68c6ad6036 | ||
|
|
10b9570429 | ||
|
|
11c8d9fcf1 | ||
|
|
61705047b0 | ||
|
|
ea4b6ff27b | ||
|
|
5ce395a60a | ||
|
|
ac79b8483f | ||
|
|
0135293127 | ||
|
|
adff287160 | ||
|
|
b651b36fff | ||
|
|
5a068410c6 | ||
|
|
ebb186c8df | ||
|
|
d582a73795 | ||
|
|
e845b63228 | ||
|
|
cfe0479dfb | ||
|
|
67e74d03ed | ||
|
|
8722318081 | ||
|
|
2d272fa51c | ||
|
|
15840d408b | ||
|
|
f9fc6c9c9d | ||
|
|
30c6b83a10 | ||
|
|
1754d027b3 | ||
|
|
a1f8595a6a | ||
|
|
5cce522ecd | ||
|
|
758ec720de | ||
|
|
6280031722 | ||
|
|
ff0343d2cc | ||
|
|
3d00d74fed | ||
|
|
30402033bc | ||
|
|
de445b32f5 | ||
|
|
34d3eb88b3 | ||
|
|
a9d91189b0 | ||
|
|
84f0d1ff1f | ||
|
|
98a8288da2 | ||
|
|
0e100cd451 | ||
|
|
e5a018f84c | ||
|
|
2b02ef0066 | ||
|
|
e5bc8fccb1 | ||
|
|
0c5bdbdcf3 | ||
|
|
a3191f1c8c | ||
|
|
787784e937 | ||
|
|
539ff03a7e | ||
|
|
e42a3e0084 | ||
|
|
8fbd4cea5b | ||
|
|
05f24ede96 | ||
|
|
54b323e557 | ||
|
|
9231706227 | ||
|
|
1f88b1ef84 | ||
|
|
10242235bc | ||
|
|
73b65ac82e | ||
|
|
f52916a2c3 | ||
|
|
98d6cdd53c | ||
|
|
e1a87a802d | ||
|
|
d9e284366d | ||
|
|
56e9a46c17 | ||
|
|
2ce6bbf291 | ||
|
|
568d5bfbe8 | ||
|
|
60435daba3 | ||
|
|
f14d1eb871 | ||
|
|
d4c0dbfacc | ||
|
|
469ae2c7f1 | ||
|
|
a9372c42cd | ||
|
|
edf67444ea | ||
|
|
4867ca353c | ||
|
|
4015723591 | ||
|
|
f70d779034 | ||
|
|
23beb46d15 | ||
|
|
e019d43d0b | ||
|
|
c37e374fdd | ||
|
|
cd1e016163 | ||
|
|
1f4b381748 | ||
|
|
fb9a80923b | ||
|
|
ed1c198897 | ||
|
|
1b808e1d7c | ||
|
|
639f427d6d | ||
|
|
539f0251d9 | ||
|
|
d91d9b02d4 | ||
|
|
0104897d69 | ||
|
|
5530a5b303 | ||
|
|
a9fceeb00f | ||
|
|
34e3538b55 | ||
|
|
851a234988 | ||
|
|
17893eff59 | ||
|
|
54e8ce1ac5 | ||
|
|
38e5e4a893 | ||
|
|
65cc804186 | ||
|
|
8fa677b8e8 | ||
|
|
6bd56d2d5f | ||
|
|
204face50d | ||
|
|
c5f5aa8208 | ||
|
|
3d9938221a | ||
|
|
f0a1f4ac7c | ||
|
|
ec1a7869f8 | ||
|
|
7e67d42d1d | ||
|
|
da9a7ff63d | ||
|
|
47f913abcb | ||
|
|
fc4199504f | ||
|
|
0fc14b72ca | ||
|
|
804ed0d26a | ||
|
|
028b3e2fbf | ||
|
|
e0a03789ae | ||
|
|
7a3e4a8a05 | ||
|
|
e16cc38b70 | ||
|
|
c43b30cf36 | ||
|
|
07a8024b45 | ||
|
|
b64147cce9 | ||
|
|
058bb95171 | ||
|
|
daf2ec063c | ||
|
|
77af247450 | ||
|
|
1fd888175f | ||
|
|
fb0ec74d08 | ||
|
|
eb73017798 | ||
|
|
139a68fd0f | ||
|
|
d758fc1b89 | ||
|
|
056b74b162 | ||
|
|
e752f8f0ef | ||
|
|
54a32adef3 | ||
|
|
ca69845238 | ||
|
|
d2fdda8bcd | ||
|
|
afc64ed9ee | ||
|
|
500435b856 | ||
|
|
a15484fe3d | ||
|
|
76c61c1b04 | ||
|
|
de58c67991 | ||
|
|
8a34a4ba68 | ||
|
|
208d5042ee | ||
|
|
ece7049390 | ||
|
|
e72afd0bd6 | ||
|
|
83db7c6a65 | ||
|
|
62384e9321 | ||
|
|
1081df3d7e | ||
|
|
3a8736374c | ||
|
|
f276fbba4f | ||
|
|
fd78f868e1 | ||
|
|
d99a56bc27 | ||
|
|
48c66b68ab | ||
|
|
8d9b90f3f5 | ||
|
|
4a86375e89 | ||
|
|
b60c57a97d | ||
|
|
8903c1bc6f | ||
|
|
e33fd450fd | ||
|
|
dc5d2c7406 | ||
|
|
1612d7ba3f | ||
|
|
681782121c | ||
|
|
35cf944cb7 | ||
|
|
4bbad7f448 | ||
|
|
74b6bfb9eb | ||
|
|
46ea868559 | ||
|
|
3290df5593 | ||
|
|
8e3b9ec879 | ||
|
|
787fc8737f | ||
|
|
fcfcf6957e | ||
|
|
8ffa2bfe25 | ||
|
|
d69b0d76dd | ||
|
|
2bcff4c7f2 | ||
|
|
d436a40739 | ||
|
|
5deba1b6f9 | ||
|
|
886102d462 | ||
|
|
7be578485e | ||
|
|
7c1b990c55 | ||
|
|
74a0d6408a | ||
|
|
92cbe580e6 | ||
|
|
d92eb0c603 | ||
|
|
11d956fa18 | ||
|
|
8e523961dc | ||
|
|
cdbe1c87d4 | ||
|
|
3f42128fff | ||
|
|
4dff2c7a0d | ||
|
|
547993f801 | ||
|
|
946228d783 | ||
|
|
23df4205f8 | ||
|
|
89f484282a | ||
|
|
9ef26e1df0 | ||
|
|
025acc0e48 | ||
|
|
6942ab1012 | ||
|
|
3bfb7b79f2 | ||
|
|
24788ddcc0 | ||
|
|
adf313a6d3 | ||
|
|
7f39318340 | ||
|
|
a432290a82 | ||
|
|
2b64e1ca8b | ||
|
|
514d5434a3 | ||
|
|
7e600a6494 | ||
|
|
17ebc1ea80 | ||
|
|
b099590b2c | ||
|
|
5323bb7bee | ||
|
|
55dbcee36a | ||
|
|
ec9434aae3 | ||
|
|
d1ec0a6093 | ||
|
|
5136fe196b | ||
|
|
4aa841786f | ||
|
|
98cc7434d2 | ||
|
|
35020a9423 | ||
|
|
1cc1c9aa38 | ||
|
|
9d392970f0 | ||
|
|
b0d440c34b | ||
|
|
21c3d9c371 | ||
|
|
dae66eab36 | ||
|
|
9474908449 | ||
|
|
eec4057e8d | ||
|
|
08bc77fb95 | ||
|
|
fff97b1cd2 | ||
|
|
2a7d1c4c8d | ||
|
|
0b674b608b | ||
|
|
14b009b093 | ||
|
|
530922848f | ||
|
|
79d51a0a0b | ||
|
|
85488cd0dc | ||
|
|
ce7ac29d06 | ||
|
|
4f363f5bf3 | ||
|
|
ff7108a3b1 | ||
|
|
77cc91d06b | ||
|
|
45395027d3 | ||
|
|
6d5ccde864 | ||
|
|
961d2d9926 | ||
|
|
75441017c6 | ||
|
|
ed2457bddf | ||
|
|
4471ad581e | ||
|
|
7ecc0d5a04 | ||
|
|
78fe567419 | ||
|
|
5ae9c2f62b | ||
|
|
705dadae8e | ||
|
|
40cdde8820 | ||
|
|
64497c9228 | ||
|
|
188175be84 | ||
|
|
2b4fa98941 | ||
|
|
36a996d704 | ||
|
|
7f1d916f04 | ||
|
|
e15546b42f | ||
|
|
8c61fe2ca5 | ||
|
|
7f865492d0 | ||
|
|
726fba61f3 | ||
|
|
0b85a525fb | ||
|
|
35d92abe73 | ||
|
|
28445058ef | ||
|
|
aab5737ff3 | ||
|
|
115f024260 | ||
|
|
952e8cf60c | ||
|
|
fcfdb9b566 | ||
|
|
ff1dc72d74 | ||
|
|
b3486b43c4 | ||
|
|
f705bf2efe | ||
|
|
6e1565c32c | ||
|
|
4ea43ebc5d | ||
|
|
f292a0cc4c | ||
|
|
4c5a83d6cf | ||
|
|
82774f89b1 | ||
|
|
19b220f39c | ||
|
|
725a7f90e9 |
@@ -28,4 +28,7 @@ b147b85e6ac19a9220cd1e2958a6ebd99373283a
|
||||
494bd9ef78313436f0424b918f200dab8fc7c20b
|
||||
|
||||
# bulk format python code with black
|
||||
baec607ff5905b1c67531096a9cf50ec7ff00a5d
|
||||
baec607ff5905b1c67531096a9cf50ec7ff00a5d
|
||||
|
||||
# bulk refactor with sourcery
|
||||
eb9ee3f79b94e594fc6dfa4f6514580e125eee8c
|
||||
|
||||
60
.github/helper/translation.py
vendored
Normal file
60
.github/helper/translation.py
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
import re
|
||||
import sys
|
||||
|
||||
errors_encounter = 0
|
||||
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
|
||||
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
|
||||
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
|
||||
f_string_pattern = re.compile(r"_\(f[\"']")
|
||||
starts_with_f_pattern = re.compile(r"_\(f")
|
||||
|
||||
# skip first argument
|
||||
files = sys.argv[1:]
|
||||
files_to_scan = [_file for _file in files if _file.endswith(('.py', '.js'))]
|
||||
|
||||
for _file in files_to_scan:
|
||||
with open(_file, 'r') as f:
|
||||
print(f'Checking: {_file}')
|
||||
file_lines = f.readlines()
|
||||
for line_number, line in enumerate(file_lines, 1):
|
||||
if 'frappe-lint: disable-translate' in line:
|
||||
continue
|
||||
|
||||
start_matches = start_pattern.search(line)
|
||||
if start_matches:
|
||||
starts_with_f = starts_with_f_pattern.search(line)
|
||||
|
||||
if starts_with_f:
|
||||
has_f_string = f_string_pattern.search(line)
|
||||
if has_f_string:
|
||||
errors_encounter += 1
|
||||
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
match = pattern.search(line)
|
||||
error_found = False
|
||||
|
||||
if not match and line.endswith((',\n', '[\n')):
|
||||
# concat remaining text to validate multiline pattern
|
||||
line = "".join(file_lines[line_number - 1:])
|
||||
line = line[start_matches.start() + 1:]
|
||||
match = pattern.match(line)
|
||||
|
||||
if not match:
|
||||
error_found = True
|
||||
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}')
|
||||
|
||||
if not error_found and not words_pattern.search(line):
|
||||
error_found = True
|
||||
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}')
|
||||
|
||||
if error_found:
|
||||
errors_encounter += 1
|
||||
|
||||
if errors_encounter > 0:
|
||||
print('\nVisit "https://frappeframework.com/docs/user/en/translations" to learn about valid translation strings.')
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('\nGood To Go!')
|
||||
26
.github/workflows/backport.yml
vendored
Normal file
26
.github/workflows/backport.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
name: Backport
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: "frappe/backport"
|
||||
path: ./actions
|
||||
ref: develop
|
||||
- name: Install Actions
|
||||
run: npm install --production --prefix ./actions
|
||||
- name: Run backport
|
||||
uses: ./actions/backport
|
||||
with:
|
||||
token: ${{secrets.RELEASE_TOKEN}}
|
||||
labelsToAdd: "backport"
|
||||
title: "{{originalTitle}}"
|
||||
32
.github/workflows/initiate_release.yml
vendored
Normal file
32
.github/workflows/initiate_release.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# This workflow is agnostic to branches. Only maintain on develop branch.
|
||||
# To add/remove versions just modify the matrix.
|
||||
|
||||
name: Create weekly release pull requests
|
||||
on:
|
||||
schedule:
|
||||
# 9:30 UTC => 3 PM IST Tuesday
|
||||
- cron: "30 9 * * 2"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stable-release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ["14", "15"]
|
||||
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
with:
|
||||
route: POST /repos/{owner}/{repo}/pulls
|
||||
owner: frappe
|
||||
repo: erpnext
|
||||
title: |-
|
||||
"chore: release v${{ matrix.version }}"
|
||||
body: "Automated weekly release."
|
||||
base: version-${{ matrix.version }}
|
||||
head: version-${{ matrix.version }}-hotfix
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||
21
.github/workflows/lock.yml
vendored
Normal file
21
.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: 'Lock threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v5
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-inactive-days: 14
|
||||
pr-inactive-days: 14
|
||||
1
.github/workflows/patch.yml
vendored
1
.github/workflows/patch.yml
vendored
@@ -134,6 +134,7 @@ jobs:
|
||||
}
|
||||
|
||||
update_to_version 14
|
||||
update_to_version 15
|
||||
|
||||
echo "Updating to latest version"
|
||||
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||
|
||||
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -2,23 +2,21 @@ name: Generate Semantic Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- version-15
|
||||
- version-13
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Entire Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
node-version: 18
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
npm install @semantic-release/git @semantic-release/exec --no-save
|
||||
|
||||
26
.github/workflows/server-tests-mariadb.yml
vendored
26
.github/workflows/server-tests-mariadb.yml
vendored
@@ -117,7 +117,7 @@ jobs:
|
||||
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
|
||||
|
||||
- name: Run Tests
|
||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 4 --build-number ${{ matrix.container }}'
|
||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --with-coverage --total-builds 4 --build-number ${{ matrix.container }}'
|
||||
env:
|
||||
TYPE: server
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
@@ -126,3 +126,27 @@ jobs:
|
||||
- name: Show bench output
|
||||
if: ${{ always() }}
|
||||
run: cat ~/frappe-bench/bench_start.log || true
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-${{ matrix.container }}
|
||||
path: /home/runner/frappe-bench/sites/coverage.xml
|
||||
|
||||
coverage:
|
||||
name: Coverage Wrap Up
|
||||
needs: test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
|
||||
- name: Upload coverage data
|
||||
uses: codecov/codecov-action@v2
|
||||
with:
|
||||
name: MariaDB
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
|
||||
46
.mergify.yml
46
.mergify.yml
@@ -17,6 +17,7 @@ pull_request_rules:
|
||||
- base=version-12
|
||||
- base=version-14
|
||||
- base=version-15
|
||||
- base=version-16
|
||||
actions:
|
||||
close:
|
||||
comment:
|
||||
@@ -24,16 +25,6 @@ pull_request_rules:
|
||||
@{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
|
||||
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
|
||||
|
||||
- name: Auto-close PRs on pre-release branch
|
||||
conditions:
|
||||
- base=version-13-pre-release
|
||||
actions:
|
||||
close:
|
||||
comment:
|
||||
message: |
|
||||
@{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches.
|
||||
|
||||
|
||||
- name: backport to develop
|
||||
conditions:
|
||||
- label="backport develop"
|
||||
@@ -54,13 +45,13 @@ pull_request_rules:
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-14-pre-release
|
||||
- name: backport to version-15-hotfix
|
||||
conditions:
|
||||
- label="backport version-14-pre-release"
|
||||
- label="backport version-15-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-14-pre-release
|
||||
- version-15-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
@@ -74,35 +65,6 @@ pull_request_rules:
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-13-pre-release
|
||||
conditions:
|
||||
- label="backport version-13-pre-release"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-13-pre-release
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-12-hotfix
|
||||
conditions:
|
||||
- label="backport version-12-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-12-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: backport to version-12-pre-release
|
||||
conditions:
|
||||
- label="backport version-12-pre-release"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-12-pre-release
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
|
||||
- name: Automatic merge on CI success and review
|
||||
conditions:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"branches": ["version-15"],
|
||||
"branches": ["version-13"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"preset": "angular",
|
||||
@@ -21,4 +21,4 @@
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -73,8 +73,6 @@ New passwords will be created for the ERPNext "Administrator" user, the MariaDB
|
||||
1. [Issue Guidelines](https://github.com/frappe/erpnext/wiki/Issue-Guidelines)
|
||||
1. [Report Security Vulnerabilities](https://erpnext.com/security)
|
||||
1. [Pull Request Requirements](https://github.com/frappe/erpnext/wiki/Contribution-Guidelines)
|
||||
1. [Translations](https://translate.erpnext.com)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "15.16.0"
|
||||
__version__ = "16.0.0-dev"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
@@ -36,7 +36,7 @@ def get_default_cost_center(company):
|
||||
|
||||
if not frappe.flags.company_cost_center:
|
||||
frappe.flags.company_cost_center = {}
|
||||
if not company in frappe.flags.company_cost_center:
|
||||
if company not in frappe.flags.company_cost_center:
|
||||
frappe.flags.company_cost_center[company] = frappe.get_cached_value(
|
||||
"Company", company, "cost_center"
|
||||
)
|
||||
@@ -47,7 +47,7 @@ def get_company_currency(company):
|
||||
"""Returns the default company currency"""
|
||||
if not frappe.flags.company_currency:
|
||||
frappe.flags.company_currency = {}
|
||||
if not company in frappe.flags.company_currency:
|
||||
if company not in frappe.flags.company_currency:
|
||||
frappe.flags.company_currency[company] = frappe.db.get_value(
|
||||
"Company", company, "default_currency", cache=True
|
||||
)
|
||||
@@ -81,7 +81,7 @@ def is_perpetual_inventory_enabled(company):
|
||||
if not hasattr(frappe.local, "enable_perpetual_inventory"):
|
||||
frappe.local.enable_perpetual_inventory = {}
|
||||
|
||||
if not company in frappe.local.enable_perpetual_inventory:
|
||||
if company not in frappe.local.enable_perpetual_inventory:
|
||||
frappe.local.enable_perpetual_inventory[company] = (
|
||||
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
|
||||
)
|
||||
@@ -96,7 +96,7 @@ def get_default_finance_book(company=None):
|
||||
if not hasattr(frappe.local, "default_finance_book"):
|
||||
frappe.local.default_finance_book = {}
|
||||
|
||||
if not company in frappe.local.default_finance_book:
|
||||
if company not in frappe.local.default_finance_book:
|
||||
frappe.local.default_finance_book[company] = frappe.get_cached_value(
|
||||
"Company", company, "default_finance_book"
|
||||
)
|
||||
@@ -108,7 +108,7 @@ def get_party_account_type(party_type):
|
||||
if not hasattr(frappe.local, "party_account_types"):
|
||||
frappe.local.party_account_types = {}
|
||||
|
||||
if not party_type in frappe.local.party_account_types:
|
||||
if party_type not in frappe.local.party_account_types:
|
||||
frappe.local.party_account_types[party_type] = (
|
||||
frappe.db.get_value("Party Type", party_type, "account_type") or ""
|
||||
)
|
||||
|
||||
@@ -232,7 +232,7 @@ def calculate_monthly_amount(
|
||||
if amount + already_booked_amount_in_account_currency > item.net_amount:
|
||||
amount = item.net_amount - already_booked_amount_in_account_currency
|
||||
|
||||
if not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
|
||||
if get_first_day(start_date) != start_date or get_last_day(end_date) != end_date:
|
||||
partial_month = flt(date_diff(end_date, start_date)) / flt(
|
||||
date_diff(get_last_day(end_date), get_first_day(start_date))
|
||||
)
|
||||
@@ -358,9 +358,11 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
|
||||
account_currency = get_account_currency(item.expense_account or item.income_account)
|
||||
if doc.doctype == "Sales Invoice":
|
||||
against_type = "Customer"
|
||||
against, project = doc.customer, doc.project
|
||||
credit_account, debit_account = item.income_account, item.deferred_revenue_account
|
||||
else:
|
||||
against_type = "Supplier"
|
||||
against, project = doc.supplier, item.project
|
||||
credit_account, debit_account = item.deferred_expense_account, item.expense_account
|
||||
|
||||
@@ -413,6 +415,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
doc,
|
||||
credit_account,
|
||||
debit_account,
|
||||
against_type,
|
||||
against,
|
||||
amount,
|
||||
base_amount,
|
||||
@@ -494,6 +497,7 @@ def make_gl_entries(
|
||||
doc,
|
||||
credit_account,
|
||||
debit_account,
|
||||
against_type,
|
||||
against,
|
||||
amount,
|
||||
base_amount,
|
||||
@@ -515,7 +519,9 @@ def make_gl_entries(
|
||||
doc.get_gl_dict(
|
||||
{
|
||||
"account": credit_account,
|
||||
"against_type": against_type,
|
||||
"against": against,
|
||||
"against_link": against,
|
||||
"credit": base_amount,
|
||||
"credit_in_account_currency": amount,
|
||||
"cost_center": cost_center,
|
||||
@@ -534,7 +540,9 @@ def make_gl_entries(
|
||||
doc.get_gl_dict(
|
||||
{
|
||||
"account": debit_account,
|
||||
"against_type": against_type,
|
||||
"against": against,
|
||||
"against_link": against,
|
||||
"debit": base_amount,
|
||||
"debit_in_account_currency": amount,
|
||||
"cost_center": cost_center,
|
||||
|
||||
@@ -91,8 +91,8 @@ class Account(NestedSet):
|
||||
super(Account, self).on_update()
|
||||
|
||||
def onload(self):
|
||||
frozen_accounts_modifier = frappe.db.get_value(
|
||||
"Accounts Settings", "Accounts Settings", "frozen_accounts_modifier"
|
||||
frozen_accounts_modifier = frappe.db.get_single_value(
|
||||
"Accounts Settings", "frozen_accounts_modifier"
|
||||
)
|
||||
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
|
||||
self.set_onload("can_freeze_account", True)
|
||||
@@ -118,7 +118,6 @@ class Account(NestedSet):
|
||||
self.validate_balance_must_be_debit_or_credit()
|
||||
self.validate_account_currency()
|
||||
self.validate_root_company_and_sync_account_to_children()
|
||||
self.validate_receivable_payable_account_type()
|
||||
|
||||
def validate_parent_child_account_type(self):
|
||||
if self.parent_account:
|
||||
@@ -189,24 +188,6 @@ class Account(NestedSet):
|
||||
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
|
||||
)
|
||||
|
||||
def validate_receivable_payable_account_type(self):
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
receivable_payable_types = ["Receivable", "Payable"]
|
||||
if (
|
||||
doc_before_save
|
||||
and doc_before_save.account_type in receivable_payable_types
|
||||
and doc_before_save.account_type != self.account_type
|
||||
):
|
||||
# check for ledger entries
|
||||
if frappe.db.get_all("GL Entry", filters={"account": self.name, "is_cancelled": 0}, limit=1):
|
||||
msg = _(
|
||||
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
|
||||
).format(
|
||||
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
|
||||
)
|
||||
frappe.msgprint(msg)
|
||||
self.add_comment("Comment", msg)
|
||||
|
||||
def validate_root_details(self):
|
||||
doc_before_save = self.get_doc_before_save()
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ frappe.treeview_settings["Account"] = {
|
||||
|
||||
// show Dr if positive since balance is calculated as debit - credit else show Cr
|
||||
const balance = account.balance_in_account_currency || account.balance;
|
||||
const dr_or_cr = balance > 0 ? "Dr": "Cr";
|
||||
const dr_or_cr = balance > 0 ? __("Dr"): __("Cr");
|
||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||
|
||||
if (account.balance!==undefined) {
|
||||
|
||||
@@ -74,7 +74,7 @@ def create_charts(
|
||||
# after all accounts are already inserted.
|
||||
frappe.local.flags.ignore_update_nsm = True
|
||||
_import_accounts(chart, None, None, root_account=True)
|
||||
rebuild_tree("Account", "parent_account")
|
||||
rebuild_tree("Account")
|
||||
frappe.local.flags.ignore_update_nsm = False
|
||||
|
||||
|
||||
@@ -231,6 +231,8 @@ def build_account_tree(tree, parent, all_accounts):
|
||||
tree[child.account_name]["account_type"] = child.account_type
|
||||
if child.tax_rate:
|
||||
tree[child.account_name]["tax_rate"] = child.tax_rate
|
||||
if child.account_currency:
|
||||
tree[child.account_name]["account_currency"] = child.account_currency
|
||||
if not parent:
|
||||
tree[child.account_name]["root_type"] = child.root_type
|
||||
|
||||
|
||||
@@ -56,9 +56,7 @@
|
||||
"Constru\u00e7\u00f5es em Andamento de Im\u00f3veis Destinados \u00e0 Venda": {},
|
||||
"Estoques Destinados \u00e0 Doa\u00e7\u00e3o": {},
|
||||
"Im\u00f3veis Destinados \u00e0 Venda": {},
|
||||
"Insumos (materiais diretos)": {
|
||||
"account_type": "Stock"
|
||||
},
|
||||
"Insumos (materiais diretos)": {},
|
||||
"Insumos Agropecu\u00e1rios": {},
|
||||
"Mercadorias para Revenda": {},
|
||||
"Outras 11": {},
|
||||
@@ -148,65 +146,6 @@
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"CUSTOS DE PRODU\u00c7\u00c3O": {
|
||||
"CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
|
||||
"CUSTO DOS PRODUTOS VENDIDOS": {
|
||||
"CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
|
||||
"Custos dos Produtos Vendidos em Geral": {
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Outros Custos 4": {},
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
|
||||
"Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
|
||||
"Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
|
||||
"Outras": {}
|
||||
},
|
||||
"CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
|
||||
"Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
|
||||
"Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
|
||||
"Outros Custos 6": {}
|
||||
},
|
||||
"CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
|
||||
"Custos dos Produtos para Sa\u00fade - Gratuidades": {},
|
||||
"Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
|
||||
"Outros Custos 5": {}
|
||||
},
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS": {
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
|
||||
"Custo dos Servi\u00e7os Prestados em Geral": {},
|
||||
"Outros Custos": {}
|
||||
},
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
|
||||
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
|
||||
"Outros Custos 2": {}
|
||||
},
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
|
||||
"Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Gratuidade": {},
|
||||
"Custo dos Servi\u00e7os Prestados ao PROUNI": {},
|
||||
"Outros Custos 1": {}
|
||||
},
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
|
||||
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
|
||||
"Outros Custos 3": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"CUSTO DOS BENS E SERVI\u00c7OS PRODUZIDOS": {
|
||||
"CUSTO DOS PRODUTOS DE FABRICA\u00c7\u00c3O PR\u00d3PRIA PRODUZIDOS": {
|
||||
"Alimenta\u00e7\u00e3o do Trabalhador": {},
|
||||
@@ -682,9 +621,7 @@
|
||||
"Receita das Unidades Imobili\u00e1rias Vendidas": {},
|
||||
"Receita de Exporta\u00e7\u00e3o Direta de Mercadorias e Produtos": {},
|
||||
"Receita de Exporta\u00e7\u00e3o de Servi\u00e7os": {},
|
||||
"Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Receita de Loca\u00e7\u00e3o de Bens M\u00f3veis e Im\u00f3veis": {},
|
||||
"Receita de Vendas de Mercadorias e Produtos a Comercial Exportadora com Fim Espec\u00edfico de Exporta\u00e7\u00e3o": {}
|
||||
}
|
||||
}
|
||||
@@ -708,6 +645,65 @@
|
||||
}
|
||||
},
|
||||
"RESULTADO OPERACIONAL": {
|
||||
"CUSTO DOS PRODUTOS E SERVI\u00c7OS VENDIDOS": {
|
||||
"CUSTO DOS PRODUTOS VENDIDOS": {
|
||||
"CUSTO DOS PRODUTOS VENDIDOS PARA AS DEMAIS ATIVIDADES": {
|
||||
"Custos dos Produtos Vendidos em Geral": {
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Outros Custos 4": {},
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"CUSTO DOS PRODUTOS VENDIDOS PARA ASSIST\u00caNCIA SOCIAL": {
|
||||
"Custos dos Produtos para Assist\u00eancia Social - Gratuidades": {},
|
||||
"Custos dos Produtos para Assist\u00eancia Social - Vendidos": {},
|
||||
"Outras": {}
|
||||
},
|
||||
"CUSTO DOS PRODUTOS VENDIDOS PARA EDUCA\u00c7\u00c3O": {
|
||||
"Custos dos Produtos para Educa\u00e7\u00e3o - Gratuidades": {},
|
||||
"Custos dos Produtos para Educa\u00e7\u00e3o - Vendidos": {},
|
||||
"Outros Custos 6": {}
|
||||
},
|
||||
"CUSTO DOS PRODUTOS VENDIDOS PARA SA\u00daDE": {
|
||||
"Custos dos Produtos para Sa\u00fade - Gratuidades": {},
|
||||
"Custos dos Produtos para Sa\u00fade \u2013 Vendidos": {},
|
||||
"Outros Custos 5": {}
|
||||
},
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS": {
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA AS DEMAIS ATIVIDADES": {
|
||||
"Custo dos Servi\u00e7os Prestados em Geral": {},
|
||||
"Outros Custos": {}
|
||||
},
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA ASSIST\u00caNCIA SOCIAL": {
|
||||
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 1": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 1": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Gratuidade 1": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares": {},
|
||||
"Outros Custos 2": {}
|
||||
},
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA EDUCA\u00c7\u00c3O": {
|
||||
"Custo dos Servi\u00e7os Prestados a Alunos N\u00e3o Bolsistas": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias (Exceto PROUNI)": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Gratuidade": {},
|
||||
"Custo dos Servi\u00e7os Prestados ao PROUNI": {},
|
||||
"Outros Custos 1": {}
|
||||
},
|
||||
"CUSTO DOS SERVI\u00c7OS PRESTADOS PARA SA\u00daDE": {
|
||||
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios SUS": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Conv\u00eanios/Contratos/Parcerias 1": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es 2": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Doa\u00e7\u00f5es/Subven\u00e7\u00f5es Vinculadas 2": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Gratuidade 2": {},
|
||||
"Custo dos Servi\u00e7os Prestados a Pacientes Particulares 1": {},
|
||||
"Outros Custos 3": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DESPESAS OPERACIONAIS": {
|
||||
"DESPESAS OPERACIONAIS 1": {
|
||||
"DESPESAS OPERACIONAIS 2": {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1653,4 +1653,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"country_code": "ni",
|
||||
"name": "Nicaragua - Catalogo de Cuentas",
|
||||
"name": "Nicaragua - Catálogo de Cuentas",
|
||||
"tree": {
|
||||
"Activo": {
|
||||
"Activo Corriente": {
|
||||
@@ -491,4 +491,4 @@
|
||||
"root_type": "Liability"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.test_runner import make_test_records
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.doctype.account.account import (
|
||||
InvalidAccountMergeError,
|
||||
@@ -325,19 +324,6 @@ class TestAccount(unittest.TestCase):
|
||||
acc.account_currency = "USD"
|
||||
self.assertRaises(frappe.ValidationError, acc.save)
|
||||
|
||||
def test_account_balance(self):
|
||||
from erpnext.accounts.utils import get_balance_on
|
||||
|
||||
if not frappe.db.exists("Account", "Test Percent Account %5 - _TC"):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "Test Percent Account %5"
|
||||
acc.parent_account = "Tax Assets - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.insert()
|
||||
|
||||
balance = get_balance_on(account="Test Percent Account %5 - _TC", date=nowdate())
|
||||
self.assertEqual(balance, 0)
|
||||
|
||||
|
||||
def _make_test_records(verbose=None):
|
||||
from frappe.test_runner import make_test_objects
|
||||
|
||||
@@ -11,10 +11,6 @@ from frappe.model import core_doctypes_list
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cstr
|
||||
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||
get_allowed_types_from_settings,
|
||||
)
|
||||
|
||||
|
||||
class AccountingDimension(Document):
|
||||
# begin: auto-generated types
|
||||
@@ -110,7 +106,6 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||
|
||||
doc_count = len(get_accounting_dimensions())
|
||||
count = 0
|
||||
repostable_doctypes = get_allowed_types_from_settings()
|
||||
|
||||
for doctype in doclist:
|
||||
|
||||
@@ -126,7 +121,6 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||
"options": doc.document_type,
|
||||
"insert_after": insert_after_field,
|
||||
"owner": "Administrator",
|
||||
"allow_on_submit": 1 if doctype in repostable_doctypes else 0,
|
||||
}
|
||||
|
||||
meta = frappe.get_meta(doctype, cached=False)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2013-06-24 15:49:57",
|
||||
"description": "Settings for Accounts",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Other",
|
||||
"editable_grid": 1,
|
||||
@@ -461,7 +462,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 14:04:26.553554",
|
||||
"modified": "2023-11-20 09:37:47.650347",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -9,7 +9,6 @@ from frappe.contacts.address_and_contact import (
|
||||
load_address_and_contact,
|
||||
)
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import comma_and, get_link_to_form
|
||||
|
||||
|
||||
class BankAccount(Document):
|
||||
@@ -53,19 +52,6 @@ class BankAccount(Document):
|
||||
def validate(self):
|
||||
self.validate_company()
|
||||
self.validate_iban()
|
||||
self.validate_account()
|
||||
|
||||
def validate_account(self):
|
||||
if self.account:
|
||||
if accounts := frappe.db.get_all(
|
||||
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
|
||||
):
|
||||
frappe.throw(
|
||||
_("'{0}' account is already used by {1}. Use another account.").format(
|
||||
frappe.bold(self.account),
|
||||
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
|
||||
)
|
||||
)
|
||||
|
||||
def validate_company(self):
|
||||
if self.is_company_account and not self.company:
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, getdate
|
||||
from pypika import Order
|
||||
|
||||
import erpnext
|
||||
|
||||
@@ -181,62 +179,39 @@ def get_payment_entries_for_bank_clearance(
|
||||
|
||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||
if include_pos_transactions:
|
||||
si_payment = frappe.qb.DocType("Sales Invoice Payment")
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
acc = frappe.qb.DocType("Account")
|
||||
pos_sales_invoices = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
|
||||
si.posting_date, si.customer as against_account, sip.clearance_date,
|
||||
account.account_currency, 0 as credit
|
||||
from `tabSales Invoice Payment` sip, `tabSales Invoice` si, `tabAccount` account
|
||||
where
|
||||
sip.account=%(account)s and si.docstatus=1 and sip.parent = si.name
|
||||
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
|
||||
order by
|
||||
si.posting_date ASC, si.name DESC
|
||||
""",
|
||||
{"account": account, "from": from_date, "to": to_date},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
pos_sales_invoices = (
|
||||
frappe.qb.from_(si_payment)
|
||||
.inner_join(si)
|
||||
.on(si_payment.parent == si.name)
|
||||
.inner_join(acc)
|
||||
.on(si_payment.account == acc.name)
|
||||
.select(
|
||||
ConstantColumn("Sales Invoice").as_("payment_document"),
|
||||
si.name.as_("payment_entry"),
|
||||
si_payment.reference_no.as_("cheque_number"),
|
||||
si_payment.amount.as_("debit"),
|
||||
si.posting_date,
|
||||
si.customer.as_("against_account"),
|
||||
si_payment.clearance_date,
|
||||
acc.account_currency,
|
||||
ConstantColumn(0).as_("credit"),
|
||||
)
|
||||
.where(
|
||||
(si.docstatus == 1)
|
||||
& (si_payment.account == account)
|
||||
& (si.posting_date >= from_date)
|
||||
& (si.posting_date <= to_date)
|
||||
)
|
||||
.orderby(si.posting_date)
|
||||
.orderby(si.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
|
||||
pos_purchase_invoices = (
|
||||
frappe.qb.from_(pi)
|
||||
.inner_join(acc)
|
||||
.on(pi.cash_bank_account == acc.name)
|
||||
.select(
|
||||
ConstantColumn("Purchase Invoice").as_("payment_document"),
|
||||
pi.name.as_("payment_entry"),
|
||||
pi.paid_amount.as_("credit"),
|
||||
pi.posting_date,
|
||||
pi.supplier.as_("against_account"),
|
||||
pi.clearance_date,
|
||||
acc.account_currency,
|
||||
ConstantColumn(0).as_("debit"),
|
||||
)
|
||||
.where(
|
||||
(pi.docstatus == 1)
|
||||
& (pi.cash_bank_account == account)
|
||||
& (pi.posting_date >= from_date)
|
||||
& (pi.posting_date <= to_date)
|
||||
)
|
||||
.orderby(pi.posting_date)
|
||||
.orderby(pi.name, order=Order.desc)
|
||||
).run(as_dict=True)
|
||||
pos_purchase_invoices = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
|
||||
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
|
||||
account.account_currency, 0 as debit
|
||||
from `tabPurchase Invoice` pi, `tabAccount` account
|
||||
where
|
||||
pi.cash_bank_account=%(account)s and pi.docstatus=1 and account.name = pi.cash_bank_account
|
||||
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
|
||||
order by
|
||||
pi.posting_date ASC, pi.name DESC
|
||||
""",
|
||||
{"account": account, "from": from_date, "to": to_date},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
entries = (
|
||||
list(payment_entries)
|
||||
|
||||
@@ -76,7 +76,6 @@ class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
|
||||
"deposit": 100,
|
||||
"bank_account": self.bank_account,
|
||||
"reference_number": "123",
|
||||
"currency": "INR",
|
||||
}
|
||||
)
|
||||
.save()
|
||||
|
||||
@@ -2,6 +2,16 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Bank Statement Import", {
|
||||
onload(frm) {
|
||||
frm.set_query("bank_account", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setup(frm) {
|
||||
frappe.realtime.on("data_import_refresh", ({ data_import }) => {
|
||||
frm.import_in_progress = false;
|
||||
|
||||
@@ -112,7 +112,8 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
for party in parties:
|
||||
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
|
||||
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
|
||||
field = party.lower() + "_name"
|
||||
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
|
||||
|
||||
for field in ["bank_party_name", "description"]:
|
||||
if not self.get(field):
|
||||
@@ -131,7 +132,11 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
||||
skip = False
|
||||
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
|
||||
result = process.extract(
|
||||
query=self.get(field),
|
||||
choices={row.get("name"): row.get("party_name") for row in names},
|
||||
scorer=fuzz.token_set_ratio,
|
||||
)
|
||||
party_name, skip = self.process_fuzzy_result(result)
|
||||
|
||||
if not party_name:
|
||||
@@ -149,14 +154,14 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
Returns: Result, Skip (whether or not to discontinue matching)
|
||||
"""
|
||||
PARTY, SCORE, CUTOFF = 0, 1, 80
|
||||
SCORE, PARTY_ID, CUTOFF = 1, 2, 80
|
||||
|
||||
if not result or not len(result):
|
||||
return None, False
|
||||
|
||||
first_result = result[0]
|
||||
if len(result) == 1:
|
||||
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
|
||||
return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
|
||||
|
||||
second_result = result[1]
|
||||
if first_result[SCORE] > CUTOFF:
|
||||
@@ -165,7 +170,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
if first_result[SCORE] == second_result[SCORE]:
|
||||
return None, True
|
||||
|
||||
return first_result[PARTY], True
|
||||
return first_result[PARTY_ID], True
|
||||
else:
|
||||
return None, False
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.docstatus import DocStatus
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import flt
|
||||
|
||||
@@ -49,24 +48,6 @@ class BankTransaction(Document):
|
||||
|
||||
def validate(self):
|
||||
self.validate_duplicate_references()
|
||||
self.validate_currency()
|
||||
|
||||
def validate_currency(self):
|
||||
"""
|
||||
Bank Transaction should be on the same currency as the Bank Account.
|
||||
"""
|
||||
if self.currency and self.bank_account:
|
||||
account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
|
||||
account_currency = frappe.get_cached_value("Account", account, "account_currency")
|
||||
|
||||
if self.currency != account_currency:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
|
||||
).format(
|
||||
frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
|
||||
)
|
||||
)
|
||||
|
||||
def set_status(self):
|
||||
if self.docstatus == 2:
|
||||
@@ -94,13 +75,10 @@ class BankTransaction(Document):
|
||||
pe.append(reference)
|
||||
|
||||
def update_allocated_amount(self):
|
||||
allocated_amount = (
|
||||
self.allocated_amount = (
|
||||
sum(p.allocated_amount for p in self.payment_entries) if self.payment_entries else 0.0
|
||||
)
|
||||
unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - allocated_amount
|
||||
|
||||
self.allocated_amount = flt(allocated_amount, self.precision("allocated_amount"))
|
||||
self.unallocated_amount = flt(unallocated_amount, self.precision("unallocated_amount"))
|
||||
self.unallocated_amount = abs(flt(self.withdrawal) - flt(self.deposit)) - self.allocated_amount
|
||||
|
||||
def before_submit(self):
|
||||
self.allocate_payment_entries()
|
||||
@@ -437,21 +415,3 @@ def unclear_reference_payment(doctype, docname, bt_name):
|
||||
bt = frappe.get_doc("Bank Transaction", bt_name)
|
||||
set_voucher_clearance(doctype, docname, None, bt)
|
||||
return docname
|
||||
|
||||
|
||||
def remove_from_bank_transaction(doctype, docname):
|
||||
"""Remove a (cancelled) voucher from all Bank Transactions."""
|
||||
for bt_name in get_reconciled_bank_transactions(doctype, docname):
|
||||
bt = frappe.get_doc("Bank Transaction", bt_name)
|
||||
if bt.docstatus == DocStatus.cancelled():
|
||||
continue
|
||||
|
||||
modified = False
|
||||
|
||||
for pe in bt.payment_entries:
|
||||
if pe.payment_document == doctype and pe.payment_entry == docname:
|
||||
bt.remove(pe)
|
||||
modified = True
|
||||
|
||||
if modified:
|
||||
bt.save()
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import utils
|
||||
from frappe.model.docstatus import DocStatus
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
|
||||
@@ -32,16 +32,8 @@ class TestBankTransaction(FrappeTestCase):
|
||||
frappe.db.delete(dt)
|
||||
clear_loan_transactions()
|
||||
make_pos_profile()
|
||||
|
||||
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
|
||||
uniq_identifier = frappe.generate_hash(length=10)
|
||||
gl_account = create_gl_account("_Test Bank " + uniq_identifier)
|
||||
bank_account = create_bank_account(
|
||||
gl_account=gl_account, bank_account_name="Checking Account " + uniq_identifier
|
||||
)
|
||||
|
||||
add_transactions(bank_account=bank_account)
|
||||
add_vouchers(gl_account=gl_account)
|
||||
add_transactions()
|
||||
add_vouchers()
|
||||
|
||||
# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
|
||||
def test_linked_payments(self):
|
||||
@@ -89,29 +81,6 @@ class TestBankTransaction(FrappeTestCase):
|
||||
clearance_date = frappe.db.get_value("Payment Entry", payment.name, "clearance_date")
|
||||
self.assertFalse(clearance_date)
|
||||
|
||||
def test_cancel_voucher(self):
|
||||
bank_transaction = frappe.get_doc(
|
||||
"Bank Transaction",
|
||||
dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"),
|
||||
)
|
||||
payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
|
||||
vouchers = json.dumps(
|
||||
[
|
||||
{
|
||||
"payment_doctype": "Payment Entry",
|
||||
"payment_name": payment.name,
|
||||
"amount": bank_transaction.unallocated_amount,
|
||||
}
|
||||
]
|
||||
)
|
||||
reconcile_vouchers(bank_transaction.name, vouchers)
|
||||
payment.reload()
|
||||
payment.cancel()
|
||||
bank_transaction.reload()
|
||||
self.assertEqual(bank_transaction.docstatus, DocStatus.submitted())
|
||||
self.assertEqual(bank_transaction.unallocated_amount, 1700)
|
||||
self.assertEqual(bank_transaction.payment_entries, [])
|
||||
|
||||
# Check if ERPNext can correctly filter a linked payments based on the debit/credit amount
|
||||
def test_debit_credit_output(self):
|
||||
bank_transaction = frappe.get_doc(
|
||||
@@ -227,9 +196,7 @@ def clear_loan_transactions():
|
||||
frappe.db.delete("Loan Repayment")
|
||||
|
||||
|
||||
def create_bank_account(
|
||||
bank_name="Citi Bank", gl_account="_Test Bank - _TC", bank_account_name="Checking Account"
|
||||
):
|
||||
def create_bank_account(bank_name="Citi Bank", account_name="_Test Bank - _TC"):
|
||||
try:
|
||||
frappe.get_doc(
|
||||
{
|
||||
@@ -241,35 +208,21 @@ def create_bank_account(
|
||||
pass
|
||||
|
||||
try:
|
||||
bank_account = frappe.get_doc(
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Bank Account",
|
||||
"account_name": bank_account_name,
|
||||
"account_name": "Checking Account",
|
||||
"bank": bank_name,
|
||||
"account": gl_account,
|
||||
"account": account_name,
|
||||
}
|
||||
).insert(ignore_if_duplicate=True)
|
||||
except frappe.DuplicateEntryError:
|
||||
pass
|
||||
|
||||
return bank_account.name
|
||||
|
||||
def add_transactions():
|
||||
create_bank_account()
|
||||
|
||||
def create_gl_account(gl_account_name="_Test Bank - _TC"):
|
||||
gl_account = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"company": "_Test Company",
|
||||
"parent_account": "Current Assets - _TC",
|
||||
"account_type": "Bank",
|
||||
"is_group": 0,
|
||||
"account_name": gl_account_name,
|
||||
}
|
||||
).insert()
|
||||
return gl_account.name
|
||||
|
||||
|
||||
def add_transactions(bank_account="_Test Bank - _TC"):
|
||||
doc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Bank Transaction",
|
||||
@@ -277,7 +230,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
|
||||
"date": "2018-10-23",
|
||||
"deposit": 1200,
|
||||
"currency": "INR",
|
||||
"bank_account": bank_account,
|
||||
"bank_account": "Checking Account - Citi Bank",
|
||||
}
|
||||
).insert()
|
||||
doc.submit()
|
||||
@@ -289,7 +242,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
|
||||
"date": "2018-10-23",
|
||||
"deposit": 1700,
|
||||
"currency": "INR",
|
||||
"bank_account": bank_account,
|
||||
"bank_account": "Checking Account - Citi Bank",
|
||||
}
|
||||
).insert()
|
||||
doc.submit()
|
||||
@@ -301,7 +254,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
|
||||
"date": "2018-10-26",
|
||||
"withdrawal": 690,
|
||||
"currency": "INR",
|
||||
"bank_account": bank_account,
|
||||
"bank_account": "Checking Account - Citi Bank",
|
||||
}
|
||||
).insert()
|
||||
doc.submit()
|
||||
@@ -313,7 +266,7 @@ def add_transactions(bank_account="_Test Bank - _TC"):
|
||||
"date": "2018-10-27",
|
||||
"deposit": 3900,
|
||||
"currency": "INR",
|
||||
"bank_account": bank_account,
|
||||
"bank_account": "Checking Account - Citi Bank",
|
||||
}
|
||||
).insert()
|
||||
doc.submit()
|
||||
@@ -325,13 +278,13 @@ def add_transactions(bank_account="_Test Bank - _TC"):
|
||||
"date": "2018-10-27",
|
||||
"withdrawal": 109080,
|
||||
"currency": "INR",
|
||||
"bank_account": bank_account,
|
||||
"bank_account": "Checking Account - Citi Bank",
|
||||
}
|
||||
).insert()
|
||||
doc.submit()
|
||||
|
||||
|
||||
def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
def add_vouchers():
|
||||
try:
|
||||
frappe.get_doc(
|
||||
{
|
||||
@@ -347,7 +300,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
|
||||
pi = make_purchase_invoice(supplier="Conrad Electronic", qty=1, rate=690)
|
||||
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "Conrad Oct 18"
|
||||
pe.reference_date = "2018-10-24"
|
||||
pe.insert()
|
||||
@@ -366,14 +319,14 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
pass
|
||||
|
||||
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1200)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "Herr G Oct 18"
|
||||
pe.reference_date = "2018-10-24"
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
pi = make_purchase_invoice(supplier="Mr G", qty=1, rate=1700)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "Herr G Nov 18"
|
||||
pe.reference_date = "2018-11-01"
|
||||
pe.insert()
|
||||
@@ -404,10 +357,10 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
pass
|
||||
|
||||
pi = make_purchase_invoice(supplier="Poore Simon's", qty=1, rate=3900, is_paid=1, do_not_save=1)
|
||||
pi.cash_bank_account = gl_account
|
||||
pi.cash_bank_account = "_Test Bank - _TC"
|
||||
pi.insert()
|
||||
pi.submit()
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account=gl_account)
|
||||
pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "Poore Simon's Oct 18"
|
||||
pe.reference_date = "2018-10-28"
|
||||
pe.paid_amount = 690
|
||||
@@ -416,7 +369,7 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
pe.submit()
|
||||
|
||||
si = create_sales_invoice(customer="Poore Simon's", qty=1, rate=3900)
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account=gl_account)
|
||||
pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
|
||||
pe.reference_no = "Poore Simon's Oct 18"
|
||||
pe.reference_date = "2018-10-28"
|
||||
pe.insert()
|
||||
@@ -439,12 +392,16 @@ def add_vouchers(gl_account="_Test Bank - _TC"):
|
||||
if not frappe.db.get_value(
|
||||
"Mode of Payment Account", {"company": "_Test Company", "parent": "Cash"}
|
||||
):
|
||||
mode_of_payment.append("accounts", {"company": "_Test Company", "default_account": gl_account})
|
||||
mode_of_payment.append(
|
||||
"accounts", {"company": "_Test Company", "default_account": "_Test Bank - _TC"}
|
||||
)
|
||||
mode_of_payment.save()
|
||||
|
||||
si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
|
||||
si.is_pos = 1
|
||||
si.append("payments", {"mode_of_payment": "Cash", "account": gl_account, "amount": 109080})
|
||||
si.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "_Test Bank - _TC", "amount": 109080}
|
||||
)
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Bisect Accounting Statements", {
|
||||
onload(frm) {
|
||||
frm.trigger("render_heatmap");
|
||||
},
|
||||
refresh(frm) {
|
||||
frm.add_custom_button(__('Bisect Left'), () => {
|
||||
frm.trigger("bisect_left");
|
||||
});
|
||||
|
||||
frm.add_custom_button(__('Bisect Right'), () => {
|
||||
frm.trigger("bisect_right");
|
||||
});
|
||||
|
||||
frm.add_custom_button(__('Up'), () => {
|
||||
frm.trigger("move_up");
|
||||
});
|
||||
frm.add_custom_button(__('Build Tree'), () => {
|
||||
frm.trigger("build_tree");
|
||||
});
|
||||
},
|
||||
render_heatmap(frm) {
|
||||
let bisect_heatmap = frm.get_field("bisect_heatmap").$wrapper;
|
||||
bisect_heatmap.addClass("bisect_heatmap_location");
|
||||
|
||||
// milliseconds in a day
|
||||
let msiad=24*60*60*1000;
|
||||
let datapoints = {};
|
||||
let fr_dt = new Date(frm.doc.from_date).getTime();
|
||||
let to_dt = new Date(frm.doc.to_date).getTime();
|
||||
let bisect_start = new Date(frm.doc.current_from_date).getTime();
|
||||
let bisect_end = new Date(frm.doc.current_to_date).getTime();
|
||||
|
||||
for(let x=fr_dt; x <= to_dt; x+=msiad){
|
||||
let epoch_in_seconds = x/1000;
|
||||
if ((bisect_start <= x) && (x <= bisect_end )) {
|
||||
datapoints[epoch_in_seconds] = 1.0;
|
||||
} else {
|
||||
datapoints[epoch_in_seconds] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
new frappe.Chart(".bisect_heatmap_location", {
|
||||
type: "heatmap",
|
||||
data: {
|
||||
dataPoints: datapoints,
|
||||
start: new Date(frm.doc.from_date),
|
||||
end: new Date(frm.doc.to_date),
|
||||
},
|
||||
countLabel: 'Bisecting',
|
||||
discreteDomains: 1,
|
||||
});
|
||||
},
|
||||
bisect_left(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: 'bisect_left',
|
||||
freeze: true,
|
||||
freeze_message: __("Bisecting Left ..."),
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
}
|
||||
});
|
||||
},
|
||||
bisect_right(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Bisecting Right ..."),
|
||||
method: 'bisect_right',
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
}
|
||||
});
|
||||
},
|
||||
move_up(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Moving up in tree ..."),
|
||||
method: 'move_up',
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
}
|
||||
});
|
||||
},
|
||||
build_tree(frm) {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: __("Rebuilding BTree for period ..."),
|
||||
method: 'build_tree',
|
||||
callback: (r) => {
|
||||
frm.trigger("render_heatmap");
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2023-09-15 21:28:28.054773",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_cvfg",
|
||||
"company",
|
||||
"column_break_hcam",
|
||||
"from_date",
|
||||
"column_break_qxbi",
|
||||
"to_date",
|
||||
"column_break_iwny",
|
||||
"algorithm",
|
||||
"section_break_8ph9",
|
||||
"current_node",
|
||||
"section_break_ngid",
|
||||
"bisect_heatmap",
|
||||
"section_break_hmsy",
|
||||
"bisecting_from",
|
||||
"current_from_date",
|
||||
"column_break_uqyd",
|
||||
"bisecting_to",
|
||||
"current_to_date",
|
||||
"section_break_hbyo",
|
||||
"heading_cppb",
|
||||
"p_l_summary",
|
||||
"column_break_aivo",
|
||||
"balance_sheet_summary",
|
||||
"b_s_summary",
|
||||
"column_break_gvwx",
|
||||
"difference_heading",
|
||||
"difference"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "column_break_qxbi",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "From Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "To Date"
|
||||
},
|
||||
{
|
||||
"default": "BFS",
|
||||
"fieldname": "algorithm",
|
||||
"fieldtype": "Select",
|
||||
"label": "Algorithm",
|
||||
"options": "BFS\nDFS"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_iwny",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_node",
|
||||
"fieldtype": "Link",
|
||||
"label": "Current Node",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_hmsy",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "current_to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_uqyd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_hbyo",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "p_l_summary",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "b_s_summary",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "difference",
|
||||
"fieldtype": "Float",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_aivo",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_gvwx",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_hcam",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_ngid",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8ph9",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "bisect_heatmap",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Heatmap"
|
||||
},
|
||||
{
|
||||
"fieldname": "heading_cppb",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Profit and Loss Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "balance_sheet_summary",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Balance Sheet Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "difference_heading",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Difference"
|
||||
},
|
||||
{
|
||||
"fieldname": "bisecting_from",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Bisecting From"
|
||||
},
|
||||
{
|
||||
"fieldname": "bisecting_to",
|
||||
"fieldtype": "Heading",
|
||||
"label": "Bisecting To"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_cvfg",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-01 16:49:54.073890",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bisect Accounting Statements",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import datetime
|
||||
from collections import deque
|
||||
from math import floor
|
||||
|
||||
import frappe
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils.data import guess_date_format
|
||||
|
||||
|
||||
class BisectAccountingStatements(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
algorithm: DF.Literal["BFS", "DFS"]
|
||||
b_s_summary: DF.Float
|
||||
company: DF.Link | None
|
||||
current_from_date: DF.Datetime | None
|
||||
current_node: DF.Link | None
|
||||
current_to_date: DF.Datetime | None
|
||||
difference: DF.Float
|
||||
from_date: DF.Datetime | None
|
||||
p_l_summary: DF.Float
|
||||
to_date: DF.Datetime | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
self.validate_dates()
|
||||
|
||||
def validate_dates(self):
|
||||
if getdate(self.from_date) > getdate(self.to_date):
|
||||
frappe.throw(
|
||||
_("From Date: {0} cannot be greater than To date: {1}").format(
|
||||
frappe.bold(self.from_date), frappe.bold(self.to_date)
|
||||
)
|
||||
)
|
||||
|
||||
def bfs(self, from_date: datetime, to_date: datetime):
|
||||
# Make Root node
|
||||
node = frappe.new_doc("Bisect Nodes")
|
||||
node.root = None
|
||||
node.period_from_date = from_date
|
||||
node.period_to_date = to_date
|
||||
node.insert()
|
||||
|
||||
period_queue = deque([node])
|
||||
while period_queue:
|
||||
cur_node = period_queue.popleft()
|
||||
delta = cur_node.period_to_date - cur_node.period_from_date
|
||||
if delta.days == 0:
|
||||
continue
|
||||
else:
|
||||
cur_floor = floor(delta.days / 2)
|
||||
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
|
||||
left_node = frappe.new_doc("Bisect Nodes")
|
||||
left_node.period_from_date = cur_node.period_from_date
|
||||
left_node.period_to_date = next_to_date
|
||||
left_node.root = cur_node.name
|
||||
left_node.generated = False
|
||||
left_node.insert()
|
||||
cur_node.left_child = left_node.name
|
||||
period_queue.append(left_node)
|
||||
|
||||
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
|
||||
right_node = frappe.new_doc("Bisect Nodes")
|
||||
right_node.period_from_date = next_from_date
|
||||
right_node.period_to_date = cur_node.period_to_date
|
||||
right_node.root = cur_node.name
|
||||
right_node.generated = False
|
||||
right_node.insert()
|
||||
cur_node.right_child = right_node.name
|
||||
period_queue.append(right_node)
|
||||
|
||||
cur_node.save()
|
||||
|
||||
def dfs(self, from_date: datetime, to_date: datetime):
|
||||
# Make Root node
|
||||
node = frappe.new_doc("Bisect Nodes")
|
||||
node.root = None
|
||||
node.period_from_date = from_date
|
||||
node.period_to_date = to_date
|
||||
node.insert()
|
||||
|
||||
period_stack = [node]
|
||||
while period_stack:
|
||||
cur_node = period_stack.pop()
|
||||
delta = cur_node.period_to_date - cur_node.period_from_date
|
||||
if delta.days == 0:
|
||||
continue
|
||||
else:
|
||||
cur_floor = floor(delta.days / 2)
|
||||
next_to_date = cur_node.period_from_date + relativedelta(days=+cur_floor)
|
||||
left_node = frappe.new_doc("Bisect Nodes")
|
||||
left_node.period_from_date = cur_node.period_from_date
|
||||
left_node.period_to_date = next_to_date
|
||||
left_node.root = cur_node.name
|
||||
left_node.generated = False
|
||||
left_node.insert()
|
||||
cur_node.left_child = left_node.name
|
||||
period_stack.append(left_node)
|
||||
|
||||
next_from_date = cur_node.period_from_date + relativedelta(days=+(cur_floor + 1))
|
||||
right_node = frappe.new_doc("Bisect Nodes")
|
||||
right_node.period_from_date = next_from_date
|
||||
right_node.period_to_date = cur_node.period_to_date
|
||||
right_node.root = cur_node.name
|
||||
right_node.generated = False
|
||||
right_node.insert()
|
||||
cur_node.right_child = right_node.name
|
||||
period_stack.append(right_node)
|
||||
|
||||
cur_node.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def build_tree(self):
|
||||
frappe.db.delete("Bisect Nodes")
|
||||
|
||||
# Convert str to datetime format
|
||||
dt_format = guess_date_format(self.from_date)
|
||||
from_date = datetime.datetime.strptime(self.from_date, dt_format)
|
||||
to_date = datetime.datetime.strptime(self.to_date, dt_format)
|
||||
|
||||
if self.algorithm == "BFS":
|
||||
self.bfs(from_date, to_date)
|
||||
|
||||
if self.algorithm == "DFS":
|
||||
self.dfs(from_date, to_date)
|
||||
|
||||
# set root as current node
|
||||
root = frappe.db.get_all("Bisect Nodes", filters={"root": ["is", "not set"]})[0]
|
||||
self.get_report_summary()
|
||||
self.current_node = root.name
|
||||
self.current_from_date = self.from_date
|
||||
self.current_to_date = self.to_date
|
||||
self.save()
|
||||
|
||||
def get_report_summary(self):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"filter_based_on": "Date Range",
|
||||
"period_start_date": self.current_from_date,
|
||||
"period_end_date": self.current_to_date,
|
||||
"periodicity": "Yearly",
|
||||
}
|
||||
pl_summary = frappe.get_doc("Report", "Profit and Loss Statement")
|
||||
self.p_l_summary = pl_summary.execute_script_report(filters=filters)[5]
|
||||
bs_summary = frappe.get_doc("Report", "Balance Sheet")
|
||||
self.b_s_summary = bs_summary.execute_script_report(filters=filters)[5]
|
||||
self.difference = abs(self.p_l_summary - self.b_s_summary)
|
||||
|
||||
def update_node(self):
|
||||
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
current_node.balance_sheet_summary = self.b_s_summary
|
||||
current_node.profit_loss_summary = self.p_l_summary
|
||||
current_node.difference = self.difference
|
||||
current_node.generated = True
|
||||
current_node.save()
|
||||
|
||||
def current_node_has_summary_info(self):
|
||||
"Assertion method"
|
||||
return frappe.db.get_value("Bisect Nodes", self.current_node, "generated")
|
||||
|
||||
def fetch_summary_info_from_current_node(self):
|
||||
current_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
self.p_l_summary = current_node.balance_sheet_summary
|
||||
self.b_s_summary = current_node.profit_loss_summary
|
||||
self.difference = abs(self.p_l_summary - self.b_s_summary)
|
||||
|
||||
def fetch_or_calculate(self):
|
||||
if self.current_node_has_summary_info():
|
||||
self.fetch_summary_info_from_current_node()
|
||||
else:
|
||||
self.get_report_summary()
|
||||
self.update_node()
|
||||
|
||||
@frappe.whitelist()
|
||||
def bisect_left(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.left_child is not None:
|
||||
lft_node = frappe.get_doc("Bisect Nodes", cur_node.left_child)
|
||||
self.current_node = cur_node.left_child
|
||||
self.current_from_date = lft_node.period_from_date
|
||||
self.current_to_date = lft_node.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("No more children on Left"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def bisect_right(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.right_child is not None:
|
||||
rgt_node = frappe.get_doc("Bisect Nodes", cur_node.right_child)
|
||||
self.current_node = cur_node.right_child
|
||||
self.current_from_date = rgt_node.period_from_date
|
||||
self.current_to_date = rgt_node.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("No more children on Right"))
|
||||
|
||||
@frappe.whitelist()
|
||||
def move_up(self):
|
||||
if self.current_node is not None:
|
||||
cur_node = frappe.get_doc("Bisect Nodes", self.current_node)
|
||||
if cur_node.root is not None:
|
||||
root = frappe.get_doc("Bisect Nodes", cur_node.root)
|
||||
self.current_node = cur_node.root
|
||||
self.current_from_date = root.period_from_date
|
||||
self.current_to_date = root.period_to_date
|
||||
self.fetch_or_calculate()
|
||||
self.save()
|
||||
else:
|
||||
frappe.msgprint(_("Reached Root"))
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestBisectAccountingStatements(FrappeTestCase):
|
||||
pass
|
||||
8
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.js
Normal file
8
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Bisect Nodes", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
97
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.json
Normal file
97
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2023-09-27 14:56:38.112462",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"root",
|
||||
"left_child",
|
||||
"right_child",
|
||||
"period_from_date",
|
||||
"period_to_date",
|
||||
"difference",
|
||||
"balance_sheet_summary",
|
||||
"profit_loss_summary",
|
||||
"generated"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "root",
|
||||
"fieldtype": "Link",
|
||||
"label": "Root",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "left_child",
|
||||
"fieldtype": "Link",
|
||||
"label": "Left Child",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "right_child",
|
||||
"fieldtype": "Link",
|
||||
"label": "Right Child",
|
||||
"options": "Bisect Nodes"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_from_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Period_from_date"
|
||||
},
|
||||
{
|
||||
"fieldname": "period_to_date",
|
||||
"fieldtype": "Datetime",
|
||||
"label": "Period To Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "difference",
|
||||
"fieldtype": "Float",
|
||||
"label": "Difference"
|
||||
},
|
||||
{
|
||||
"fieldname": "balance_sheet_summary",
|
||||
"fieldtype": "Float",
|
||||
"label": "Balance Sheet Summary"
|
||||
},
|
||||
{
|
||||
"fieldname": "profit_loss_summary",
|
||||
"fieldtype": "Float",
|
||||
"label": "Profit and Loss Summary"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "generated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Generated"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-01 17:46:12.437996",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bisect Nodes",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Administrator",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"read_only": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
29
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.py
Normal file
29
erpnext/accounts/doctype/bisect_nodes/bisect_nodes.py
Normal file
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class BisectNodes(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
balance_sheet_summary: DF.Float
|
||||
difference: DF.Float
|
||||
generated: DF.Check
|
||||
left_child: DF.Link | None
|
||||
name: DF.Int | None
|
||||
period_from_date: DF.Datetime | None
|
||||
period_to_date: DF.Datetime | None
|
||||
profit_loss_summary: DF.Float
|
||||
right_child: DF.Link | None
|
||||
root: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -5,5 +5,5 @@
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestPlantFloor(FrappeTestCase):
|
||||
class TestBisectNodes(FrappeTestCase):
|
||||
pass
|
||||
@@ -3,21 +3,16 @@
|
||||
|
||||
frappe.ui.form.on('Cost Center Allocation', {
|
||||
setup: function(frm) {
|
||||
let filters = {"is_group": 0};
|
||||
if (frm.doc.company) {
|
||||
$.extend(filters, {
|
||||
"company": frm.doc.company
|
||||
});
|
||||
}
|
||||
|
||||
frm.set_query('main_cost_center', function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query('cost_center', 'allocation_percentages', function() {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0
|
||||
}
|
||||
filters: filters
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class ExchangeRateRevaluation(Document):
|
||||
self.set_total_gain_loss()
|
||||
|
||||
def validate_rounding_loss_allowance(self):
|
||||
if not (self.rounding_loss_allowance >= 0 and self.rounding_loss_allowance < 1):
|
||||
if self.rounding_loss_allowance < 0 or self.rounding_loss_allowance >= 1:
|
||||
frappe.throw(_("Rounding Loss Allowance should be between 0 and 1"))
|
||||
|
||||
def set_total_gain_loss(self):
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"allow_import": 1,
|
||||
"autoname": "field:year",
|
||||
"creation": "2013-01-22 16:50:25",
|
||||
"description": "Represents a Financial Year. All accounting entries and other major transactions are tracked against the Fiscal Year.",
|
||||
"description": "**Fiscal Year** represents a Financial Year. All accounting entries and other major transactions are tracked against **Fiscal Year**.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
@@ -82,11 +82,10 @@
|
||||
"icon": "fa fa-calendar",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 12:35:38.645968",
|
||||
"modified": "2020-11-05 12:16:53.081573",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Fiscal Year",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -123,6 +122,5 @@
|
||||
],
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "name",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -39,7 +39,7 @@ def test_record_generator():
|
||||
]
|
||||
|
||||
start = 2012
|
||||
end = now_datetime().year + 25
|
||||
end = now_datetime().year + 5
|
||||
for year in range(start, end):
|
||||
test_records.append(
|
||||
{
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
"account_currency",
|
||||
"debit_in_account_currency",
|
||||
"credit_in_account_currency",
|
||||
"against_type",
|
||||
"against",
|
||||
"against_link",
|
||||
"against_voucher_type",
|
||||
"against_voucher",
|
||||
"voucher_type",
|
||||
@@ -129,6 +131,13 @@
|
||||
"label": "Credit Amount in Account Currency",
|
||||
"options": "account_currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "against_type",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"label": "Against Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "against",
|
||||
"fieldtype": "Text",
|
||||
@@ -137,6 +146,13 @@
|
||||
"oldfieldname": "against",
|
||||
"oldfieldtype": "Text"
|
||||
},
|
||||
{
|
||||
"fieldname": "against_link",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_filter": 1,
|
||||
"label": "Against",
|
||||
"options": "against_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher_type",
|
||||
"fieldtype": "Link",
|
||||
|
||||
@@ -13,9 +13,16 @@ import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_checks_for_pl_and_bs_accounts,
|
||||
)
|
||||
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
|
||||
get_dimension_filter_map,
|
||||
)
|
||||
from erpnext.accounts.party import validate_party_frozen_disabled, validate_party_gle_currency
|
||||
from erpnext.accounts.utils import get_account_currency, get_fiscal_year
|
||||
from erpnext.exceptions import InvalidAccountCurrency
|
||||
from erpnext.exceptions import (
|
||||
InvalidAccountCurrency,
|
||||
InvalidAccountDimensionError,
|
||||
MandatoryAccountDimensionError,
|
||||
)
|
||||
|
||||
exclude_from_linked_with = True
|
||||
|
||||
@@ -91,6 +98,7 @@ class GLEntry(Document):
|
||||
if not self.flags.from_repost and self.voucher_type != "Period Closing Voucher":
|
||||
self.validate_account_details(adv_adj)
|
||||
self.validate_dimensions_for_pl_and_bs()
|
||||
self.validate_allowed_dimensions()
|
||||
validate_balance_type(self.account, adv_adj)
|
||||
validate_frozen_account(self.account, adv_adj)
|
||||
|
||||
@@ -200,6 +208,42 @@ class GLEntry(Document):
|
||||
)
|
||||
)
|
||||
|
||||
def validate_allowed_dimensions(self):
|
||||
dimension_filter_map = get_dimension_filter_map()
|
||||
for key, value in dimension_filter_map.items():
|
||||
dimension = key[0]
|
||||
account = key[1]
|
||||
|
||||
if self.account == account:
|
||||
if value["is_mandatory"] and not self.get(dimension):
|
||||
frappe.throw(
|
||||
_("{0} is mandatory for account {1}").format(
|
||||
frappe.bold(frappe.unscrub(dimension)), frappe.bold(self.account)
|
||||
),
|
||||
MandatoryAccountDimensionError,
|
||||
)
|
||||
|
||||
if value["allow_or_restrict"] == "Allow":
|
||||
if self.get(dimension) and self.get(dimension) not in value["allowed_dimensions"]:
|
||||
frappe.throw(
|
||||
_("Invalid value {0} for {1} against account {2}").format(
|
||||
frappe.bold(self.get(dimension)),
|
||||
frappe.bold(frappe.unscrub(dimension)),
|
||||
frappe.bold(self.account),
|
||||
),
|
||||
InvalidAccountDimensionError,
|
||||
)
|
||||
else:
|
||||
if self.get(dimension) and self.get(dimension) in value["allowed_dimensions"]:
|
||||
frappe.throw(
|
||||
_("Invalid value {0} for {1} against account {2}").format(
|
||||
frappe.bold(self.get(dimension)),
|
||||
frappe.bold(frappe.unscrub(dimension)),
|
||||
frappe.bold(self.account),
|
||||
),
|
||||
InvalidAccountDimensionError,
|
||||
)
|
||||
|
||||
def check_pl_account(self):
|
||||
if (
|
||||
self.is_opening == "Yes"
|
||||
@@ -391,8 +435,8 @@ def update_outstanding_amt(
|
||||
def validate_frozen_account(account, adv_adj=None):
|
||||
frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
|
||||
if frozen_account == "Yes" and not adv_adj:
|
||||
frozen_accounts_modifier = frappe.db.get_value(
|
||||
"Accounts Settings", None, "frozen_accounts_modifier"
|
||||
frozen_accounts_modifier = frappe.db.get_single_value(
|
||||
"Accounts Settings", "frozen_accounts_modifier"
|
||||
)
|
||||
|
||||
if not frozen_accounts_modifier:
|
||||
|
||||
@@ -153,7 +153,9 @@ class InvoiceDiscounting(AccountsController):
|
||||
"account": inv.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": d.customer,
|
||||
"against_type": "Account",
|
||||
"against": self.accounts_receivable_credit,
|
||||
"against_link": self.accounts_receivable_credit,
|
||||
"credit": outstanding_in_company_currency,
|
||||
"credit_in_account_currency": outstanding_in_company_currency
|
||||
if inv.party_account_currency == company_currency
|
||||
@@ -173,7 +175,9 @@ class InvoiceDiscounting(AccountsController):
|
||||
"account": self.accounts_receivable_credit,
|
||||
"party_type": "Customer",
|
||||
"party": d.customer,
|
||||
"against_type": "Account",
|
||||
"against": inv.debit_to,
|
||||
"against_link": inv.debit_to,
|
||||
"debit": outstanding_in_company_currency,
|
||||
"debit_in_account_currency": outstanding_in_company_currency
|
||||
if ar_credit_account_currency == company_currency
|
||||
|
||||
@@ -8,31 +8,12 @@ frappe.provide("erpnext.journal_entry");
|
||||
frappe.ui.form.on("Journal Entry", {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch("bank_account", "account", "account");
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Bank Transaction"];
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', 'Asset Depreciation Schedule', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
erpnext.toggle_naming_series();
|
||||
|
||||
if (frm.doc.repost_required && frm.doc.docstatus===1) {
|
||||
frm.set_intro(__("Accounting entries for this Journal Entry need to be reposted. Please click on 'Repost' button to update."));
|
||||
frm.add_custom_button(__('Repost Accounting Entries'),
|
||||
() => {
|
||||
frm.call({
|
||||
doc: frm.doc,
|
||||
method: 'repost_accounting_entries',
|
||||
freeze: true,
|
||||
freeze_message: __('Reposting...'),
|
||||
callback: (r) => {
|
||||
if (!r.exc) {
|
||||
frappe.msgprint(__('Accounting Entries are reposted.'));
|
||||
frm.refresh();
|
||||
}
|
||||
}
|
||||
});
|
||||
}).removeClass('btn-default').addClass('btn-warning');
|
||||
}
|
||||
|
||||
if(frm.doc.docstatus > 0) {
|
||||
frm.add_custom_button(__('Ledger'), function() {
|
||||
frappe.route_options = {
|
||||
@@ -203,6 +184,7 @@ var update_jv_details = function(doc, r) {
|
||||
$.each(r, function(i, d) {
|
||||
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
|
||||
frappe.model.set_value(row.doctype, row.name, "account", d.account)
|
||||
frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
|
||||
});
|
||||
refresh_field("accounts");
|
||||
}
|
||||
@@ -211,6 +193,7 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
onload() {
|
||||
this.load_defaults();
|
||||
this.setup_queries();
|
||||
this.setup_balance_formatter();
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
}
|
||||
|
||||
@@ -237,6 +220,16 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
return erpnext.journal_entry.account_query(me.frm);
|
||||
});
|
||||
|
||||
me.frm.set_query("against_account_link", "accounts", function(doc, cdt, cdn) {
|
||||
return erpnext.journal_entry.against_account_query(me.frm);
|
||||
});
|
||||
|
||||
me.frm.set_query("against_type", "accounts", function(){
|
||||
return {
|
||||
query: "erpnext.accounts.doctype.journal_entry.journal_entry.get_against_type",
|
||||
}
|
||||
})
|
||||
|
||||
me.frm.set_query("party_type", "accounts", function(doc, cdt, cdn) {
|
||||
const row = locals[cdt][cdn];
|
||||
|
||||
@@ -309,6 +302,19 @@ erpnext.accounts.JournalEntry = class JournalEntry extends frappe.ui.form.Contro
|
||||
|
||||
}
|
||||
|
||||
setup_balance_formatter() {
|
||||
const formatter = function(value, df, options, doc) {
|
||||
var currency = frappe.meta.get_field_currency(df, doc);
|
||||
var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
|
||||
return "<div style='text-align: right'>"
|
||||
+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
|
||||
+ " " + dr_or_cr
|
||||
+ "</div>";
|
||||
};
|
||||
this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
|
||||
this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
|
||||
}
|
||||
|
||||
reference_name(doc, cdt, cdn) {
|
||||
var d = frappe.get_doc(cdt, cdn);
|
||||
|
||||
@@ -404,22 +410,23 @@ frappe.ui.form.on("Journal Entry Account", {
|
||||
if(!d.account && d.party_type && d.party) {
|
||||
if(!frm.doc.company) frappe.throw(__("Please select Company"));
|
||||
return frm.call({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_currency",
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_party_account_and_balance",
|
||||
child: d,
|
||||
args: {
|
||||
company: frm.doc.company,
|
||||
party_type: d.party_type,
|
||||
party: d.party,
|
||||
cost_center: d.cost_center
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
cost_center: function(frm, dt, dn) {
|
||||
erpnext.journal_entry.set_account_details(frm, dt, dn);
|
||||
erpnext.journal_entry.set_account_balance(frm, dt, dn);
|
||||
},
|
||||
|
||||
account: function(frm, dt, dn) {
|
||||
erpnext.journal_entry.set_account_details(frm, dt, dn);
|
||||
erpnext.journal_entry.set_account_balance(frm, dt, dn);
|
||||
},
|
||||
|
||||
debit_in_account_currency: function(frm, cdt, cdn) {
|
||||
@@ -594,6 +601,21 @@ $.extend(erpnext.journal_entry, {
|
||||
return { filters: filters };
|
||||
},
|
||||
|
||||
against_account_query: function(frm) {
|
||||
if (frm.doc.against_type != "Account"){
|
||||
return { filters: {} };
|
||||
}
|
||||
else {
|
||||
let filters = { company: frm.doc.company, is_group: 0 };
|
||||
if(!frm.doc.multi_currency) {
|
||||
$.extend(filters, {
|
||||
account_currency: ['in', [frappe.get_doc(":Company", frm.doc.company).default_currency, null]]
|
||||
});
|
||||
}
|
||||
return { filters: filters };
|
||||
}
|
||||
},
|
||||
|
||||
reverse_journal_entry: function() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.make_reverse_journal_entry",
|
||||
@@ -603,14 +625,14 @@ $.extend(erpnext.journal_entry, {
|
||||
});
|
||||
|
||||
$.extend(erpnext.journal_entry, {
|
||||
set_account_details: function(frm, dt, dn) {
|
||||
set_account_balance: function(frm, dt, dn) {
|
||||
var d = locals[dt][dn];
|
||||
if(d.account) {
|
||||
if(!frm.doc.company) frappe.throw(__("Please select Company first"));
|
||||
if(!frm.doc.posting_date) frappe.throw(__("Please select Posting Date first"));
|
||||
|
||||
return frappe.call({
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_details_and_party_type",
|
||||
method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_account_balance_and_party_type",
|
||||
args: {
|
||||
account: d.account,
|
||||
date: frm.doc.posting_date,
|
||||
@@ -618,6 +640,7 @@ $.extend(erpnext.journal_entry, {
|
||||
debit: flt(d.debit_in_account_currency),
|
||||
credit: flt(d.credit_in_account_currency),
|
||||
exchange_rate: d.exchange_rate,
|
||||
cost_center: d.cost_center
|
||||
},
|
||||
callback: function(r) {
|
||||
if(r.message) {
|
||||
|
||||
@@ -64,8 +64,7 @@
|
||||
"stock_entry",
|
||||
"subscription_section",
|
||||
"auto_repeat",
|
||||
"amended_from",
|
||||
"repost_required"
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -544,15 +543,6 @@
|
||||
"label": "Is System Generated",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "repost_required",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Repost Required",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
|
||||
@@ -13,10 +13,6 @@ from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
|
||||
from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import (
|
||||
get_party_account_based_on_invoice_discounting,
|
||||
)
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
@@ -144,6 +140,7 @@ class JournalEntry(AccountsController):
|
||||
self.set_print_format_fields()
|
||||
self.validate_credit_debit_note()
|
||||
self.validate_empty_accounts_table()
|
||||
self.set_account_and_party_balance()
|
||||
self.validate_inter_company_accounts()
|
||||
self.validate_depr_entry_voucher_type()
|
||||
|
||||
@@ -153,24 +150,6 @@ class JournalEntry(AccountsController):
|
||||
if not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
def validate_for_repost(self):
|
||||
validate_docs_for_voucher_types(["Journal Entry"])
|
||||
validate_docs_for_deferred_accounting([self.name], [])
|
||||
|
||||
def submit(self):
|
||||
if len(self.accounts) > 100:
|
||||
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
||||
self.queue_action("submit", timeout=4600)
|
||||
else:
|
||||
return self._submit()
|
||||
|
||||
def cancel(self):
|
||||
if len(self.accounts) > 100:
|
||||
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
||||
self.queue_action("cancel", timeout=4600)
|
||||
else:
|
||||
return self._cancel()
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_cheque_info()
|
||||
self.check_credit_limit()
|
||||
@@ -180,15 +159,6 @@ class JournalEntry(AccountsController):
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
if hasattr(self, "repost_required"):
|
||||
self.needs_repost = self.check_if_fields_updated(
|
||||
fields_to_check=[], child_tables={"accounts": []}
|
||||
)
|
||||
if self.needs_repost:
|
||||
self.validate_for_repost()
|
||||
self.db_set("repost_required", self.needs_repost)
|
||||
|
||||
def on_cancel(self):
|
||||
# References for this Journal are removed on the `on_cancel` event in accounts_controller
|
||||
super(JournalEntry, self).on_cancel()
|
||||
@@ -334,6 +304,7 @@ class JournalEntry(AccountsController):
|
||||
"account": tax_withholding_details.get("account_head"),
|
||||
rev_debit_or_credit: tax_withholding_details.get("tax_amount"),
|
||||
"against_account": parties[0],
|
||||
"against_account_link": parties[0],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -575,28 +546,17 @@ class JournalEntry(AccountsController):
|
||||
elif d.party_type == "Supplier" and flt(d.credit) > 0:
|
||||
frappe.throw(_("Row {0}: Advance against Supplier must be debit").format(d.idx))
|
||||
|
||||
def system_generated_gain_loss(self):
|
||||
return (
|
||||
self.voucher_type == "Exchange Gain Or Loss"
|
||||
and self.multi_currency
|
||||
and self.is_system_generated
|
||||
)
|
||||
|
||||
def validate_against_jv(self):
|
||||
for d in self.get("accounts"):
|
||||
if d.reference_type == "Journal Entry":
|
||||
account_root_type = frappe.get_cached_value("Account", d.account, "root_type")
|
||||
if account_root_type == "Asset" and flt(d.debit) > 0 and not self.system_generated_gain_loss():
|
||||
if account_root_type == "Asset" and flt(d.debit) > 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: For {1}, you can select reference document only if account gets credited"
|
||||
).format(d.idx, d.account)
|
||||
)
|
||||
elif (
|
||||
account_root_type == "Liability"
|
||||
and flt(d.credit) > 0
|
||||
and not self.system_generated_gain_loss()
|
||||
):
|
||||
elif account_root_type == "Liability" and flt(d.credit) > 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{0}: For {1}, you can select reference document only if account gets debited"
|
||||
@@ -628,7 +588,7 @@ class JournalEntry(AccountsController):
|
||||
for jvd in against_entries:
|
||||
if flt(jvd[dr_or_cr]) > 0:
|
||||
valid = True
|
||||
if not valid and not self.system_generated_gain_loss():
|
||||
if not valid:
|
||||
frappe.throw(
|
||||
_("Against Journal Entry {0} does not have any unmatched {1} entry").format(
|
||||
d.reference_name, dr_or_cr
|
||||
@@ -672,7 +632,7 @@ class JournalEntry(AccountsController):
|
||||
)
|
||||
|
||||
# set totals
|
||||
if not d.reference_name in self.reference_totals:
|
||||
if d.reference_name not in self.reference_totals:
|
||||
self.reference_totals[d.reference_name] = 0.0
|
||||
|
||||
if self.voucher_type not in ("Deferred Revenue", "Deferred Expense"):
|
||||
@@ -791,27 +751,90 @@ class JournalEntry(AccountsController):
|
||||
)
|
||||
|
||||
def set_against_account(self):
|
||||
accounts_debited, accounts_credited = [], []
|
||||
if self.voucher_type in ("Deferred Revenue", "Deferred Expense"):
|
||||
for d in self.get("accounts"):
|
||||
if d.reference_type == "Sales Invoice":
|
||||
field = "customer"
|
||||
against_type = "Customer"
|
||||
else:
|
||||
field = "supplier"
|
||||
against_type = "Supplier"
|
||||
|
||||
d.against_account = frappe.db.get_value(d.reference_type, d.reference_name, field)
|
||||
against_account = frappe.db.get_value(d.reference_type, d.reference_name, against_type.lower())
|
||||
d.against_type = against_type
|
||||
d.against_account_link = against_account
|
||||
else:
|
||||
for d in self.get("accounts"):
|
||||
if flt(d.debit) > 0:
|
||||
accounts_debited.append(d.party or d.account)
|
||||
if flt(d.credit) > 0:
|
||||
accounts_credited.append(d.party or d.account)
|
||||
self.get_debited_credited_accounts()
|
||||
if len(self.accounts_credited) > 1 and len(self.accounts_debited) > 1:
|
||||
self.auto_set_against_accounts()
|
||||
return
|
||||
self.get_against_accounts()
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if flt(d.debit) > 0:
|
||||
d.against_account = ", ".join(list(set(accounts_credited)))
|
||||
if flt(d.credit) > 0:
|
||||
d.against_account = ", ".join(list(set(accounts_debited)))
|
||||
def auto_set_against_accounts(self):
|
||||
for i in range(0, len(self.accounts), 2):
|
||||
acc = self.accounts[i]
|
||||
against_acc = self.accounts[i + 1]
|
||||
if acc.debit_in_account_currency > 0:
|
||||
current_val = acc.debit_in_account_currency * flt(acc.exchange_rate)
|
||||
against_val = against_acc.credit_in_account_currency * flt(against_acc.exchange_rate)
|
||||
else:
|
||||
current_val = acc.credit_in_account_currency * flt(acc.exchange_rate)
|
||||
against_val = against_acc.debit_in_account_currency * flt(against_acc.exchange_rate)
|
||||
|
||||
if current_val == against_val:
|
||||
acc.against_type = against_acc.party_type or "Account"
|
||||
against_acc.against_type = acc.party_type or "Account"
|
||||
|
||||
acc.against_account_link = against_acc.party or against_acc.account
|
||||
against_acc.against_account_link = acc.party or acc.account
|
||||
else:
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Unable to automatically determine {0} accounts. Set them up in the {1} table if needed."
|
||||
).format(frappe.bold("against"), frappe.bold("Accounting Entries")),
|
||||
alert=True,
|
||||
)
|
||||
break
|
||||
|
||||
def get_against_accounts(self):
|
||||
self.against_accounts = []
|
||||
self.split_account = {}
|
||||
self.get_debited_credited_accounts()
|
||||
|
||||
if self.separate_against_account_entries:
|
||||
no_of_credited_acc, no_of_debited_acc = len(self.accounts_credited), len(self.accounts_debited)
|
||||
if no_of_credited_acc <= 1 and no_of_debited_acc <= 1:
|
||||
self.set_against_accounts_for_single_dr_cr()
|
||||
self.separate_against_account_entries = 0
|
||||
elif no_of_credited_acc == 1:
|
||||
self.against_accounts = self.accounts_debited
|
||||
self.split_account = self.accounts_credited[0]
|
||||
elif no_of_debited_acc == 1:
|
||||
self.against_accounts = self.accounts_credited
|
||||
self.split_account = self.accounts_debited[0]
|
||||
|
||||
def get_debited_credited_accounts(self):
|
||||
self.accounts_debited, self.accounts_credited = [], []
|
||||
self.separate_against_account_entries = 1
|
||||
for d in self.get("accounts"):
|
||||
if flt(d.debit) > 0:
|
||||
self.accounts_debited.append(d)
|
||||
elif flt(d.credit) > 0:
|
||||
self.accounts_credited.append(d)
|
||||
|
||||
if d.against_account_link:
|
||||
self.separate_against_account_entries = 0
|
||||
break
|
||||
|
||||
def set_against_accounts_for_single_dr_cr(self):
|
||||
against_account = None
|
||||
for d in self.accounts:
|
||||
if flt(d.debit) > 0:
|
||||
against_account = self.accounts_credited[0]
|
||||
elif flt(d.credit) > 0:
|
||||
against_account = self.accounts_debited[0]
|
||||
if against_account:
|
||||
d.against_type = against_account.party_type or "Account"
|
||||
d.against_account = against_account.party or against_account.account
|
||||
d.against_account_link = against_account.party or against_account.account
|
||||
|
||||
def validate_debit_credit_amount(self):
|
||||
if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
|
||||
@@ -1008,42 +1031,108 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def build_gl_map(self):
|
||||
gl_map = []
|
||||
conversion_rate_map = self.get_conversion_rate_map()
|
||||
transaction_currency_map = self.get_transaction_currency_map()
|
||||
company_currency = erpnext.get_company_currency(self.company)
|
||||
|
||||
self.get_against_accounts()
|
||||
for d in self.get("accounts"):
|
||||
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
|
||||
r = [d.user_remark, self.remark]
|
||||
r = [x for x in r if x]
|
||||
remarks = "\n".join(r)
|
||||
|
||||
gl_map.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": d.account,
|
||||
"party_type": d.party_type,
|
||||
"due_date": self.due_date,
|
||||
"party": d.party,
|
||||
"against": d.against_account,
|
||||
"debit": flt(d.debit, d.precision("debit")),
|
||||
"credit": flt(d.credit, d.precision("credit")),
|
||||
"account_currency": d.account_currency,
|
||||
"debit_in_account_currency": flt(
|
||||
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||
),
|
||||
"credit_in_account_currency": flt(
|
||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||
),
|
||||
"against_voucher_type": d.reference_type,
|
||||
"against_voucher": d.reference_name,
|
||||
"remarks": remarks,
|
||||
"voucher_detail_no": d.reference_detail_no,
|
||||
"cost_center": d.cost_center,
|
||||
"project": d.project,
|
||||
"finance_book": self.finance_book,
|
||||
},
|
||||
item=d,
|
||||
)
|
||||
gl_dict = self.get_gl_dict(
|
||||
{
|
||||
"account": d.account,
|
||||
"party_type": d.party_type,
|
||||
"due_date": self.due_date,
|
||||
"party": d.party,
|
||||
"debit": flt(d.debit, d.precision("debit")),
|
||||
"credit": flt(d.credit, d.precision("credit")),
|
||||
"account_currency": d.account_currency,
|
||||
"debit_in_account_currency": flt(
|
||||
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||
),
|
||||
"credit_in_account_currency": flt(
|
||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||
),
|
||||
"against_voucher_type": d.reference_type,
|
||||
"against_voucher": d.reference_name,
|
||||
"remarks": remarks,
|
||||
"voucher_detail_no": d.reference_detail_no,
|
||||
"cost_center": d.cost_center,
|
||||
"project": d.project,
|
||||
"finance_book": self.finance_book,
|
||||
"conversion_rate": conversion_rate_map.get(d.against_account_link, 1)
|
||||
if d.account_currency == company_currency
|
||||
else 1,
|
||||
"currency": transaction_currency_map.get(d.against_account_link, d.account_currency)
|
||||
if d.account_currency == company_currency
|
||||
else d.account_currency,
|
||||
},
|
||||
item=d,
|
||||
)
|
||||
|
||||
if not self.separate_against_account_entries:
|
||||
gl_dict.update(
|
||||
{
|
||||
"against_type": d.against_type,
|
||||
"against_link": d.against_account_link,
|
||||
}
|
||||
)
|
||||
gl_map.append(gl_dict)
|
||||
|
||||
elif d in self.against_accounts:
|
||||
gl_dict.update(
|
||||
{
|
||||
"against_type": self.split_account.get("party_type") or "Account",
|
||||
"against": self.split_account.get("party") or self.split_account.get("account"),
|
||||
"against_link": self.split_account.get("party") or self.split_account.get("account"),
|
||||
}
|
||||
)
|
||||
gl_map.append(gl_dict)
|
||||
|
||||
else:
|
||||
for against_account in self.against_accounts:
|
||||
against_account = against_account.as_dict()
|
||||
debit = against_account.credit or against_account.credit_in_account_currency
|
||||
credit = against_account.debit or against_account.debit_in_account_currency
|
||||
gl_dict = gl_dict.copy()
|
||||
gl_dict.update(
|
||||
{
|
||||
"against_type": against_account.party_type or "Account",
|
||||
"against": against_account.party or against_account.account,
|
||||
"against_link": against_account.party or against_account.account,
|
||||
"debit": flt(debit, d.precision("debit")),
|
||||
"credit": flt(credit, d.precision("credit")),
|
||||
"account_currency": d.account_currency,
|
||||
"debit_in_account_currency": flt(
|
||||
debit / d.exchange_rate, d.precision("debit_in_account_currency")
|
||||
),
|
||||
"credit_in_account_currency": flt(
|
||||
credit / d.exchange_rate, d.precision("credit_in_account_currency")
|
||||
),
|
||||
}
|
||||
)
|
||||
gl_map.append(gl_dict)
|
||||
|
||||
return gl_map
|
||||
|
||||
def get_transaction_currency_map(self):
|
||||
transaction_currency_map = {}
|
||||
for account in self.get("accounts"):
|
||||
transaction_currency_map.setdefault(account.party or account.account, account.account_currency)
|
||||
|
||||
return transaction_currency_map
|
||||
|
||||
def get_conversion_rate_map(self):
|
||||
conversion_rate_map = {}
|
||||
for account in self.get("accounts"):
|
||||
conversion_rate_map.setdefault(account.party or account.account, account.exchange_rate)
|
||||
|
||||
return conversion_rate_map
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
|
||||
@@ -1176,6 +1265,21 @@ class JournalEntry(AccountsController):
|
||||
if not self.get("accounts"):
|
||||
frappe.throw(_("Accounts table cannot be blank."))
|
||||
|
||||
def set_account_and_party_balance(self):
|
||||
account_balance = {}
|
||||
party_balance = {}
|
||||
for d in self.get("accounts"):
|
||||
if d.account not in account_balance:
|
||||
account_balance[d.account] = get_balance_on(account=d.account, date=self.posting_date)
|
||||
|
||||
if (d.party_type, d.party) not in party_balance:
|
||||
party_balance[(d.party_type, d.party)] = get_balance_on(
|
||||
party_type=d.party_type, party=d.party, date=self.posting_date, company=self.company
|
||||
)
|
||||
|
||||
d.account_balance = account_balance[d.account]
|
||||
d.party_balance = party_balance[(d.party_type, d.party)]
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_default_bank_cash_account(company, account_type=None, mode_of_payment=None, account=None):
|
||||
@@ -1341,6 +1445,8 @@ def get_payment_entry(ref_doc, args):
|
||||
"account_type": frappe.get_cached_value("Account", args.get("party_account"), "account_type"),
|
||||
"account_currency": args.get("party_account_currency")
|
||||
or get_account_currency(args.get("party_account")),
|
||||
"balance": get_balance_on(args.get("party_account")),
|
||||
"party_balance": get_balance_on(party=args.get("party"), party_type=args.get("party_type")),
|
||||
"exchange_rate": exchange_rate,
|
||||
args.get("amount_field_party"): args.get("amount"),
|
||||
"is_advance": args.get("is_advance"),
|
||||
@@ -1488,23 +1594,30 @@ def get_outstanding(args):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_party_account_and_currency(company, party_type, party):
|
||||
def get_party_account_and_balance(company, party_type, party, cost_center=None):
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
account = get_party_account(party_type, party, company)
|
||||
|
||||
account_balance = get_balance_on(account=account, cost_center=cost_center)
|
||||
party_balance = get_balance_on(
|
||||
party_type=party_type, party=party, company=company, cost_center=cost_center
|
||||
)
|
||||
|
||||
return {
|
||||
"account": account,
|
||||
"balance": account_balance,
|
||||
"party_balance": party_balance,
|
||||
"account_currency": frappe.get_cached_value("Account", account, "account_currency"),
|
||||
}
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_account_details_and_party_type(
|
||||
account, date, company, debit=None, credit=None, exchange_rate=None
|
||||
def get_account_balance_and_party_type(
|
||||
account, date, company, debit=None, credit=None, exchange_rate=None, cost_center=None
|
||||
):
|
||||
"""Returns dict of account details and party type to be set in Journal Entry on selection of account."""
|
||||
"""Returns dict of account balance and party type to be set in Journal Entry on selection of account."""
|
||||
if not frappe.has_permission("Account"):
|
||||
frappe.msgprint(_("No Permission"), raise_exception=1)
|
||||
|
||||
@@ -1524,6 +1637,7 @@ def get_account_details_and_party_type(
|
||||
party_type = ""
|
||||
|
||||
grid_values = {
|
||||
"balance": get_balance_on(account, date, cost_center=cost_center),
|
||||
"party_type": party_type,
|
||||
"account_type": account_details.account_type,
|
||||
"account_currency": account_details.account_currency or company_currency,
|
||||
@@ -1641,3 +1755,10 @@ def make_reverse_journal_entry(source_name, target_doc=None):
|
||||
)
|
||||
|
||||
return doclist
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_against_type(doctype, txt, searchfield, start, page_len, filters):
|
||||
against_types = frappe.db.get_list("Party Type", pluck="name") + ["Account"]
|
||||
doctype = frappe.qb.DocType("DocType")
|
||||
return frappe.qb.from_(doctype).select(doctype.name).where(doctype.name.isin(against_types)).run()
|
||||
|
||||
@@ -166,37 +166,43 @@ class TestJournalEntry(unittest.TestCase):
|
||||
jv.get("accounts")[1].credit_in_account_currency = 5000
|
||||
jv.submit()
|
||||
|
||||
self.voucher_no = jv.name
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, account_currency, debit, credit,
|
||||
debit_in_account_currency, credit_in_account_currency
|
||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
||||
order by account asc""",
|
||||
jv.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.fields = [
|
||||
"account",
|
||||
"account_currency",
|
||||
"debit",
|
||||
"debit_in_account_currency",
|
||||
"credit",
|
||||
"credit_in_account_currency",
|
||||
]
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
self.expected_gle = [
|
||||
{
|
||||
"account": "_Test Bank - _TC",
|
||||
"account_currency": "INR",
|
||||
"debit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit": 5000,
|
||||
"credit_in_account_currency": 5000,
|
||||
},
|
||||
{
|
||||
"account": "_Test Bank USD - _TC",
|
||||
expected_values = {
|
||||
"_Test Bank USD - _TC": {
|
||||
"account_currency": "USD",
|
||||
"debit": 5000,
|
||||
"debit_in_account_currency": 100,
|
||||
"credit": 0,
|
||||
"credit_in_account_currency": 0,
|
||||
},
|
||||
]
|
||||
"_Test Bank - _TC": {
|
||||
"account_currency": "INR",
|
||||
"debit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit": 5000,
|
||||
"credit_in_account_currency": 5000,
|
||||
},
|
||||
}
|
||||
|
||||
self.check_gl_entries()
|
||||
for field in (
|
||||
"account_currency",
|
||||
"debit",
|
||||
"debit_in_account_currency",
|
||||
"credit",
|
||||
"credit_in_account_currency",
|
||||
):
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account][field], gle[field])
|
||||
|
||||
# cancel
|
||||
jv.cancel()
|
||||
@@ -222,37 +228,43 @@ class TestJournalEntry(unittest.TestCase):
|
||||
rjv.posting_date = nowdate()
|
||||
rjv.submit()
|
||||
|
||||
self.voucher_no = rjv.name
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, account_currency, debit, credit,
|
||||
debit_in_account_currency, credit_in_account_currency
|
||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
||||
order by account asc""",
|
||||
rjv.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.fields = [
|
||||
"account",
|
||||
"account_currency",
|
||||
"debit",
|
||||
"credit",
|
||||
"debit_in_account_currency",
|
||||
"credit_in_account_currency",
|
||||
]
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
self.expected_gle = [
|
||||
{
|
||||
"account": "_Test Bank USD - _TC",
|
||||
expected_values = {
|
||||
"_Test Bank USD - _TC": {
|
||||
"account_currency": "USD",
|
||||
"debit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
"credit": 5000,
|
||||
"credit_in_account_currency": 100,
|
||||
},
|
||||
{
|
||||
"account": "Sales - _TC",
|
||||
"Sales - _TC": {
|
||||
"account_currency": "INR",
|
||||
"debit": 5000,
|
||||
"debit_in_account_currency": 5000,
|
||||
"credit": 0,
|
||||
"credit_in_account_currency": 0,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
self.check_gl_entries()
|
||||
for field in (
|
||||
"account_currency",
|
||||
"debit",
|
||||
"debit_in_account_currency",
|
||||
"credit",
|
||||
"credit_in_account_currency",
|
||||
):
|
||||
for i, gle in enumerate(gl_entries):
|
||||
self.assertEqual(expected_values[gle.account][field], gle[field])
|
||||
|
||||
def test_disallow_change_in_account_currency_for_a_party(self):
|
||||
# create jv in USD
|
||||
@@ -332,25 +344,23 @@ class TestJournalEntry(unittest.TestCase):
|
||||
jv.insert()
|
||||
jv.submit()
|
||||
|
||||
self.voucher_no = jv.name
|
||||
expected_values = {
|
||||
"_Test Cash - _TC": {"cost_center": cost_center},
|
||||
"_Test Bank - _TC": {"cost_center": cost_center},
|
||||
}
|
||||
|
||||
self.fields = [
|
||||
"account",
|
||||
"cost_center",
|
||||
]
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, cost_center, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
||||
order by account asc""",
|
||||
jv.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.expected_gle = [
|
||||
{
|
||||
"account": "_Test Bank - _TC",
|
||||
"cost_center": cost_center,
|
||||
},
|
||||
{
|
||||
"account": "_Test Cash - _TC",
|
||||
"cost_center": cost_center,
|
||||
},
|
||||
]
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
self.check_gl_entries()
|
||||
for gle in gl_entries:
|
||||
self.assertEqual(expected_values[gle.account]["cost_center"], gle.cost_center)
|
||||
|
||||
def test_jv_with_project(self):
|
||||
from erpnext.projects.doctype.project.test_project import make_project
|
||||
@@ -377,22 +387,23 @@ class TestJournalEntry(unittest.TestCase):
|
||||
jv.insert()
|
||||
jv.submit()
|
||||
|
||||
self.voucher_no = jv.name
|
||||
expected_values = {
|
||||
"_Test Cash - _TC": {"project": project_name},
|
||||
"_Test Bank - _TC": {"project": project_name},
|
||||
}
|
||||
|
||||
self.fields = ["account", "project"]
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, project, debit, credit
|
||||
from `tabGL Entry` where voucher_type='Journal Entry' and voucher_no=%s
|
||||
order by account asc""",
|
||||
jv.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.expected_gle = [
|
||||
{
|
||||
"account": "_Test Bank - _TC",
|
||||
"project": project_name,
|
||||
},
|
||||
{
|
||||
"account": "_Test Cash - _TC",
|
||||
"project": project_name,
|
||||
},
|
||||
]
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
self.check_gl_entries()
|
||||
for gle in gl_entries:
|
||||
self.assertEqual(expected_values[gle.account]["project"], gle.project)
|
||||
|
||||
def test_jv_account_and_party_balance_with_cost_centre(self):
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
@@ -415,79 +426,6 @@ class TestJournalEntry(unittest.TestCase):
|
||||
account_balance = get_balance_on(account="_Test Bank - _TC", cost_center=cost_center)
|
||||
self.assertEqual(expected_account_balance, account_balance)
|
||||
|
||||
def test_repost_accounting_entries(self):
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
|
||||
# Configure Repost Accounting Ledger for JVs
|
||||
settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||
if not [x for x in settings.allowed_types if x.document_type == "Journal Entry"]:
|
||||
settings.append("allowed_types", {"document_type": "Journal Entry", "allowed": True})
|
||||
settings.save()
|
||||
|
||||
# Create JV with defaut cost center - _Test Cost Center
|
||||
jv = make_journal_entry("_Test Cash - _TC", "_Test Bank - _TC", 100, save=False)
|
||||
jv.multi_currency = 0
|
||||
jv.submit()
|
||||
|
||||
# Check GL entries before reposting
|
||||
self.voucher_no = jv.name
|
||||
|
||||
self.fields = [
|
||||
"account",
|
||||
"debit_in_account_currency",
|
||||
"credit_in_account_currency",
|
||||
"cost_center",
|
||||
]
|
||||
|
||||
self.expected_gle = [
|
||||
{
|
||||
"account": "_Test Bank - _TC",
|
||||
"debit_in_account_currency": 0,
|
||||
"credit_in_account_currency": 100,
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
},
|
||||
{
|
||||
"account": "_Test Cash - _TC",
|
||||
"debit_in_account_currency": 100,
|
||||
"credit_in_account_currency": 0,
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
},
|
||||
]
|
||||
|
||||
self.check_gl_entries()
|
||||
|
||||
# Change cost center for bank account - _Test Cost Center for BS Account
|
||||
create_cost_center(cost_center_name="_Test Cost Center for BS Account", company="_Test Company")
|
||||
jv.accounts[1].cost_center = "_Test Cost Center for BS Account - _TC"
|
||||
jv.save()
|
||||
|
||||
# Check if repost flag gets set on update after submit
|
||||
self.assertTrue(jv.repost_required)
|
||||
jv.repost_accounting_entries()
|
||||
|
||||
# Check GL entries after reposting
|
||||
jv.load_from_db()
|
||||
self.expected_gle[0]["cost_center"] = "_Test Cost Center for BS Account - _TC"
|
||||
self.check_gl_entries()
|
||||
|
||||
def check_gl_entries(self):
|
||||
gl = frappe.qb.DocType("GL Entry")
|
||||
query = frappe.qb.from_(gl)
|
||||
for field in self.fields:
|
||||
query = query.select(gl[field])
|
||||
|
||||
query = query.where(
|
||||
(gl.voucher_type == "Journal Entry")
|
||||
& (gl.voucher_no == self.voucher_no)
|
||||
& (gl.is_cancelled == 0)
|
||||
).orderby(gl.account)
|
||||
|
||||
gl_entries = query.run(as_dict=True)
|
||||
|
||||
for i in range(len(self.expected_gle)):
|
||||
for field in self.fields:
|
||||
self.assertEqual(self.expected_gle[i][field], gl_entries[i][field])
|
||||
|
||||
|
||||
def make_journal_entry(
|
||||
account1,
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
"field_order": [
|
||||
"account",
|
||||
"account_type",
|
||||
"balance",
|
||||
"col_break1",
|
||||
"bank_account",
|
||||
"party_type",
|
||||
"party",
|
||||
"party_balance",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
@@ -35,7 +37,9 @@
|
||||
"col_break3",
|
||||
"is_advance",
|
||||
"user_remark",
|
||||
"against_account"
|
||||
"against_type",
|
||||
"against_account",
|
||||
"against_account_link"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -62,7 +66,17 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "balance",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Account Balance",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "balance",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "account_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": ":Company",
|
||||
"description": "If Income or Expense",
|
||||
"fieldname": "cost_center",
|
||||
@@ -95,6 +109,14 @@
|
||||
"label": "Party",
|
||||
"options": "party_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "party_balance",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Party Balance",
|
||||
"options": "account_currency",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "currency_section",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -203,7 +225,6 @@
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
@@ -231,14 +252,21 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_account",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"fieldname": "against_account",
|
||||
"fieldtype": "Text",
|
||||
"hidden": 1,
|
||||
"label": "Against Account",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "against_account",
|
||||
"oldfieldtype": "Text",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_account_link",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Against Account",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "against_account",
|
||||
"oldfieldtype": "Text",
|
||||
"print_hide": 1
|
||||
"options": "against_type"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
@@ -261,14 +289,19 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Reference Detail No",
|
||||
"no_copy": 1,
|
||||
"search_index": 1
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "against_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Against Type",
|
||||
"options": "DocType"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-05 01:10:50.224840",
|
||||
"modified": "2023-12-02 23:21:22.205409",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
||||
@@ -140,7 +140,7 @@ class TestLoyaltyProgram(unittest.TestCase):
|
||||
"Loyalty Point Entry",
|
||||
{"invoice_type": "Sales Invoice", "invoice": si.name, "customer": si.customer},
|
||||
)
|
||||
self.assertEqual(True, not (lpe is None))
|
||||
self.assertEqual(True, lpe is not None)
|
||||
|
||||
# cancelling sales invoice
|
||||
si.cancel()
|
||||
|
||||
@@ -1,77 +1,173 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "field:distribution_id",
|
||||
"creation": "2013-01-10 16:34:05",
|
||||
"description": "Helps you distribute the Budget/Target across months if you have seasonality in your business.",
|
||||
"doctype": "DocType",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"distribution_id",
|
||||
"fiscal_year",
|
||||
"percentages"
|
||||
],
|
||||
"allow_copy": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"autoname": "field:distribution_id",
|
||||
"beta": 0,
|
||||
"creation": "2013-01-10 16:34:05",
|
||||
"custom": 0,
|
||||
"description": "**Monthly Distribution** helps you distribute the Budget/Target across months if you have seasonality in your business.",
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"description": "Name of the Monthly Distribution",
|
||||
"fieldname": "distribution_id",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Distribution Name",
|
||||
"oldfieldname": "distribution_id",
|
||||
"oldfieldtype": "Data",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"description": "Name of the Monthly Distribution",
|
||||
"fieldname": "distribution_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Distribution Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "distribution_id",
|
||||
"oldfieldtype": "Data",
|
||||
"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,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "fiscal_year",
|
||||
"fieldtype": "Link",
|
||||
"in_filter": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Fiscal Year",
|
||||
"oldfieldname": "fiscal_year",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Fiscal Year",
|
||||
"search_index": 1
|
||||
},
|
||||
"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": 1,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "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": 0,
|
||||
"search_index": 1,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
{
|
||||
"fieldname": "percentages",
|
||||
"fieldtype": "Table",
|
||||
"label": "Monthly Distribution Percentages",
|
||||
"oldfieldname": "budget_distribution_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Monthly Distribution Percentage"
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "percentages",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Monthly Distribution Percentages",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"oldfieldname": "budget_distribution_details",
|
||||
"oldfieldtype": "Table",
|
||||
"options": "Monthly Distribution Percentage",
|
||||
"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,
|
||||
"unique": 0
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-bar-chart",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 13:57:55.802744",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Monthly Distribution",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": "fa fa-bar-chart",
|
||||
"idx": 1,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2016-11-21 14:54:35.998761",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Monthly Distribution",
|
||||
"name_case": "Title Case",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"share": 1,
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 0,
|
||||
"write": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"permlevel": 2,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager"
|
||||
"amend": 0,
|
||||
"apply_user_permissions": 0,
|
||||
"cancel": 0,
|
||||
"create": 0,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"is_custom": 0,
|
||||
"permlevel": 2,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"write": 0
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_seen": 0
|
||||
}
|
||||
@@ -9,7 +9,7 @@ erpnext.accounts.taxes.setup_tax_filters("Advance Taxes and Charges");
|
||||
|
||||
frappe.ui.form.on('Payment Entry', {
|
||||
onload: function(frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries', "Bank Transaction"];
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries'];
|
||||
|
||||
if(frm.doc.__islocal) {
|
||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||
@@ -149,7 +149,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
erpnext.hide_company(frm);
|
||||
erpnext.hide_company();
|
||||
frm.events.hide_unhide_fields(frm);
|
||||
frm.events.set_dynamic_labels(frm);
|
||||
frm.events.show_general_ledger(frm);
|
||||
@@ -640,7 +640,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
|
||||
const today = frappe.datetime.get_today();
|
||||
let fields = [
|
||||
const fields = [
|
||||
{fieldtype:"Section Break", label: __("Posting Date")},
|
||||
{fieldtype:"Date", label: __("From Date"),
|
||||
fieldname:"from_posting_date", default:frappe.datetime.add_days(today, -30)},
|
||||
@@ -655,29 +655,18 @@ frappe.ui.form.on('Payment Entry', {
|
||||
fieldname:"outstanding_amt_greater_than", default: 0},
|
||||
{fieldtype:"Column Break"},
|
||||
{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
|
||||
];
|
||||
|
||||
if (frm.dimension_filters) {
|
||||
let column_break_insertion_point = Math.ceil((frm.dimension_filters.length)/2);
|
||||
|
||||
fields.push({fieldtype:"Section Break"});
|
||||
frm.dimension_filters.map((elem, idx)=>{
|
||||
fields.push({
|
||||
fieldtype: "Link",
|
||||
label: elem.document_type == "Cost Center" ? "Cost Center" : elem.label,
|
||||
options: elem.document_type,
|
||||
fieldname: elem.fieldname || elem.document_type
|
||||
});
|
||||
if(idx+1 == column_break_insertion_point) {
|
||||
fields.push({fieldtype:"Column Break"});
|
||||
{fieldtype:"Section Break"},
|
||||
{fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
|
||||
"get_query": function() {
|
||||
return {
|
||||
"filters": {"company": frm.doc.company}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fields = fields.concat([
|
||||
},
|
||||
{fieldtype:"Column Break"},
|
||||
{fieldtype:"Section Break"},
|
||||
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
|
||||
]);
|
||||
];
|
||||
|
||||
let btn_text = "";
|
||||
|
||||
@@ -944,7 +933,7 @@ 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 - flt(frm.doc.base_total_taxes_and_charges)
|
||||
unallocated_amount = (frm.doc.base_received_amount + total_deductions + flt(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
|
||||
|
||||
@@ -87,14 +87,12 @@
|
||||
"status",
|
||||
"custom_remarks",
|
||||
"remarks",
|
||||
"base_in_words",
|
||||
"column_break_16",
|
||||
"letter_head",
|
||||
"print_heading",
|
||||
"bank",
|
||||
"bank_account_no",
|
||||
"payment_order",
|
||||
"in_words",
|
||||
"subscription_section",
|
||||
"auto_repeat",
|
||||
"amended_from",
|
||||
@@ -225,6 +223,7 @@
|
||||
"fieldname": "party_balance",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Party Balance",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -748,20 +747,6 @@
|
||||
"hidden": 1,
|
||||
"label": "Book Advance Payments in Separate Party Account",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "base_in_words",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "In Words (Company Currency)",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "in_words",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "In Words",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
@@ -775,7 +760,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2024-01-03 12:46:41.759121",
|
||||
"modified": "2024-01-08 13:17:15.744754",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -13,7 +13,6 @@ from pypika import Case
|
||||
from pypika.functions import Coalesce, Sum
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||
from erpnext.accounts.doctype.bank_account.bank_account import (
|
||||
get_bank_account_details,
|
||||
get_party_bank_account,
|
||||
@@ -51,6 +50,88 @@ class InvalidPaymentEntry(ValidationError):
|
||||
|
||||
|
||||
class PaymentEntry(AccountsController):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.advance_taxes_and_charges.advance_taxes_and_charges import (
|
||||
AdvanceTaxesandCharges,
|
||||
)
|
||||
from erpnext.accounts.doctype.payment_entry_deduction.payment_entry_deduction import (
|
||||
PaymentEntryDeduction,
|
||||
)
|
||||
from erpnext.accounts.doctype.payment_entry_reference.payment_entry_reference import (
|
||||
PaymentEntryReference,
|
||||
)
|
||||
|
||||
amended_from: DF.Link | None
|
||||
apply_tax_withholding_amount: DF.Check
|
||||
auto_repeat: DF.Link | None
|
||||
bank: DF.ReadOnly | None
|
||||
bank_account: DF.Link | None
|
||||
bank_account_no: DF.ReadOnly | None
|
||||
base_paid_amount: DF.Currency
|
||||
base_paid_amount_after_tax: DF.Currency
|
||||
base_received_amount: DF.Currency
|
||||
base_received_amount_after_tax: DF.Currency
|
||||
base_total_allocated_amount: DF.Currency
|
||||
base_total_taxes_and_charges: DF.Currency
|
||||
book_advance_payments_in_separate_party_account: DF.Check
|
||||
clearance_date: DF.Date | None
|
||||
company: DF.Link
|
||||
contact_email: DF.Data | None
|
||||
contact_person: DF.Link | None
|
||||
cost_center: DF.Link | None
|
||||
custom_remarks: DF.Check
|
||||
deductions: DF.Table[PaymentEntryDeduction]
|
||||
difference_amount: DF.Currency
|
||||
letter_head: DF.Link | None
|
||||
mode_of_payment: DF.Link | None
|
||||
naming_series: DF.Literal["ACC-PAY-.YYYY.-"]
|
||||
paid_amount: DF.Currency
|
||||
paid_amount_after_tax: DF.Currency
|
||||
paid_from: DF.Link
|
||||
paid_from_account_balance: DF.Currency
|
||||
paid_from_account_currency: DF.Link
|
||||
paid_from_account_type: DF.Data | None
|
||||
paid_to: DF.Link
|
||||
paid_to_account_balance: DF.Currency
|
||||
paid_to_account_currency: DF.Link
|
||||
paid_to_account_type: DF.Data | None
|
||||
party: DF.DynamicLink | None
|
||||
party_balance: DF.Currency
|
||||
party_bank_account: DF.Link | None
|
||||
party_name: DF.Data | None
|
||||
party_type: DF.Link | None
|
||||
payment_order: DF.Link | None
|
||||
payment_order_status: DF.Literal["Initiated", "Payment Ordered"]
|
||||
payment_type: DF.Literal["Receive", "Pay", "Internal Transfer"]
|
||||
posting_date: DF.Date
|
||||
print_heading: DF.Link | None
|
||||
project: DF.Link | None
|
||||
purchase_taxes_and_charges_template: DF.Link | None
|
||||
received_amount: DF.Currency
|
||||
received_amount_after_tax: DF.Currency
|
||||
reference_date: DF.Date | None
|
||||
reference_no: DF.Data | None
|
||||
references: DF.Table[PaymentEntryReference]
|
||||
remarks: DF.SmallText | None
|
||||
sales_taxes_and_charges_template: DF.Link | None
|
||||
source_exchange_rate: DF.Float
|
||||
status: DF.Literal["", "Draft", "Submitted", "Cancelled"]
|
||||
target_exchange_rate: DF.Float
|
||||
tax_withholding_category: DF.Link | None
|
||||
taxes: DF.Table[AdvanceTaxesandCharges]
|
||||
title: DF.Data | None
|
||||
total_allocated_amount: DF.Currency
|
||||
total_taxes_and_charges: DF.Currency
|
||||
unallocated_amount: DF.Currency
|
||||
# end: auto-generated types
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PaymentEntry, self).__init__(*args, **kwargs)
|
||||
if not self.is_new():
|
||||
@@ -96,7 +177,6 @@ class PaymentEntry(AccountsController):
|
||||
self.validate_paid_invoices()
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
self.set_status()
|
||||
self.set_total_in_words()
|
||||
|
||||
def on_submit(self):
|
||||
if self.difference_amount:
|
||||
@@ -109,7 +189,7 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
def set_liability_account(self):
|
||||
# Auto setting liability account should only be done during 'draft' status
|
||||
if self.docstatus > 0 or self.payment_type == "Internal Transfer":
|
||||
if self.docstatus > 0:
|
||||
return
|
||||
|
||||
if not frappe.db.get_value(
|
||||
@@ -372,12 +452,12 @@ class PaymentEntry(AccountsController):
|
||||
self.set(self.party_account_field, party_account)
|
||||
self.party_account = party_account
|
||||
|
||||
if self.paid_from and not (self.paid_from_account_currency or self.paid_from_account_balance):
|
||||
if self.paid_from and not self.paid_from_account_currency and not self.paid_from_account_balance:
|
||||
acc = get_account_details(self.paid_from, self.posting_date, self.cost_center)
|
||||
self.paid_from_account_currency = acc.account_currency
|
||||
self.paid_from_account_balance = acc.account_balance
|
||||
|
||||
if self.paid_to and not (self.paid_to_account_currency or self.paid_to_account_balance):
|
||||
if self.paid_to and not self.paid_to_account_currency and not self.paid_to_account_balance:
|
||||
acc = get_account_details(self.paid_to, self.posting_date, self.cost_center)
|
||||
self.paid_to_account_currency = acc.account_currency
|
||||
self.paid_to_account_balance = acc.account_balance
|
||||
@@ -389,23 +469,19 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
def set_missing_ref_details(
|
||||
self,
|
||||
force: bool = False,
|
||||
update_ref_details_only_for: list | None = None,
|
||||
ref_exchange_rate: float | None = None,
|
||||
self, force: bool = False, update_ref_details_only_for: list | None = None
|
||||
) -> None:
|
||||
for d in self.get("references"):
|
||||
if d.allocated_amount:
|
||||
if update_ref_details_only_for and (
|
||||
not (d.reference_doctype, d.reference_name) in update_ref_details_only_for
|
||||
if (
|
||||
update_ref_details_only_for
|
||||
and (d.reference_doctype, d.reference_name) not in update_ref_details_only_for
|
||||
):
|
||||
continue
|
||||
|
||||
ref_details = get_reference_details(
|
||||
d.reference_doctype, d.reference_name, self.party_account_currency
|
||||
)
|
||||
if ref_exchange_rate:
|
||||
ref_details.update({"exchange_rate": ref_exchange_rate})
|
||||
|
||||
for field, value in ref_details.items():
|
||||
if d.exchange_gain_loss:
|
||||
@@ -709,23 +785,8 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
self.db_set("status", self.status, update_modified=True)
|
||||
|
||||
def set_total_in_words(self):
|
||||
from frappe.utils import money_in_words
|
||||
|
||||
if self.payment_type in ("Pay", "Internal Transfer"):
|
||||
base_amount = abs(self.base_paid_amount)
|
||||
amount = abs(self.paid_amount)
|
||||
currency = self.paid_from_account_currency
|
||||
elif self.payment_type == "Receive":
|
||||
base_amount = abs(self.base_received_amount)
|
||||
amount = abs(self.received_amount)
|
||||
currency = self.paid_to_account_currency
|
||||
|
||||
self.base_in_words = money_in_words(base_amount, self.company_currency)
|
||||
self.in_words = money_in_words(amount, currency)
|
||||
|
||||
def set_tax_withholding(self):
|
||||
if not self.party_type == "Supplier":
|
||||
if self.party_type != "Supplier":
|
||||
return
|
||||
|
||||
if not self.apply_tax_withholding_amount:
|
||||
@@ -816,7 +877,7 @@ class PaymentEntry(AccountsController):
|
||||
self.base_received_amount = self.base_paid_amount
|
||||
if (
|
||||
self.paid_from_account_currency == self.paid_to_account_currency
|
||||
and not self.payment_type == "Internal Transfer"
|
||||
and self.payment_type != "Internal Transfer"
|
||||
):
|
||||
self.received_amount = self.paid_amount
|
||||
|
||||
@@ -951,19 +1012,19 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
base_party_amount = flt(self.base_total_allocated_amount) + flt(base_unallocated_amount)
|
||||
included_taxes = self.get_included_taxes()
|
||||
|
||||
if self.payment_type == "Receive":
|
||||
self.difference_amount = base_party_amount - self.base_received_amount + included_taxes
|
||||
self.difference_amount = base_party_amount - self.base_received_amount
|
||||
elif self.payment_type == "Pay":
|
||||
self.difference_amount = self.base_paid_amount - base_party_amount - included_taxes
|
||||
self.difference_amount = self.base_paid_amount - base_party_amount
|
||||
else:
|
||||
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount) - included_taxes
|
||||
self.difference_amount = self.base_paid_amount - flt(self.base_received_amount)
|
||||
|
||||
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.precision("difference_amount")
|
||||
self.difference_amount - total_deductions - included_taxes, self.precision("difference_amount")
|
||||
)
|
||||
|
||||
def get_included_taxes(self):
|
||||
@@ -1083,7 +1144,9 @@ class PaymentEntry(AccountsController):
|
||||
"account": self.party_account,
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
"against_type": "Account",
|
||||
"against": against_account,
|
||||
"against_link": against_account,
|
||||
"account_currency": self.party_account_currency,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
@@ -1248,7 +1311,9 @@ class PaymentEntry(AccountsController):
|
||||
{
|
||||
"account": self.paid_from,
|
||||
"account_currency": self.paid_from_account_currency,
|
||||
"against_type": self.party_type if self.payment_type == "Pay" else "Account",
|
||||
"against": self.party if self.payment_type == "Pay" else self.paid_to,
|
||||
"against_link": self.party if self.payment_type == "Pay" else self.paid_to,
|
||||
"credit_in_account_currency": self.paid_amount,
|
||||
"credit": self.base_paid_amount,
|
||||
"cost_center": self.cost_center,
|
||||
@@ -1263,7 +1328,9 @@ class PaymentEntry(AccountsController):
|
||||
{
|
||||
"account": self.paid_to,
|
||||
"account_currency": self.paid_to_account_currency,
|
||||
"against_type": self.party_type if self.payment_type == "Receive" else "Account",
|
||||
"against": self.party if self.payment_type == "Receive" else self.paid_from,
|
||||
"against_link": self.party if self.payment_type == "Receive" else self.paid_from,
|
||||
"debit_in_account_currency": self.received_amount,
|
||||
"debit": self.base_received_amount,
|
||||
"cost_center": self.cost_center,
|
||||
@@ -1287,6 +1354,7 @@ class PaymentEntry(AccountsController):
|
||||
rev_dr_or_cr = "credit" if dr_or_cr == "debit" else "debit"
|
||||
against = self.party or self.paid_to
|
||||
|
||||
against_type = self.party_type or "Account"
|
||||
payment_account = self.get_party_account_for_taxes()
|
||||
tax_amount = d.tax_amount
|
||||
base_tax_amount = d.base_tax_amount
|
||||
@@ -1295,7 +1363,9 @@ class PaymentEntry(AccountsController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": d.account_head,
|
||||
"against_type": against_type,
|
||||
"against": against,
|
||||
"against_link": against,
|
||||
dr_or_cr: tax_amount,
|
||||
dr_or_cr + "_in_account_currency": base_tax_amount
|
||||
if account_currency == self.company_currency
|
||||
@@ -1320,7 +1390,9 @@ class PaymentEntry(AccountsController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": payment_account,
|
||||
"against_type": against_type,
|
||||
"against": against,
|
||||
"against_link": against,
|
||||
rev_dr_or_cr: tax_amount,
|
||||
rev_dr_or_cr + "_in_account_currency": base_tax_amount
|
||||
if account_currency == self.company_currency
|
||||
@@ -1345,7 +1417,9 @@ class PaymentEntry(AccountsController):
|
||||
{
|
||||
"account": d.account,
|
||||
"account_currency": account_currency,
|
||||
"against_type": self.party_type or "Account",
|
||||
"against": self.party or self.paid_from,
|
||||
"against_link": self.party or self.paid_from,
|
||||
"debit_in_account_currency": d.amount,
|
||||
"debit": d.amount,
|
||||
"cost_center": d.cost_center,
|
||||
@@ -1610,13 +1684,6 @@ def get_outstanding_reference_documents(args, validate=False):
|
||||
condition += " and cost_center='%s'" % args.get("cost_center")
|
||||
accounting_dimensions_filter.append(ple.cost_center == args.get("cost_center"))
|
||||
|
||||
# dynamic dimension filters
|
||||
active_dimensions = get_dimensions()[0]
|
||||
for dim in active_dimensions:
|
||||
if args.get(dim.fieldname):
|
||||
condition += " and {0}='{1}'".format(dim.fieldname, args.get(dim.fieldname))
|
||||
accounting_dimensions_filter.append(ple[dim.fieldname] == args.get(dim.fieldname))
|
||||
|
||||
date_fields_dict = {
|
||||
"posting_date": ["from_posting_date", "to_posting_date"],
|
||||
"due_date": ["from_due_date", "to_due_date"],
|
||||
@@ -1792,7 +1859,7 @@ def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates:
|
||||
"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
|
||||
)
|
||||
for payment_term in payment_schedule:
|
||||
if not payment_term.outstanding > 0.1:
|
||||
if payment_term.outstanding <= 0.1:
|
||||
continue
|
||||
|
||||
doc_details = exc_rates.get(payment_term.parent, None)
|
||||
@@ -1850,12 +1917,6 @@ def get_orders_to_be_billed(
|
||||
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||
condition = " and cost_center='%s'" % cost_center
|
||||
|
||||
# dynamic dimension filters
|
||||
active_dimensions = get_dimensions()[0]
|
||||
for dim in active_dimensions:
|
||||
if filters.get(dim.fieldname):
|
||||
condition += " and {0}='{1}'".format(dim.fieldname, filters.get(dim.fieldname))
|
||||
|
||||
if party_account_currency == company_currency:
|
||||
grand_total_field = "base_grand_total"
|
||||
rounded_total_field = "base_rounded_total"
|
||||
|
||||
@@ -1251,6 +1251,45 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, so.rounded_total)
|
||||
|
||||
def test_outstanding_invoices_api(self):
|
||||
"""
|
||||
Test if `get_outstanding_reference_documents` fetches invoices in the right order.
|
||||
"""
|
||||
customer = create_customer("Max Mustermann", "INR")
|
||||
create_payment_terms_template()
|
||||
|
||||
# SI has an earlier due date and SI2 has a later due date
|
||||
si = create_sales_invoice(
|
||||
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
|
||||
)
|
||||
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
|
||||
si2.payment_terms_template = "Test Receivable Template"
|
||||
si2.submit()
|
||||
|
||||
args = {
|
||||
"posting_date": nowdate(),
|
||||
"company": "_Test Company",
|
||||
"party_type": "Customer",
|
||||
"payment_type": "Pay",
|
||||
"party": customer,
|
||||
"party_account": "Debtors - _TC",
|
||||
}
|
||||
args.update(
|
||||
{
|
||||
"get_outstanding_invoices": True,
|
||||
"from_posting_date": add_days(nowdate(), -4),
|
||||
"to_posting_date": add_days(nowdate(), 2),
|
||||
}
|
||||
)
|
||||
references = get_outstanding_reference_documents(args)
|
||||
|
||||
self.assertEqual(len(references), 3)
|
||||
self.assertEqual(references[0].voucher_no, si.name)
|
||||
self.assertEqual(references[1].voucher_no, si2.name)
|
||||
self.assertEqual(references[2].voucher_no, si2.name)
|
||||
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
|
||||
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||
|
||||
def test_receive_payment_from_payable_party_type(self):
|
||||
"""
|
||||
Checks GL entries generated while receiving payments from a Payable Party Type.
|
||||
@@ -1472,45 +1511,6 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
for field in ["account", "debit", "credit"]:
|
||||
self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
|
||||
|
||||
def test_outstanding_invoices_api(self):
|
||||
"""
|
||||
Test if `get_outstanding_reference_documents` fetches invoices in the right order.
|
||||
"""
|
||||
customer = create_customer("Max Mustermann", "INR")
|
||||
create_payment_terms_template()
|
||||
|
||||
# SI has an earlier due date and SI2 has a later due date
|
||||
si = create_sales_invoice(
|
||||
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
|
||||
)
|
||||
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
|
||||
si2.payment_terms_template = "Test Receivable Template"
|
||||
si2.submit()
|
||||
|
||||
args = {
|
||||
"posting_date": nowdate(),
|
||||
"company": "_Test Company",
|
||||
"party_type": "Customer",
|
||||
"payment_type": "Pay",
|
||||
"party": customer,
|
||||
"party_account": "Debtors - _TC",
|
||||
}
|
||||
args.update(
|
||||
{
|
||||
"get_outstanding_invoices": True,
|
||||
"from_posting_date": add_days(nowdate(), -4),
|
||||
"to_posting_date": add_days(nowdate(), 2),
|
||||
}
|
||||
)
|
||||
references = get_outstanding_reference_documents(args)
|
||||
|
||||
self.assertEqual(len(references), 3)
|
||||
self.assertEqual(references[0].voucher_no, si.name)
|
||||
self.assertEqual(references[1].voucher_no, si2.name)
|
||||
self.assertEqual(references[2].voucher_no, si2.name)
|
||||
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
|
||||
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
|
||||
@@ -4,13 +4,9 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import getdate
|
||||
|
||||
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import (
|
||||
create_bank_account,
|
||||
create_gl_account,
|
||||
)
|
||||
from erpnext.accounts.doctype.bank_transaction.test_bank_transaction import create_bank_account
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
get_payment_entry,
|
||||
make_payment_order,
|
||||
@@ -18,32 +14,28 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
|
||||
|
||||
class TestPaymentOrder(FrappeTestCase):
|
||||
class TestPaymentOrder(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# generate and use a uniq hash identifier for 'Bank Account' and it's linked GL 'Account' to avoid validation error
|
||||
uniq_identifier = frappe.generate_hash(length=10)
|
||||
self.gl_account = create_gl_account("_Test Bank " + uniq_identifier)
|
||||
self.bank_account = create_bank_account(
|
||||
gl_account=self.gl_account, bank_account_name="Checking Account " + uniq_identifier
|
||||
)
|
||||
create_bank_account()
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
for bt in frappe.get_all("Payment Order"):
|
||||
doc = frappe.get_doc("Payment Order", bt.name)
|
||||
doc.cancel()
|
||||
doc.delete()
|
||||
|
||||
def test_payment_order_creation_against_payment_entry(self):
|
||||
purchase_invoice = make_purchase_invoice()
|
||||
payment_entry = get_payment_entry(
|
||||
"Purchase Invoice", purchase_invoice.name, bank_account=self.gl_account
|
||||
"Purchase Invoice", purchase_invoice.name, bank_account="_Test Bank - _TC"
|
||||
)
|
||||
payment_entry.reference_no = "_Test_Payment_Order"
|
||||
payment_entry.reference_date = getdate()
|
||||
payment_entry.party_bank_account = self.bank_account
|
||||
payment_entry.party_bank_account = "Checking Account - Citi Bank"
|
||||
payment_entry.insert()
|
||||
payment_entry.submit()
|
||||
|
||||
doc = create_payment_order_against_payment_entry(
|
||||
payment_entry, "Payment Entry", self.bank_account
|
||||
)
|
||||
doc = create_payment_order_against_payment_entry(payment_entry, "Payment Entry")
|
||||
reference_doc = doc.get("references")[0]
|
||||
self.assertEqual(reference_doc.reference_name, payment_entry.name)
|
||||
self.assertEqual(reference_doc.reference_doctype, "Payment Entry")
|
||||
@@ -51,13 +43,13 @@ class TestPaymentOrder(FrappeTestCase):
|
||||
self.assertEqual(reference_doc.amount, 250)
|
||||
|
||||
|
||||
def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account):
|
||||
def create_payment_order_against_payment_entry(ref_doc, order_type):
|
||||
payment_order = frappe.get_doc(
|
||||
dict(
|
||||
doctype="Payment Order",
|
||||
company="_Test Company",
|
||||
payment_order_type=order_type,
|
||||
company_bank_account=bank_account,
|
||||
company_bank_account="Checking Account - Citi Bank",
|
||||
)
|
||||
)
|
||||
doc = make_payment_order(ref_doc.name, payment_order)
|
||||
|
||||
@@ -95,8 +95,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
this.frm.change_custom_button_type(__('Allocate'), null, 'default');
|
||||
}
|
||||
|
||||
this.frm.trigger("set_query_for_dimension_filters");
|
||||
|
||||
// check for any running reconciliation jobs
|
||||
if (this.frm.doc.receivable_payable_account) {
|
||||
this.frm.call({
|
||||
@@ -127,25 +125,6 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
}
|
||||
|
||||
}
|
||||
set_query_for_dimension_filters() {
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.payment_reconciliation.payment_reconciliation.get_queries_for_dimension_filters",
|
||||
args: {
|
||||
company: this.frm.doc.company,
|
||||
},
|
||||
callback: (r) => {
|
||||
if (!r.exc && r.message) {
|
||||
r.message.forEach(x => {
|
||||
this.frm.set_query(x.fieldname, () => {
|
||||
return {
|
||||
'filters': x.filters
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
company() {
|
||||
this.frm.set_value('party', '');
|
||||
@@ -250,6 +229,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
this.data = [];
|
||||
const dialog = new frappe.ui.Dialog({
|
||||
title: __("Select Difference Account"),
|
||||
size: 'extra-large',
|
||||
fields: [
|
||||
{
|
||||
fieldname: "allocation",
|
||||
@@ -273,6 +253,13 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
in_list_view: 1,
|
||||
read_only: 1
|
||||
}, {
|
||||
fieldtype:'Date',
|
||||
fieldname:"gain_loss_posting_date",
|
||||
label: __("Posting Date"),
|
||||
in_list_view: 1,
|
||||
reqd: 1,
|
||||
}, {
|
||||
|
||||
fieldtype:'Link',
|
||||
options: 'Account',
|
||||
in_list_view: 1,
|
||||
@@ -306,6 +293,9 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
args.forEach(d => {
|
||||
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
||||
"difference_account", d.difference_account);
|
||||
frappe.model.set_value("Payment Reconciliation Allocation", d.docname,
|
||||
"gain_loss_posting_date", d.gain_loss_posting_date);
|
||||
|
||||
});
|
||||
|
||||
this.reconcile_payment_entries();
|
||||
@@ -321,6 +311,7 @@ erpnext.accounts.PaymentReconciliationController = class PaymentReconciliationCo
|
||||
'reference_name': d.reference_name,
|
||||
'difference_amount': d.difference_amount,
|
||||
'difference_account': d.difference_account,
|
||||
'gain_loss_posting_date': d.gain_loss_posting_date
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -25,9 +25,7 @@
|
||||
"invoice_limit",
|
||||
"payment_limit",
|
||||
"bank_cash_account",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"sec_break1",
|
||||
"invoice_name",
|
||||
"invoices",
|
||||
@@ -210,18 +208,6 @@
|
||||
"fieldname": "payment_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Filter on Payment"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.invoices.length == 0",
|
||||
"depends_on": "eval:doc.receivable_payable_account",
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions Filter"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
@@ -229,7 +215,7 @@
|
||||
"is_virtual": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-14 13:38:16.264013",
|
||||
"modified": "2023-11-17 17:33:55.701726",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation",
|
||||
|
||||
@@ -10,7 +10,6 @@ from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_dimensions
|
||||
from erpnext.accounts.doctype.process_payment_reconciliation.process_payment_reconciliation import (
|
||||
is_any_doc_running,
|
||||
)
|
||||
@@ -71,7 +70,6 @@ class PaymentReconciliation(Document):
|
||||
self.common_filter_conditions = []
|
||||
self.accounting_dimension_filter_conditions = []
|
||||
self.ple_posting_date_filter = []
|
||||
self.dimensions = get_dimensions()[0]
|
||||
|
||||
def load_from_db(self):
|
||||
# 'modified' attribute is required for `run_doc_method` to work properly.
|
||||
@@ -174,14 +172,6 @@ class PaymentReconciliation(Document):
|
||||
if self.payment_name:
|
||||
condition.update({"name": self.payment_name})
|
||||
|
||||
# pass dynamic dimension filter values to query builder
|
||||
dimensions = {}
|
||||
for x in self.dimensions:
|
||||
dimension = x.fieldname
|
||||
if self.get(dimension):
|
||||
dimensions.update({dimension: self.get(dimension)})
|
||||
condition.update({"accounting_dimensions": dimensions})
|
||||
|
||||
payment_entries = get_advance_payment_entries_for_regional(
|
||||
self.party_type,
|
||||
self.party,
|
||||
@@ -195,67 +185,66 @@ class PaymentReconciliation(Document):
|
||||
return payment_entries
|
||||
|
||||
def get_jv_entries(self):
|
||||
je = qb.DocType("Journal Entry")
|
||||
jea = qb.DocType("Journal Entry Account")
|
||||
conditions = self.get_journal_filter_conditions()
|
||||
|
||||
# Dimension filters
|
||||
for x in self.dimensions:
|
||||
dimension = x.fieldname
|
||||
if self.get(dimension):
|
||||
conditions.append(jea[dimension] == self.get(dimension))
|
||||
condition = self.get_conditions()
|
||||
|
||||
if self.payment_name:
|
||||
conditions.append(je.name.like(f"%%{self.payment_name}%%"))
|
||||
condition += f" and t1.name like '%%{self.payment_name}%%'"
|
||||
|
||||
if self.get("cost_center"):
|
||||
conditions.append(jea.cost_center == self.cost_center)
|
||||
condition += f" and t2.cost_center = '{self.cost_center}' "
|
||||
|
||||
dr_or_cr = (
|
||||
"credit_in_account_currency"
|
||||
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
||||
else "debit_in_account_currency"
|
||||
)
|
||||
conditions.append(jea[dr_or_cr].gt(0))
|
||||
|
||||
if self.bank_cash_account:
|
||||
conditions.append(jea.against_account.like(f"%%{self.bank_cash_account}%%"))
|
||||
|
||||
journal_query = (
|
||||
qb.from_(je)
|
||||
.inner_join(jea)
|
||||
.on(jea.parent == je.name)
|
||||
.select(
|
||||
ConstantColumn("Journal Entry").as_("reference_type"),
|
||||
je.name.as_("reference_name"),
|
||||
je.posting_date,
|
||||
je.remark.as_("remarks"),
|
||||
jea.name.as_("reference_row"),
|
||||
jea[dr_or_cr].as_("amount"),
|
||||
jea.is_advance,
|
||||
jea.exchange_rate,
|
||||
jea.account_currency.as_("currency"),
|
||||
jea.cost_center.as_("cost_center"),
|
||||
)
|
||||
.where(
|
||||
(je.docstatus == 1)
|
||||
& (jea.party_type == self.party_type)
|
||||
& (jea.party == self.party)
|
||||
& (jea.account == self.receivable_payable_account)
|
||||
& (
|
||||
(jea.reference_type == "")
|
||||
| (jea.reference_type.isnull())
|
||||
| (jea.reference_type.isin(("Sales Order", "Purchase Order")))
|
||||
)
|
||||
)
|
||||
.where(Criterion.all(conditions))
|
||||
.orderby(je.posting_date)
|
||||
bank_account_condition = (
|
||||
"t2.against_account like %(bank_cash_account)s" if self.bank_cash_account else "1=1"
|
||||
)
|
||||
|
||||
if self.payment_limit:
|
||||
journal_query = journal_query.limit(self.payment_limit)
|
||||
limit = f"limit {self.payment_limit}" if self.payment_limit else " "
|
||||
|
||||
journal_entries = journal_query.run(as_dict=True)
|
||||
# nosemgrep
|
||||
journal_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
"Journal Entry" as reference_type, t1.name as reference_name,
|
||||
t1.posting_date, t1.remark as remarks, t2.name as reference_row,
|
||||
{dr_or_cr} as amount, t2.is_advance, t2.exchange_rate,
|
||||
t2.account_currency as currency, t2.cost_center as cost_center
|
||||
from
|
||||
`tabJournal Entry` t1, `tabJournal Entry Account` t2
|
||||
where
|
||||
t1.name = t2.parent and t1.docstatus = 1 and t2.docstatus = 1
|
||||
and t2.party_type = %(party_type)s and t2.party = %(party)s
|
||||
and t2.account = %(account)s and {dr_or_cr} > 0 {condition}
|
||||
and (t2.reference_type is null or t2.reference_type = '' or
|
||||
(t2.reference_type in ('Sales Order', 'Purchase Order')
|
||||
and t2.reference_name is not null and t2.reference_name != ''))
|
||||
and (CASE
|
||||
WHEN t1.voucher_type in ('Debit Note', 'Credit Note')
|
||||
THEN 1=1
|
||||
ELSE {bank_account_condition}
|
||||
END)
|
||||
order by t1.posting_date
|
||||
{limit}
|
||||
""".format(
|
||||
**{
|
||||
"dr_or_cr": dr_or_cr,
|
||||
"bank_account_condition": bank_account_condition,
|
||||
"condition": condition,
|
||||
"limit": limit,
|
||||
}
|
||||
),
|
||||
{
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
"account": self.receivable_payable_account,
|
||||
"bank_cash_account": "%%%s%%" % self.bank_cash_account,
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
return list(journal_entries)
|
||||
|
||||
@@ -309,7 +298,6 @@ class PaymentReconciliation(Document):
|
||||
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
|
||||
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
|
||||
get_payments=True,
|
||||
accounting_dimensions=self.accounting_dimension_filter_conditions,
|
||||
)
|
||||
|
||||
for inv in return_outstanding:
|
||||
@@ -441,6 +429,7 @@ class PaymentReconciliation(Document):
|
||||
res.difference_amount = self.get_difference_amount(pay, inv, res["allocated_amount"])
|
||||
res.difference_account = default_exchange_gain_loss_account
|
||||
res.exchange_rate = inv.get("exchange_rate")
|
||||
res.update({"gain_loss_posting_date": pay.get("posting_date")})
|
||||
|
||||
if pay.get("amount") == 0:
|
||||
entries.append(res)
|
||||
@@ -458,15 +447,8 @@ class PaymentReconciliation(Document):
|
||||
row = self.append("allocation", {})
|
||||
row.update(entry)
|
||||
|
||||
def update_dimension_values_in_allocated_entries(self, res):
|
||||
for x in self.dimensions:
|
||||
dimension = x.fieldname
|
||||
if self.get(dimension):
|
||||
res[dimension] = self.get(dimension)
|
||||
return res
|
||||
|
||||
def get_allocated_entry(self, pay, inv, allocated_amount):
|
||||
res = frappe._dict(
|
||||
return frappe._dict(
|
||||
{
|
||||
"reference_type": pay.get("reference_type"),
|
||||
"reference_name": pay.get("reference_name"),
|
||||
@@ -482,9 +464,6 @@ class PaymentReconciliation(Document):
|
||||
}
|
||||
)
|
||||
|
||||
res = self.update_dimension_values_in_allocated_entries(res)
|
||||
return res
|
||||
|
||||
def reconcile_allocations(self, skip_ref_details_update_for_pe=False):
|
||||
adjust_allocations_for_taxes(self)
|
||||
dr_or_cr = (
|
||||
@@ -507,10 +486,10 @@ class PaymentReconciliation(Document):
|
||||
reconciled_entry.append(payment_details)
|
||||
|
||||
if entry_list:
|
||||
reconcile_against_document(entry_list, skip_ref_details_update_for_pe, self.dimensions)
|
||||
reconcile_against_document(entry_list, skip_ref_details_update_for_pe)
|
||||
|
||||
if dr_or_cr_notes:
|
||||
reconcile_dr_cr_note(dr_or_cr_notes, self.company, self.dimensions)
|
||||
reconcile_dr_cr_note(dr_or_cr_notes, self.company)
|
||||
|
||||
@frappe.whitelist()
|
||||
def reconcile(self):
|
||||
@@ -539,7 +518,7 @@ class PaymentReconciliation(Document):
|
||||
self.get_unreconciled_entries()
|
||||
|
||||
def get_payment_details(self, row, dr_or_cr):
|
||||
payment_details = frappe._dict(
|
||||
return frappe._dict(
|
||||
{
|
||||
"voucher_type": row.get("reference_type"),
|
||||
"voucher_no": row.get("reference_name"),
|
||||
@@ -557,16 +536,11 @@ class PaymentReconciliation(Document):
|
||||
"allocated_amount": flt(row.get("allocated_amount")),
|
||||
"difference_amount": flt(row.get("difference_amount")),
|
||||
"difference_account": row.get("difference_account"),
|
||||
"difference_posting_date": row.get("gain_loss_posting_date"),
|
||||
"cost_center": row.get("cost_center"),
|
||||
}
|
||||
)
|
||||
|
||||
for x in self.dimensions:
|
||||
if row.get(x.fieldname):
|
||||
payment_details[x.fieldname] = row.get(x.fieldname)
|
||||
|
||||
return payment_details
|
||||
|
||||
def check_mandatory_to_fetch(self):
|
||||
for fieldname in ["company", "party_type", "party", "receivable_payable_account"]:
|
||||
if not self.get(fieldname):
|
||||
@@ -631,12 +605,7 @@ class PaymentReconciliation(Document):
|
||||
journals_map = frappe._dict(
|
||||
frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
filters={
|
||||
"parent": ("in", journals),
|
||||
"account": ("in", [self.receivable_payable_account]),
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
},
|
||||
filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])},
|
||||
fields=[
|
||||
"parent as `name`",
|
||||
"exchange_rate",
|
||||
@@ -679,13 +648,6 @@ class PaymentReconciliation(Document):
|
||||
if not invoices_to_reconcile:
|
||||
frappe.throw(_("No records found in Allocation table"))
|
||||
|
||||
def build_dimensions_filter_conditions(self):
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
for x in self.dimensions:
|
||||
dimension = x.fieldname
|
||||
if self.get(dimension):
|
||||
self.accounting_dimension_filter_conditions.append(ple[dimension] == self.get(dimension))
|
||||
|
||||
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
|
||||
self.common_filter_conditions.clear()
|
||||
self.accounting_dimension_filter_conditions.clear()
|
||||
@@ -709,30 +671,40 @@ class PaymentReconciliation(Document):
|
||||
if self.to_payment_date:
|
||||
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
|
||||
|
||||
self.build_dimensions_filter_conditions()
|
||||
def get_conditions(self, get_payments=False):
|
||||
condition = " and company = '{0}' ".format(self.company)
|
||||
|
||||
def get_journal_filter_conditions(self):
|
||||
conditions = []
|
||||
je = qb.DocType("Journal Entry")
|
||||
jea = qb.DocType("Journal Entry Account")
|
||||
conditions.append(je.company == self.company)
|
||||
if self.get("cost_center") and get_payments:
|
||||
condition = " and cost_center = '{0}' ".format(self.cost_center)
|
||||
|
||||
if self.from_payment_date:
|
||||
conditions.append(je.posting_date.gte(self.from_payment_date))
|
||||
|
||||
if self.to_payment_date:
|
||||
conditions.append(je.posting_date.lte(self.to_payment_date))
|
||||
condition += (
|
||||
" and posting_date >= {0}".format(frappe.db.escape(self.from_payment_date))
|
||||
if self.from_payment_date
|
||||
else ""
|
||||
)
|
||||
condition += (
|
||||
" and posting_date <= {0}".format(frappe.db.escape(self.to_payment_date))
|
||||
if self.to_payment_date
|
||||
else ""
|
||||
)
|
||||
|
||||
if self.minimum_payment_amount:
|
||||
conditions.append(je.total_debit.gte(self.minimum_payment_amount))
|
||||
|
||||
condition += (
|
||||
" and unallocated_amount >= {0}".format(flt(self.minimum_payment_amount))
|
||||
if get_payments
|
||||
else " and total_debit >= {0}".format(flt(self.minimum_payment_amount))
|
||||
)
|
||||
if self.maximum_payment_amount:
|
||||
conditions.append(je.total_debit.lte(self.maximum_payment_amount))
|
||||
condition += (
|
||||
" and unallocated_amount <= {0}".format(flt(self.maximum_payment_amount))
|
||||
if get_payments
|
||||
else " and total_debit <= {0}".format(flt(self.maximum_payment_amount))
|
||||
)
|
||||
|
||||
return conditions
|
||||
return condition
|
||||
|
||||
|
||||
def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
|
||||
def reconcile_dr_cr_note(dr_cr_notes, company):
|
||||
for inv in dr_cr_notes:
|
||||
voucher_type = "Credit Note" if inv.voucher_type == "Sales Invoice" else "Debit Note"
|
||||
|
||||
@@ -782,15 +754,6 @@ def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
|
||||
}
|
||||
)
|
||||
|
||||
# Credit Note(JE) will inherit the same dimension values as payment
|
||||
dimensions_dict = frappe._dict()
|
||||
if active_dimensions:
|
||||
for dim in active_dimensions:
|
||||
dimensions_dict[dim.fieldname] = inv.get(dim.fieldname)
|
||||
|
||||
jv.accounts[0].update(dimensions_dict)
|
||||
jv.accounts[1].update(dimensions_dict)
|
||||
|
||||
jv.flags.ignore_mandatory = True
|
||||
jv.flags.ignore_exchange_rate = True
|
||||
jv.remark = None
|
||||
@@ -824,27 +787,9 @@ def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
|
||||
inv.against_voucher,
|
||||
None,
|
||||
inv.cost_center,
|
||||
dimensions_dict,
|
||||
)
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def adjust_allocations_for_taxes(doc):
|
||||
pass
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_queries_for_dimension_filters(company: str = None):
|
||||
dimensions_with_filters = []
|
||||
for d in get_dimensions()[0]:
|
||||
filters = {}
|
||||
meta = frappe.get_meta(d.document_type)
|
||||
if meta.has_field("company") and company:
|
||||
filters.update({"company": company})
|
||||
|
||||
if meta.is_tree:
|
||||
filters.update({"is_group": 0})
|
||||
|
||||
dimensions_with_filters.append({"fieldname": d.fieldname, "filters": filters})
|
||||
|
||||
return dimensions_with_filters
|
||||
|
||||
@@ -56,7 +56,6 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.expense_account = "Cost of Goods Sold - _PR"
|
||||
self.debit_to = "Debtors - _PR"
|
||||
self.creditors = "Creditors - _PR"
|
||||
self.cash = "Cash - _PR"
|
||||
|
||||
# create bank account
|
||||
if frappe.db.exists("Account", "HDFC - _PR"):
|
||||
@@ -487,91 +486,6 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(len(pr.get("invoices")), 0)
|
||||
self.assertEqual(len(pr.get("payments")), 0)
|
||||
|
||||
def test_payment_against_foreign_currency_journal(self):
|
||||
transaction_date = nowdate()
|
||||
|
||||
self.supplier = "_Test Supplier USD"
|
||||
self.supplier2 = make_supplier("_Test Supplier2 USD", "USD")
|
||||
amount = 100
|
||||
exc_rate1 = 80
|
||||
exc_rate2 = 83
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.posting_date = transaction_date
|
||||
je.company = self.company
|
||||
je.user_remark = "test"
|
||||
je.multi_currency = 1
|
||||
je.set(
|
||||
"accounts",
|
||||
[
|
||||
{
|
||||
"account": self.creditors_usd,
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier,
|
||||
"exchange_rate": exc_rate1,
|
||||
"cost_center": self.cost_center,
|
||||
"credit": amount * exc_rate1,
|
||||
"credit_in_account_currency": amount,
|
||||
},
|
||||
{
|
||||
"account": self.creditors_usd,
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier2,
|
||||
"exchange_rate": exc_rate2,
|
||||
"cost_center": self.cost_center,
|
||||
"credit": amount * exc_rate2,
|
||||
"credit_in_account_currency": amount,
|
||||
},
|
||||
{
|
||||
"account": self.expense_account,
|
||||
"cost_center": self.cost_center,
|
||||
"debit": (amount * exc_rate1) + (amount * exc_rate2),
|
||||
"debit_in_account_currency": (amount * exc_rate1) + (amount * exc_rate2),
|
||||
},
|
||||
],
|
||||
)
|
||||
je.save().submit()
|
||||
|
||||
pe = self.create_payment_entry(amount=amount, posting_date=transaction_date)
|
||||
pe.payment_type = "Pay"
|
||||
pe.party_type = "Supplier"
|
||||
pe.party = self.supplier
|
||||
pe.paid_to = self.creditors_usd
|
||||
pe.paid_from = self.cash
|
||||
pe.paid_amount = 8000
|
||||
pe.received_amount = 100
|
||||
pe.target_exchange_rate = exc_rate1
|
||||
pe.paid_to_account_currency = "USD"
|
||||
pe.save().submit()
|
||||
|
||||
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||
pr.receivable_payable_account = self.creditors_usd
|
||||
pr.minimum_invoice_amount = pr.maximum_invoice_amount = amount
|
||||
pr.from_invoice_date = pr.to_invoice_date = transaction_date
|
||||
pr.from_payment_date = pr.to_payment_date = transaction_date
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
invoices = [x.as_dict() for x in pr.get("invoices")]
|
||||
payments = [x.as_dict() for x in pr.get("payments")]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
|
||||
# There should no difference_amount as the Journal and Payment have same exchange rate - 'exc_rate1'
|
||||
for row in pr.allocation:
|
||||
self.assertEqual(flt(row.get("difference_amount")), 0.0)
|
||||
|
||||
pr.reconcile()
|
||||
|
||||
# check PR tool output
|
||||
self.assertEqual(len(pr.get("invoices")), 0)
|
||||
self.assertEqual(len(pr.get("payments")), 0)
|
||||
|
||||
journals = frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
filters={"reference_type": je.doctype, "reference_name": je.name, "docstatus": 1},
|
||||
fields=["parent"],
|
||||
)
|
||||
self.assertEqual([], journals)
|
||||
|
||||
def test_journal_against_invoice(self):
|
||||
transaction_date = nowdate()
|
||||
amount = 100
|
||||
@@ -677,70 +591,6 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(si.status, "Paid")
|
||||
self.assertEqual(si.outstanding_amount, 0)
|
||||
|
||||
def test_invoice_status_after_cr_note_cancellation(self):
|
||||
# This test case is made after the 'always standalone Credit/Debit notes' feature is introduced
|
||||
transaction_date = nowdate()
|
||||
amount = 100
|
||||
|
||||
si = self.create_sales_invoice(qty=1, rate=amount, posting_date=transaction_date)
|
||||
|
||||
cr_note = self.create_sales_invoice(
|
||||
qty=-1, rate=amount, posting_date=transaction_date, do_not_save=True, do_not_submit=True
|
||||
)
|
||||
cr_note.is_return = 1
|
||||
cr_note.return_against = si.name
|
||||
cr_note = cr_note.save().submit()
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
invoices = [x.as_dict() for x in pr.get("invoices")]
|
||||
payments = [x.as_dict() for x in pr.get("payments")]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
self.assertEqual(pr.get("invoices"), [])
|
||||
self.assertEqual(pr.get("payments"), [])
|
||||
|
||||
journals = frappe.db.get_all(
|
||||
"Journal Entry",
|
||||
filters={
|
||||
"is_system_generated": 1,
|
||||
"docstatus": 1,
|
||||
"voucher_type": "Credit Note",
|
||||
"reference_type": si.doctype,
|
||||
"reference_name": si.name,
|
||||
},
|
||||
pluck="name",
|
||||
)
|
||||
self.assertEqual(len(journals), 1)
|
||||
|
||||
# assert status and outstanding
|
||||
si.reload()
|
||||
self.assertEqual(si.status, "Credit Note Issued")
|
||||
self.assertEqual(si.outstanding_amount, 0)
|
||||
|
||||
cr_note.reload()
|
||||
cr_note.cancel()
|
||||
# 'Credit Note' Journal should be auto cancelled
|
||||
journals = frappe.db.get_all(
|
||||
"Journal Entry",
|
||||
filters={
|
||||
"is_system_generated": 1,
|
||||
"docstatus": 1,
|
||||
"voucher_type": "Credit Note",
|
||||
"reference_type": si.doctype,
|
||||
"reference_name": si.name,
|
||||
},
|
||||
pluck="name",
|
||||
)
|
||||
self.assertEqual(len(journals), 0)
|
||||
# assert status and outstanding
|
||||
si.reload()
|
||||
self.assertEqual(si.status, "Unpaid")
|
||||
self.assertEqual(si.outstanding_amount, 100)
|
||||
|
||||
def test_cr_note_partial_against_invoice(self):
|
||||
transaction_date = nowdate()
|
||||
amount = 100
|
||||
@@ -1334,17 +1184,3 @@ def make_customer(customer_name, currency=None):
|
||||
return customer.name
|
||||
else:
|
||||
return customer_name
|
||||
|
||||
|
||||
def make_supplier(supplier_name, currency=None):
|
||||
if not frappe.db.exists("Supplier", supplier_name):
|
||||
supplier = frappe.new_doc("Supplier")
|
||||
supplier.supplier_name = supplier_name
|
||||
supplier.type = "Individual"
|
||||
|
||||
if currency:
|
||||
supplier.default_currency = currency
|
||||
supplier.save()
|
||||
return supplier.name
|
||||
else:
|
||||
return supplier_name
|
||||
|
||||
@@ -19,13 +19,12 @@
|
||||
"is_advance",
|
||||
"section_break_5",
|
||||
"difference_amount",
|
||||
"gain_loss_posting_date",
|
||||
"column_break_7",
|
||||
"difference_account",
|
||||
"exchange_rate",
|
||||
"currency",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break"
|
||||
"cost_center"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -158,21 +157,12 @@
|
||||
"fieldname": "gain_loss_posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Difference Posting Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-14 13:38:26.104150",
|
||||
"modified": "2023-11-17 17:33:38.612615",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
|
||||
@@ -234,13 +234,6 @@ class PaymentRequest(Document):
|
||||
if self.payment_url:
|
||||
self.db_set("payment_url", self.payment_url)
|
||||
|
||||
if (
|
||||
self.payment_url
|
||||
or not self.payment_gateway_account
|
||||
or (self.payment_gateway_account and self.payment_channel == "Phone")
|
||||
):
|
||||
self.db_set("status", "Initiated")
|
||||
|
||||
def get_payment_url(self):
|
||||
if self.reference_doctype != "Fees":
|
||||
data = frappe.db.get_value(
|
||||
|
||||
@@ -11,6 +11,7 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_lo
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
SalesInvoice,
|
||||
get_bank_cash_account,
|
||||
get_mode_of_payment_info,
|
||||
update_multi_mode_option,
|
||||
)
|
||||
@@ -207,6 +208,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_stock_availablility()
|
||||
self.validate_return_items_qty()
|
||||
self.set_status()
|
||||
self.set_account_for_mode_of_payment()
|
||||
self.validate_pos()
|
||||
self.validate_payment_amount()
|
||||
self.validate_loyalty_transaction()
|
||||
@@ -641,6 +643,11 @@ class POSInvoice(SalesInvoice):
|
||||
update_multi_mode_option(self, pos_profile)
|
||||
self.paid_amount = 0
|
||||
|
||||
def set_account_for_mode_of_payment(self):
|
||||
for pay in self.payments:
|
||||
if not pay.account:
|
||||
pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_payment_request(self):
|
||||
for pay in self.payments:
|
||||
|
||||
@@ -80,16 +80,13 @@
|
||||
"target_warehouse",
|
||||
"quality_inspection",
|
||||
"serial_and_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"batch_no",
|
||||
"col_break5",
|
||||
"allow_zero_valuation_rate",
|
||||
"serial_no",
|
||||
"item_tax_rate",
|
||||
"actual_batch_qty",
|
||||
"actual_qty",
|
||||
"section_break_tlhi",
|
||||
"serial_no",
|
||||
"column_break_ciit",
|
||||
"batch_no",
|
||||
"edit_references",
|
||||
"sales_order",
|
||||
"so_detail",
|
||||
@@ -631,13 +628,13 @@
|
||||
"options": "Quality Inspection"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break5",
|
||||
@@ -652,14 +649,14 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Serial No",
|
||||
"oldfieldname": "serial_no",
|
||||
"oldfieldtype": "Small Text"
|
||||
"oldfieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_tax_rate",
|
||||
@@ -827,33 +824,17 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1",
|
||||
"fieldname": "section_break_tlhi",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ciit",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-25 15:50:17.140269",
|
||||
"modified": "2023-11-14 18:33:22.585715",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
|
||||
@@ -72,7 +72,7 @@ class POSInvoiceItem(Document):
|
||||
rate_with_margin: DF.Currency
|
||||
sales_order: DF.Link | None
|
||||
serial_and_batch_bundle: DF.Link | None
|
||||
serial_no: DF.Text | None
|
||||
serial_no: DF.SmallText | None
|
||||
service_end_date: DF.Date | None
|
||||
service_start_date: DF.Date | None
|
||||
service_stop_date: DF.Date | None
|
||||
@@ -82,7 +82,6 @@ class POSInvoiceItem(Document):
|
||||
target_warehouse: DF.Link | None
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
weight_per_unit: DF.Float
|
||||
weight_uom: DF.Link | None
|
||||
|
||||
@@ -286,7 +286,7 @@ class PricingRule(Document):
|
||||
def validate_price_list_with_currency(self):
|
||||
if self.currency and self.for_price_list:
|
||||
price_list_currency = frappe.db.get_value("Price List", self.for_price_list, "currency", True)
|
||||
if not self.currency == price_list_currency:
|
||||
if self.currency != price_list_currency:
|
||||
throw(_("Currency should be same as Price List Currency: {0}").format(price_list_currency))
|
||||
|
||||
def validate_dates(self):
|
||||
@@ -579,17 +579,12 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||
item_details[field] += pricing_rule.get(field, 0) if pricing_rule else args.get(field, 0)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, rate=None):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (
|
||||
get_applied_pricing_rules,
|
||||
get_pricing_rule_items,
|
||||
)
|
||||
|
||||
if isinstance(item_details, str):
|
||||
item_details = json.loads(item_details)
|
||||
item_details = frappe._dict(item_details)
|
||||
|
||||
for d in get_applied_pricing_rules(pricing_rules):
|
||||
if not d or not frappe.db.exists("Pricing Rule", d):
|
||||
continue
|
||||
|
||||
@@ -475,7 +475,7 @@ def reconcile(doc: None | str = None) -> None:
|
||||
frappe.db.set_value("Process Payment Reconciliation", doc, "status", "Completed")
|
||||
else:
|
||||
|
||||
if not (frappe.db.get_value("Process Payment Reconciliation", doc, "status") == "Paused"):
|
||||
if frappe.db.get_value("Process Payment Reconciliation", doc, "status") != "Paused":
|
||||
# trigger next batch in job
|
||||
# generate reconcile job name
|
||||
allocation = get_next_allocation(log)
|
||||
|
||||
@@ -120,6 +120,18 @@ def get_statement_dict(doc, get_statement_dict=False):
|
||||
statement_dict = {}
|
||||
ageing = ""
|
||||
|
||||
err_journals = None
|
||||
if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
|
||||
err_journals = frappe.db.get_all(
|
||||
"Journal Entry",
|
||||
filters={
|
||||
"company": doc.company,
|
||||
"docstatus": 1,
|
||||
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
|
||||
},
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
for entry in doc.customers:
|
||||
if doc.include_ageing:
|
||||
ageing = set_ageing(doc, entry)
|
||||
@@ -132,8 +144,8 @@ def get_statement_dict(doc, get_statement_dict=False):
|
||||
)
|
||||
|
||||
filters = get_common_filters(doc)
|
||||
if doc.ignore_exchange_rate_revaluation_journals:
|
||||
filters.update({"ignore_err": True})
|
||||
if err_journals:
|
||||
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
|
||||
|
||||
if doc.report == "General Ledger":
|
||||
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
||||
|
||||
@@ -35,17 +35,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
super.onload();
|
||||
|
||||
// Ignore linked advances
|
||||
this.frm.ignore_doctypes_on_cancel_all = [
|
||||
"Journal Entry",
|
||||
"Payment Entry",
|
||||
"Purchase Invoice",
|
||||
"Repost Payment Ledger",
|
||||
"Repost Accounting Ledger",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Serial and Batch Bundle",
|
||||
"Bank Transaction",
|
||||
];
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries", "Serial and Batch Bundle"];
|
||||
|
||||
if(!this.frm.doc.__islocal) {
|
||||
// show credit_to in print format
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
"is_paid",
|
||||
"is_return",
|
||||
"return_against",
|
||||
"update_billed_amount_in_purchase_order",
|
||||
"update_billed_amount_in_purchase_receipt",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"amended_from",
|
||||
@@ -414,20 +412,6 @@
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval: doc.is_return",
|
||||
"fieldname": "update_billed_amount_in_purchase_order",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Purchase Order"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval: doc.is_return",
|
||||
"fieldname": "update_billed_amount_in_purchase_receipt",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Purchase Receipt"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_addresses",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -1628,7 +1612,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-25 11:20:28.366808",
|
||||
"modified": "2023-11-29 15:35:44.697496",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
@@ -1691,4 +1675,4 @@
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -214,8 +214,6 @@ class PurchaseInvoice(BuyingController):
|
||||
total_qty: DF.Float
|
||||
total_taxes_and_charges: DF.Currency
|
||||
unrealized_profit_loss_account: DF.Link | None
|
||||
update_billed_amount_in_purchase_order: DF.Check
|
||||
update_billed_amount_in_purchase_receipt: DF.Check
|
||||
update_stock: DF.Check
|
||||
use_company_roundoff_cost_center: DF.Check
|
||||
use_transaction_date_exchange_rate: DF.Check
|
||||
@@ -298,18 +296,6 @@ class PurchaseInvoice(BuyingController):
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
self.reset_default_field_value("rejected_warehouse", "items", "rejected_warehouse")
|
||||
self.reset_default_field_value("set_from_warehouse", "items", "from_warehouse")
|
||||
self.set_percentage_received()
|
||||
|
||||
def set_percentage_received(self):
|
||||
total_billed_qty = 0.0
|
||||
total_received_qty = 0.0
|
||||
for row in self.items:
|
||||
if row.purchase_receipt and row.pr_detail and row.received_qty:
|
||||
total_billed_qty += row.qty
|
||||
total_received_qty += row.received_qty
|
||||
|
||||
if total_billed_qty and total_received_qty:
|
||||
self.per_received = total_received_qty / total_billed_qty * 100
|
||||
|
||||
def validate_release_date(self):
|
||||
if self.release_date and getdate(nowdate()) >= getdate(self.release_date):
|
||||
@@ -385,7 +371,7 @@ class PurchaseInvoice(BuyingController):
|
||||
check_list = []
|
||||
|
||||
for d in self.get("items"):
|
||||
if d.purchase_order and not d.purchase_order in check_list and not d.purchase_receipt:
|
||||
if d.purchase_order and d.purchase_order not in check_list and not d.purchase_receipt:
|
||||
check_list.append(d.purchase_order)
|
||||
check_on_hold_or_closed_status("Purchase Order", d.purchase_order)
|
||||
|
||||
@@ -566,7 +552,7 @@ class PurchaseInvoice(BuyingController):
|
||||
self.against_expense_account = ",".join(against_accounts)
|
||||
|
||||
def po_required(self):
|
||||
if frappe.db.get_value("Buying Settings", None, "po_required") == "Yes":
|
||||
if frappe.db.get_single_value("Buying Settings", "po_required") == "Yes":
|
||||
|
||||
if frappe.get_value(
|
||||
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_order"
|
||||
@@ -586,7 +572,7 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
def pr_required(self):
|
||||
stock_items = self.get_stock_items()
|
||||
if frappe.db.get_value("Buying Settings", None, "pr_required") == "Yes":
|
||||
if frappe.db.get_single_value("Buying Settings", "pr_required") == "Yes":
|
||||
|
||||
if frappe.get_value(
|
||||
"Supplier", self.supplier, "allow_purchase_invoice_creation_without_purchase_receipt"
|
||||
@@ -681,11 +667,6 @@ class PurchaseInvoice(BuyingController):
|
||||
super(PurchaseInvoice, self).on_submit()
|
||||
|
||||
self.check_prev_docstatus()
|
||||
|
||||
if self.is_return and not self.update_billed_amount_in_purchase_order:
|
||||
# NOTE status updating bypassed for is_return
|
||||
self.status_updater = []
|
||||
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
|
||||
@@ -703,7 +684,6 @@ class PurchaseInvoice(BuyingController):
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating ordered qty in bin depends upon updated ordered qty in PO
|
||||
if self.update_stock == 1:
|
||||
self.make_bundle_using_old_serial_batch_fields()
|
||||
self.update_stock_ledger()
|
||||
|
||||
if self.is_old_subcontracting_flow:
|
||||
@@ -731,7 +711,6 @@ class PurchaseInvoice(BuyingController):
|
||||
"cash_bank_account",
|
||||
"write_off_account",
|
||||
"unrealized_profit_loss_account",
|
||||
"is_opening",
|
||||
]
|
||||
child_tables = {"items": ("expense_account",), "taxes": ("account_head",)}
|
||||
self.needs_repost = self.check_if_fields_updated(fields_to_check, child_tables)
|
||||
@@ -836,7 +815,9 @@ class PurchaseInvoice(BuyingController):
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier,
|
||||
"due_date": self.due_date,
|
||||
"against_type": "Account",
|
||||
"against": self.against_expense_account,
|
||||
"against_link": self.against_expense_account,
|
||||
"credit": base_grand_total,
|
||||
"credit_in_account_currency": base_grand_total
|
||||
if self.party_account_currency == self.company_currency
|
||||
@@ -909,7 +890,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_account[item.warehouse]["account"],
|
||||
"against_type": "Account",
|
||||
"against": warehouse_account[item.from_warehouse]["account"],
|
||||
"against_link": warehouse_account[item.from_warehouse]["account"],
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
@@ -929,7 +912,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_account[item.from_warehouse]["account"],
|
||||
"against_type": "Account",
|
||||
"against": warehouse_account[item.warehouse]["account"],
|
||||
"against_link": warehouse_account[item.warehouse]["account"],
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
@@ -946,7 +931,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"debit": flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
@@ -963,7 +950,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"debit": warehouse_debit_amount,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"cost_center": item.cost_center,
|
||||
@@ -982,7 +971,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": account,
|
||||
"against_type": "Account",
|
||||
"against": item.expense_account,
|
||||
"against_link": item.expense_account,
|
||||
"cost_center": item.cost_center,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"credit": flt(amount["base_amount"]),
|
||||
@@ -1002,7 +993,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": supplier_warehouse_account,
|
||||
"against_type": "Account",
|
||||
"against": item.expense_account,
|
||||
"against_link": item.expense_account,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
@@ -1024,14 +1017,9 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
if provisional_accounting_for_non_stock_items:
|
||||
if item.purchase_receipt:
|
||||
provisional_account, pr_qty, pr_base_rate = frappe.get_cached_value(
|
||||
"Purchase Receipt Item",
|
||||
item.pr_detail,
|
||||
["provisional_expense_account", "qty", "base_rate"],
|
||||
)
|
||||
provisional_account = provisional_account or self.get_company_default(
|
||||
"default_provisional_account"
|
||||
)
|
||||
provisional_account = frappe.db.get_value(
|
||||
"Purchase Receipt Item", item.pr_detail, "provisional_expense_account"
|
||||
) or self.get_company_default("default_provisional_account")
|
||||
purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
|
||||
|
||||
if not purchase_receipt_doc:
|
||||
@@ -1048,18 +1036,13 @@ class PurchaseInvoice(BuyingController):
|
||||
"voucher_detail_no": item.pr_detail,
|
||||
"account": provisional_account,
|
||||
},
|
||||
"name",
|
||||
["name"],
|
||||
)
|
||||
|
||||
if expense_booked_in_pr:
|
||||
# Intentionally passing purchase invoice item to handle partial billing
|
||||
purchase_receipt_doc.add_provisional_gl_entry(
|
||||
item,
|
||||
gl_entries,
|
||||
self.posting_date,
|
||||
provisional_account,
|
||||
reverse=1,
|
||||
item_amount=(min(item.qty, pr_qty) * pr_base_rate),
|
||||
item, gl_entries, self.posting_date, provisional_account, reverse=1
|
||||
)
|
||||
|
||||
if not self.is_internal_transfer():
|
||||
@@ -1067,7 +1050,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"debit": amount,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
@@ -1093,7 +1078,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"debit": discrepancy_caused_by_exchange_rate_difference,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
@@ -1106,7 +1093,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.get_company_default("exchange_gain_loss_account"),
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"credit": discrepancy_caused_by_exchange_rate_difference,
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
@@ -1115,6 +1104,17 @@ class PurchaseInvoice(BuyingController):
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of asset bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)
|
||||
)
|
||||
|
||||
if (
|
||||
self.auto_accounting_for_stock
|
||||
and self.is_opening == "No"
|
||||
@@ -1140,7 +1140,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": stock_rbnb,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"debit": flt(item.item_tax_amount, item.precision("item_tax_amount")),
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||
"cost_center": self.cost_center,
|
||||
@@ -1154,24 +1156,17 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount, item.precision("item_tax_amount")
|
||||
)
|
||||
|
||||
if item.is_fixed_asset and item.landed_cost_voucher_amount:
|
||||
self.update_gross_purchase_amount_for_linked_assets(item)
|
||||
|
||||
def update_gross_purchase_amount_for_linked_assets(self, item):
|
||||
assets = frappe.db.get_all(
|
||||
"Asset",
|
||||
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
||||
fields=["name", "asset_quantity"],
|
||||
)
|
||||
for asset in assets:
|
||||
purchase_amount = flt(item.valuation_rate) * asset.asset_quantity
|
||||
frappe.db.set_value(
|
||||
"Asset",
|
||||
asset.name,
|
||||
{
|
||||
"gross_purchase_amount": purchase_amount,
|
||||
"purchase_receipt_amount": purchase_amount,
|
||||
},
|
||||
"Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate) * asset.asset_quantity
|
||||
)
|
||||
|
||||
def make_stock_adjustment_entry(
|
||||
@@ -1201,7 +1196,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": cost_of_goods_sold_account,
|
||||
"against_type": "Account",
|
||||
"against": item.expense_account,
|
||||
"against_link": item.expense_account,
|
||||
"debit": stock_adjustment_amt,
|
||||
"remarks": self.get("remarks") or _("Stock Adjustment"),
|
||||
"cost_center": item.cost_center,
|
||||
@@ -1231,7 +1228,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": tax.account_head,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
dr_or_cr: base_amount,
|
||||
dr_or_cr + "_in_account_currency": base_amount
|
||||
if account_currency == self.company_currency
|
||||
@@ -1279,8 +1278,10 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": tax.account_head,
|
||||
"against_type": "Supplier",
|
||||
"cost_center": tax.cost_center,
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"credit": applicable_amount,
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||
},
|
||||
@@ -1298,7 +1299,9 @@ class PurchaseInvoice(BuyingController):
|
||||
{
|
||||
"account": tax.account_head,
|
||||
"cost_center": tax.cost_center,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"credit": valuation_tax[tax.name],
|
||||
"remarks": self.remarks or _("Accounting Entry for Stock"),
|
||||
},
|
||||
@@ -1313,7 +1316,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.unrealized_profit_loss_account,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"credit": flt(self.total_taxes_and_charges),
|
||||
"credit_in_account_currency": flt(self.base_total_taxes_and_charges),
|
||||
"cost_center": self.cost_center,
|
||||
@@ -1334,7 +1339,9 @@ class PurchaseInvoice(BuyingController):
|
||||
"account": self.credit_to,
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier,
|
||||
"against_type": "Account",
|
||||
"against": self.cash_bank_account,
|
||||
"against_link": self.cash_bank_account,
|
||||
"debit": self.base_paid_amount,
|
||||
"debit_in_account_currency": self.base_paid_amount
|
||||
if self.party_account_currency == self.company_currency
|
||||
@@ -1355,7 +1362,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.cash_bank_account,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"credit": self.base_paid_amount,
|
||||
"credit_in_account_currency": self.base_paid_amount
|
||||
if bank_account_currency == self.company_currency
|
||||
@@ -1379,7 +1388,9 @@ class PurchaseInvoice(BuyingController):
|
||||
"account": self.credit_to,
|
||||
"party_type": "Supplier",
|
||||
"party": self.supplier,
|
||||
"against_type": "Account",
|
||||
"against": self.write_off_account,
|
||||
"against_link": self.write_off_account,
|
||||
"debit": self.base_write_off_amount,
|
||||
"debit_in_account_currency": self.base_write_off_amount
|
||||
if self.party_account_currency == self.company_currency
|
||||
@@ -1399,7 +1410,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.write_off_account,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"credit": flt(self.base_write_off_amount),
|
||||
"credit_in_account_currency": self.base_write_off_amount
|
||||
if write_off_account_currency == self.company_currency
|
||||
@@ -1426,7 +1439,9 @@ class PurchaseInvoice(BuyingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": round_off_account,
|
||||
"against_type": "Supplier",
|
||||
"against": self.supplier,
|
||||
"against_link": self.supplier,
|
||||
"debit_in_account_currency": self.rounding_adjustment,
|
||||
"debit": self.base_rounding_adjustment,
|
||||
"cost_center": round_off_cost_center
|
||||
@@ -1444,10 +1459,6 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
self.check_on_hold_or_closed_status()
|
||||
|
||||
if self.is_return and not self.update_billed_amount_in_purchase_order:
|
||||
# NOTE status updating bypassed for is_return
|
||||
self.status_updater = []
|
||||
|
||||
self.update_status_updater_args()
|
||||
self.update_prevdoc_status()
|
||||
|
||||
@@ -1542,9 +1553,6 @@ class PurchaseInvoice(BuyingController):
|
||||
frappe.throw(_("Supplier Invoice No exists in Purchase Invoice {0}").format(pi))
|
||||
|
||||
def update_billing_status_in_pr(self, update_modified=True):
|
||||
if self.is_return and not self.update_billed_amount_in_purchase_receipt:
|
||||
return
|
||||
|
||||
updated_pr = []
|
||||
po_details = []
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_ent
|
||||
from erpnext.buying.doctype.purchase_order.purchase_order import get_mapped_purchase_invoice
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
from erpnext.controllers.accounts_controller import get_payment_terms
|
||||
from erpnext.controllers.accounts_controller import InvalidQtyError, get_payment_terms
|
||||
from erpnext.controllers.buying_controller import QtyMismatchError
|
||||
from erpnext.exceptions import InvalidCurrency
|
||||
from erpnext.projects.doctype.project.test_project import make_project
|
||||
@@ -51,6 +51,16 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_purchase_invoice_qty(self):
|
||||
pi = make_purchase_invoice(qty=0, do_not_save=True)
|
||||
with self.assertRaises(InvalidQtyError):
|
||||
pi.save()
|
||||
|
||||
# No error with qty=1
|
||||
pi.items[0].qty = 1
|
||||
pi.save()
|
||||
self.assertEqual(pi.items[0].qty, 1)
|
||||
|
||||
def test_purchase_invoice_received_qty(self):
|
||||
"""
|
||||
1. Test if received qty is validated against accepted + rejected
|
||||
@@ -1529,7 +1539,18 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
self.assertEqual(payment_entry.taxes[0].allocated_amount, 0)
|
||||
|
||||
def test_provisional_accounting_entry(self):
|
||||
setup_provisional_accounting()
|
||||
create_item("_Test Non Stock Item", is_stock_item=0)
|
||||
|
||||
provisional_account = create_account(
|
||||
account_name="Provision Account",
|
||||
parent_account="Current Liabilities - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
company = frappe.get_doc("Company", "_Test Company")
|
||||
company.enable_provisional_accounting_for_non_stock_items = 1
|
||||
company.default_provisional_account = provisional_account
|
||||
company.save()
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code="_Test Non Stock Item", posting_date=add_days(nowdate(), -2)
|
||||
@@ -1573,97 +1594,8 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
|
||||
)
|
||||
|
||||
toggle_provisional_accounting_setting()
|
||||
|
||||
def test_provisional_accounting_entry_for_over_billing(self):
|
||||
setup_provisional_accounting()
|
||||
|
||||
# Configure Buying Settings to allow rate change
|
||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
|
||||
|
||||
# Create PR: rate = 1000, qty = 5
|
||||
pr = make_purchase_receipt(
|
||||
item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2)
|
||||
)
|
||||
|
||||
# Overbill PR: rate = 2000, qty = 10
|
||||
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||
pi.set_posting_time = 1
|
||||
pi.posting_date = add_days(pr.posting_date, -1)
|
||||
pi.items[0].qty = 10
|
||||
pi.items[0].rate = 2000
|
||||
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["Cost of Goods Sold - _TC", 20000, 0, add_days(pr.posting_date, -1)],
|
||||
["Creditors - _TC", 0, 20000, add_days(pr.posting_date, -1)],
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
|
||||
|
||||
expected_gle_for_purchase_receipt = [
|
||||
["Provision Account - _TC", 5000, 0, pr.posting_date],
|
||||
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date],
|
||||
["Provision Account - _TC", 0, 5000, pi.posting_date],
|
||||
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
|
||||
]
|
||||
|
||||
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
|
||||
|
||||
# Cancel purchase invoice to check reverse provisional entry cancellation
|
||||
pi.cancel()
|
||||
|
||||
expected_gle_for_purchase_receipt_post_pi_cancel = [
|
||||
["Provision Account - _TC", 0, 5000, pi.posting_date],
|
||||
["_Test Account Cost for Goods Sold - _TC", 5000, 0, pi.posting_date],
|
||||
]
|
||||
|
||||
check_gl_entries(
|
||||
self, pr.name, expected_gle_for_purchase_receipt_post_pi_cancel, pr.posting_date
|
||||
)
|
||||
|
||||
toggle_provisional_accounting_setting()
|
||||
|
||||
def test_provisional_accounting_entry_for_partial_billing(self):
|
||||
setup_provisional_accounting()
|
||||
|
||||
# Configure Buying Settings to allow rate change
|
||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
|
||||
|
||||
# Create PR: rate = 1000, qty = 5
|
||||
pr = make_purchase_receipt(
|
||||
item_code="_Test Non Stock Item", rate=1000, posting_date=add_days(nowdate(), -2)
|
||||
)
|
||||
|
||||
# Partially bill PR: rate = 500, qty = 2
|
||||
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||
pi.set_posting_time = 1
|
||||
pi.posting_date = add_days(pr.posting_date, -1)
|
||||
pi.items[0].qty = 2
|
||||
pi.items[0].rate = 500
|
||||
pi.items[0].expense_account = "Cost of Goods Sold - _TC"
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["Cost of Goods Sold - _TC", 1000, 0, add_days(pr.posting_date, -1)],
|
||||
["Creditors - _TC", 0, 1000, add_days(pr.posting_date, -1)],
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, pi.posting_date)
|
||||
|
||||
expected_gle_for_purchase_receipt = [
|
||||
["Provision Account - _TC", 5000, 0, pr.posting_date],
|
||||
["_Test Account Cost for Goods Sold - _TC", 0, 5000, pr.posting_date],
|
||||
["Provision Account - _TC", 0, 1000, pi.posting_date],
|
||||
["_Test Account Cost for Goods Sold - _TC", 1000, 0, pi.posting_date],
|
||||
]
|
||||
|
||||
check_gl_entries(self, pr.name, expected_gle_for_purchase_receipt, pr.posting_date)
|
||||
|
||||
toggle_provisional_accounting_setting()
|
||||
company.enable_provisional_accounting_for_non_stock_items = 0
|
||||
company.save()
|
||||
|
||||
def test_adjust_incoming_rate(self):
|
||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
|
||||
@@ -2063,21 +1995,6 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
|
||||
self.assertEqual(pi.items[0].cost_center, "_Test Cost Center Buying - _TC")
|
||||
|
||||
def test_debit_note_with_account_mismatch(self):
|
||||
new_creditors = create_account(
|
||||
parent_account="Accounts Payable - _TC",
|
||||
account_name="Creditors 2",
|
||||
company="_Test Company",
|
||||
account_type="Payable",
|
||||
)
|
||||
pi = make_purchase_invoice(qty=1, rate=1000)
|
||||
dr_note = make_purchase_invoice(
|
||||
qty=-1, rate=1000, is_return=1, return_against=pi.name, do_not_save=True
|
||||
)
|
||||
dr_note.credit_to = new_creditors
|
||||
|
||||
self.assertRaises(frappe.ValidationError, dr_note.save)
|
||||
|
||||
def test_debit_note_without_item(self):
|
||||
pi = make_purchase_invoice(item_name="_Test Item", qty=10, do_not_submit=True)
|
||||
pi.items[0].item_code = ""
|
||||
@@ -2207,7 +2124,7 @@ def make_purchase_invoice(**args):
|
||||
bundle_id = None
|
||||
if args.get("batch_no") or args.get("serial_no"):
|
||||
batches = {}
|
||||
qty = args.qty or 5
|
||||
qty = args.qty if args.qty is not None else 5
|
||||
item_code = args.item or args.item_code or "_Test Item"
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
@@ -2236,7 +2153,7 @@ def make_purchase_invoice(**args):
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"item_name": args.item_name,
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty or 5,
|
||||
"qty": args.qty if args.qty is not None else 5,
|
||||
"received_qty": args.received_qty or 0,
|
||||
"rejected_qty": args.rejected_qty or 0,
|
||||
"rate": args.rate or 50,
|
||||
@@ -2342,26 +2259,4 @@ def make_purchase_invoice_against_cost_center(**args):
|
||||
return pi
|
||||
|
||||
|
||||
def setup_provisional_accounting(**args):
|
||||
args = frappe._dict(args)
|
||||
create_item("_Test Non Stock Item", is_stock_item=0)
|
||||
company = args.company or "_Test Company"
|
||||
provisional_account = create_account(
|
||||
account_name=args.account_name or "Provision Account",
|
||||
parent_account=args.parent_account or "Current Liabilities - _TC",
|
||||
company=company,
|
||||
)
|
||||
toggle_provisional_accounting_setting(
|
||||
enable=1, company=company, provisional_account=provisional_account
|
||||
)
|
||||
|
||||
|
||||
def toggle_provisional_accounting_setting(**args):
|
||||
args = frappe._dict(args)
|
||||
company = frappe.get_doc("Company", args.company or "_Test Company")
|
||||
company.enable_provisional_accounting_for_non_stock_items = args.enable or 0
|
||||
company.default_provisional_account = args.provisional_account
|
||||
company.save()
|
||||
|
||||
|
||||
test_records = frappe.get_test_records("Purchase Invoice")
|
||||
|
||||
@@ -62,19 +62,15 @@
|
||||
"rm_supp_cost",
|
||||
"warehouse_section",
|
||||
"warehouse",
|
||||
"add_serial_batch_bundle",
|
||||
"serial_and_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"col_br_wh",
|
||||
"from_warehouse",
|
||||
"quality_inspection",
|
||||
"serial_and_batch_bundle",
|
||||
"serial_no",
|
||||
"col_br_wh",
|
||||
"rejected_warehouse",
|
||||
"rejected_serial_and_batch_bundle",
|
||||
"section_break_rqbe",
|
||||
"serial_no",
|
||||
"rejected_serial_no",
|
||||
"column_break_vbbb",
|
||||
"batch_no",
|
||||
"rejected_serial_no",
|
||||
"manufacture_details",
|
||||
"manufacturer",
|
||||
"column_break_13",
|
||||
@@ -443,11 +439,13 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@@ -455,18 +453,21 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Serial No"
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"fieldname": "rejected_serial_no",
|
||||
"fieldtype": "Text",
|
||||
"label": "Rejected Serial No",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "accounting",
|
||||
@@ -889,7 +890,7 @@
|
||||
"label": "Apply TDS"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@@ -899,7 +900,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
"fieldname": "rejected_serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Rejected Serial and Batch Bundle",
|
||||
@@ -912,33 +913,12 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "WIP Composite Asset",
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock === 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"fieldname": "add_serial_batch_bundle",
|
||||
"fieldtype": "Button",
|
||||
"label": "Add Serial / Batch No"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.is_fixed_asset && doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "section_break_rqbe",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vbbb",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-04 14:11:52.742228",
|
||||
"modified": "2023-12-25 22:00:28.043555",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -88,7 +88,6 @@ class PurchaseInvoiceItem(Document):
|
||||
stock_uom_rate: DF.Currency
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
valuation_rate: DF.Currency
|
||||
warehouse: DF.Link | None
|
||||
weight_per_unit: DF.Float
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Rate",
|
||||
"label": "Rate",
|
||||
"oldfieldname": "rate",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
@@ -230,7 +230,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-14 10:04:36.618240",
|
||||
"modified": "2021-08-05 20:04:36.618240",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges",
|
||||
@@ -239,4 +239,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2013-01-10 16:34:08",
|
||||
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain a list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\", etc.",
|
||||
"description": "Standard tax template that can be applied to all Purchase Transactions. This template can contain list of tax heads and also other expense heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Consider Tax or Charge for: In this section you can specify if the tax / charge is only for valuation (not a part of total) or only for total (does not add value to the item) or for both.\n10. Add or Deduct: Whether you want to add or deduct the tax.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
@@ -77,7 +77,7 @@
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 13:08:09.537242",
|
||||
"modified": "2022-05-16 16:15:29.059370",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Taxes and Charges Template",
|
||||
|
||||
@@ -88,7 +88,6 @@ class RepostAccountingLedger(Document):
|
||||
).append(gle.update({"old": True}))
|
||||
|
||||
def generate_preview_data(self):
|
||||
frappe.flags.through_repost_accounting_ledger = True
|
||||
self.gl_entries = []
|
||||
self.get_existing_ledger_entries()
|
||||
for x in self.vouchers:
|
||||
@@ -142,7 +141,6 @@ class RepostAccountingLedger(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def start_repost(account_repost_doc=str) -> None:
|
||||
frappe.flags.through_repost_accounting_ledger = True
|
||||
if account_repost_doc:
|
||||
repost_doc = frappe.get_doc("Repost Accounting Ledger", account_repost_doc)
|
||||
|
||||
|
||||
@@ -14,26 +14,31 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
super.setup(doc);
|
||||
}
|
||||
company() {
|
||||
super.company();
|
||||
erpnext.accounts.dimensions.update_dimension(this.frm, this.frm.doctype);
|
||||
|
||||
let me = this;
|
||||
if (this.frm.doc.company) {
|
||||
frappe.call({
|
||||
method:
|
||||
"erpnext.accounts.party.get_party_account",
|
||||
args: {
|
||||
party_type: 'Customer',
|
||||
party: this.frm.doc.customer,
|
||||
company: this.frm.doc.company
|
||||
},
|
||||
callback: (response) => {
|
||||
if (response) me.frm.set_value("debit_to", response.message);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
onload() {
|
||||
var me = this;
|
||||
super.onload();
|
||||
|
||||
this.frm.ignore_doctypes_on_cancel_all = [
|
||||
"POS Invoice",
|
||||
"Timesheet",
|
||||
"POS Invoice Merge Log",
|
||||
"POS Closing Entry",
|
||||
"Journal Entry",
|
||||
"Payment Entry",
|
||||
"Repost Payment Ledger",
|
||||
"Repost Accounting Ledger",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Serial and Batch Bundle",
|
||||
"Bank Transaction",
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
|
||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries",
|
||||
'Serial and Batch Bundle'
|
||||
];
|
||||
|
||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||
@@ -893,8 +898,8 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
frm.events.append_time_log(frm, timesheet, 1.0);
|
||||
}
|
||||
});
|
||||
frm.refresh_field("timesheets");
|
||||
frm.trigger("calculate_timesheet_totals");
|
||||
frm.refresh();
|
||||
},
|
||||
|
||||
async get_exchange_rate(frm, from_currency, to_currency) {
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
"loyalty_amount",
|
||||
"column_break_77",
|
||||
"loyalty_program",
|
||||
"dont_create_loyalty_points",
|
||||
"loyalty_redemption_account",
|
||||
"loyalty_redemption_cost_center",
|
||||
"contact_and_address_tab",
|
||||
@@ -1041,8 +1042,7 @@
|
||||
"label": "Loyalty Program",
|
||||
"no_copy": 1,
|
||||
"options": "Loyalty Program",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
@@ -2162,6 +2162,14 @@
|
||||
"fieldname": "update_billed_amount_in_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Delivery Note"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "loyalty_program",
|
||||
"fieldname": "dont_create_loyalty_points",
|
||||
"fieldtype": "Check",
|
||||
"label": "Don't Create Loyalty Points",
|
||||
"no_copy": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -2174,7 +2182,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2023-11-23 16:56:29.679499",
|
||||
"modified": "2024-01-02 17:25:46.027523",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -7,7 +7,7 @@ from frappe import _, msgprint, throw
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
|
||||
from frappe.utils import add_days, cint, flt, formatdate, get_link_to_form, getdate, nowdate
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
@@ -117,6 +117,7 @@ class SalesInvoice(SellingController):
|
||||
discount_amount: DF.Currency
|
||||
dispatch_address: DF.SmallText | None
|
||||
dispatch_address_name: DF.Link | None
|
||||
dont_create_loyalty_points: DF.Check
|
||||
due_date: DF.Date | None
|
||||
from_date: DF.Date | None
|
||||
grand_total: DF.Currency
|
||||
@@ -269,7 +270,7 @@ class SalesInvoice(SellingController):
|
||||
super(SalesInvoice, self).validate()
|
||||
self.validate_auto_set_posting_time()
|
||||
|
||||
if not (self.is_pos or self.is_debit_note):
|
||||
if not self.is_pos:
|
||||
self.so_dn_required()
|
||||
|
||||
self.set_tax_withholding()
|
||||
@@ -420,8 +421,7 @@ class SalesInvoice(SellingController):
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
def before_save(self):
|
||||
self.set_account_for_mode_of_payment()
|
||||
self.set_paid_amount()
|
||||
set_account_for_mode_of_payment(self)
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_pos_paid_amount()
|
||||
@@ -446,11 +446,6 @@ class SalesInvoice(SellingController):
|
||||
# Updating stock ledger should always be called after updating prevdoc status,
|
||||
# because updating reserved qty in bin depends upon updated delivered qty in SO
|
||||
if self.update_stock == 1:
|
||||
for table_name in ["items", "packed_items"]:
|
||||
if not self.get(table_name):
|
||||
continue
|
||||
|
||||
self.make_bundle_using_old_serial_batch_fields(table_name)
|
||||
self.update_stock_ledger()
|
||||
|
||||
# this sequence because outstanding may get -ve
|
||||
@@ -464,7 +459,7 @@ class SalesInvoice(SellingController):
|
||||
self.update_billing_status_for_zero_amount_refdoc("Sales Order")
|
||||
self.check_credit_limit()
|
||||
|
||||
if not cint(self.is_pos) == 1 and not self.is_return:
|
||||
if cint(self.is_pos) != 1 and not self.is_return:
|
||||
self.update_against_document_in_jv()
|
||||
|
||||
self.update_time_sheet(self.name)
|
||||
@@ -477,7 +472,12 @@ class SalesInvoice(SellingController):
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
|
||||
# create the loyalty point ledger entry if the customer is enrolled in any loyalty program
|
||||
if not self.is_return and not self.is_consolidated and self.loyalty_program:
|
||||
if (
|
||||
not self.is_return
|
||||
and not self.is_consolidated
|
||||
and self.loyalty_program
|
||||
and not self.dont_create_loyalty_points
|
||||
):
|
||||
self.make_loyalty_point_entry()
|
||||
elif (
|
||||
self.is_return and self.return_against and not self.is_consolidated and self.loyalty_program
|
||||
@@ -712,6 +712,9 @@ class SalesInvoice(SellingController):
|
||||
):
|
||||
data.sales_invoice = sales_invoice
|
||||
|
||||
def on_update(self):
|
||||
self.set_paid_amount()
|
||||
|
||||
def on_update_after_submit(self):
|
||||
if hasattr(self, "repost_required"):
|
||||
fields_to_check = [
|
||||
@@ -721,7 +724,6 @@ class SalesInvoice(SellingController):
|
||||
"write_off_account",
|
||||
"loyalty_redemption_account",
|
||||
"unrealized_profit_loss_account",
|
||||
"is_opening",
|
||||
]
|
||||
child_tables = {
|
||||
"items": ("income_account", "expense_account", "discount_account"),
|
||||
@@ -743,11 +745,6 @@ class SalesInvoice(SellingController):
|
||||
self.paid_amount = paid_amount
|
||||
self.base_paid_amount = base_paid_amount
|
||||
|
||||
def set_account_for_mode_of_payment(self):
|
||||
for payment in self.payments:
|
||||
if not payment.account:
|
||||
payment.account = get_bank_cash_account(payment.mode_of_payment, self.company).get("account")
|
||||
|
||||
def validate_time_sheets_are_submitted(self):
|
||||
for data in self.timesheets:
|
||||
if data.time_sheet:
|
||||
@@ -1236,7 +1233,9 @@ class SalesInvoice(SellingController):
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"due_date": self.due_date,
|
||||
"against_type": "Account",
|
||||
"against": self.against_income_account,
|
||||
"against_link": self.against_income_account,
|
||||
"debit": base_grand_total,
|
||||
"debit_in_account_currency": base_grand_total
|
||||
if self.party_account_currency == self.company_currency
|
||||
@@ -1265,7 +1264,9 @@ class SalesInvoice(SellingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": tax.account_head,
|
||||
"against_type": "Customer",
|
||||
"against": self.customer,
|
||||
"against_link": self.customer,
|
||||
"credit": flt(base_amount, tax.precision("tax_amount_after_discount_amount")),
|
||||
"credit_in_account_currency": (
|
||||
flt(base_amount, tax.precision("base_tax_amount_after_discount_amount"))
|
||||
@@ -1286,7 +1287,9 @@ class SalesInvoice(SellingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.unrealized_profit_loss_account,
|
||||
"against_type": "Customer",
|
||||
"against": self.customer,
|
||||
"against_link": self.customer,
|
||||
"debit": flt(self.total_taxes_and_charges),
|
||||
"debit_in_account_currency": flt(self.base_total_taxes_and_charges),
|
||||
"cost_center": self.cost_center,
|
||||
@@ -1354,7 +1357,9 @@ class SalesInvoice(SellingController):
|
||||
add_asset_activity(asset.name, _("Asset sold"))
|
||||
|
||||
for gle in fixed_asset_gl_entries:
|
||||
gle["against_type"] = "Customer"
|
||||
gle["against"] = self.customer
|
||||
gle["against_link"] = self.customer
|
||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||
|
||||
self.set_asset_status(asset)
|
||||
@@ -1375,7 +1380,9 @@ class SalesInvoice(SellingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": income_account,
|
||||
"against_type": "Customer",
|
||||
"against": self.customer,
|
||||
"against_link": self.customer,
|
||||
"credit": flt(base_amount, item.precision("base_net_amount")),
|
||||
"credit_in_account_currency": (
|
||||
flt(base_amount, item.precision("base_net_amount"))
|
||||
@@ -1429,9 +1436,9 @@ class SalesInvoice(SellingController):
|
||||
"account": self.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"against": "Expense account - "
|
||||
+ cstr(self.loyalty_redemption_account)
|
||||
+ " for the Loyalty Program",
|
||||
"against_type": "Account",
|
||||
"against": self.loyalty_redemption_account,
|
||||
"against_link": self.loyalty_redemption_account,
|
||||
"credit": self.loyalty_amount,
|
||||
"against_voucher": self.return_against if cint(self.is_return) else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
@@ -1445,7 +1452,9 @@ class SalesInvoice(SellingController):
|
||||
{
|
||||
"account": self.loyalty_redemption_account,
|
||||
"cost_center": self.cost_center or self.loyalty_redemption_cost_center,
|
||||
"against_type": "Customer",
|
||||
"against": self.customer,
|
||||
"against_link": self.customer,
|
||||
"debit": self.loyalty_amount,
|
||||
"remark": "Loyalty Points redeemed by the customer",
|
||||
},
|
||||
@@ -1472,12 +1481,16 @@ class SalesInvoice(SellingController):
|
||||
"account": self.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"against_type": "Account",
|
||||
"against": payment_mode.account,
|
||||
"against_link": payment_mode.account,
|
||||
"credit": payment_mode.base_amount,
|
||||
"credit_in_account_currency": payment_mode.base_amount
|
||||
if self.party_account_currency == self.company_currency
|
||||
else payment_mode.amount,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher": self.return_against
|
||||
if cint(self.is_return) and self.return_against
|
||||
else self.name,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
@@ -1491,7 +1504,9 @@ class SalesInvoice(SellingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": payment_mode.account,
|
||||
"against_type": "Customer",
|
||||
"against": self.customer,
|
||||
"against_link": self.customer,
|
||||
"debit": payment_mode.base_amount,
|
||||
"debit_in_account_currency": payment_mode.base_amount
|
||||
if payment_mode_account_currency == self.company_currency
|
||||
@@ -1515,7 +1530,9 @@ class SalesInvoice(SellingController):
|
||||
"account": self.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"against_type": "Account",
|
||||
"against": self.account_for_change_amount,
|
||||
"against_link": self.account_for_change_amount,
|
||||
"debit": flt(self.base_change_amount),
|
||||
"debit_in_account_currency": flt(self.base_change_amount)
|
||||
if self.party_account_currency == self.company_currency
|
||||
@@ -1536,7 +1553,9 @@ class SalesInvoice(SellingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.account_for_change_amount,
|
||||
"against_type": "Customer",
|
||||
"against": self.customer,
|
||||
"against_link": self.customer,
|
||||
"credit": self.base_change_amount,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
@@ -1562,7 +1581,9 @@ class SalesInvoice(SellingController):
|
||||
"account": self.debit_to,
|
||||
"party_type": "Customer",
|
||||
"party": self.customer,
|
||||
"against_type": "Account",
|
||||
"against": self.write_off_account,
|
||||
"against_link": self.write_off_account,
|
||||
"credit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
|
||||
"credit_in_account_currency": (
|
||||
flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
|
||||
@@ -1582,7 +1603,9 @@ class SalesInvoice(SellingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.write_off_account,
|
||||
"against_type": "Customer",
|
||||
"against": self.customer,
|
||||
"against_link": self.customer,
|
||||
"debit": flt(self.base_write_off_amount, self.precision("base_write_off_amount")),
|
||||
"debit_in_account_currency": (
|
||||
flt(self.base_write_off_amount, self.precision("base_write_off_amount"))
|
||||
@@ -1610,7 +1633,9 @@ class SalesInvoice(SellingController):
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": round_off_account,
|
||||
"against_type": "Customer",
|
||||
"against": self.customer,
|
||||
"against_link": self.customer,
|
||||
"credit_in_account_currency": flt(
|
||||
self.rounding_adjustment, self.precision("rounding_adjustment")
|
||||
),
|
||||
@@ -1969,9 +1994,9 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc
|
||||
if inter_company_reference:
|
||||
doc = frappe.get_doc(ref_doc, inter_company_reference)
|
||||
ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer
|
||||
if not frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") == party:
|
||||
if frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") != party:
|
||||
frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(_(partytype)))
|
||||
if not frappe.get_cached_value(ref_partytype, ref_party, "represents_company") == company:
|
||||
if frappe.get_cached_value(ref_partytype, ref_party, "represents_company") != company:
|
||||
frappe.throw(_("Invalid Company for Inter Company Transaction."))
|
||||
|
||||
elif frappe.db.get_value(partytype, {"name": party, internal: 1}, "name") == party:
|
||||
@@ -1981,7 +2006,7 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc
|
||||
filters={"parenttype": partytype, "parent": party},
|
||||
)
|
||||
companies = [d.company for d in companies]
|
||||
if not company in companies:
|
||||
if company not in companies:
|
||||
frappe.throw(
|
||||
_("{0} not allowed to transact with {1}. Please change the Company.").format(
|
||||
_(partytype), company
|
||||
@@ -2114,6 +2139,12 @@ def make_sales_return(source_name, target_doc=None):
|
||||
return make_return_doc("Sales Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
def set_account_for_mode_of_payment(self):
|
||||
for data in self.payments:
|
||||
if not data.account:
|
||||
data.account = get_bank_cash_account(data.mode_of_payment, self.company).get("account")
|
||||
|
||||
|
||||
def get_inter_company_details(doc, doctype):
|
||||
if doctype in ["Sales Invoice", "Sales Order", "Delivery Note"]:
|
||||
parties = frappe.db.get_all(
|
||||
|
||||
@@ -23,7 +23,7 @@ from erpnext.assets.doctype.asset.test_asset import create_asset, create_asset_d
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
get_depr_schedule,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import update_invoice_status
|
||||
from erpnext.controllers.accounts_controller import InvalidQtyError, update_invoice_status
|
||||
from erpnext.controllers.taxes_and_totals import get_itemised_tax_breakup_data
|
||||
from erpnext.exceptions import InvalidAccountCurrency, InvalidCurrency
|
||||
from erpnext.selling.doctype.customer.test_customer import get_customer_dict
|
||||
@@ -72,6 +72,16 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
def tearDownClass(self):
|
||||
unlink_payment_on_cancel_of_invoice(0)
|
||||
|
||||
def test_sales_invoice_qty(self):
|
||||
si = create_sales_invoice(qty=0, do_not_save=True)
|
||||
with self.assertRaises(InvalidQtyError):
|
||||
si.save()
|
||||
|
||||
# No error with qty=1
|
||||
si.items[0].qty = 1
|
||||
si.save()
|
||||
self.assertEqual(si.items[0].qty, 1)
|
||||
|
||||
def test_timestamp_change(self):
|
||||
w = frappe.copy_doc(test_records[0])
|
||||
w.docstatus = 0
|
||||
@@ -1088,44 +1098,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertEqual(pos.grand_total, 100.0)
|
||||
self.assertEqual(pos.write_off_amount, 10)
|
||||
|
||||
def test_ledger_entries_of_return_pos_invoice(self):
|
||||
make_pos_profile()
|
||||
|
||||
pos = create_sales_invoice(do_not_save=True)
|
||||
pos.is_pos = 1
|
||||
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 100})
|
||||
pos.save().submit()
|
||||
self.assertEqual(pos.outstanding_amount, 0.0)
|
||||
self.assertEqual(pos.status, "Paid")
|
||||
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
||||
|
||||
pos_return = make_sales_return(pos.name)
|
||||
pos_return.save().submit()
|
||||
pos_return.reload()
|
||||
pos.reload()
|
||||
self.assertEqual(pos_return.is_return, 1)
|
||||
self.assertEqual(pos_return.return_against, pos.name)
|
||||
self.assertEqual(pos_return.outstanding_amount, 0.0)
|
||||
self.assertEqual(pos_return.status, "Return")
|
||||
self.assertEqual(pos.outstanding_amount, 0.0)
|
||||
self.assertEqual(pos.status, "Credit Note Issued")
|
||||
|
||||
expected = (
|
||||
("Cash - _TC", 0.0, 100.0, pos_return.name, None),
|
||||
("Debtors - _TC", 0.0, 100.0, pos_return.name, pos_return.name),
|
||||
("Debtors - _TC", 100.0, 0.0, pos_return.name, pos_return.name),
|
||||
("Sales - _TC", 100.0, 0.0, pos_return.name, None),
|
||||
)
|
||||
res = frappe.db.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": pos_return.name, "is_cancelled": 0},
|
||||
fields=["account", "debit", "credit", "voucher_no", "against_voucher"],
|
||||
order_by="account, debit, credit",
|
||||
as_list=1,
|
||||
)
|
||||
self.assertEqual(expected, res)
|
||||
|
||||
def test_pos_with_no_gl_entry_for_change_amount(self):
|
||||
frappe.db.set_single_value("Accounts Settings", "post_change_gl_entries", 0)
|
||||
|
||||
@@ -1571,19 +1543,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
|
||||
self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
|
||||
|
||||
def test_return_invoice_with_account_mismatch(self):
|
||||
debtors2 = create_account(
|
||||
parent_account="Accounts Receivable - _TC",
|
||||
account_name="Debtors 2",
|
||||
company="_Test Company",
|
||||
account_type="Receivable",
|
||||
)
|
||||
si = create_sales_invoice(qty=1, rate=1000)
|
||||
cr_note = create_sales_invoice(
|
||||
qty=-1, rate=1000, is_return=1, return_against=si.name, debit_to=debtors2, do_not_save=True
|
||||
)
|
||||
self.assertRaises(frappe.ValidationError, cr_note.save)
|
||||
|
||||
def test_gle_made_when_asset_is_returned(self):
|
||||
create_asset_data()
|
||||
asset = create_asset(item_code="Macbook Pro")
|
||||
@@ -2572,12 +2531,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
"stock_received_but_not_billed",
|
||||
"Stock Received But Not Billed - _TC1",
|
||||
)
|
||||
frappe.db.set_value(
|
||||
"Company",
|
||||
"_Test Company 1",
|
||||
"expenses_included_in_valuation",
|
||||
"Expenses Included In Valuation - _TC1",
|
||||
)
|
||||
|
||||
# begin test
|
||||
si = create_sales_invoice(
|
||||
@@ -3602,33 +3555,6 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry")
|
||||
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
||||
|
||||
def test_pulling_advance_based_on_debit_to(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||
|
||||
debtors2 = create_account(
|
||||
parent_account="Accounts Receivable - _TC",
|
||||
account_name="Debtors 2",
|
||||
company="_Test Company",
|
||||
account_type="Receivable",
|
||||
)
|
||||
si = create_sales_invoice(do_not_submit=True)
|
||||
si.debit_to = debtors2
|
||||
si.save()
|
||||
|
||||
pe = create_payment_entry(
|
||||
company=si.company,
|
||||
payment_type="Receive",
|
||||
party_type="Customer",
|
||||
party=si.customer,
|
||||
paid_from=debtors2,
|
||||
paid_to="Cash - _TC",
|
||||
paid_amount=1000,
|
||||
)
|
||||
pe.submit()
|
||||
advances = si.get_advance_entries()
|
||||
self.assertEqual(1, len(advances))
|
||||
self.assertEqual(advances[0].reference_name, pe.name)
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
@@ -3720,7 +3646,7 @@ def create_sales_invoice(**args):
|
||||
bundle_id = None
|
||||
if si.update_stock and (args.get("batch_no") or args.get("serial_no")):
|
||||
batches = {}
|
||||
qty = args.qty or 1
|
||||
qty = args.qty if args.qty is not None else 1
|
||||
item_code = args.item or args.item_code or "_Test Item"
|
||||
if args.get("batch_no"):
|
||||
batches = frappe._dict({args.batch_no: qty})
|
||||
@@ -3752,7 +3678,7 @@ def create_sales_invoice(**args):
|
||||
"description": args.description or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"target_warehouse": args.target_warehouse,
|
||||
"qty": args.qty or 1,
|
||||
"qty": args.qty if args.qty is not None else 1,
|
||||
"uom": args.uom or "Nos",
|
||||
"stock_uom": args.uom or "Nos",
|
||||
"rate": args.rate if args.get("rate") is not None else 100,
|
||||
|
||||
@@ -83,17 +83,14 @@
|
||||
"quality_inspection",
|
||||
"pick_serial_and_batch",
|
||||
"serial_and_batch_bundle",
|
||||
"use_serial_batch_fields",
|
||||
"batch_no",
|
||||
"incoming_rate",
|
||||
"col_break5",
|
||||
"allow_zero_valuation_rate",
|
||||
"incoming_rate",
|
||||
"serial_no",
|
||||
"item_tax_rate",
|
||||
"actual_batch_qty",
|
||||
"actual_qty",
|
||||
"section_break_eoec",
|
||||
"serial_no",
|
||||
"column_break_ytgd",
|
||||
"batch_no",
|
||||
"edit_references",
|
||||
"sales_order",
|
||||
"so_detail",
|
||||
@@ -603,11 +600,12 @@
|
||||
"options": "Quality Inspection"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
@@ -623,12 +621,13 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Text",
|
||||
"fieldtype": "Small Text",
|
||||
"hidden": 1,
|
||||
"label": "Serial No",
|
||||
"oldfieldname": "serial_no",
|
||||
"oldfieldtype": "Small Text"
|
||||
"oldfieldtype": "Small Text",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_group",
|
||||
@@ -892,7 +891,6 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1 && (doc.use_serial_batch_fields === 0 || doc.docstatus === 1)",
|
||||
"fieldname": "serial_and_batch_bundle",
|
||||
"fieldtype": "Link",
|
||||
"label": "Serial and Batch Bundle",
|
||||
@@ -906,27 +904,12 @@
|
||||
"fieldname": "pick_serial_and_batch",
|
||||
"fieldtype": "Button",
|
||||
"label": "Pick Serial / Batch No"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "use_serial_batch_fields",
|
||||
"fieldtype": "Check",
|
||||
"label": "Use Serial No / Batch Fields"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.use_serial_batch_fields === 1 && parent.update_stock === 1",
|
||||
"fieldname": "section_break_eoec",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_ytgd",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-25 15:56:44.828634",
|
||||
"modified": "2023-12-29 13:03:14.121298",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -75,7 +75,7 @@ class SalesInvoiceItem(Document):
|
||||
sales_invoice_item: DF.Data | None
|
||||
sales_order: DF.Link | None
|
||||
serial_and_batch_bundle: DF.Link | None
|
||||
serial_no: DF.Text | None
|
||||
serial_no: DF.SmallText | None
|
||||
service_end_date: DF.Date | None
|
||||
service_start_date: DF.Date | None
|
||||
service_stop_date: DF.Date | None
|
||||
@@ -86,7 +86,6 @@ class SalesInvoiceItem(Document):
|
||||
target_warehouse: DF.Link | None
|
||||
total_weight: DF.Float
|
||||
uom: DF.Link
|
||||
use_serial_batch_fields: DF.Check
|
||||
warehouse: DF.Link | None
|
||||
weight_per_unit: DF.Float
|
||||
weight_uom: DF.Link | None
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"default",
|
||||
"mode_of_payment",
|
||||
"amount",
|
||||
"reference_no",
|
||||
"column_break_3",
|
||||
"account",
|
||||
"type",
|
||||
@@ -76,16 +75,11 @@
|
||||
"hidden": 1,
|
||||
"label": "Default",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_no",
|
||||
"fieldtype": "Data",
|
||||
"label": "Reference No"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-23 16:20:06.436979",
|
||||
"modified": "2020-08-03 12:45:39.986598",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Payment",
|
||||
@@ -93,6 +87,5 @@
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
@@ -23,7 +23,6 @@ class SalesInvoicePayment(Document):
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
reference_no: DF.Data | None
|
||||
type: DF.ReadOnly | None
|
||||
# end: auto-generated types
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Tax Rate",
|
||||
"label": "Rate",
|
||||
"oldfieldname": "rate",
|
||||
"oldfieldtype": "Currency"
|
||||
},
|
||||
@@ -218,7 +218,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-14 10:08:17.776528",
|
||||
"modified": "2022-10-17 13:08:17.776528",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Taxes and Charges",
|
||||
@@ -227,4 +227,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"creation": "2013-01-10 16:34:09",
|
||||
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain a list of tax heads and also other expense/income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.",
|
||||
"description": "Standard tax template that can be applied to all Sales Transactions. This template can contain list of tax heads and also other expense / income heads like \"Shipping\", \"Insurance\", \"Handling\" etc.\n\n#### Note\n\nThe tax rate you define here will be the standard tax rate for all **Items**. If there are **Items** that have different rates, they must be added in the **Item Tax** table in the **Item** master.\n\n#### Description of Columns\n\n1. Calculation Type: \n - This can be on **Net Total** (that is the sum of basic amount).\n - **On Previous Row Total / Amount** (for cumulative taxes or charges). If you select this option, the tax will be applied as a percentage of the previous row (in the tax table) amount or total.\n - **Actual** (as mentioned).\n2. Account Head: The Account ledger under which this tax will be booked\n3. Cost Center: If the tax / charge is an income (like shipping) or expense it needs to be booked against a Cost Center.\n4. Description: Description of the tax (that will be printed in invoices / quotes).\n5. Rate: Tax rate.\n6. Amount: Tax amount.\n7. Total: Cumulative total to this point.\n8. Enter Row: If based on \"Previous Row Total\" you can select the row number which will be taken as a base for this calculation (default is the previous row).\n9. Is this Tax included in Basic Rate?: If you check this, it means that this tax will not be shown below the item table, but will be included in the Basic Rate in your main item table. This is useful where you want give a flat price (inclusive of all taxes) price to customers.",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
@@ -79,7 +79,7 @@
|
||||
"icon": "fa fa-money",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2024-01-30 13:07:28.801104",
|
||||
"modified": "2022-05-16 16:14:52.061672",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Taxes and Charges Template",
|
||||
|
||||
@@ -142,12 +142,12 @@ class ShippingRule(Document):
|
||||
}
|
||||
if self.shipping_rule_type == "Selling":
|
||||
# check if not applied on purchase
|
||||
if not doc.meta.get_field("taxes").options == "Sales Taxes and Charges":
|
||||
if doc.meta.get_field("taxes").options != "Sales Taxes and Charges":
|
||||
frappe.throw(_("Shipping rule only applicable for Selling"))
|
||||
shipping_charge["doctype"] = "Sales Taxes and Charges"
|
||||
else:
|
||||
# check if not applied on sales
|
||||
if not doc.meta.get_field("taxes").options == "Purchase Taxes and Charges":
|
||||
if doc.meta.get_field("taxes").options != "Purchase Taxes and Charges":
|
||||
frappe.throw(_("Shipping rule only applicable for Buying"))
|
||||
|
||||
shipping_charge["doctype"] = "Purchase Taxes and Charges"
|
||||
|
||||
@@ -16,7 +16,6 @@ from frappe.utils.data import (
|
||||
date_diff,
|
||||
flt,
|
||||
get_last_day,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
nowdate,
|
||||
)
|
||||
@@ -318,37 +317,6 @@ class Subscription(Document):
|
||||
if self.is_new():
|
||||
self.set_subscription_status()
|
||||
|
||||
self.validate_party_billing_currency()
|
||||
|
||||
def validate_party_billing_currency(self):
|
||||
"""
|
||||
Subscription should be of the same currency as the Party's default billing currency or company default.
|
||||
"""
|
||||
if self.party:
|
||||
party_billing_currency = frappe.get_cached_value(
|
||||
self.party_type, self.party, "default_currency"
|
||||
) or frappe.get_cached_value("Company", self.company, "default_currency")
|
||||
|
||||
plans = [x.plan for x in self.plans]
|
||||
subscription_plan_currencies = frappe.db.get_all(
|
||||
"Subscription Plan", filters={"name": ("in", plans)}, fields=["name", "currency"]
|
||||
)
|
||||
unsupported_plans = []
|
||||
for x in subscription_plan_currencies:
|
||||
if x.currency != party_billing_currency:
|
||||
unsupported_plans.append("{0}".format(get_link_to_form("Subscription Plan", x.name)))
|
||||
|
||||
if unsupported_plans:
|
||||
unsupported_plans = [
|
||||
_(
|
||||
"Below Subscription Plans are of different currency to the party default billing currency/Company currency: {0}"
|
||||
).format(frappe.bold(party_billing_currency))
|
||||
] + unsupported_plans
|
||||
|
||||
frappe.throw(
|
||||
unsupported_plans, frappe.ValidationError, "Unsupported Subscription Plans", as_list=True
|
||||
)
|
||||
|
||||
def validate_trial_period(self) -> None:
|
||||
"""
|
||||
Runs sanity checks on trial period dates for the `Subscription`
|
||||
@@ -595,8 +563,6 @@ class Subscription(Document):
|
||||
) and self.can_generate_new_invoice(posting_date):
|
||||
self.generate_invoice(posting_date=posting_date)
|
||||
self.update_subscription_period(add_days(self.current_invoice_end, 1))
|
||||
elif posting_date and getdate(posting_date) > getdate(self.current_invoice_end):
|
||||
self.update_subscription_period()
|
||||
|
||||
if self.cancel_at_period_end and (
|
||||
getdate(posting_date) >= getdate(self.current_invoice_end)
|
||||
@@ -715,7 +681,7 @@ class Subscription(Document):
|
||||
to_generate_invoice = (
|
||||
True
|
||||
if self.status == "Active"
|
||||
and not self.generate_invoice_at == "Beginning of the current subscription period"
|
||||
and self.generate_invoice_at != "Beginning of the current subscription period"
|
||||
else False
|
||||
)
|
||||
self.status = "Cancelled"
|
||||
@@ -733,7 +699,7 @@ class Subscription(Document):
|
||||
subscription and the `Subscription` will lose all the history of generated invoices
|
||||
it has.
|
||||
"""
|
||||
if not self.status == "Cancelled":
|
||||
if self.status != "Cancelled":
|
||||
frappe.throw(_("You cannot restart a Subscription that is not cancelled."), InvoiceNotCancelled)
|
||||
|
||||
self.status = "Active"
|
||||
|
||||
@@ -460,13 +460,11 @@ class TestSubscription(FrappeTestCase):
|
||||
self.assertEqual(len(subscription.invoices), 1)
|
||||
|
||||
def test_multi_currency_subscription(self):
|
||||
party = "_Test Subscription Customer"
|
||||
frappe.db.set_value("Customer", party, "default_currency", "USD")
|
||||
subscription = create_subscription(
|
||||
start_date="2018-01-01",
|
||||
generate_invoice_at="Beginning of the current subscription period",
|
||||
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1, "currency": "USD"}],
|
||||
party=party,
|
||||
plans=[{"plan": "_Test Plan Multicurrency", "qty": 1}],
|
||||
party="_Test Subscription Customer",
|
||||
)
|
||||
|
||||
subscription.process()
|
||||
@@ -530,21 +528,13 @@ class TestSubscription(FrappeTestCase):
|
||||
|
||||
|
||||
def make_plans():
|
||||
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")
|
||||
create_plan(plan_name="_Test Plan Name 2", cost=1999, currency="INR")
|
||||
create_plan(plan_name="_Test Plan Name", cost=900)
|
||||
create_plan(plan_name="_Test Plan Name 2", cost=1999)
|
||||
create_plan(
|
||||
plan_name="_Test Plan Name 3",
|
||||
cost=1999,
|
||||
billing_interval="Day",
|
||||
billing_interval_count=14,
|
||||
currency="INR",
|
||||
plan_name="_Test Plan Name 3", cost=1999, billing_interval="Day", billing_interval_count=14
|
||||
)
|
||||
create_plan(
|
||||
plan_name="_Test Plan Name 4",
|
||||
cost=20000,
|
||||
billing_interval="Month",
|
||||
billing_interval_count=3,
|
||||
currency="INR",
|
||||
plan_name="_Test Plan Name 4", cost=20000, billing_interval="Month", billing_interval_count=3
|
||||
)
|
||||
create_plan(
|
||||
plan_name="_Test Plan Multicurrency", cost=50, billing_interval="Month", currency="USD"
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Link",
|
||||
"label": "Currency",
|
||||
"options": "Currency",
|
||||
"reqd": 1
|
||||
"options": "Currency"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
@@ -149,11 +148,10 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2024-01-14 17:59:34.687977",
|
||||
"modified": "2021-12-10 15:24:15.794477",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Subscription Plan",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -195,6 +193,5 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class SubscriptionPlan(Document):
|
||||
billing_interval_count: DF.Int
|
||||
cost: DF.Currency
|
||||
cost_center: DF.Link | None
|
||||
currency: DF.Link
|
||||
currency: DF.Link | None
|
||||
item: DF.Link
|
||||
payment_gateway: DF.Link | None
|
||||
plan_name: DF.Data
|
||||
|
||||
@@ -546,7 +546,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||
"GL Entry",
|
||||
{
|
||||
"is_cancelled": 0,
|
||||
"party_type": "Customer",
|
||||
"party": ["in", parties],
|
||||
"company": inv.company,
|
||||
"voucher_no": ["in", vouchers],
|
||||
@@ -561,7 +560,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||
conditions = []
|
||||
conditions.append(ple.amount.lt(0))
|
||||
conditions.append(ple.delinked == 0)
|
||||
conditions.append(ple.party_type == "Customer")
|
||||
conditions.append(ple.party.isin(parties))
|
||||
conditions.append(ple.voucher_no == ple.against_voucher_no)
|
||||
conditions.append(ple.company == inv.company)
|
||||
@@ -581,7 +579,6 @@ def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||
{
|
||||
"is_cancelled": 0,
|
||||
"credit": [">", 0],
|
||||
"party_type": "Customer",
|
||||
"party": ["in", parties],
|
||||
"posting_date": ["between", (tax_details.from_date, tax_details.to_date)],
|
||||
"company": inv.company,
|
||||
|
||||
@@ -8,7 +8,6 @@ from frappe.utils import today
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
|
||||
class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
|
||||
@@ -50,16 +49,6 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
|
||||
)
|
||||
return pe
|
||||
|
||||
def create_sales_order(self):
|
||||
so = make_sales_order(
|
||||
company=self.company,
|
||||
customer=self.customer,
|
||||
item=self.item,
|
||||
rate=100,
|
||||
transaction_date=today(),
|
||||
)
|
||||
return so
|
||||
|
||||
def test_01_unreconcile_invoice(self):
|
||||
si1 = self.create_sales_invoice()
|
||||
si2 = self.create_sales_invoice()
|
||||
@@ -325,41 +314,3 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
|
||||
),
|
||||
1,
|
||||
)
|
||||
|
||||
def test_05_unreconcile_order(self):
|
||||
so = self.create_sales_order()
|
||||
|
||||
pe = self.create_payment_entry()
|
||||
# Allocation payment against Sales Order
|
||||
pe.paid_amount = 100
|
||||
pe.append(
|
||||
"references",
|
||||
{"reference_doctype": so.doctype, "reference_name": so.name, "allocated_amount": 100},
|
||||
)
|
||||
pe.save().submit()
|
||||
|
||||
# Assert 'Advance Paid'
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, 100)
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
}
|
||||
)
|
||||
unreconcile.add_references()
|
||||
self.assertEqual(len(unreconcile.allocations), 1)
|
||||
allocations = [x.reference_name for x in unreconcile.allocations]
|
||||
self.assertEquals([so.name], allocations)
|
||||
# unreconcile so
|
||||
unreconcile.save().submit()
|
||||
|
||||
# Assert 'Advance Paid'
|
||||
so.reload()
|
||||
pe.reload()
|
||||
self.assertEqual(so.advance_paid, 0)
|
||||
self.assertEqual(len(pe.references), 0)
|
||||
self.assertEqual(pe.unallocated_amount, 100)
|
||||
|
||||
@@ -37,7 +37,7 @@ class UnreconcilePayment(Document):
|
||||
|
||||
def validate(self):
|
||||
self.supported_types = ["Payment Entry", "Journal Entry"]
|
||||
if not self.voucher_type in self.supported_types:
|
||||
if self.voucher_type not in self.supported_types:
|
||||
frappe.throw(_("Only {0} are supported").format(comma_and(self.supported_types)))
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -82,9 +82,6 @@ class UnreconcilePayment(Document):
|
||||
update_voucher_outstanding(
|
||||
alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
|
||||
)
|
||||
if doc.doctype in frappe.get_hooks("advance_payment_doctypes"):
|
||||
doc.set_total_advance_paid()
|
||||
|
||||
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
|
||||
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user