mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-05 14:40:52 +00:00
Compare commits
883 Commits
cache_jour
...
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.14.3"
|
||||
__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
|
||||
|
||||
@@ -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
|
||||
@@ -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,7 +8,7 @@ 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) {
|
||||
@@ -220,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];
|
||||
|
||||
@@ -591,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",
|
||||
|
||||
@@ -7,7 +7,6 @@ import json
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.utils import cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
|
||||
from frappe.utils.caching import redis_cache
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
|
||||
@@ -151,20 +150,6 @@ class JournalEntry(AccountsController):
|
||||
if not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
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()
|
||||
@@ -319,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],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -646,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"):
|
||||
@@ -765,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):
|
||||
@@ -982,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
|
||||
|
||||
@@ -1479,7 +1594,6 @@ def get_outstanding(args):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@redis_cache(ttl=5 * 60, user=True)
|
||||
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)
|
||||
@@ -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()
|
||||
|
||||
@@ -37,7 +37,9 @@
|
||||
"col_break3",
|
||||
"is_advance",
|
||||
"user_remark",
|
||||
"against_account"
|
||||
"against_type",
|
||||
"against_account",
|
||||
"against_account_link"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -250,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,
|
||||
@@ -280,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": "2023-11-23 11:44:25.841187",
|
||||
"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:
|
||||
@@ -835,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
|
||||
@@ -908,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"),
|
||||
@@ -928,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"),
|
||||
@@ -945,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,
|
||||
@@ -962,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,
|
||||
@@ -981,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"]),
|
||||
@@ -1001,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"),
|
||||
@@ -1056,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,
|
||||
@@ -1082,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,
|
||||
@@ -1095,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,
|
||||
@@ -1104,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"
|
||||
@@ -1129,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,
|
||||
@@ -1143,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(
|
||||
@@ -1190,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,
|
||||
@@ -1220,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
|
||||
@@ -1268,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"),
|
||||
},
|
||||
@@ -1287,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"),
|
||||
},
|
||||
@@ -1302,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,
|
||||
@@ -1323,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
|
||||
@@ -1344,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
|
||||
@@ -1368,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
|
||||
@@ -1388,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
|
||||
@@ -1415,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
|
||||
@@ -1433,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()
|
||||
|
||||
@@ -1531,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
|
||||
@@ -1985,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 = ""
|
||||
@@ -2129,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})
|
||||
@@ -2158,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,
|
||||
|
||||
@@ -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 = [
|
||||
@@ -742,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:
|
||||
@@ -1235,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
|
||||
@@ -1264,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"))
|
||||
@@ -1285,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,
|
||||
@@ -1353,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)
|
||||
@@ -1374,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"))
|
||||
@@ -1428,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,
|
||||
@@ -1444,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",
|
||||
},
|
||||
@@ -1471,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,
|
||||
},
|
||||
@@ -1490,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
|
||||
@@ -1514,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
|
||||
@@ -1535,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,
|
||||
},
|
||||
@@ -1561,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"))
|
||||
@@ -1581,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"))
|
||||
@@ -1609,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")
|
||||
),
|
||||
@@ -1968,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:
|
||||
@@ -1980,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
|
||||
@@ -2113,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -13,13 +13,9 @@ import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
)
|
||||
from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_filter import (
|
||||
get_dimension_filter_map,
|
||||
)
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
|
||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||
from erpnext.accounts.utils import create_payment_ledger_entry
|
||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||
|
||||
|
||||
def make_gl_entries(
|
||||
@@ -45,7 +41,7 @@ def make_gl_entries(
|
||||
from_repost=from_repost,
|
||||
)
|
||||
save_entries(gl_map, adv_adj, update_outstanding, from_repost)
|
||||
# Post GL Map proccess there may no be any GL Entries
|
||||
# Post GL Map process there may no be any GL Entries
|
||||
elif gl_map:
|
||||
frappe.throw(
|
||||
_(
|
||||
@@ -284,6 +280,7 @@ def check_if_in_list(gle, gl_map, dimensions=None):
|
||||
"project",
|
||||
"finance_book",
|
||||
"voucher_no",
|
||||
"against_link",
|
||||
]
|
||||
|
||||
if dimensions:
|
||||
@@ -359,7 +356,6 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
|
||||
|
||||
process_debit_credit_difference(gl_map)
|
||||
|
||||
dimension_filter_map = get_dimension_filter_map()
|
||||
if gl_map:
|
||||
check_freezing_date(gl_map[0]["posting_date"], adv_adj)
|
||||
is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
|
||||
@@ -367,7 +363,6 @@ def save_entries(gl_map, adv_adj, update_outstanding, from_repost=False):
|
||||
validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
|
||||
|
||||
for entry in gl_map:
|
||||
validate_allowed_dimensions(entry, dimension_filter_map)
|
||||
make_entry(entry, adv_adj, update_outstanding, from_repost)
|
||||
|
||||
|
||||
@@ -659,10 +654,10 @@ def check_freezing_date(posting_date, adv_adj=False):
|
||||
Hence stop admin to bypass if accounts are freezed
|
||||
"""
|
||||
if not adv_adj:
|
||||
acc_frozen_upto = frappe.db.get_value("Accounts Settings", None, "acc_frozen_upto")
|
||||
acc_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
|
||||
if acc_frozen_upto:
|
||||
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 getdate(posting_date) <= getdate(acc_frozen_upto) and (
|
||||
frozen_accounts_modifier not in frappe.get_roles() or frappe.session.user == "Administrator"
|
||||
@@ -706,39 +701,3 @@ def set_as_cancel(voucher_type, voucher_no):
|
||||
where voucher_type=%s and voucher_no=%s and is_cancelled = 0""",
|
||||
(now(), frappe.session.user, voucher_type, voucher_no),
|
||||
)
|
||||
|
||||
|
||||
def validate_allowed_dimensions(gl_entry, dimension_filter_map):
|
||||
for key, value in dimension_filter_map.items():
|
||||
dimension = key[0]
|
||||
account = key[1]
|
||||
|
||||
if gl_entry.account == account:
|
||||
if value["is_mandatory"] and not gl_entry.get(dimension):
|
||||
frappe.throw(
|
||||
_("{0} is mandatory for account {1}").format(
|
||||
frappe.bold(frappe.unscrub(dimension)), frappe.bold(gl_entry.account)
|
||||
),
|
||||
MandatoryAccountDimensionError,
|
||||
)
|
||||
|
||||
if value["allow_or_restrict"] == "Allow":
|
||||
if gl_entry.get(dimension) and gl_entry.get(dimension) not in value["allowed_dimensions"]:
|
||||
frappe.throw(
|
||||
_("Invalid value {0} for {1} against account {2}").format(
|
||||
frappe.bold(gl_entry.get(dimension)),
|
||||
frappe.bold(frappe.unscrub(dimension)),
|
||||
frappe.bold(gl_entry.account),
|
||||
),
|
||||
InvalidAccountDimensionError,
|
||||
)
|
||||
else:
|
||||
if gl_entry.get(dimension) and gl_entry.get(dimension) in value["allowed_dimensions"]:
|
||||
frappe.throw(
|
||||
_("Invalid value {0} for {1} against account {2}").format(
|
||||
frappe.bold(gl_entry.get(dimension)),
|
||||
frappe.bold(frappe.unscrub(dimension)),
|
||||
frappe.bold(gl_entry.account),
|
||||
),
|
||||
InvalidAccountDimensionError,
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ from frappe import _, msgprint, qb, scrub
|
||||
from frappe.contacts.doctype.address.address import get_company_address, get_default_address
|
||||
from frappe.core.doctype.user_permission.user_permission import get_permitted_documents
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.query_builder.functions import Abs, Count, Date, Sum
|
||||
from frappe.query_builder.functions import Abs, Date, Sum
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
@@ -771,7 +771,7 @@ def validate_party_frozen_disabled(party_type, party_name):
|
||||
frozen_accounts_modifier = frappe.db.get_single_value(
|
||||
"Accounts Settings", "frozen_accounts_modifier"
|
||||
)
|
||||
if not frozen_accounts_modifier in frappe.get_roles():
|
||||
if frozen_accounts_modifier not in frappe.get_roles():
|
||||
frappe.throw(_("{0} {1} is frozen").format(party_type, party_name), PartyFrozen)
|
||||
|
||||
elif party_type == "Employee":
|
||||
@@ -784,37 +784,34 @@ def get_timeline_data(doctype, name):
|
||||
from frappe.desk.form.load import get_communication_data
|
||||
|
||||
out = {}
|
||||
fields = "creation, count(*)"
|
||||
after = add_years(None, -1).strftime("%Y-%m-%d")
|
||||
group_by = "group by Date(creation)"
|
||||
|
||||
data = get_communication_data(
|
||||
doctype,
|
||||
name,
|
||||
after=after,
|
||||
group_by="group by communication_date",
|
||||
fields="C.communication_date as communication_date, count(C.name)",
|
||||
group_by="group by creation",
|
||||
fields="C.creation as creation, count(C.name)",
|
||||
as_dict=False,
|
||||
)
|
||||
|
||||
# fetch and append data from Activity Log
|
||||
activity_log = frappe.qb.DocType("Activity Log")
|
||||
data += (
|
||||
frappe.qb.from_(activity_log)
|
||||
.select(activity_log.communication_date, Count(activity_log.name))
|
||||
.where(
|
||||
(
|
||||
((activity_log.reference_doctype == doctype) & (activity_log.reference_name == name))
|
||||
| ((activity_log.timeline_doctype == doctype) & (activity_log.timeline_name == name))
|
||||
| (
|
||||
(activity_log.reference_doctype.isin(["Quotation", "Opportunity"]))
|
||||
& (activity_log.timeline_name == name)
|
||||
)
|
||||
)
|
||||
& (activity_log.status != "Success")
|
||||
& (activity_log.creation > after)
|
||||
)
|
||||
.groupby(activity_log.communication_date)
|
||||
.orderby(activity_log.communication_date, order=frappe.qb.desc)
|
||||
).run()
|
||||
data += frappe.db.sql(
|
||||
"""select {fields}
|
||||
from `tabActivity Log`
|
||||
where (reference_doctype=%(doctype)s and reference_name=%(name)s)
|
||||
or (timeline_doctype in (%(doctype)s) and timeline_name=%(name)s)
|
||||
or (reference_doctype in ("Quotation", "Opportunity") and timeline_name=%(name)s)
|
||||
and status!='Success' and creation > {after}
|
||||
{group_by} order by creation desc
|
||||
""".format(
|
||||
fields=fields, group_by=group_by, after=after
|
||||
),
|
||||
{"doctype": doctype, "name": name},
|
||||
as_dict=False,
|
||||
)
|
||||
|
||||
timeline_items = dict(data)
|
||||
|
||||
|
||||
@@ -144,6 +144,11 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"label": __("Show Future Payments"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "in_party_currency",
|
||||
"label": __("In Party Currency"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
@@ -153,12 +158,8 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "in_party_currency",
|
||||
"label": __("In Party Currency"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
|
||||
<h2 class="text-center" style="margin-top:0">{%= __(report.report_name) %}</h2>
|
||||
<h4 class="text-center">
|
||||
{% if (filters.party) { %}
|
||||
{%= __(filters.party) %}
|
||||
{% if (filters.customer_name) { %}
|
||||
{%= filters.customer_name %}
|
||||
{% } else { %}
|
||||
{%= filters.customer || filters.supplier %}
|
||||
{% } %}
|
||||
</h4>
|
||||
<h6 class="text-center">
|
||||
@@ -139,7 +141,7 @@
|
||||
<th style="width: 24%">{%= __("Reference") %}</th>
|
||||
{% } %}
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
<th style="width: 20%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
|
||||
<th style="width: 20%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
|
||||
{% } %}
|
||||
<th style="width: 10%; text-align: right">{%= __("Invoiced Amount") %}</th>
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
@@ -156,7 +158,7 @@
|
||||
<th style="width: 10%">{%= __("Remaining Balance") %}</th>
|
||||
{% } %}
|
||||
{% } else { %}
|
||||
<th style="width: 40%">{%= (filters.party) ? __("Remarks"): __("Party") %}</th>
|
||||
<th style="width: 40%">{%= (filters.customer || filters.supplier) ? __("Remarks"): __("Party") %}</th>
|
||||
<th style="width: 15%">{%= __("Total Invoiced Amount") %}</th>
|
||||
<th style="width: 15%">{%= __("Total Paid Amount") %}</th>
|
||||
<th style="width: 15%">{%= report.report_name === "Accounts Receivable Summary" ? __('Credit Note Amount') : __('Debit Note Amount') %}</th>
|
||||
@@ -185,7 +187,7 @@
|
||||
|
||||
{% if(!filters.show_future_payments) { %}
|
||||
<td>
|
||||
{% if(!(filters.party)) { %}
|
||||
{% if(!(filters.customer || filters.supplier)) { %}
|
||||
{%= data[i]["party"] %}
|
||||
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
|
||||
<br> {%= data[i]["customer_name"] %}
|
||||
@@ -258,7 +260,7 @@
|
||||
{% if(data[i]["party"]|| " ") { %}
|
||||
{% if(!data[i]["is_total_row"]) { %}
|
||||
<td>
|
||||
{% if(!(filters.party)) { %}
|
||||
{% if(!(filters.customer || filters.supplier)) { %}
|
||||
{%= data[i]["party"] %}
|
||||
{% if(data[i]["customer_name"] && data[i]["customer_name"] != data[i]["party"]) { %}
|
||||
<br> {%= data[i]["customer_name"] %}
|
||||
|
||||
@@ -176,6 +176,11 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "in_party_currency",
|
||||
"label": __("In Party Currency"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
@@ -185,12 +190,9 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "in_party_currency",
|
||||
"label": __("In Party Currency"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
|
||||
|
||||
],
|
||||
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user