Compare commits
869 Commits
v14.0.0-be
...
fix-error-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a3a0a83b7 | ||
|
|
b8d75ff241 | ||
|
|
a3b7682935 | ||
|
|
200f6da8b2 | ||
|
|
7c9018f401 | ||
|
|
ffadd671b7 | ||
|
|
47befa697d | ||
|
|
676c5280cc | ||
|
|
e0decb0ae2 | ||
|
|
b906cc20ae | ||
|
|
c98421c69a | ||
|
|
d7afb9ef65 | ||
|
|
a915b9cf72 | ||
|
|
a022e01d3f | ||
|
|
867cfa04b2 | ||
|
|
6e63dc1360 | ||
|
|
698214bd59 | ||
|
|
873d166a4e | ||
|
|
9166d58717 | ||
|
|
7895d2a048 | ||
|
|
5a06ee9230 | ||
|
|
b6609d1649 | ||
|
|
9431bb9466 | ||
|
|
fdd9cc76be | ||
|
|
f8348ab681 | ||
|
|
eecfb25c90 | ||
|
|
41a0e12954 | ||
|
|
f0383289d8 | ||
|
|
46209023ce | ||
|
|
e69bd39cdd | ||
|
|
0fcb3cd918 | ||
|
|
44ab131792 | ||
|
|
b648d77316 | ||
|
|
0012f0b2da | ||
|
|
1796f09c0f | ||
|
|
64b58b148f | ||
|
|
4415bf9968 | ||
|
|
1b7d94d70d | ||
|
|
952c60b3f5 | ||
|
|
8c33103838 | ||
|
|
a8c966eb25 | ||
|
|
88b1c1c87e | ||
|
|
771213c415 | ||
|
|
d86f5ec1ba | ||
|
|
a568fc7924 | ||
|
|
c040256793 | ||
|
|
29996ee726 | ||
|
|
1120506e11 | ||
|
|
e64751e3a2 | ||
|
|
bf47c6836e | ||
|
|
e80192e2da | ||
|
|
b2f1b02e34 | ||
|
|
ed090f2e3e | ||
|
|
d796172249 | ||
|
|
ff9f6366ad | ||
|
|
b8683d5532 | ||
|
|
4a156cdc2e | ||
|
|
771b076448 | ||
|
|
e6346ac982 | ||
|
|
547e173fe0 | ||
|
|
37bd0ecf87 | ||
|
|
8244980d79 | ||
|
|
10b239ec50 | ||
|
|
c9c4a9995b | ||
|
|
b6dc71679e | ||
|
|
5bdb6041b9 | ||
|
|
89828defc5 | ||
|
|
816236b587 | ||
|
|
69f17721ef | ||
|
|
125bb1f99a | ||
|
|
6f786b42a9 | ||
|
|
fed80177de | ||
|
|
2ce36d1edc | ||
|
|
7e44c30404 | ||
|
|
47ced6810f | ||
|
|
6c3f5687f2 | ||
|
|
1b632b683f | ||
|
|
c5660e8511 | ||
|
|
c4338d184e | ||
|
|
d598a61556 | ||
|
|
d262d0ac27 | ||
|
|
7dc2f95932 | ||
|
|
cf87c9138e | ||
|
|
1a42f82d14 | ||
|
|
2c5a0bff47 | ||
|
|
a831e6b552 | ||
|
|
f844c36ab2 | ||
|
|
091c2f3023 | ||
|
|
119c2f01e1 | ||
|
|
40aac908d1 | ||
|
|
4e6d588ae1 | ||
|
|
504f2f06d3 | ||
|
|
d96fd60878 | ||
|
|
d9a219850a | ||
|
|
cb6d884058 | ||
|
|
4102f799dc | ||
|
|
3c53c5b660 | ||
|
|
0a4b3d8129 | ||
|
|
bad489426a | ||
|
|
729e29d268 | ||
|
|
8108d4761b | ||
|
|
00cb04df84 | ||
|
|
dd0cefbeb9 | ||
|
|
d11c215f85 | ||
|
|
5ea5bfad3d | ||
|
|
4da5cb36e7 | ||
|
|
56b58cbeea | ||
|
|
cd87931cee | ||
|
|
355ae1fca6 | ||
|
|
91baa22d59 | ||
|
|
af57e1e299 | ||
|
|
1d3ba46107 | ||
|
|
65a590ced1 | ||
|
|
1a68e10742 | ||
|
|
71f676eedd | ||
|
|
4fe827e86b | ||
|
|
f573840c9f | ||
|
|
0bc83deca0 | ||
|
|
696a01765b | ||
|
|
047debffeb | ||
|
|
e57037b4cf | ||
|
|
633cd0c1a8 | ||
|
|
51b2fcc8ef | ||
|
|
ff062d501f | ||
|
|
2cbd5a9fcf | ||
|
|
89e421a01d | ||
|
|
287a7b29e1 | ||
|
|
47a7eeca54 | ||
|
|
a09647b3c8 | ||
|
|
62bbf0fe45 | ||
|
|
ae8c1ae311 | ||
|
|
c1c5aa7262 | ||
|
|
b4f06c0d77 | ||
|
|
758939eddb | ||
|
|
8f5ab94b70 | ||
|
|
fa944382c5 | ||
|
|
9507b2d752 | ||
|
|
7e018f94ce | ||
|
|
2cfafede44 | ||
|
|
df1f8fddf6 | ||
|
|
a17fed9cd9 | ||
|
|
d8479a41e5 | ||
|
|
b1244df045 | ||
|
|
d2f5d31f98 | ||
|
|
28aa9a7215 | ||
|
|
44ee44dec5 | ||
|
|
bebd77c27d | ||
|
|
0c55a98190 | ||
|
|
32d72fdecb | ||
|
|
046ec928e0 | ||
|
|
0660d6ed01 | ||
|
|
877820b902 | ||
|
|
ab0e381cfc | ||
|
|
5e34cdf00f | ||
|
|
953073bc77 | ||
|
|
4ecb798585 | ||
|
|
91d269fe1a | ||
|
|
4535a9415f | ||
|
|
e17713c9d6 | ||
|
|
21a5498d5d | ||
|
|
c0b17edbbf | ||
|
|
e6909a0899 | ||
|
|
68cce8eb98 | ||
|
|
869c1beb0a | ||
|
|
e9ed379b57 | ||
|
|
a453fc6385 | ||
|
|
00978a195a | ||
|
|
4d0d642db7 | ||
|
|
f02438eb54 | ||
|
|
62fa1f0305 | ||
|
|
77d4849ce8 | ||
|
|
66286881f3 | ||
|
|
c2e4bbe9cc | ||
|
|
02984551ce | ||
|
|
42f0a97d26 | ||
|
|
fe4df3a14a | ||
|
|
ff570f48a0 | ||
|
|
e4b89d2fcd | ||
|
|
3529622a0d | ||
|
|
477d36413b | ||
|
|
b5fc3076bf | ||
|
|
2c9162160a | ||
|
|
8f98238114 | ||
|
|
86e3adf344 | ||
|
|
d6152df3b4 | ||
|
|
4837238f3d | ||
|
|
e4f12f0458 | ||
|
|
3e7a029869 | ||
|
|
23431cf261 | ||
|
|
57e66f958c | ||
|
|
05374cb8b2 | ||
|
|
f572a4e0e5 | ||
|
|
36b519c962 | ||
|
|
348f082490 | ||
|
|
49174cd414 | ||
|
|
15c9c08261 | ||
|
|
5d1de91b68 | ||
|
|
cf6e10ac7b | ||
|
|
6d99bb5ce6 | ||
|
|
6b38778dcb | ||
|
|
7c47f36a4c | ||
|
|
6ce2111b6d | ||
|
|
f0af24fc6d | ||
|
|
b478e72cef | ||
|
|
471a8ddce0 | ||
|
|
7fc7405be7 | ||
|
|
26c16412bb | ||
|
|
c8396f4970 | ||
|
|
aeffaf312a | ||
|
|
23863c7663 | ||
|
|
887e3765bf | ||
|
|
633847c514 | ||
|
|
72986abab9 | ||
|
|
6b32fa7b1c | ||
|
|
212492220d | ||
|
|
fbae3c7fd6 | ||
|
|
745abef0e7 | ||
|
|
1acdadea00 | ||
|
|
2ee9da3504 | ||
|
|
6eb9a114be | ||
|
|
e8de8b8454 | ||
|
|
1b9e96f760 | ||
|
|
0e59873cc0 | ||
|
|
1063ced7cd | ||
|
|
904c592978 | ||
|
|
7a8ff85308 | ||
|
|
8a8921fb5b | ||
|
|
705f094a1a | ||
|
|
19b06b0745 | ||
|
|
2108bf9f73 | ||
|
|
677a59b005 | ||
|
|
af531372da | ||
|
|
986ca8815d | ||
|
|
646f8b7871 | ||
|
|
08e24bd907 | ||
|
|
e5a062ceb6 | ||
|
|
9ebabb86b3 | ||
|
|
5a8d57f2ae | ||
|
|
47c6fba317 | ||
|
|
19446a8ddd | ||
|
|
fec19a9532 | ||
|
|
6ba1b03ee5 | ||
|
|
1ba4afa986 | ||
|
|
10cffe81b0 | ||
|
|
72c22a4219 | ||
|
|
684ca451d3 | ||
|
|
8cdd6eacdd | ||
|
|
2dc590c0c0 | ||
|
|
55393d160d | ||
|
|
9b46dd512b | ||
|
|
7aaecbcac9 | ||
|
|
846d128c5d | ||
|
|
c800ff5068 | ||
|
|
de8b3570f5 | ||
|
|
5978286b52 | ||
|
|
d6ed6d53e9 | ||
|
|
a741fd1cfe | ||
|
|
666eaae6ce | ||
|
|
9ffb65b5a4 | ||
|
|
8c01ae952b | ||
|
|
e8cf32e1c8 | ||
|
|
e6d0a57f0c | ||
|
|
5451aed115 | ||
|
|
bd1c823aa6 | ||
|
|
6ed40463a0 | ||
|
|
8dfdab9dc1 | ||
|
|
e990bb8816 | ||
|
|
e2eb72eb5b | ||
|
|
450e1e8ed4 | ||
|
|
24c08f5092 | ||
|
|
adc5bf7b9c | ||
|
|
e9683fad47 | ||
|
|
5a34520f3f | ||
|
|
8b3ef8e96e | ||
|
|
79d1cf161e | ||
|
|
fb55b57f5c | ||
|
|
0fb112107f | ||
|
|
4c51002cb2 | ||
|
|
abd3aee5b5 | ||
|
|
6d2d97bac4 | ||
|
|
978028c880 | ||
|
|
05663268e6 | ||
|
|
4bd1bceee2 | ||
|
|
7d733af983 | ||
|
|
a87cc12024 | ||
|
|
d1a4761955 | ||
|
|
9adb4c8b69 | ||
|
|
0572c0ae3e | ||
|
|
9e0fb74ab2 | ||
|
|
604f13c69e | ||
|
|
397fad7eb2 | ||
|
|
5f4a58fb83 | ||
|
|
50fe23308a | ||
|
|
247d9bf5c4 | ||
|
|
2ae48eeac8 | ||
|
|
9659acb31e | ||
|
|
e4cbe12a71 | ||
|
|
a3d64c6cb9 | ||
|
|
52157cc000 | ||
|
|
2a9fbc609d | ||
|
|
4081ecfcd7 | ||
|
|
e6771f43a7 | ||
|
|
cf2d1681d8 | ||
|
|
d76e5dcb93 | ||
|
|
3c3f0adbd9 | ||
|
|
b26da043c4 | ||
|
|
d5f4160260 | ||
|
|
7112c6b9d3 | ||
|
|
e8411e8bc2 | ||
|
|
49fa06f849 | ||
|
|
ca2420f921 | ||
|
|
0b07107144 | ||
|
|
10754831c3 | ||
|
|
a3db15ccb7 | ||
|
|
9fda447dd9 | ||
|
|
c9e7e72a29 | ||
|
|
0b296e2190 | ||
|
|
c161daa4be | ||
|
|
1fa4962723 | ||
|
|
9c35e3aa89 | ||
|
|
86220e9ed6 | ||
|
|
b86454e7f4 | ||
|
|
8fd1aec76c | ||
|
|
9708bf3750 | ||
|
|
173ae56aec | ||
|
|
f101241907 | ||
|
|
0bb16b6b59 | ||
|
|
7d61181f8b | ||
|
|
e8edeecc05 | ||
|
|
5c9f2bf4a4 | ||
|
|
dab0fe56f1 | ||
|
|
3887a67f7e | ||
|
|
52a99d8da6 | ||
|
|
dc5f7a0c09 | ||
|
|
64796d3029 | ||
|
|
72d1cf0537 | ||
|
|
bb8d90d741 | ||
|
|
e39db1abe3 | ||
|
|
71e4230ab0 | ||
|
|
5c1f0c98f8 | ||
|
|
00c4e346c7 | ||
|
|
74fa6fab4c | ||
|
|
dd352df168 | ||
|
|
0ecaf23722 | ||
|
|
becf471a3a | ||
|
|
75e108ab81 | ||
|
|
8337ec9fe0 | ||
|
|
a33de1bdfe | ||
|
|
135e3b0f09 | ||
|
|
c25bc3403d | ||
|
|
3b7e981ccd | ||
|
|
3cac39c584 | ||
|
|
a6a564c43d | ||
|
|
0950e246c3 | ||
|
|
ef4b1dba26 | ||
|
|
b1eb0c5c75 | ||
|
|
064ce90e8c | ||
|
|
5126b3f93e | ||
|
|
81dbf1ed00 | ||
|
|
70c5533646 | ||
|
|
fea6291399 | ||
|
|
47c548a070 | ||
|
|
2d3c036070 | ||
|
|
30e02f092d | ||
|
|
67174ef1b3 | ||
|
|
7dce900ff5 | ||
|
|
d7e7a5519a | ||
|
|
d623eac084 | ||
|
|
c128618185 | ||
|
|
d67460bf22 | ||
|
|
7f1d611b47 | ||
|
|
4727bf5806 | ||
|
|
29e0d315db | ||
|
|
5e97d1b620 | ||
|
|
255155c5b8 | ||
|
|
210fc4481a | ||
|
|
4f3e2240b8 | ||
|
|
ede188d138 | ||
|
|
454630f2bd | ||
|
|
40fa4cf77c | ||
|
|
e8eb137cd4 | ||
|
|
821d55b0e0 | ||
|
|
f28cb55d0f | ||
|
|
adb07ebe09 | ||
|
|
2565b1fb33 | ||
|
|
1708e8b7ce | ||
|
|
61e71d45fa | ||
|
|
05e06e8455 | ||
|
|
6bab2b0f86 | ||
|
|
1ff2562f98 | ||
|
|
e9da4ed34d | ||
|
|
aee2e04a45 | ||
|
|
d3330cec87 | ||
|
|
4b2be2999f | ||
|
|
477870eb78 | ||
|
|
a32bb25407 | ||
|
|
99ec4cc7fe | ||
|
|
910b9bc3f4 | ||
|
|
513822488e | ||
|
|
1a17f0f844 | ||
|
|
853f479f58 | ||
|
|
9d05b2ebfb | ||
|
|
6a9dcf2558 | ||
|
|
96aee284d2 | ||
|
|
0cc301a82b | ||
|
|
0228db1e61 | ||
|
|
37e7b3e3ac | ||
|
|
115fb944c2 | ||
|
|
20c7237de6 | ||
|
|
debffbfb2d | ||
|
|
17bf32d8dc | ||
|
|
758a23eff7 | ||
|
|
b5ef69dd34 | ||
|
|
4538071e0d | ||
|
|
361605a2b6 | ||
|
|
c757198d8b | ||
|
|
39ff46afa8 | ||
|
|
9e399ecc87 | ||
|
|
d0b4aa7504 | ||
|
|
890705221e | ||
|
|
3c0883d69b | ||
|
|
4ca150f4ca | ||
|
|
18680990bf | ||
|
|
f2c4c1dc0e | ||
|
|
fd8482757e | ||
|
|
34546d02be | ||
|
|
6bfd5daba0 | ||
|
|
2d9138ab64 | ||
|
|
f10fdb4eeb | ||
|
|
ba73df161d | ||
|
|
bb7ad885da | ||
|
|
288d0191ab | ||
|
|
b9a33eca97 | ||
|
|
1ecec7154b | ||
|
|
2b56e62805 | ||
|
|
084b27bb34 | ||
|
|
9039effd69 | ||
|
|
3e177ede13 | ||
|
|
fd8657d2f7 | ||
|
|
498377fbbc | ||
|
|
7ef5952176 | ||
|
|
8478a83148 | ||
|
|
fc9557bbb6 | ||
|
|
b14a87eb2b | ||
|
|
e52b0612fc | ||
|
|
ae40c2ad34 | ||
|
|
d730f0e092 | ||
|
|
39a3ffcb56 | ||
|
|
80b3a032fb | ||
|
|
58ef1e30a5 | ||
|
|
c37dc7ca33 | ||
|
|
0967d226ed | ||
|
|
272ca96d2c | ||
|
|
3b23341d35 | ||
|
|
316b65face | ||
|
|
0fa13fb3f1 | ||
|
|
55781171cc | ||
|
|
6ab58c7faf | ||
|
|
cacecbbaa5 | ||
|
|
d2d9f7e758 | ||
|
|
416d1ceb6f | ||
|
|
fdfae33ff4 | ||
|
|
1f147e5b9e | ||
|
|
7a7665d1d0 | ||
|
|
20f1b74024 | ||
|
|
f9a1b6682c | ||
|
|
0fd037eb6a | ||
|
|
10a450cb2c | ||
|
|
c5efb6dc35 | ||
|
|
258efd5949 | ||
|
|
6751290988 | ||
|
|
7c09a79dd4 | ||
|
|
e10fede1ae | ||
|
|
6d7ecb9290 | ||
|
|
cf4ff46c1e | ||
|
|
6440e4f970 | ||
|
|
702eea3b54 | ||
|
|
e362e23941 | ||
|
|
68482b223f | ||
|
|
9d0b5f1a0d | ||
|
|
5a9750c144 | ||
|
|
4069f6c9ac | ||
|
|
4fe475b93d | ||
|
|
f0bcb753fb | ||
|
|
77aef302e4 | ||
|
|
b92310eb35 | ||
|
|
9ad5de2e57 | ||
|
|
187c3f9630 | ||
|
|
1c63cbb1e4 | ||
|
|
0519b59aa9 | ||
|
|
81391ed54f | ||
|
|
8746466412 | ||
|
|
76f1ed624a | ||
|
|
71e1615925 | ||
|
|
331ec13012 | ||
|
|
d382c3c101 | ||
|
|
c4bda6374b | ||
|
|
5518900af9 | ||
|
|
d81e837667 | ||
|
|
7ab858a9b0 | ||
|
|
1f1841d232 | ||
|
|
883e1394ba | ||
|
|
22dc317f20 | ||
|
|
95208ab274 | ||
|
|
0f00bcda34 | ||
|
|
c31bf155f0 | ||
|
|
1063d85569 | ||
|
|
1b338d678a | ||
|
|
d641dd68d4 | ||
|
|
634abb783f | ||
|
|
155df936cd | ||
|
|
d5d193b015 | ||
|
|
891bccf2c1 | ||
|
|
0c4f29edcf | ||
|
|
ead1397a75 | ||
|
|
63cf012c24 | ||
|
|
a1e5a64a66 | ||
|
|
2dc2dac56a | ||
|
|
366eb86fdf | ||
|
|
c1354047a3 | ||
|
|
45059571f0 | ||
|
|
a3c12939ed | ||
|
|
fb8c917b97 | ||
|
|
f20913fb69 | ||
|
|
fd44b6b340 | ||
|
|
bafc9ddde4 | ||
|
|
aa04051416 | ||
|
|
d9b9888ad5 | ||
|
|
076e8e9da0 | ||
|
|
03dcecff67 | ||
|
|
a0cf396712 | ||
|
|
3d87d9f1d3 | ||
|
|
b2d3b8a5ff | ||
|
|
dc948cab3e | ||
|
|
f71ff830ef | ||
|
|
085585cbe2 | ||
|
|
ee76032fca | ||
|
|
d88346c6cd | ||
|
|
1e97f87c5b | ||
|
|
c4a845d29b | ||
|
|
1b3ff3a8f3 | ||
|
|
8547cb5448 | ||
|
|
0767d2dac2 | ||
|
|
1eb2526d0b | ||
|
|
64fab5b7d1 | ||
|
|
85fe755f89 | ||
|
|
03039b9e00 | ||
|
|
21d5b3cf4d | ||
|
|
25305cbeb3 | ||
|
|
69d88a9212 | ||
|
|
485b55a799 | ||
|
|
8dae084f23 | ||
|
|
d3702947b8 | ||
|
|
dd1aa5559a | ||
|
|
a9e78e2d3a | ||
|
|
6d28507def | ||
|
|
31561c3f9a | ||
|
|
e553fa22f3 | ||
|
|
7c31e1f8bf | ||
|
|
9ff14f8609 | ||
|
|
c5c78641e1 | ||
|
|
409cc95b7b | ||
|
|
2ad531f66b | ||
|
|
06a6fd512a | ||
|
|
6ed52799d7 | ||
|
|
614ee71778 | ||
|
|
6e136c4377 | ||
|
|
8db44189b6 | ||
|
|
57972cab48 | ||
|
|
55ff6277a7 | ||
|
|
d12fe79494 | ||
|
|
c3b1376517 | ||
|
|
58dca11cae | ||
|
|
ed617d0939 | ||
|
|
f7f573b11b | ||
|
|
6636d857f7 | ||
|
|
466ff46045 | ||
|
|
abde7fc085 | ||
|
|
970e5af051 | ||
|
|
fb6af0481c | ||
|
|
e0649c132e | ||
|
|
f5afd51fa4 | ||
|
|
a87139e16a | ||
|
|
da54f0a583 | ||
|
|
2cb8cc525c | ||
|
|
fb94726d26 | ||
|
|
5a06618280 | ||
|
|
16fbee30a6 | ||
|
|
327be1cd9d | ||
|
|
6609321399 | ||
|
|
7f27586cbe | ||
|
|
4837d8872e | ||
|
|
014df08e7b | ||
|
|
09fb90b8ac | ||
|
|
7b9a23eb7a | ||
|
|
0fe6995816 | ||
|
|
f8ec0b6a86 | ||
|
|
c703ce1c61 | ||
|
|
4c3034ad79 | ||
|
|
1be810479c | ||
|
|
d97a87e28d | ||
|
|
7ac4916191 | ||
|
|
4eb7c2a011 | ||
|
|
dc6a8863e9 | ||
|
|
c1f6898f1a | ||
|
|
fcb17f047d | ||
|
|
688fe4192c | ||
|
|
2b875bbf52 | ||
|
|
1810b73113 | ||
|
|
2ea108ae92 | ||
|
|
2f71b740fd | ||
|
|
5b411dc1f6 | ||
|
|
f13ae4de0b | ||
|
|
b3ffa0eb57 | ||
|
|
8220117500 | ||
|
|
74073ddc85 | ||
|
|
8474961b79 | ||
|
|
487952a04e | ||
|
|
c7508a034a | ||
|
|
38898d33c6 | ||
|
|
8c4d80a58a | ||
|
|
9e96861f15 | ||
|
|
b58496dda4 | ||
|
|
0bf9d1b29f | ||
|
|
a7cdba24bc | ||
|
|
f84740e6e4 | ||
|
|
24b2a31581 | ||
|
|
f7e0edecc9 | ||
|
|
d55d200b47 | ||
|
|
c8092b7e7a | ||
|
|
795efcd017 | ||
|
|
1604b6cc63 | ||
|
|
27fad29ad6 | ||
|
|
0476accf26 | ||
|
|
a14b93d0d8 | ||
|
|
341d9b4b6d | ||
|
|
925a4a28e2 | ||
|
|
ac0800511d | ||
|
|
d360819384 | ||
|
|
ff6cda8547 | ||
|
|
60c06d3194 | ||
|
|
5320f3e5ea | ||
|
|
cc7ed1573a | ||
|
|
b932b3f252 | ||
|
|
1e3a6a8a98 | ||
|
|
0a23328151 | ||
|
|
f08d7410be | ||
|
|
280f615ded | ||
|
|
352157c9fc | ||
|
|
85f582b145 | ||
|
|
3d047b83fd | ||
|
|
6814509f07 | ||
|
|
dbca11071e | ||
|
|
dd688db54c | ||
|
|
be57dee57a | ||
|
|
67dbb2bd7f | ||
|
|
571178ffbe | ||
|
|
9542da80c5 | ||
|
|
593ab98575 | ||
|
|
43813875ea | ||
|
|
d0e393a4cc | ||
|
|
05c7905fa3 | ||
|
|
3f05d928a3 | ||
|
|
2fd823ffb6 | ||
|
|
52570cc1f9 | ||
|
|
5c6f6c16d6 | ||
|
|
ee9b6d158a | ||
|
|
c192e9457e | ||
|
|
74af3be968 | ||
|
|
02a23bae58 | ||
|
|
df32fe3d49 | ||
|
|
92b3743c54 | ||
|
|
b42c23cad6 | ||
|
|
23c713cc9b | ||
|
|
f0d3a074e0 | ||
|
|
9bb69e711a | ||
|
|
e536f6d13f | ||
|
|
f37747da25 | ||
|
|
cada9b679a | ||
|
|
25b705e2ad | ||
|
|
ecbb59a1ce | ||
|
|
9f79415186 | ||
|
|
87326dd489 | ||
|
|
8ea5782c69 | ||
|
|
6a35d580e4 | ||
|
|
77ad668a6f | ||
|
|
5e1ed2d7eb | ||
|
|
36f18935d3 | ||
|
|
01a538123b | ||
|
|
44919ac807 | ||
|
|
e555e8cf05 | ||
|
|
0f15ded0cd | ||
|
|
46372fe5cd | ||
|
|
6950844a74 | ||
|
|
8cd3ffc84d | ||
|
|
9c1d739946 | ||
|
|
5e17b82779 | ||
|
|
9c401e75bb | ||
|
|
c8449702b4 | ||
|
|
90818d57f1 | ||
|
|
0a5dff1e1f | ||
|
|
333d962ac2 | ||
|
|
2730f51ca9 | ||
|
|
f8a47525e1 | ||
|
|
ace8cf965d | ||
|
|
1c23544c42 | ||
|
|
e7143d8711 | ||
|
|
94f2c41475 | ||
|
|
48a11591cc | ||
|
|
6e921b1ccc | ||
|
|
133486a5c7 | ||
|
|
71b0e0a146 | ||
|
|
94030e08f1 | ||
|
|
663e550824 | ||
|
|
d076ba5c94 | ||
|
|
3c9b8dce21 | ||
|
|
64dfbfaecb | ||
|
|
1351d6e3be | ||
|
|
2aa0daf47b | ||
|
|
5fddd27cab | ||
|
|
321dd33015 | ||
|
|
0f2f11cb33 | ||
|
|
fc717665ea | ||
|
|
560483eb98 | ||
|
|
6aed9e26ac | ||
|
|
20a5795d67 | ||
|
|
434692ad34 | ||
|
|
34df274bf2 | ||
|
|
7881536e09 | ||
|
|
cb583a349f | ||
|
|
a2a5800b23 | ||
|
|
ffb174c489 | ||
|
|
adfa11d449 | ||
|
|
9209c1f91a | ||
|
|
2416634385 | ||
|
|
1fec5bbdb6 | ||
|
|
262c1823a5 | ||
|
|
703b081172 | ||
|
|
f3ae956eae | ||
|
|
6691d2ca54 | ||
|
|
fd325a123c | ||
|
|
a16ab92e00 | ||
|
|
cf9734f98a | ||
|
|
42b340cc66 | ||
|
|
c26f95e3b2 | ||
|
|
e3422f4ba8 | ||
|
|
0992b2eeef | ||
|
|
1854581d33 | ||
|
|
480dd8b056 | ||
|
|
8da3a5cdd4 | ||
|
|
ce44e11caf | ||
|
|
d477b99c1d | ||
|
|
20bc65c97b | ||
|
|
c6c2773e02 | ||
|
|
950521299a | ||
|
|
c82611aa62 | ||
|
|
1110f88e5a | ||
|
|
b57521a337 | ||
|
|
c2b5b0edee | ||
|
|
75f23aed1c | ||
|
|
c02e42ff84 | ||
|
|
a21f76f2a1 | ||
|
|
37a886a8a9 | ||
|
|
7ef2af203f | ||
|
|
ece4f391ac | ||
|
|
ea96afca62 | ||
|
|
401cbc01a9 | ||
|
|
bb60523fa7 | ||
|
|
dfadae6a92 | ||
|
|
fed3b575eb | ||
|
|
f14596a8f3 | ||
|
|
449c58d809 | ||
|
|
a1a4e8d616 | ||
|
|
e60a349432 | ||
|
|
872cd1cac8 | ||
|
|
d160e73c03 | ||
|
|
905aebc310 | ||
|
|
47c2317b1a | ||
|
|
cf88b517c8 | ||
|
|
9920747a26 | ||
|
|
f39c6db08a | ||
|
|
c844f3a612 | ||
|
|
2107883301 | ||
|
|
66a7924d97 | ||
|
|
bcaea4932d | ||
|
|
cc57be51be | ||
|
|
05b93063aa | ||
|
|
1701125fca | ||
|
|
db792c307f | ||
|
|
7ed8c8c11f | ||
|
|
2d13d5ec31 | ||
|
|
e81a7cf44e | ||
|
|
edb38ad4c7 | ||
|
|
69d5e2a6f8 | ||
|
|
e957c026bb | ||
|
|
f34f0a40c4 | ||
|
|
697a8bce7f | ||
|
|
8032095d81 | ||
|
|
025f4b21de | ||
|
|
867ed3a1e4 | ||
|
|
2f9f562046 | ||
|
|
d6a32ea832 | ||
|
|
4ba78e73dd | ||
|
|
cdcb18bd16 | ||
|
|
791e244b4f | ||
|
|
bc3ab093b8 | ||
|
|
5f3e4f0e82 | ||
|
|
62faa5ba1f | ||
|
|
e000be5145 | ||
|
|
acfdc6f22e | ||
|
|
4f73deeb78 | ||
|
|
fd3ce1b573 | ||
|
|
8a25523ad7 | ||
|
|
6799eb98db | ||
|
|
ea4ec3bc7c | ||
|
|
0712cafa0d | ||
|
|
50d7989e50 | ||
|
|
9499db8cd9 | ||
|
|
bba0bc874a | ||
|
|
82cc7f1027 | ||
|
|
fe47761f20 | ||
|
|
2bc68f3f28 | ||
|
|
681ada9aaf | ||
|
|
a0bbe7fea7 | ||
|
|
6a802f354b | ||
|
|
321b8afbff | ||
|
|
a975f9915b | ||
|
|
163a62476e | ||
|
|
acb6ce8f95 | ||
|
|
7a278208c8 | ||
|
|
1ebb1b822c | ||
|
|
b52d283739 | ||
|
|
4356ffbfe8 | ||
|
|
eb955c7e99 | ||
|
|
28c2f5d832 | ||
|
|
a6deace37c | ||
|
|
58d08ee307 | ||
|
|
d22951b014 | ||
|
|
c376b67725 | ||
|
|
fdcfa41a6f | ||
|
|
4a0136b524 | ||
|
|
cc402a5d0c | ||
|
|
b2e607bb55 | ||
|
|
ed0aabbb19 | ||
|
|
8bc90a65e4 | ||
|
|
94d0149756 | ||
|
|
c2bd7e504d | ||
|
|
f10a08ad6b | ||
|
|
6d85e821c1 | ||
|
|
aff53b762e | ||
|
|
be68f75348 | ||
|
|
470ec148a4 | ||
|
|
fe6e77791b | ||
|
|
f42716f348 | ||
|
|
cf3f2eadfb | ||
|
|
075ce7f01a | ||
|
|
0b3921e3cf | ||
|
|
0783e3deac | ||
|
|
1c8ee168fb | ||
|
|
fc40b57c21 | ||
|
|
e13f7622ba | ||
|
|
aa989a95e3 | ||
|
|
31d97b43e6 | ||
|
|
d93e3a32da | ||
|
|
333d08f459 | ||
|
|
2a3f5da6af | ||
|
|
862a924cd2 | ||
|
|
675e2af3ea | ||
|
|
978e8aa31c |
4
.flake8
4
.flake8
@@ -28,10 +28,6 @@ ignore =
|
|||||||
B007,
|
B007,
|
||||||
B950,
|
B950,
|
||||||
W191,
|
W191,
|
||||||
E124, # closing bracket, irritating while writing QB code
|
|
||||||
E131, # continuation line unaligned for hanging indent
|
|
||||||
E123, # closing bracket does not match indentation of opening bracket's line
|
|
||||||
E101, # ensured by use of black
|
|
||||||
|
|
||||||
max-line-length = 200
|
max-line-length = 200
|
||||||
exclude=.github/helper/semgrep_rules
|
exclude=.github/helper/semgrep_rules
|
||||||
|
|||||||
@@ -8,24 +8,12 @@
|
|||||||
#
|
#
|
||||||
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
|
# $ git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||||
|
|
||||||
# Replace use of Class.extend with native JS class
|
|
||||||
1fe891b287a1b3f225d29ee3d07e7b1824aba9e7
|
|
||||||
|
|
||||||
# This commit just changes spaces to tabs for indentation in some files
|
# This commit just changes spaces to tabs for indentation in some files
|
||||||
5f473611bd6ed57703716244a054d3fb5ba9cd23
|
5f473611bd6ed57703716244a054d3fb5ba9cd23
|
||||||
|
|
||||||
# Whitespace fix throughout codebase
|
# Whitespace trimming throughout codebase
|
||||||
4551d7d6029b6f587f6c99d4f8df5519241c6a86
|
9bb69e711a5da43aaf8c8ecb5601aeffd89dbe5a
|
||||||
b147b85e6ac19a9220cd1e2958a6ebd99373283a
|
f0bcb753fb7ebbb64bb0d6906d431d002f0f7d8f
|
||||||
|
|
||||||
# sort and cleanup imports
|
# imports cleanup
|
||||||
915b34391c2066dfc83e60a5813c5a877cebe7ac
|
4b2be2999f2203493b49bf74c5b440d49e38b5e3
|
||||||
|
|
||||||
# removing six compatibility layer
|
|
||||||
8fe5feb6a4372bf5f2dfaf65fca41bbcc25c8ce7
|
|
||||||
|
|
||||||
# bulk format python code with black
|
|
||||||
494bd9ef78313436f0424b918f200dab8fc7c20b
|
|
||||||
|
|
||||||
# bulk format python code with black
|
|
||||||
baec607ff5905b1c67531096a9cf50ec7ff00a5d
|
|
||||||
|
|||||||
2
.github/CONTRIBUTING.md
vendored
2
.github/CONTRIBUTING.md
vendored
@@ -23,7 +23,7 @@ If your issue is not clear or does not meet the guidelines, then it will be clos
|
|||||||
1. **Steps to Reproduce:** The bug report must have a list of steps needed to reproduce a bug. If we cannot reproduce it, then we cannot solve it.
|
1. **Steps to Reproduce:** The bug report must have a list of steps needed to reproduce a bug. If we cannot reproduce it, then we cannot solve it.
|
||||||
1. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version
|
1. **Version Number:** Please add the version number in your report. Often a bug is fixed in the latest version
|
||||||
1. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit"
|
1. **Clear Title:** Add a clear subject to your bug report like "Unable to submit Purchase Order without Basic Rate" instead of just "Cannot Submit"
|
||||||
1. **Screenshots:** Screenshots are a great way of communicating issues. Try adding annotations or using LiceCAP to take a screencast in `gif`.
|
1. **Screenshots:** Screenshots are a great way of communicating the issues. Try adding annotations or using LiceCAP to take a screencast in `gif`.
|
||||||
|
|
||||||
### Feature Request Guidelines
|
### Feature Request Guidelines
|
||||||
|
|
||||||
|
|||||||
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
47
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a bug encountered while using ERPNext
|
||||||
|
labels: bug
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
|
||||||
|
|
||||||
|
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
|
||||||
|
- For questions and general support, checkout the manual https://erpnext.com/docs/user/manual/en or use https://discuss.erpnext.com
|
||||||
|
- For documentation issues, refer to https://github.com/frappe/erpnext_com
|
||||||
|
2. Use the search function before creating a new issue. Duplicates will be closed and directed to
|
||||||
|
the original discussion.
|
||||||
|
3. When making a bug report, make sure you provide all required information. The easier it is for
|
||||||
|
maintainers to reproduce, the faster it'll be fixed.
|
||||||
|
4. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Description of the issue
|
||||||
|
|
||||||
|
## Context information (for bug reports)
|
||||||
|
|
||||||
|
**Output of `bench version`**
|
||||||
|
```
|
||||||
|
(paste here)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Steps to reproduce the issue
|
||||||
|
|
||||||
|
1.
|
||||||
|
2.
|
||||||
|
3.
|
||||||
|
|
||||||
|
### Observed result
|
||||||
|
|
||||||
|
### Expected result
|
||||||
|
|
||||||
|
### Stacktrace / full error message
|
||||||
|
|
||||||
|
```
|
||||||
|
(paste here)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Additional information
|
||||||
|
|
||||||
|
OS version / distribution, `ERPNext` install method, etc.
|
||||||
89
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
89
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
@@ -1,89 +0,0 @@
|
|||||||
name: Bug Report
|
|
||||||
description: Report a bug encountered while using ERPNext
|
|
||||||
labels: ["bug"]
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
Welcome to ERPNext issue tracker! Before creating an issue, please heed the following:
|
|
||||||
|
|
||||||
1. This tracker should only be used to report bugs and request features / enhancements to ERPNext
|
|
||||||
- For questions and general support, checkout the [user manual](https://docs.erpnext.com/) or use [forum](https://discuss.erpnext.com)
|
|
||||||
- For documentation issues, propose edit on [documentation site](https://docs.erpnext.com/) directly.
|
|
||||||
2. When making a bug report, make sure you provide all required information. The easier it is for
|
|
||||||
maintainers to reproduce, the faster it'll be fixed.
|
|
||||||
3. If you think you know what the reason for the bug is, share it with us. Maybe put in a PR 😉
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: bug-info
|
|
||||||
attributes:
|
|
||||||
label: Information about bug
|
|
||||||
description: Also tell us, what did you expect to happen?
|
|
||||||
placeholder: Please provide as much information as possible.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
id: module
|
|
||||||
attributes:
|
|
||||||
label: Module
|
|
||||||
description: Select affected module of ERPNext.
|
|
||||||
multiple: true
|
|
||||||
options:
|
|
||||||
- accounts
|
|
||||||
- stock
|
|
||||||
- buying
|
|
||||||
- selling
|
|
||||||
- ecommerce
|
|
||||||
- manufacturing
|
|
||||||
- HR
|
|
||||||
- projects
|
|
||||||
- support
|
|
||||||
- CRM
|
|
||||||
- assets
|
|
||||||
- integrations
|
|
||||||
- quality
|
|
||||||
- regional
|
|
||||||
- portal
|
|
||||||
- agriculture
|
|
||||||
- education
|
|
||||||
- non-profit
|
|
||||||
- other
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: exact-version
|
|
||||||
attributes:
|
|
||||||
label: Version
|
|
||||||
description: Share exact version number of Frappe and ERPNext you are using.
|
|
||||||
placeholder: |
|
|
||||||
Frappe version -
|
|
||||||
ERPNext Verion -
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: dropdown
|
|
||||||
id: install-method
|
|
||||||
attributes:
|
|
||||||
label: Installation method
|
|
||||||
options:
|
|
||||||
- docker
|
|
||||||
- easy-install
|
|
||||||
- manual install
|
|
||||||
- FrappeCloud
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: logs
|
|
||||||
attributes:
|
|
||||||
label: Relevant log output / Stack trace / Full Error Message.
|
|
||||||
description: Please copy and paste any relevant log output. This will be automatically formatted.
|
|
||||||
render: shell
|
|
||||||
|
|
||||||
- type: markdown
|
|
||||||
attributes:
|
|
||||||
value: |
|
|
||||||
By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/frappe/erpnext/blob/develop/CODE_OF_CONDUCT.md)
|
|
||||||
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,10 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: Feature request
|
name: Feature request
|
||||||
about: Suggest an idea to improve ERPNext
|
about: Suggest an idea to improve ERPNext
|
||||||
title: ''
|
|
||||||
labels: feature-request
|
labels: feature-request
|
||||||
assignees: ''
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
17
.github/ISSUE_TEMPLATE/question-about-using-erpnext.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/question-about-using-erpnext.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: Question about using ERPNext
|
||||||
|
about: This is not the appropriate channel
|
||||||
|
labels: invalid
|
||||||
|
---
|
||||||
|
|
||||||
|
Please post on our forums:
|
||||||
|
|
||||||
|
for questions about using `ERPNext`: https://discuss.erpnext.com
|
||||||
|
|
||||||
|
for questions about using the `Frappe Framework`: ~~https://discuss.frappe.io~~ => [stackoverflow](https://stackoverflow.com/questions/tagged/frappe) tagged under `frappe`
|
||||||
|
|
||||||
|
for questions about using `bench`, probably the best place to start is the [bench repo](https://github.com/frappe/bench)
|
||||||
|
|
||||||
|
For documentation issues, use the [ERPNext Documentation](https://erpnext.com/docs/) or [Frappe Framework Documentation](https://frappe.io/docs/user/en) or the [developer cheetsheet](https://github.com/frappe/frappe/wiki/Developer-Cheatsheet)
|
||||||
|
|
||||||
|
> **Posts that are not bug reports or feature requests will not be addressed on this issue tracker.**
|
||||||
2
.github/helper/documentation.py
vendored
2
.github/helper/documentation.py
vendored
@@ -1,7 +1,7 @@
|
|||||||
import sys
|
import sys
|
||||||
import requests
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
docs_repos = [
|
docs_repos = [
|
||||||
"frappe_docs",
|
"frappe_docs",
|
||||||
|
|||||||
50
.github/helper/install.sh
vendored
50
.github/helper/install.sh
vendored
@@ -8,46 +8,26 @@ sudo apt-get install redis-server libcups2-dev
|
|||||||
|
|
||||||
pip install frappe-bench
|
pip install frappe-bench
|
||||||
|
|
||||||
frappeuser=${FRAPPE_USER:-"frappe"}
|
git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
|
||||||
frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}}
|
|
||||||
|
|
||||||
git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
|
|
||||||
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
|
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
|
||||||
|
|
||||||
mkdir ~/frappe-bench/sites/test_site
|
mkdir ~/frappe-bench/sites/test_site
|
||||||
|
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/
|
||||||
|
|
||||||
if [ "$DB" == "mariadb" ];then
|
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
||||||
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_mariadb.json" ~/frappe-bench/sites/test_site/site_config.json
|
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
||||||
else
|
|
||||||
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config_postgres.json" ~/frappe-bench/sites/test_site/site_config.json
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
|
||||||
|
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
||||||
|
|
||||||
if [ "$DB" == "mariadb" ];then
|
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL character_set_server = 'utf8mb4'"
|
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
|
|
||||||
|
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
|
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -e "CREATE DATABASE test_frappe"
|
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
|
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
|
||||||
|
sudo chmod o+x /usr/local/bin/wkhtmltopdf
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -e "UPDATE mysql.user SET Password=PASSWORD('travis') WHERE User='root'"
|
|
||||||
mysql --host 127.0.0.1 --port 3306 -u root -e "FLUSH PRIVILEGES"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$DB" == "postgres" ];then
|
|
||||||
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe" -U postgres;
|
|
||||||
echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe WITH PASSWORD 'test_frappe'" -U postgres;
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
install_whktml() {
|
|
||||||
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
|
|
||||||
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
|
|
||||||
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
|
|
||||||
sudo chmod o+x /usr/local/bin/wkhtmltopdf
|
|
||||||
}
|
|
||||||
install_whktml &
|
|
||||||
|
|
||||||
cd ~/frappe-bench || exit
|
cd ~/frappe-bench || exit
|
||||||
|
|
||||||
@@ -57,9 +37,5 @@ sed -i 's/socketio:/# socketio:/g' Procfile
|
|||||||
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
|
||||||
|
|
||||||
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
bench get-app erpnext "${GITHUB_WORKSPACE}"
|
||||||
|
|
||||||
if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
|
|
||||||
|
|
||||||
bench start &> bench_run_logs.txt &
|
bench start &> bench_run_logs.txt &
|
||||||
CI=Yes bench build --app frappe &
|
|
||||||
bench --site test_site reinstall --yes
|
bench --site test_site reinstall --yes
|
||||||
|
|||||||
38
.github/helper/semgrep_rules/README.md
vendored
Normal file
38
.github/helper/semgrep_rules/README.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Semgrep linting
|
||||||
|
|
||||||
|
## What is semgrep?
|
||||||
|
Semgrep or "semantic grep" is language agnostic static analysis tool. In simple terms semgrep is syntax-aware `grep`, so unlike regex it doesn't get confused by different ways of writing same thing or whitespaces or code split in multiple lines etc.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
To check if a translate function is using f-string or not the regex would be `r"_\(\s*f[\"']"` while equivalent rule in semgrep would be `_(f"...")`. As semgrep knows grammer of language it takes care of unnecessary whitespace, type of quotation marks etc.
|
||||||
|
|
||||||
|
You can read more such examples in `.github/helper/semgrep_rules` directory.
|
||||||
|
|
||||||
|
# Why/when to use this?
|
||||||
|
We want to maintain quality of contributions, at the same time remembering all the good practices can be pain to deal with while evaluating contributions. Using semgrep if you can translate "best practice" into a rule then it can automate the task for us.
|
||||||
|
|
||||||
|
## Running locally
|
||||||
|
|
||||||
|
Install semgrep using homebrew `brew install semgrep` or pip `pip install semgrep`.
|
||||||
|
|
||||||
|
To run locally use following command:
|
||||||
|
|
||||||
|
`semgrep --config=.github/helper/semgrep_rules [file/folder names]`
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
semgrep allows testing the tests. Refer to this page: https://semgrep.dev/docs/writing-rules/testing-rules/
|
||||||
|
|
||||||
|
When writing new rules you should write few positive and few negative cases as shown in the guide and current tests.
|
||||||
|
|
||||||
|
To run current tests: `semgrep --test --test-ignore-todo .github/helper/semgrep_rules`
|
||||||
|
|
||||||
|
|
||||||
|
## Reference
|
||||||
|
|
||||||
|
If you are new to Semgrep read following pages to get started on writing/modifying rules:
|
||||||
|
|
||||||
|
- https://semgrep.dev/docs/getting-started/
|
||||||
|
- https://semgrep.dev/docs/writing-rules/rule-syntax
|
||||||
|
- https://semgrep.dev/docs/writing-rules/pattern-examples/
|
||||||
|
- https://semgrep.dev/docs/writing-rules/rule-ideas/#common-use-cases
|
||||||
63
.github/helper/semgrep_rules/frappe_correctness.py
vendored
Normal file
63
.github/helper/semgrep_rules/frappe_correctness.py
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _
|
||||||
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
|
# ruleid: frappe-modifying-but-not-comitting
|
||||||
|
def on_submit(self):
|
||||||
|
if self.value_of_goods == 0:
|
||||||
|
frappe.throw(_('Value of goods cannot be 0'))
|
||||||
|
self.status = 'Submitted'
|
||||||
|
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting
|
||||||
|
def on_submit(self):
|
||||||
|
if self.value_of_goods == 0:
|
||||||
|
frappe.throw(_('Value of goods cannot be 0'))
|
||||||
|
self.status = 'Submitted'
|
||||||
|
self.db_set('status', 'Submitted')
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting
|
||||||
|
def on_submit(self):
|
||||||
|
if self.value_of_goods == 0:
|
||||||
|
frappe.throw(_('Value of goods cannot be 0'))
|
||||||
|
x = "y"
|
||||||
|
self.status = x
|
||||||
|
self.db_set('status', x)
|
||||||
|
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting
|
||||||
|
def on_submit(self):
|
||||||
|
x = "y"
|
||||||
|
self.status = x
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
# ruleid: frappe-modifying-but-not-comitting-other-method
|
||||||
|
class DoctypeClass(Document):
|
||||||
|
def on_submit(self):
|
||||||
|
self.good_method()
|
||||||
|
self.tainted_method()
|
||||||
|
|
||||||
|
def tainted_method(self):
|
||||||
|
self.status = "uptate"
|
||||||
|
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting-other-method
|
||||||
|
class DoctypeClass(Document):
|
||||||
|
def on_submit(self):
|
||||||
|
self.good_method()
|
||||||
|
self.tainted_method()
|
||||||
|
|
||||||
|
def tainted_method(self):
|
||||||
|
self.status = "update"
|
||||||
|
self.db_set("status", "update")
|
||||||
|
|
||||||
|
# ok: frappe-modifying-but-not-comitting-other-method
|
||||||
|
class DoctypeClass(Document):
|
||||||
|
def on_submit(self):
|
||||||
|
self.good_method()
|
||||||
|
self.tainted_method()
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def tainted_method(self):
|
||||||
|
self.status = "uptate"
|
||||||
151
.github/helper/semgrep_rules/frappe_correctness.yml
vendored
Normal file
151
.github/helper/semgrep_rules/frappe_correctness.yml
vendored
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# This file specifies rules for correctness according to how frappe doctype data model works.
|
||||||
|
|
||||||
|
rules:
|
||||||
|
- id: frappe-modifying-but-not-comitting
|
||||||
|
patterns:
|
||||||
|
- pattern: |
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
- pattern-not: |
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
...
|
||||||
|
self.db_set(..., self.$ATTR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = $SOME_VAR
|
||||||
|
...
|
||||||
|
self.db_set(..., $SOME_VAR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = $SOME_VAR
|
||||||
|
...
|
||||||
|
self.save()
|
||||||
|
- metavariable-regex:
|
||||||
|
metavariable: '$ATTR'
|
||||||
|
# this is negative look-ahead, add more attrs to ignore like (ignore|ignore_this_too|ignore_me)
|
||||||
|
regex: '^(?!ignore_linked_doctypes|status_updater)(.*)$'
|
||||||
|
- metavariable-regex:
|
||||||
|
metavariable: "$METHOD"
|
||||||
|
regex: "(on_submit|on_cancel)"
|
||||||
|
message: |
|
||||||
|
DocType modified in self.$METHOD. Please check if modification of self.$ATTR is commited to database.
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-modifying-but-not-comitting-other-method
|
||||||
|
patterns:
|
||||||
|
- pattern: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
...
|
||||||
|
self.db_set(..., self.$ATTR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = $SOME_VAR
|
||||||
|
...
|
||||||
|
self.db_set(..., $SOME_VAR, ...)
|
||||||
|
- pattern-not: |
|
||||||
|
class $DOCTYPE(...):
|
||||||
|
def $METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ANOTHER_METHOD()
|
||||||
|
...
|
||||||
|
self.save()
|
||||||
|
def $ANOTHER_METHOD(self, ...):
|
||||||
|
...
|
||||||
|
self.$ATTR = ...
|
||||||
|
- metavariable-regex:
|
||||||
|
metavariable: "$METHOD"
|
||||||
|
regex: "(on_submit|on_cancel)"
|
||||||
|
message: |
|
||||||
|
self.$ANOTHER_METHOD is called from self.$METHOD, check if changes to self.$ATTR are commited to database.
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-print-function-in-doctypes
|
||||||
|
pattern: print(...)
|
||||||
|
message: |
|
||||||
|
Did you mean to leave this print statement in? Consider using msgprint or logger instead of print statement.
|
||||||
|
languages: [python]
|
||||||
|
severity: WARNING
|
||||||
|
paths:
|
||||||
|
include:
|
||||||
|
- "*/**/doctype/*"
|
||||||
|
|
||||||
|
- id: frappe-modifying-child-tables-while-iterating
|
||||||
|
pattern-either:
|
||||||
|
- pattern: |
|
||||||
|
for $ROW in self.$TABLE:
|
||||||
|
...
|
||||||
|
self.remove(...)
|
||||||
|
- pattern: |
|
||||||
|
for $ROW in self.$TABLE:
|
||||||
|
...
|
||||||
|
self.append(...)
|
||||||
|
message: |
|
||||||
|
Child table being modified while iterating on it.
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
|
paths:
|
||||||
|
include:
|
||||||
|
- "*/**/doctype/*"
|
||||||
|
|
||||||
|
- id: frappe-same-key-assigned-twice
|
||||||
|
pattern-either:
|
||||||
|
- pattern: |
|
||||||
|
{..., $X: $A, ..., $X: $B, ...}
|
||||||
|
- pattern: |
|
||||||
|
dict(..., ($X, $A), ..., ($X, $B), ...)
|
||||||
|
- pattern: |
|
||||||
|
_dict(..., ($X, $A), ..., ($X, $B), ...)
|
||||||
|
message: |
|
||||||
|
key `$X` is uselessly assigned twice. This could be a potential bug.
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
|
||||||
|
- id: frappe-manual-commit
|
||||||
|
patterns:
|
||||||
|
- pattern: frappe.db.commit()
|
||||||
|
- pattern-not-inside: |
|
||||||
|
try:
|
||||||
|
...
|
||||||
|
except ...:
|
||||||
|
...
|
||||||
|
message: |
|
||||||
|
Manually commiting a transaction is highly discouraged. Read about the transaction model implemented by Frappe Framework before adding manual commits: https://frappeframework.com/docs/user/en/api/database#database-transaction-model If you think manual commit is required then add a comment explaining why and `// nosemgrep` on the same line.
|
||||||
|
paths:
|
||||||
|
exclude:
|
||||||
|
- "**/patches/**"
|
||||||
|
- "**/demo/**"
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
14
.github/helper/semgrep_rules/report.py
vendored
Normal file
14
.github/helper/semgrep_rules/report.py
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from frappe import _
|
||||||
|
|
||||||
|
# ruleid: frappe-missing-translate-function-in-report-python
|
||||||
|
{"label": "Field Label"}
|
||||||
|
|
||||||
|
# ruleid: frappe-missing-translate-function-in-report-python
|
||||||
|
dict(label="Field Label")
|
||||||
|
|
||||||
|
|
||||||
|
# ok: frappe-missing-translate-function-in-report-python
|
||||||
|
{"label": _("Field Label")}
|
||||||
|
|
||||||
|
# ok: frappe-missing-translate-function-in-report-python
|
||||||
|
dict(label=_("Field Label"))
|
||||||
34
.github/helper/semgrep_rules/report.yml
vendored
Normal file
34
.github/helper/semgrep_rules/report.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
rules:
|
||||||
|
- id: frappe-missing-translate-function-in-report-python
|
||||||
|
paths:
|
||||||
|
include:
|
||||||
|
- "**/report"
|
||||||
|
exclude:
|
||||||
|
- "**/regional"
|
||||||
|
pattern-either:
|
||||||
|
- patterns:
|
||||||
|
- pattern: |
|
||||||
|
{..., "label": "...", ...}
|
||||||
|
- pattern-not: |
|
||||||
|
{..., "label": _("..."), ...}
|
||||||
|
- patterns:
|
||||||
|
- pattern: dict(..., label="...", ...)
|
||||||
|
- pattern-not: dict(..., label=_("..."), ...)
|
||||||
|
message: |
|
||||||
|
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-translated-values-in-business-logic
|
||||||
|
paths:
|
||||||
|
include:
|
||||||
|
- "**/report"
|
||||||
|
patterns:
|
||||||
|
- pattern-inside: |
|
||||||
|
{..., filters: [...], ...}
|
||||||
|
- pattern: |
|
||||||
|
{..., options: [..., __("..."), ...], ...}
|
||||||
|
message: |
|
||||||
|
Using translated values in options field will require you to translate the values while comparing in business logic. Instead of passing translated labels provide objects that contain both label and value. e.g. { label: __("Option value"), value: "Option value"}
|
||||||
|
languages: [javascript]
|
||||||
|
severity: ERROR
|
||||||
6
.github/helper/semgrep_rules/security.py
vendored
Normal file
6
.github/helper/semgrep_rules/security.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
def function_name(input):
|
||||||
|
# ruleid: frappe-codeinjection-eval
|
||||||
|
eval(input)
|
||||||
|
|
||||||
|
# ok: frappe-codeinjection-eval
|
||||||
|
eval("1 + 1")
|
||||||
10
.github/helper/semgrep_rules/security.yml
vendored
Normal file
10
.github/helper/semgrep_rules/security.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
rules:
|
||||||
|
- id: frappe-codeinjection-eval
|
||||||
|
patterns:
|
||||||
|
- pattern-not: eval("...")
|
||||||
|
- pattern: eval(...)
|
||||||
|
message: |
|
||||||
|
Detected the use of eval(). eval() can be dangerous if used to evaluate
|
||||||
|
dynamic content. Avoid it or use safe_eval().
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
44
.github/helper/semgrep_rules/translate.js
vendored
Normal file
44
.github/helper/semgrep_rules/translate.js
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// ruleid: frappe-translation-empty-string
|
||||||
|
__("")
|
||||||
|
// ruleid: frappe-translation-empty-string
|
||||||
|
__('')
|
||||||
|
|
||||||
|
// ok: frappe-translation-js-formatting
|
||||||
|
__('Welcome {0}, get started with ERPNext in just a few clicks.', [full_name]);
|
||||||
|
|
||||||
|
// ruleid: frappe-translation-js-formatting
|
||||||
|
__(`Welcome ${full_name}, get started with ERPNext in just a few clicks.`);
|
||||||
|
|
||||||
|
// ok: frappe-translation-js-formatting
|
||||||
|
__('This is fine');
|
||||||
|
|
||||||
|
|
||||||
|
// ok: frappe-translation-trailing-spaces
|
||||||
|
__('This is fine');
|
||||||
|
|
||||||
|
// ruleid: frappe-translation-trailing-spaces
|
||||||
|
__(' this is not ok ');
|
||||||
|
// ruleid: frappe-translation-trailing-spaces
|
||||||
|
__('this is not ok ');
|
||||||
|
// ruleid: frappe-translation-trailing-spaces
|
||||||
|
__(' this is not ok');
|
||||||
|
|
||||||
|
// ok: frappe-translation-js-splitting
|
||||||
|
__('You have {0} subscribers in your mailing list.', [subscribers.length])
|
||||||
|
|
||||||
|
// todoruleid: frappe-translation-js-splitting
|
||||||
|
__('You have') + subscribers.length + __('subscribers in your mailing list.')
|
||||||
|
|
||||||
|
// ruleid: frappe-translation-js-splitting
|
||||||
|
__('You have' + 'subscribers in your mailing list.')
|
||||||
|
|
||||||
|
// ruleid: frappe-translation-js-splitting
|
||||||
|
__('You have {0} subscribers' +
|
||||||
|
'in your mailing list', [subscribers.length])
|
||||||
|
|
||||||
|
// ok: frappe-translation-js-splitting
|
||||||
|
__("Ctrl+Enter to add comment")
|
||||||
|
|
||||||
|
// ruleid: frappe-translation-js-splitting
|
||||||
|
__('You have {0} subscribers \
|
||||||
|
in your mailing list', [subscribers.length])
|
||||||
61
.github/helper/semgrep_rules/translate.py
vendored
Normal file
61
.github/helper/semgrep_rules/translate.py
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Examples taken from https://frappeframework.com/docs/user/en/translations
|
||||||
|
# This file is used for testing the tests.
|
||||||
|
|
||||||
|
from frappe import _
|
||||||
|
|
||||||
|
full_name = "Jon Doe"
|
||||||
|
# ok: frappe-translation-python-formatting
|
||||||
|
_('Welcome {0}, get started with ERPNext in just a few clicks.').format(full_name)
|
||||||
|
|
||||||
|
# ruleid: frappe-translation-python-formatting
|
||||||
|
_('Welcome %s, get started with ERPNext in just a few clicks.' % full_name)
|
||||||
|
# ruleid: frappe-translation-python-formatting
|
||||||
|
_('Welcome %(name)s, get started with ERPNext in just a few clicks.' % {'name': full_name})
|
||||||
|
|
||||||
|
# ruleid: frappe-translation-python-formatting
|
||||||
|
_('Welcome {0}, get started with ERPNext in just a few clicks.'.format(full_name))
|
||||||
|
|
||||||
|
|
||||||
|
subscribers = ["Jon", "Doe"]
|
||||||
|
# ok: frappe-translation-python-formatting
|
||||||
|
_('You have {0} subscribers in your mailing list.').format(len(subscribers))
|
||||||
|
|
||||||
|
# ruleid: frappe-translation-python-splitting
|
||||||
|
_('You have') + len(subscribers) + _('subscribers in your mailing list.')
|
||||||
|
|
||||||
|
# ruleid: frappe-translation-python-splitting
|
||||||
|
_('You have {0} subscribers \
|
||||||
|
in your mailing list').format(len(subscribers))
|
||||||
|
|
||||||
|
# ok: frappe-translation-python-splitting
|
||||||
|
_('You have {0} subscribers') \
|
||||||
|
+ 'in your mailing list'
|
||||||
|
|
||||||
|
# ruleid: frappe-translation-trailing-spaces
|
||||||
|
msg = _(" You have {0} pending invoice ")
|
||||||
|
# ruleid: frappe-translation-trailing-spaces
|
||||||
|
msg = _("You have {0} pending invoice ")
|
||||||
|
# ruleid: frappe-translation-trailing-spaces
|
||||||
|
msg = _(" You have {0} pending invoice")
|
||||||
|
|
||||||
|
# ok: frappe-translation-trailing-spaces
|
||||||
|
msg = ' ' + _("You have {0} pending invoices") + ' '
|
||||||
|
|
||||||
|
# ruleid: frappe-translation-python-formatting
|
||||||
|
_(f"can not format like this - {subscribers}")
|
||||||
|
# ruleid: frappe-translation-python-splitting
|
||||||
|
_(f"what" + f"this is also not cool")
|
||||||
|
|
||||||
|
|
||||||
|
# ruleid: frappe-translation-empty-string
|
||||||
|
_("")
|
||||||
|
# ruleid: frappe-translation-empty-string
|
||||||
|
_('')
|
||||||
|
|
||||||
|
|
||||||
|
class Test:
|
||||||
|
# ok: frappe-translation-python-splitting
|
||||||
|
def __init__(
|
||||||
|
args
|
||||||
|
):
|
||||||
|
pass
|
||||||
64
.github/helper/semgrep_rules/translate.yml
vendored
Normal file
64
.github/helper/semgrep_rules/translate.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
rules:
|
||||||
|
- id: frappe-translation-empty-string
|
||||||
|
pattern-either:
|
||||||
|
- pattern: _("")
|
||||||
|
- pattern: __("")
|
||||||
|
message: |
|
||||||
|
Empty string is useless for translation.
|
||||||
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
|
languages: [python, javascript, json]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-translation-trailing-spaces
|
||||||
|
pattern-either:
|
||||||
|
- pattern: _("=~/(^[ \t]+|[ \t]+$)/")
|
||||||
|
- pattern: __("=~/(^[ \t]+|[ \t]+$)/")
|
||||||
|
message: |
|
||||||
|
Trailing or leading whitespace not allowed in translate strings.
|
||||||
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
|
languages: [python, javascript, json]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-translation-python-formatting
|
||||||
|
pattern-either:
|
||||||
|
- pattern: _("..." % ...)
|
||||||
|
- pattern: _("...".format(...))
|
||||||
|
- pattern: _(f"...")
|
||||||
|
message: |
|
||||||
|
Only positional formatters are allowed and formatting should not be done before translating.
|
||||||
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-translation-js-formatting
|
||||||
|
patterns:
|
||||||
|
- pattern: __(`...`)
|
||||||
|
- pattern-not: __("...")
|
||||||
|
message: |
|
||||||
|
Template strings are not allowed for text formatting.
|
||||||
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
|
languages: [javascript, json]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-translation-python-splitting
|
||||||
|
pattern-either:
|
||||||
|
- pattern: _(...) + _(...)
|
||||||
|
- pattern: _("..." + "...")
|
||||||
|
- pattern-regex: '[\s\.]_\([^\)]*\\\s*' # lines broken by `\`
|
||||||
|
- pattern-regex: '[\s\.]_\(\s*\n' # line breaks allowed by python for using ( )
|
||||||
|
message: |
|
||||||
|
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||||
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-translation-js-splitting
|
||||||
|
pattern-either:
|
||||||
|
- pattern-regex: '__\([^\)]*[\\]\s+'
|
||||||
|
- pattern: __('...' + '...', ...)
|
||||||
|
- pattern: __('...') + __('...')
|
||||||
|
message: |
|
||||||
|
Do not split strings inside translate function. Do not concatenate using translate functions.
|
||||||
|
Please refer: https://frappeframework.com/docs/user/en/translations
|
||||||
|
languages: [javascript, json]
|
||||||
|
severity: ERROR
|
||||||
9
.github/helper/semgrep_rules/ux.js
vendored
Normal file
9
.github/helper/semgrep_rules/ux.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
// ok: frappe-missing-translate-function-js
|
||||||
|
frappe.msgprint('{{ _("Both login and password required") }}');
|
||||||
|
|
||||||
|
// ruleid: frappe-missing-translate-function-js
|
||||||
|
frappe.msgprint('What');
|
||||||
|
|
||||||
|
// ok: frappe-missing-translate-function-js
|
||||||
|
frappe.throw(' {{ _("Both login and password required") }}. ');
|
||||||
30
.github/helper/semgrep_rules/ux.py
vendored
Normal file
30
.github/helper/semgrep_rules/ux.py
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import frappe
|
||||||
|
from frappe import _, msgprint, throw
|
||||||
|
|
||||||
|
# ruleid: frappe-missing-translate-function-python
|
||||||
|
throw("Error Occured")
|
||||||
|
|
||||||
|
# ruleid: frappe-missing-translate-function-python
|
||||||
|
frappe.throw("Error Occured")
|
||||||
|
|
||||||
|
# ruleid: frappe-missing-translate-function-python
|
||||||
|
frappe.msgprint("Useful message")
|
||||||
|
|
||||||
|
# ruleid: frappe-missing-translate-function-python
|
||||||
|
msgprint("Useful message")
|
||||||
|
|
||||||
|
|
||||||
|
# ok: frappe-missing-translate-function-python
|
||||||
|
translatedmessage = _("Hello")
|
||||||
|
|
||||||
|
# ok: frappe-missing-translate-function-python
|
||||||
|
throw(translatedmessage)
|
||||||
|
|
||||||
|
# ok: frappe-missing-translate-function-python
|
||||||
|
msgprint(translatedmessage)
|
||||||
|
|
||||||
|
# ok: frappe-missing-translate-function-python
|
||||||
|
msgprint(_("Helpful message"))
|
||||||
|
|
||||||
|
# ok: frappe-missing-translate-function-python
|
||||||
|
frappe.throw(_("Error occured"))
|
||||||
30
.github/helper/semgrep_rules/ux.yml
vendored
Normal file
30
.github/helper/semgrep_rules/ux.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
rules:
|
||||||
|
- id: frappe-missing-translate-function-python
|
||||||
|
pattern-either:
|
||||||
|
- patterns:
|
||||||
|
- pattern: frappe.msgprint("...", ...)
|
||||||
|
- pattern-not: frappe.msgprint(_("..."), ...)
|
||||||
|
- patterns:
|
||||||
|
- pattern: frappe.throw("...", ...)
|
||||||
|
- pattern-not: frappe.throw(_("..."), ...)
|
||||||
|
message: |
|
||||||
|
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
|
||||||
|
languages: [python]
|
||||||
|
severity: ERROR
|
||||||
|
|
||||||
|
- id: frappe-missing-translate-function-js
|
||||||
|
pattern-either:
|
||||||
|
- patterns:
|
||||||
|
- pattern: frappe.msgprint("...", ...)
|
||||||
|
- pattern-not: frappe.msgprint(__("..."), ...)
|
||||||
|
# ignore microtemplating e.g. msgprint("{{ _("server side translation") }}")
|
||||||
|
- pattern-not: frappe.msgprint("=~/\{\{.*\_.*\}\}/i", ...)
|
||||||
|
- patterns:
|
||||||
|
- pattern: frappe.throw("...", ...)
|
||||||
|
- pattern-not: frappe.throw(__("..."), ...)
|
||||||
|
# ignore microtemplating
|
||||||
|
- pattern-not: frappe.throw("=~/\{\{.*\_.*\}\}/i", ...)
|
||||||
|
message: |
|
||||||
|
All user facing text must be wrapped in translate function. Please refer to translation documentation. https://frappeframework.com/docs/user/en/guides/basics/translations
|
||||||
|
languages: [javascript]
|
||||||
|
severity: ERROR
|
||||||
18
.github/helper/site_config_postgres.json
vendored
18
.github/helper/site_config_postgres.json
vendored
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"db_host": "127.0.0.1",
|
|
||||||
"db_port": 5432,
|
|
||||||
"db_name": "test_frappe",
|
|
||||||
"db_password": "test_frappe",
|
|
||||||
"db_type": "postgres",
|
|
||||||
"allow_tests": true,
|
|
||||||
"auto_email_id": "test@example.com",
|
|
||||||
"mail_server": "smtp.example.com",
|
|
||||||
"mail_login": "test@example.com",
|
|
||||||
"mail_password": "test",
|
|
||||||
"admin_password": "admin",
|
|
||||||
"root_login": "postgres",
|
|
||||||
"root_password": "travis",
|
|
||||||
"host_name": "http://test_site:8000",
|
|
||||||
"install_apps": ["erpnext"],
|
|
||||||
"throttle_user_limit": 100
|
|
||||||
}
|
|
||||||
55
.github/labeler.yml
vendored
55
.github/labeler.yml
vendored
@@ -1,55 +0,0 @@
|
|||||||
accounts:
|
|
||||||
- erpnext/accounts/*
|
|
||||||
- erpnext/controllers/accounts_controller.py
|
|
||||||
- erpnext/controllers/taxes_and_totals.py
|
|
||||||
|
|
||||||
stock:
|
|
||||||
- erpnext/stock/*
|
|
||||||
- erpnext/controllers/stock_controller.py
|
|
||||||
- erpnext/controllers/item_variant.py
|
|
||||||
|
|
||||||
assets:
|
|
||||||
- erpnext/assets/*
|
|
||||||
|
|
||||||
regional:
|
|
||||||
- erpnext/regional/*
|
|
||||||
|
|
||||||
selling:
|
|
||||||
- erpnext/selling/*
|
|
||||||
- erpnext/controllers/selling_controller.py
|
|
||||||
|
|
||||||
buying:
|
|
||||||
- erpnext/buying/*
|
|
||||||
- erpnext/controllers/buying_controller.py
|
|
||||||
|
|
||||||
support:
|
|
||||||
- erpnext/support/*
|
|
||||||
|
|
||||||
POS:
|
|
||||||
- pos*
|
|
||||||
|
|
||||||
ecommerce:
|
|
||||||
- erpnext/e_commerce/*
|
|
||||||
|
|
||||||
maintenance:
|
|
||||||
- erpnext/maintenance/*
|
|
||||||
|
|
||||||
manufacturing:
|
|
||||||
- erpnext/manufacturing/*
|
|
||||||
|
|
||||||
crm:
|
|
||||||
- erpnext/crm/*
|
|
||||||
|
|
||||||
HR:
|
|
||||||
- erpnext/hr/*
|
|
||||||
|
|
||||||
payroll:
|
|
||||||
- erpnext/payroll*
|
|
||||||
|
|
||||||
projects:
|
|
||||||
- erpnext/projects/*
|
|
||||||
|
|
||||||
# Any python files modifed but no test files modified
|
|
||||||
needs-tests:
|
|
||||||
- any: ['erpnext/**/*.py']
|
|
||||||
all: ['!erpnext/**/test*.py']
|
|
||||||
51
.github/stale.yml
vendored
51
.github/stale.yml
vendored
@@ -1,37 +1,34 @@
|
|||||||
# Configuration for probot-stale - https://github.com/probot/stale
|
# Configuration for probot-stale - https://github.com/probot/stale
|
||||||
|
|
||||||
# Label to use when marking as stale
|
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||||
staleLabel: inactive
|
daysUntilStale: 30
|
||||||
|
|
||||||
# Limit the number of actions per hour, from 1-30. Default is 30
|
# Number of days of inactivity before a stale Issue or Pull Request is closed.
|
||||||
limitPerRun: 10
|
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||||
|
daysUntilClose: 7
|
||||||
|
|
||||||
|
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||||
|
exemptLabels:
|
||||||
|
- hotfix
|
||||||
|
|
||||||
# Set to true to ignore issues in a project (defaults to false)
|
# Set to true to ignore issues in a project (defaults to false)
|
||||||
exemptProjects: true
|
exemptProjects: false
|
||||||
|
|
||||||
# Set to true to ignore issues in a milestone (defaults to false)
|
# Set to true to ignore issues in a milestone (defaults to false)
|
||||||
exemptMilestones: true
|
exemptMilestones: true
|
||||||
|
|
||||||
pulls:
|
# Label to use when marking as stale
|
||||||
daysUntilStale: 15
|
staleLabel: inactive
|
||||||
daysUntilClose: 3
|
|
||||||
exemptLabels:
|
|
||||||
- hotfix
|
|
||||||
markComment: >
|
|
||||||
This pull request has been automatically marked as inactive because it has
|
|
||||||
not had recent activity. It will be closed within 3 days if no further
|
|
||||||
activity occurs, but it only takes a comment to keep a contribution alive
|
|
||||||
:) Also, even if it is closed, you can always reopen the PR when you're
|
|
||||||
ready. Thank you for contributing.
|
|
||||||
|
|
||||||
issues:
|
# Comment to post when marking as stale. Set to `false` to disable
|
||||||
daysUntilStale: 60
|
markComment: >
|
||||||
daysUntilClose: 7
|
This pull request has been automatically marked as stale because it has not had
|
||||||
exemptLabels:
|
recent activity. It will be closed within a week if no further activity occurs, but it
|
||||||
- valid
|
only takes a comment to keep a contribution alive :) Also, even if it is closed,
|
||||||
- to-validate
|
you can always reopen the PR when you're ready. Thank you for contributing.
|
||||||
- QA
|
|
||||||
markComment: >
|
# Limit the number of actions per hour, from 1-30. Default is 30
|
||||||
This issue has been automatically marked as inactive because it has not had
|
limitPerRun: 30
|
||||||
recent activity and it wasn't validated by maintainer team. It will be
|
|
||||||
closed within a week if no further activity occurs.
|
# Limit to only `issues` or `pulls`
|
||||||
|
only: pulls
|
||||||
|
|||||||
32
.github/try-on-f-cloud-button.svg
vendored
32
.github/try-on-f-cloud-button.svg
vendored
@@ -1,32 +0,0 @@
|
|||||||
<svg width="201" height="60" viewBox="0 0 201 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g filter="url(#filter0_dd)">
|
|
||||||
<rect x="4" y="2" width="193" height="52" rx="6" fill="#2490EF"/>
|
|
||||||
<path d="M28 22.2891H32.8786V35.5H36.2088V22.2891H41.0874V19.5H28V22.2891Z" fill="white"/>
|
|
||||||
<path d="M41.6982 35.5H45.0129V28.7109C45.0129 27.2344 46.0866 26.2188 47.5494 26.2188C48.0085 26.2188 48.6388 26.2969 48.95 26.3984V23.4453C48.6543 23.375 48.2419 23.3281 47.9074 23.3281C46.5691 23.3281 45.472 24.1094 45.0362 25.5938H44.9117V23.5H41.6982V35.5Z" fill="white"/>
|
|
||||||
<path d="M52.8331 40C55.2996 40 56.6068 38.7344 57.2837 36.7969L61.9289 23.5156L58.4197 23.5L55.9221 32.3125H55.7976L53.3233 23.5H49.8374L54.1247 35.8437L53.9302 36.3516C53.4944 37.4766 52.6619 37.5312 51.4947 37.1719L50.7478 39.6562C51.2224 39.8594 51.9927 40 52.8331 40Z" fill="white"/>
|
|
||||||
<path d="M73.6142 35.7344C77.2401 35.7344 79.4966 33.2422 79.4966 29.5469C79.4966 25.8281 77.2401 23.3438 73.6142 23.3438C69.9883 23.3438 67.7319 25.8281 67.7319 29.5469C67.7319 33.2422 69.9883 35.7344 73.6142 35.7344ZM73.6298 33.1562C71.9569 33.1562 71.101 31.6171 71.101 29.5233C71.101 27.4296 71.9569 25.8827 73.6298 25.8827C75.2715 25.8827 76.1274 27.4296 76.1274 29.5233C76.1274 31.6171 75.2715 33.1562 73.6298 33.1562Z" fill="white"/>
|
|
||||||
<path d="M84.7253 28.5625C84.7331 27.0156 85.6512 26.1094 86.9895 26.1094C88.3201 26.1094 89.1215 26.9844 89.1137 28.4531V35.5H92.4284V27.8594C92.4284 25.0625 90.7945 23.3438 88.3046 23.3438C86.5306 23.3438 85.2466 24.2187 84.7097 25.6172H84.5697V23.5H81.4106V35.5H84.7253V28.5625Z" fill="white"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M102.429 19.5H113.429V22.3141H102.429V19.5ZM102.429 35.5V26.6794H112.699V29.4982H105.94V35.5H102.429Z" fill="white"/>
|
|
||||||
<path d="M131.584 24.9625C131.09 21.5057 128.345 19.5 124.785 19.5C120.589 19.5 117.429 22.463 117.429 27.4924C117.429 32.5142 120.55 35.4848 124.785 35.4848C128.604 35.4848 131.137 33.0916 131.584 30.1211L128.651 30.1059C128.282 31.9293 126.745 32.9549 124.824 32.9549C122.22 32.9549 120.354 31.0632 120.354 27.4924C120.354 23.9824 122.204 22.0299 124.832 22.0299C126.784 22.0299 128.314 23.1011 128.651 24.9625H131.584Z" fill="white"/>
|
|
||||||
<path d="M136.409 19.7124H133.571V35.2718H136.409V19.7124Z" fill="white"/>
|
|
||||||
<path d="M144.031 35.5001C147.56 35.5001 149.803 33.0917 149.803 29.483C149.803 25.8667 147.56 23.4507 144.031 23.4507C140.502 23.4507 138.259 25.8667 138.259 29.483C138.259 33.0917 140.502 35.5001 144.031 35.5001ZM144.047 33.2969C142.094 33.2969 141.137 31.6103 141.137 29.4754C141.137 27.3406 142.094 25.6312 144.047 25.6312C145.968 25.6312 146.925 27.3406 146.925 29.4754C146.925 31.6103 145.968 33.2969 144.047 33.2969Z" fill="white"/>
|
|
||||||
<path d="M159.338 30.3641C159.338 32.1419 158.028 33.0232 156.773 33.0232C155.409 33.0232 154.499 32.0887 154.499 30.6072V23.6025H151.66V31.0327C151.66 33.8361 153.307 35.4239 155.675 35.4239C157.479 35.4239 158.749 34.5046 159.298 33.1979H159.424V35.272H162.176V23.6025H159.338V30.3641Z" fill="white"/>
|
|
||||||
<path d="M169.014 35.4769C171.084 35.4769 172.017 34.2841 172.464 33.4332H172.637V35.2718H175.429V19.7124H172.582V25.532H172.464C172.033 24.6887 171.147 23.4503 169.022 23.4503C166.238 23.4503 164.05 25.5624 164.05 29.4522C164.05 33.2965 166.175 35.4769 169.014 35.4769ZM169.806 33.2205C167.931 33.2205 166.943 31.6251 166.943 29.437C166.943 27.2642 167.916 25.7067 169.806 25.7067C171.633 25.7067 172.637 27.173 172.637 29.437C172.637 31.701 171.617 33.2205 169.806 33.2205Z" fill="white"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<filter id="filter0_dd" x="0" y="0" width="201" height="60" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
|
||||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset/>
|
|
||||||
<feGaussianBlur stdDeviation="0.25"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.5 0"/>
|
|
||||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
|
|
||||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
|
||||||
<feOffset dy="2"/>
|
|
||||||
<feGaussianBlur stdDeviation="2"/>
|
|
||||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.13 0"/>
|
|
||||||
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
|
|
||||||
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.4 KiB |
1
.github/workflows/backport.yml
vendored
1
.github/workflows/backport.yml
vendored
@@ -8,7 +8,6 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
main:
|
main:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 60
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Actions
|
- name: Checkout Actions
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
2
.github/workflows/docker-release.yml
vendored
2
.github/workflows/docker-release.yml
vendored
@@ -11,4 +11,4 @@ jobs:
|
|||||||
- name: curl
|
- name: curl
|
||||||
run: |
|
run: |
|
||||||
apk add curl bash
|
apk add curl bash
|
||||||
curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'
|
curl -s -X POST -H "Content-Type: application/json" -H "Accept: application/json" -H "Travis-API-Version: 3" -H "Authorization: token ${{ secrets.TRAVIS_CI_TOKEN }}" -d '{"request":{"branch":"master"}}' https://api.travis-ci.com/repo/frappe%2Ffrappe_docker/requests
|
||||||
|
|||||||
3
.github/workflows/docs-checker.yml
vendored
3
.github/workflows/docs-checker.yml
vendored
@@ -6,13 +6,12 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 10
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup Environment'
|
- name: 'Setup Environment'
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.6
|
||||||
|
|
||||||
- name: 'Clone repo'
|
- name: 'Clone repo'
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|||||||
12
.github/workflows/labeller.yml
vendored
12
.github/workflows/labeller.yml
vendored
@@ -1,12 +0,0 @@
|
|||||||
name: "Pull Request Labeler"
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, reopened]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
triage:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/labeler@v3
|
|
||||||
with:
|
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
20
.github/workflows/linters.yml
vendored
20
.github/workflows/linters.yml
vendored
@@ -10,6 +10,13 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- uses: returntocorp/semgrep-action@v1
|
||||||
|
env:
|
||||||
|
SEMGREP_TIMEOUT: 120
|
||||||
|
with:
|
||||||
|
config: >-
|
||||||
|
r/python.lang.correctness
|
||||||
|
.github/helper/semgrep_rules
|
||||||
|
|
||||||
- name: Set up Python 3.8
|
- name: Set up Python 3.8
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
@@ -17,15 +24,4 @@ jobs:
|
|||||||
python-version: 3.8
|
python-version: 3.8
|
||||||
|
|
||||||
- name: Install and Run Pre-commit
|
- name: Install and Run Pre-commit
|
||||||
uses: pre-commit/action@v2.0.3
|
uses: pre-commit/action@v2.0.0
|
||||||
|
|
||||||
- name: Download Semgrep rules
|
|
||||||
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
|
|
||||||
|
|
||||||
- uses: returntocorp/semgrep-action@v1
|
|
||||||
env:
|
|
||||||
SEMGREP_TIMEOUT: 120
|
|
||||||
with:
|
|
||||||
config: >-
|
|
||||||
r/python.lang.correctness
|
|
||||||
./frappe-semgrep-rules/rules
|
|
||||||
|
|||||||
39
.github/workflows/patch.yml
vendored
39
.github/workflows/patch.yml
vendored
@@ -4,20 +4,13 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.js'
|
- '**.js'
|
||||||
- '**.css'
|
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.html'
|
|
||||||
- '**.csv'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: patch-develop-${{ github.event.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-18.04
|
||||||
timeout-minutes: 60
|
|
||||||
|
|
||||||
name: Patch Test
|
name: Patch Test
|
||||||
|
|
||||||
@@ -37,12 +30,12 @@ jobs:
|
|||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.6
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 12
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Add to Hosts
|
- name: Add to Hosts
|
||||||
@@ -83,36 +76,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
env:
|
|
||||||
DB: mariadb
|
|
||||||
TYPE: server
|
|
||||||
|
|
||||||
- name: Run Patch Tests
|
- name: Run Patch Tests
|
||||||
run: |
|
run: |
|
||||||
cd ~/frappe-bench/
|
cd ~/frappe-bench/
|
||||||
wget https://erpnext.com/files/v10-erpnext.sql.gz
|
wget https://erpnext.com/files/v10-erpnext.sql.gz
|
||||||
bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
|
bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
|
||||||
|
|
||||||
git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
|
|
||||||
git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
|
|
||||||
|
|
||||||
for version in $(seq 12 13)
|
|
||||||
do
|
|
||||||
echo "Updating to v$version"
|
|
||||||
branch_name="version-$version-hotfix"
|
|
||||||
|
|
||||||
git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
|
|
||||||
git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
|
|
||||||
|
|
||||||
git -C "apps/frappe" checkout -q -f $branch_name
|
|
||||||
git -C "apps/erpnext" checkout -q -f $branch_name
|
|
||||||
|
|
||||||
bench setup requirements --python
|
|
||||||
bench --site test_site migrate
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
echo "Updating to latest version"
|
|
||||||
git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
|
||||||
git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
|
|
||||||
bench --site test_site migrate
|
bench --site test_site migrate
|
||||||
|
|||||||
@@ -1,39 +1,20 @@
|
|||||||
name: Server (Mariadb)
|
name: Server
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.js'
|
- '**.js'
|
||||||
- '**.css'
|
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.html'
|
workflow_dispatch:
|
||||||
- '**.csv'
|
|
||||||
push:
|
push:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.js'
|
- '**.js'
|
||||||
- '**.md'
|
- '**.md'
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
user:
|
|
||||||
description: 'user'
|
|
||||||
required: true
|
|
||||||
default: 'frappe'
|
|
||||||
type: string
|
|
||||||
branch:
|
|
||||||
description: 'Branch name'
|
|
||||||
default: 'develop'
|
|
||||||
required: false
|
|
||||||
type: string
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: server-mariadb-develop-${{ github.event.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-18.04
|
||||||
timeout-minutes: 60
|
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@@ -59,12 +40,12 @@ jobs:
|
|||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.7
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 12
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Add to Hosts
|
- name: Add to Hosts
|
||||||
@@ -105,11 +86,6 @@ jobs:
|
|||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
env:
|
|
||||||
DB: mariadb
|
|
||||||
TYPE: server
|
|
||||||
FRAPPE_USER: ${{ github.event.inputs.user }}
|
|
||||||
FRAPPE_BRANCH: ${{ github.event.inputs.branch }}
|
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
|
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator --with-coverage
|
||||||
@@ -118,10 +94,33 @@ jobs:
|
|||||||
CI_BUILD_ID: ${{ github.run_id }}
|
CI_BUILD_ID: ${{ github.run_id }}
|
||||||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
|
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
|
||||||
|
|
||||||
- name: Upload coverage data
|
- name: Upload Coverage Data
|
||||||
uses: codecov/codecov-action@v2
|
run: |
|
||||||
with:
|
cp ~/frappe-bench/sites/.coverage ${GITHUB_WORKSPACE}
|
||||||
name: MariaDB
|
cd ${GITHUB_WORKSPACE}
|
||||||
fail_ci_if_error: true
|
pip3 install coverage==5.5
|
||||||
files: /home/runner/frappe-bench/sites/coverage.xml
|
pip3 install coveralls==3.0.1
|
||||||
verbose: true
|
coveralls
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
COVERALLS_FLAG_NAME: run-${{ matrix.container }}
|
||||||
|
COVERALLS_SERVICE_NAME: ${{ github.event_name == 'pull_request' && 'github' || 'github-actions' }}
|
||||||
|
COVERALLS_PARALLEL: true
|
||||||
|
|
||||||
|
coveralls:
|
||||||
|
name: Coverage Wrap Up
|
||||||
|
needs: test
|
||||||
|
container: python:3-slim
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Clone
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Coveralls Finished
|
||||||
|
run: |
|
||||||
|
cd ${GITHUB_WORKSPACE}
|
||||||
|
pip3 install coverage==5.5
|
||||||
|
pip3 install coveralls==3.0.1
|
||||||
|
coveralls --finish
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
22
.github/workflows/translation_linter.yml
vendored
Normal file
22
.github/workflows/translation_linter.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Frappe Linter
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- develop
|
||||||
|
- version-12-hotfix
|
||||||
|
- version-11-hotfix
|
||||||
|
jobs:
|
||||||
|
check_translation:
|
||||||
|
name: Translation Syntax Check
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Setup python3
|
||||||
|
uses: actions/setup-python@v1
|
||||||
|
with:
|
||||||
|
python-version: 3.6
|
||||||
|
- name: Validating Translation Syntax
|
||||||
|
run: |
|
||||||
|
git fetch origin $GITHUB_BASE_REF:$GITHUB_BASE_REF -q
|
||||||
|
files=$(git diff --name-only --diff-filter=d $GITHUB_BASE_REF)
|
||||||
|
python $GITHUB_WORKSPACE/.github/helper/translation.py $files
|
||||||
@@ -1,61 +1,46 @@
|
|||||||
name: Server (Postgres)
|
name: UI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.js'
|
|
||||||
- '**.md'
|
- '**.md'
|
||||||
- '**.html'
|
workflow_dispatch:
|
||||||
types: [opened, labelled, synchronize, reopened]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: server-postgres-develop-${{ github.event.number }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'postgres') }}
|
runs-on: ubuntu-18.04
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 60
|
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
|
||||||
container: [1, 2, 3]
|
|
||||||
|
|
||||||
name: Python Unit Tests
|
name: UI Tests (Cypress)
|
||||||
|
|
||||||
services:
|
services:
|
||||||
postgres:
|
mysql:
|
||||||
image: postgres:13.3
|
image: mariadb:10.3
|
||||||
env:
|
env:
|
||||||
POSTGRES_PASSWORD: travis
|
MYSQL_ALLOW_EMPTY_PASSWORD: YES
|
||||||
options: >-
|
|
||||||
--health-cmd pg_isready
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
--health-retries 5
|
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 3306:3306
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Clone
|
- name: Clone
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.8
|
python-version: 3.7
|
||||||
|
|
||||||
- name: Setup Node
|
- uses: actions/setup-node@v2
|
||||||
uses: actions/setup-node@v2
|
|
||||||
with:
|
with:
|
||||||
node-version: 14
|
node-version: 14
|
||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Add to Hosts
|
- name: Add to Hosts
|
||||||
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
run: |
|
||||||
|
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
|
||||||
|
|
||||||
- name: Cache pip
|
- name: Cache pip
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2
|
||||||
@@ -90,16 +75,38 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-yarn-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
|
- name: Cache cypress binary
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.cache
|
||||||
|
key: ${{ runner.os }}-cypress-
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-cypress-
|
||||||
|
${{ runner.os }}-
|
||||||
|
|
||||||
- name: Install
|
- name: Install
|
||||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||||
env:
|
env:
|
||||||
DB: postgres
|
DB: mariadb
|
||||||
TYPE: server
|
TYPE: ui
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Site Setup
|
||||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator
|
run: cd ~/frappe-bench/ && bench --site test_site execute erpnext.setup.utils.before_tests
|
||||||
|
|
||||||
|
- name: cypress pre-requisites
|
||||||
|
run: cd ~/frappe-bench/apps/frappe && yarn add cypress-file-upload@^5 @testing-library/cypress@^8 --no-lockfile
|
||||||
|
|
||||||
|
|
||||||
|
- name: Build Assets
|
||||||
|
run: cd ~/frappe-bench/ && bench build
|
||||||
env:
|
env:
|
||||||
TYPE: server
|
CI: Yes
|
||||||
CI_BUILD_ID: ${{ github.run_id }}
|
|
||||||
ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
|
- name: UI Tests
|
||||||
|
run: cd ~/frappe-bench/ && bench --site test_site run-ui-tests erpnext --headless
|
||||||
|
env:
|
||||||
|
CYPRESS_RECORD_KEY: 60a8e3bf-08f5-45b1-9269-2b207d7d30cd
|
||||||
|
|
||||||
|
- name: Show bench console if tests failed
|
||||||
|
if: ${{ failure() }}
|
||||||
|
run: cat ~/frappe-bench/bench_run_logs.txt
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,7 +7,6 @@ latest_updates.json
|
|||||||
.wnf-lang-status
|
.wnf-lang-status
|
||||||
*.egg-info
|
*.egg-info
|
||||||
dist/
|
dist/
|
||||||
erpnext/public/dist
|
|
||||||
erpnext/docs/current
|
erpnext/docs/current
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
@@ -16,4 +15,3 @@ __pycache__
|
|||||||
.idea/
|
.idea/
|
||||||
.vscode/
|
.vscode/
|
||||||
node_modules/
|
node_modules/
|
||||||
.backportrc.json
|
|
||||||
66
.mergify.yml
66
.mergify.yml
@@ -7,8 +7,6 @@ pull_request_rules:
|
|||||||
- author!=gavindsouza
|
- author!=gavindsouza
|
||||||
- author!=rohitwaghchaure
|
- author!=rohitwaghchaure
|
||||||
- author!=nabinhait
|
- author!=nabinhait
|
||||||
- author!=ankush
|
|
||||||
- author!=deepeshgarg007
|
|
||||||
- or:
|
- or:
|
||||||
- base=version-13
|
- base=version-13
|
||||||
- base=version-12
|
- base=version-12
|
||||||
@@ -19,36 +17,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.
|
@{{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
|
https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
|
||||||
|
|
||||||
- name: backport to develop
|
|
||||||
conditions:
|
|
||||||
- label="backport develop"
|
|
||||||
actions:
|
|
||||||
backport:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
assignees:
|
|
||||||
- "{{ author }}"
|
|
||||||
|
|
||||||
- name: backport to version-14-hotfix
|
|
||||||
conditions:
|
|
||||||
- label="backport version-14-hotfix"
|
|
||||||
actions:
|
|
||||||
backport:
|
|
||||||
branches:
|
|
||||||
- version-14-hotfix
|
|
||||||
assignees:
|
|
||||||
- "{{ author }}"
|
|
||||||
|
|
||||||
- name: backport to version-14-pre-release
|
|
||||||
conditions:
|
|
||||||
- label="backport version-14-pre-release"
|
|
||||||
actions:
|
|
||||||
backport:
|
|
||||||
branches:
|
|
||||||
- version-14-pre-release
|
|
||||||
assignees:
|
|
||||||
- "{{ author }}"
|
|
||||||
|
|
||||||
- name: backport to version-13-hotfix
|
- name: backport to version-13-hotfix
|
||||||
conditions:
|
conditions:
|
||||||
- label="backport version-13-hotfix"
|
- label="backport version-13-hotfix"
|
||||||
@@ -88,37 +56,3 @@ pull_request_rules:
|
|||||||
- version-12-pre-release
|
- version-12-pre-release
|
||||||
assignees:
|
assignees:
|
||||||
- "{{ author }}"
|
- "{{ author }}"
|
||||||
|
|
||||||
- name: Automatic merge on CI success and review
|
|
||||||
conditions:
|
|
||||||
- status-success=linters
|
|
||||||
- status-success=Sider
|
|
||||||
- status-success=Semantic Pull Request
|
|
||||||
- status-success=Patch Test
|
|
||||||
- status-success=Python Unit Tests (1)
|
|
||||||
- status-success=Python Unit Tests (2)
|
|
||||||
- status-success=Python Unit Tests (3)
|
|
||||||
- label!=dont-merge
|
|
||||||
- label!=squash
|
|
||||||
- "#approved-reviews-by>=1"
|
|
||||||
actions:
|
|
||||||
merge:
|
|
||||||
method: merge
|
|
||||||
- name: Automatic squash on CI success and review
|
|
||||||
conditions:
|
|
||||||
- status-success=linters
|
|
||||||
- status-success=Sider
|
|
||||||
- status-success=Patch Test
|
|
||||||
- status-success=Python Unit Tests (1)
|
|
||||||
- status-success=Python Unit Tests (2)
|
|
||||||
- status-success=Python Unit Tests (3)
|
|
||||||
- label!=dont-merge
|
|
||||||
- label=squash
|
|
||||||
- "#approved-reviews-by>=1"
|
|
||||||
actions:
|
|
||||||
merge:
|
|
||||||
method: squash
|
|
||||||
commit_message_template: |
|
|
||||||
{{ title }} (#{{ number }})
|
|
||||||
|
|
||||||
{{ body }}
|
|
||||||
|
|||||||
@@ -26,19 +26,12 @@ repos:
|
|||||||
args: ['--config', '.github/helper/.flake8_strict']
|
args: ['--config', '.github/helper/.flake8_strict']
|
||||||
exclude: ".*setup.py$"
|
exclude: ".*setup.py$"
|
||||||
|
|
||||||
- repo: https://github.com/adityahase/black
|
|
||||||
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
|
|
||||||
hooks:
|
|
||||||
- id: black
|
|
||||||
additional_dependencies: ['click==8.0.4']
|
|
||||||
|
|
||||||
- repo: https://github.com/timothycrosley/isort
|
- repo: https://github.com/timothycrosley/isort
|
||||||
rev: 5.9.1
|
rev: 5.9.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
exclude: ".*setup.py$"
|
exclude: ".*setup.py$"
|
||||||
|
|
||||||
|
|
||||||
ci:
|
ci:
|
||||||
autoupdate_schedule: weekly
|
autoupdate_schedule: weekly
|
||||||
skip: []
|
skip: []
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ erpnext/stock/ @marination @rohitwaghchaure @ankush
|
|||||||
|
|
||||||
erpnext/crm/ @ruchamahabal @pateljannat
|
erpnext/crm/ @ruchamahabal @pateljannat
|
||||||
erpnext/education/ @ruchamahabal @pateljannat
|
erpnext/education/ @ruchamahabal @pateljannat
|
||||||
|
erpnext/healthcare/ @ruchamahabal @pateljannat @chillaranand
|
||||||
erpnext/hr/ @ruchamahabal @pateljannat
|
erpnext/hr/ @ruchamahabal @pateljannat
|
||||||
|
erpnext/non_profit/ @ruchamahabal
|
||||||
erpnext/payroll @ruchamahabal @pateljannat
|
erpnext/payroll @ruchamahabal @pateljannat
|
||||||
erpnext/projects/ @ruchamahabal @pateljannat
|
erpnext/projects/ @ruchamahabal @pateljannat
|
||||||
|
|
||||||
erpnext/controllers/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination @ankush
|
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||||
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination @ankush
|
|
||||||
erpnext/public/ @nextchamp-saqib @marination
|
|
||||||
|
|
||||||
.github/ @ankush
|
.github/ @surajshetty3416 @ankush
|
||||||
requirements.txt @gavindsouza
|
requirements.txt @gavindsouza
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -5,11 +5,9 @@
|
|||||||
<p>ERP made simple</p>
|
<p>ERP made simple</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/frappe/erpnext/actions/workflows/server-tests.yml)
|
[](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml)
|
||||||
[](https://github.com/erpnext/erpnext_ui_tests/actions/workflows/ui-tests.yml)
|
|
||||||
[](https://www.codetriage.com/frappe/erpnext)
|
[](https://www.codetriage.com/frappe/erpnext)
|
||||||
[](https://codecov.io/gh/frappe/erpnext)
|
[](https://coveralls.io/github/frappe/erpnext?branch=develop)
|
||||||
[](https://hub.docker.com/r/frappe/erpnext-worker)
|
|
||||||
|
|
||||||
[https://erpnext.com](https://erpnext.com)
|
[https://erpnext.com](https://erpnext.com)
|
||||||
|
|
||||||
@@ -41,12 +39,6 @@ ERPNext is built on the [Frappe Framework](https://github.com/frappe/frappe), a
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<a href="https://frappecloud.com/deploy?apps=frappe,erpnext&source=erpnext_readme">
|
|
||||||
<img src=".github/try-on-f-cloud-button.svg" height="40">
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
### Containerized Installation
|
### Containerized Installation
|
||||||
|
|
||||||
Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
|
Use docker to deploy ERPNext in production or for development of [Frappe](https://github.com/frappe/frappe) apps. See https://github.com/frappe/frappe_docker for more details.
|
||||||
@@ -57,6 +49,14 @@ The Easy Way: our install script for bench will install all dependencies (e.g. M
|
|||||||
|
|
||||||
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
|
New passwords will be created for the ERPNext "Administrator" user, the MariaDB root user, and the frappe user (the script displays the passwords and saves them to ~/frappe_passwords.txt).
|
||||||
|
|
||||||
|
### Virtual Image
|
||||||
|
|
||||||
|
You can download a virtual image to run ERPNext in a virtual machine on your local system.
|
||||||
|
|
||||||
|
- [ERPNext Download](http://erpnext.com/download)
|
||||||
|
|
||||||
|
System and user credentials are listed on the download page.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## License
|
## License
|
||||||
@@ -77,12 +77,6 @@ The ERPNext code is licensed as GNU General Public License (v3) and the Document
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Learning
|
|
||||||
|
|
||||||
1. [Frappe School](https://frappe.school) - Learn Frappe Framework and ERPNext from the various courses by the maintainers or from the community.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Logo and Trademark
|
## Logo and Trademark
|
||||||
|
|
||||||
The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd.
|
The brand name ERPNext and the logo are trademarks of Frappe Technologies Pvt. Ltd.
|
||||||
|
|||||||
27
codecov.yml
27
codecov.yml
@@ -1,27 +0,0 @@
|
|||||||
codecov:
|
|
||||||
require_ci_to_pass: yes
|
|
||||||
|
|
||||||
coverage:
|
|
||||||
status:
|
|
||||||
project:
|
|
||||||
default:
|
|
||||||
target: auto
|
|
||||||
threshold: 0.5%
|
|
||||||
|
|
||||||
patch:
|
|
||||||
default:
|
|
||||||
target: 85%
|
|
||||||
threshold: 0%
|
|
||||||
base: auto
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
if_ci_failed: ignore
|
|
||||||
only_pulls: true
|
|
||||||
|
|
||||||
comment:
|
|
||||||
layout: "diff, files"
|
|
||||||
require_changes: true
|
|
||||||
after_n_builds: 3
|
|
||||||
|
|
||||||
ignore:
|
|
||||||
- "erpnext/demo"
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"baseUrl": "http://test_site:8000/",
|
"baseUrl": "http://test_site:8000",
|
||||||
"projectId": "da59y9",
|
"projectId": "da59y9",
|
||||||
"adminPassword": "admin",
|
"adminPassword": "admin",
|
||||||
"defaultCommandTimeout": 20000,
|
"defaultCommandTimeout": 20000,
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
describe("Bulk Transaction Processing", () => {
|
|
||||||
before(() => {
|
|
||||||
cy.login();
|
|
||||||
cy.visit("/app/website");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Creates To Sales Order", () => {
|
|
||||||
cy.visit("/app/sales-order");
|
|
||||||
cy.url().should("include", "/sales-order");
|
|
||||||
cy.window()
|
|
||||||
.its("frappe.csrf_token")
|
|
||||||
.then((csrf_token) => {
|
|
||||||
return cy
|
|
||||||
.request({
|
|
||||||
url: "/api/method/erpnext.tests.ui_test_bulk_transaction_processing.create_records",
|
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"X-Frappe-CSRF-Token": csrf_token,
|
|
||||||
},
|
|
||||||
timeout: 60000,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
expect(res.status).eq(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
cy.wait(5000);
|
|
||||||
cy.get(
|
|
||||||
".list-row-head > .list-header-subject > .list-row-col > .list-check-all"
|
|
||||||
).check({ force: true });
|
|
||||||
cy.wait(3000);
|
|
||||||
cy.get(".actions-btn-group > .btn-primary").click({ force: true });
|
|
||||||
cy.wait(3000);
|
|
||||||
cy.get(".dropdown-menu-right > .user-action > .dropdown-item")
|
|
||||||
.contains("Sales Invoice")
|
|
||||||
.click({ force: true });
|
|
||||||
cy.wait(3000);
|
|
||||||
cy.get(".modal-content > .modal-footer > .standard-actions")
|
|
||||||
.contains("Yes")
|
|
||||||
.click({ force: true });
|
|
||||||
cy.contains("Creation of Sales Invoice successful");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
describe("Test Item Dashboard", () => {
|
|
||||||
before(() => {
|
|
||||||
cy.login();
|
|
||||||
cy.visit("/app/item");
|
|
||||||
cy.insert_doc(
|
|
||||||
"Item",
|
|
||||||
{
|
|
||||||
item_code: "e2e_test_item",
|
|
||||||
item_group: "All Item Groups",
|
|
||||||
opening_stock: 42,
|
|
||||||
valuation_rate: 100,
|
|
||||||
},
|
|
||||||
true
|
|
||||||
);
|
|
||||||
cy.go_to_doc("item", "e2e_test_item");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should show dashboard with correct data on first load", () => {
|
|
||||||
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
|
|
||||||
cy.get(".stock-levels").contains("e2e_test_item").should("exist");
|
|
||||||
|
|
||||||
// reserved and available qty
|
|
||||||
cy.get(".stock-levels .inline-graph-count")
|
|
||||||
.eq(0)
|
|
||||||
.contains("0")
|
|
||||||
.should("exist");
|
|
||||||
cy.get(".stock-levels .inline-graph-count")
|
|
||||||
.eq(1)
|
|
||||||
.contains("42")
|
|
||||||
.should("exist");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should persist on field change", () => {
|
|
||||||
cy.get('input[data-fieldname="disabled"]').check();
|
|
||||||
cy.wait(500);
|
|
||||||
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
|
|
||||||
cy.get(".stock-levels").should("have.length", 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should persist on reload", () => {
|
|
||||||
cy.reload();
|
|
||||||
cy.get(".stock-levels").contains("Stock Levels").should("be.visible");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
hypothesis~=6.31.0
|
|
||||||
@@ -1,58 +1,55 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
|
|
||||||
__version__ = "14.0.0-beta.4"
|
from erpnext.hooks import regional_overrides
|
||||||
|
|
||||||
|
__version__ = '13.13.0'
|
||||||
|
|
||||||
def get_default_company(user=None):
|
def get_default_company(user=None):
|
||||||
"""Get default company for user"""
|
'''Get default company for user'''
|
||||||
from frappe.defaults import get_user_default_as_list
|
from frappe.defaults import get_user_default_as_list
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
user = frappe.session.user
|
user = frappe.session.user
|
||||||
|
|
||||||
companies = get_user_default_as_list(user, "company")
|
companies = get_user_default_as_list(user, 'company')
|
||||||
if companies:
|
if companies:
|
||||||
default_company = companies[0]
|
default_company = companies[0]
|
||||||
else:
|
else:
|
||||||
default_company = frappe.db.get_single_value("Global Defaults", "default_company")
|
default_company = frappe.db.get_single_value('Global Defaults', 'default_company')
|
||||||
|
|
||||||
return default_company
|
return default_company
|
||||||
|
|
||||||
|
|
||||||
def get_default_currency():
|
def get_default_currency():
|
||||||
"""Returns the currency of the default company"""
|
'''Returns the currency of the default company'''
|
||||||
company = get_default_company()
|
company = get_default_company()
|
||||||
if company:
|
if company:
|
||||||
return frappe.get_cached_value("Company", company, "default_currency")
|
return frappe.get_cached_value('Company', company, 'default_currency')
|
||||||
|
|
||||||
|
|
||||||
def get_default_cost_center(company):
|
def get_default_cost_center(company):
|
||||||
"""Returns the default cost center of the company"""
|
'''Returns the default cost center of the company'''
|
||||||
if not company:
|
if not company:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if not frappe.flags.company_cost_center:
|
if not frappe.flags.company_cost_center:
|
||||||
frappe.flags.company_cost_center = {}
|
frappe.flags.company_cost_center = {}
|
||||||
if not company in frappe.flags.company_cost_center:
|
if not company in frappe.flags.company_cost_center:
|
||||||
frappe.flags.company_cost_center[company] = frappe.get_cached_value(
|
frappe.flags.company_cost_center[company] = frappe.get_cached_value('Company', company, 'cost_center')
|
||||||
"Company", company, "cost_center"
|
|
||||||
)
|
|
||||||
return frappe.flags.company_cost_center[company]
|
return frappe.flags.company_cost_center[company]
|
||||||
|
|
||||||
|
|
||||||
def get_company_currency(company):
|
def get_company_currency(company):
|
||||||
"""Returns the default company currency"""
|
'''Returns the default company currency'''
|
||||||
if not frappe.flags.company_currency:
|
if not frappe.flags.company_currency:
|
||||||
frappe.flags.company_currency = {}
|
frappe.flags.company_currency = {}
|
||||||
if not company in frappe.flags.company_currency:
|
if not company in frappe.flags.company_currency:
|
||||||
frappe.flags.company_currency[company] = frappe.db.get_value(
|
frappe.flags.company_currency[company] = frappe.db.get_value('Company', company, 'default_currency', cache=True)
|
||||||
"Company", company, "default_currency", cache=True
|
|
||||||
)
|
|
||||||
return frappe.flags.company_currency[company]
|
return frappe.flags.company_currency[company]
|
||||||
|
|
||||||
|
|
||||||
def set_perpetual_inventory(enable=1, company=None):
|
def set_perpetual_inventory(enable=1, company=None):
|
||||||
if not company:
|
if not company:
|
||||||
company = "_Test Company" if frappe.flags.in_test else get_default_company()
|
company = "_Test Company" if frappe.flags.in_test else get_default_company()
|
||||||
@@ -61,10 +58,9 @@ def set_perpetual_inventory(enable=1, company=None):
|
|||||||
company.enable_perpetual_inventory = enable
|
company.enable_perpetual_inventory = enable
|
||||||
company.save()
|
company.save()
|
||||||
|
|
||||||
|
def encode_company_abbr(name, company):
|
||||||
def encode_company_abbr(name, company=None, abbr=None):
|
'''Returns name encoded with company abbreviation'''
|
||||||
"""Returns name encoded with company abbreviation"""
|
company_abbr = frappe.get_cached_value('Company', company, "abbr")
|
||||||
company_abbr = abbr or frappe.get_cached_value("Company", company, "abbr")
|
|
||||||
parts = name.rsplit(" - ", 1)
|
parts = name.rsplit(" - ", 1)
|
||||||
|
|
||||||
if parts[-1].lower() != company_abbr.lower():
|
if parts[-1].lower() != company_abbr.lower():
|
||||||
@@ -72,78 +68,76 @@ def encode_company_abbr(name, company=None, abbr=None):
|
|||||||
|
|
||||||
return " - ".join(parts)
|
return " - ".join(parts)
|
||||||
|
|
||||||
|
|
||||||
def is_perpetual_inventory_enabled(company):
|
def is_perpetual_inventory_enabled(company):
|
||||||
if not company:
|
if not company:
|
||||||
company = "_Test Company" if frappe.flags.in_test else get_default_company()
|
company = "_Test Company" if frappe.flags.in_test else get_default_company()
|
||||||
|
|
||||||
if not hasattr(frappe.local, "enable_perpetual_inventory"):
|
if not hasattr(frappe.local, 'enable_perpetual_inventory'):
|
||||||
frappe.local.enable_perpetual_inventory = {}
|
frappe.local.enable_perpetual_inventory = {}
|
||||||
|
|
||||||
if not company in frappe.local.enable_perpetual_inventory:
|
if not company in frappe.local.enable_perpetual_inventory:
|
||||||
frappe.local.enable_perpetual_inventory[company] = (
|
frappe.local.enable_perpetual_inventory[company] = frappe.get_cached_value('Company',
|
||||||
frappe.get_cached_value("Company", company, "enable_perpetual_inventory") or 0
|
company, "enable_perpetual_inventory") or 0
|
||||||
)
|
|
||||||
|
|
||||||
return frappe.local.enable_perpetual_inventory[company]
|
return frappe.local.enable_perpetual_inventory[company]
|
||||||
|
|
||||||
|
|
||||||
def get_default_finance_book(company=None):
|
def get_default_finance_book(company=None):
|
||||||
if not company:
|
if not company:
|
||||||
company = get_default_company()
|
company = get_default_company()
|
||||||
|
|
||||||
if not hasattr(frappe.local, "default_finance_book"):
|
if not hasattr(frappe.local, 'default_finance_book'):
|
||||||
frappe.local.default_finance_book = {}
|
frappe.local.default_finance_book = {}
|
||||||
|
|
||||||
if not company in frappe.local.default_finance_book:
|
if not company in frappe.local.default_finance_book:
|
||||||
frappe.local.default_finance_book[company] = frappe.get_cached_value(
|
frappe.local.default_finance_book[company] = frappe.get_cached_value('Company',
|
||||||
"Company", company, "default_finance_book"
|
company, "default_finance_book")
|
||||||
)
|
|
||||||
|
|
||||||
return frappe.local.default_finance_book[company]
|
return frappe.local.default_finance_book[company]
|
||||||
|
|
||||||
|
|
||||||
def get_party_account_type(party_type):
|
def get_party_account_type(party_type):
|
||||||
if not hasattr(frappe.local, "party_account_types"):
|
if not hasattr(frappe.local, 'party_account_types'):
|
||||||
frappe.local.party_account_types = {}
|
frappe.local.party_account_types = {}
|
||||||
|
|
||||||
if not party_type in frappe.local.party_account_types:
|
if not party_type in frappe.local.party_account_types:
|
||||||
frappe.local.party_account_types[party_type] = (
|
frappe.local.party_account_types[party_type] = frappe.db.get_value("Party Type",
|
||||||
frappe.db.get_value("Party Type", party_type, "account_type") or ""
|
party_type, "account_type") or ''
|
||||||
)
|
|
||||||
|
|
||||||
return frappe.local.party_account_types[party_type]
|
return frappe.local.party_account_types[party_type]
|
||||||
|
|
||||||
|
|
||||||
def get_region(company=None):
|
def get_region(company=None):
|
||||||
"""Return the default country based on flag, company or global settings
|
'''Return the default country based on flag, company or global settings
|
||||||
|
|
||||||
You can also set global company flag in `frappe.flags.company`
|
You can also set global company flag in `frappe.flags.company`
|
||||||
"""
|
'''
|
||||||
if company or frappe.flags.company:
|
if company or frappe.flags.company:
|
||||||
return frappe.get_cached_value("Company", company or frappe.flags.company, "country")
|
return frappe.get_cached_value('Company',
|
||||||
|
company or frappe.flags.company, 'country')
|
||||||
elif frappe.flags.country:
|
elif frappe.flags.country:
|
||||||
return frappe.flags.country
|
return frappe.flags.country
|
||||||
else:
|
else:
|
||||||
return frappe.get_system_settings("country")
|
return frappe.get_system_settings('country')
|
||||||
|
|
||||||
|
|
||||||
def allow_regional(fn):
|
def allow_regional(fn):
|
||||||
"""Decorator to make a function regionally overridable
|
'''Decorator to make a function regionally overridable
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@erpnext.allow_regional
|
@erpnext.allow_regional
|
||||||
def myfunction():
|
def myfunction():
|
||||||
pass"""
|
pass'''
|
||||||
|
|
||||||
def caller(*args, **kwargs):
|
def caller(*args, **kwargs):
|
||||||
overrides = frappe.get_hooks("regional_overrides", {}).get(get_region())
|
region = get_region()
|
||||||
function_path = f"{inspect.getmodule(fn).__name__}.{fn.__name__}"
|
fn_name = inspect.getmodule(fn).__name__ + '.' + fn.__name__
|
||||||
|
if region in regional_overrides and fn_name in regional_overrides[region]:
|
||||||
if not overrides or function_path not in overrides:
|
return frappe.get_attr(regional_overrides[region][fn_name])(*args, **kwargs)
|
||||||
|
else:
|
||||||
return fn(*args, **kwargs)
|
return fn(*args, **kwargs)
|
||||||
|
|
||||||
# Priority given to last installed app
|
|
||||||
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
|
|
||||||
|
|
||||||
return caller
|
return caller
|
||||||
|
|
||||||
|
def get_last_membership(member):
|
||||||
|
'''Returns last membership if exists'''
|
||||||
|
last_membership = frappe.get_all('Membership', 'name,to_date,membership_type',
|
||||||
|
dict(member=member, paid=1), order_by='to_date desc', limit=1)
|
||||||
|
|
||||||
|
if last_membership:
|
||||||
|
return last_membership[0]
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ from frappe.contacts.doctype.address.address import (
|
|||||||
class ERPNextAddress(Address):
|
class ERPNextAddress(Address):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_reference()
|
self.validate_reference()
|
||||||
self.update_compnay_address()
|
|
||||||
super(ERPNextAddress, self).validate()
|
super(ERPNextAddress, self).validate()
|
||||||
|
|
||||||
def link_address(self):
|
def link_address(self):
|
||||||
@@ -20,40 +19,36 @@ class ERPNextAddress(Address):
|
|||||||
|
|
||||||
return super(ERPNextAddress, self).link_address()
|
return super(ERPNextAddress, self).link_address()
|
||||||
|
|
||||||
def update_compnay_address(self):
|
|
||||||
for link in self.get("links"):
|
|
||||||
if link.link_doctype == "Company":
|
|
||||||
self.is_your_company_address = 1
|
|
||||||
|
|
||||||
def validate_reference(self):
|
def validate_reference(self):
|
||||||
if self.is_your_company_address and not [
|
if self.is_your_company_address and not [
|
||||||
row for row in self.links if row.link_doctype == "Company"
|
row for row in self.links if row.link_doctype == "Company"
|
||||||
]:
|
]:
|
||||||
frappe.throw(
|
frappe.throw(_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
|
||||||
_("Address needs to be linked to a Company. Please add a row for Company in the Links table."),
|
title=_("Company Not Linked"))
|
||||||
title=_("Company Not Linked"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
"""
|
"""
|
||||||
After Address is updated, update the related 'Primary Address' on Customer.
|
After Address is updated, update the related 'Primary Address' on Customer.
|
||||||
"""
|
"""
|
||||||
address_display = get_address_display(self.as_dict())
|
address_display = get_address_display(self.as_dict())
|
||||||
filters = {"customer_primary_address": self.name}
|
filters = {
|
||||||
|
"customer_primary_address": self.name
|
||||||
|
}
|
||||||
|
|
||||||
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
|
customers = frappe.db.get_all("Customer", filters=filters, as_list=True)
|
||||||
for customer_name in customers:
|
for customer_name in customers:
|
||||||
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
|
frappe.db.set_value("Customer", customer_name[0], "primary_address", address_display)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_shipping_address(company, address=None):
|
def get_shipping_address(company, address = None):
|
||||||
filters = [
|
filters = [
|
||||||
["Dynamic Link", "link_doctype", "=", "Company"],
|
["Dynamic Link", "link_doctype", "=", "Company"],
|
||||||
["Dynamic Link", "link_name", "=", company],
|
["Dynamic Link", "link_name", "=", company],
|
||||||
["Address", "is_your_company_address", "=", 1],
|
["Address", "is_your_company_address", "=", 1]
|
||||||
]
|
]
|
||||||
fields = ["*"]
|
fields = ["*"]
|
||||||
if address and frappe.db.get_value("Dynamic Link", {"parent": address, "link_name": company}):
|
if address and frappe.db.get_value('Dynamic Link',
|
||||||
|
{'parent': address, 'link_name': company}):
|
||||||
filters.append(["Address", "name", "=", address])
|
filters.append(["Address", "name", "=", address])
|
||||||
if not address:
|
if not address:
|
||||||
filters.append(["Address", "is_shipping_address", "=", 1])
|
filters.append(["Address", "is_shipping_address", "=", 1])
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@@ -12,24 +13,15 @@ from frappe.utils.nestedset import get_descendants_of
|
|||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@cache_source
|
@cache_source
|
||||||
def get(
|
def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None,
|
||||||
chart_name=None,
|
to_date = None, timespan = None, time_interval = None, heatmap_year = None):
|
||||||
chart=None,
|
|
||||||
no_cache=None,
|
|
||||||
filters=None,
|
|
||||||
from_date=None,
|
|
||||||
to_date=None,
|
|
||||||
timespan=None,
|
|
||||||
time_interval=None,
|
|
||||||
heatmap_year=None,
|
|
||||||
):
|
|
||||||
if chart_name:
|
if chart_name:
|
||||||
chart = frappe.get_doc("Dashboard Chart", chart_name)
|
chart = frappe.get_doc('Dashboard Chart', chart_name)
|
||||||
else:
|
else:
|
||||||
chart = frappe._dict(frappe.parse_json(chart))
|
chart = frappe._dict(frappe.parse_json(chart))
|
||||||
timespan = chart.timespan
|
timespan = chart.timespan
|
||||||
|
|
||||||
if chart.timespan == "Select Date Range":
|
if chart.timespan == 'Select Date Range':
|
||||||
from_date = chart.from_date
|
from_date = chart.from_date
|
||||||
to_date = chart.to_date
|
to_date = chart.to_date
|
||||||
|
|
||||||
@@ -40,23 +32,17 @@ def get(
|
|||||||
company = filters.get("company")
|
company = filters.get("company")
|
||||||
|
|
||||||
if not account and chart_name:
|
if not account and chart_name:
|
||||||
frappe.throw(
|
frappe.throw(_("Account is not set for the dashboard chart {0}")
|
||||||
_("Account is not set for the dashboard chart {0}").format(
|
.format(get_link_to_form("Dashboard Chart", chart_name)))
|
||||||
get_link_to_form("Dashboard Chart", chart_name)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not frappe.db.exists("Account", account) and chart_name:
|
if not frappe.db.exists("Account", account) and chart_name:
|
||||||
frappe.throw(
|
frappe.throw(_("Account {0} does not exists in the dashboard chart {1}")
|
||||||
_("Account {0} does not exists in the dashboard chart {1}").format(
|
.format(account, get_link_to_form("Dashboard Chart", chart_name)))
|
||||||
account, get_link_to_form("Dashboard Chart", chart_name)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if not to_date:
|
if not to_date:
|
||||||
to_date = nowdate()
|
to_date = nowdate()
|
||||||
if not from_date:
|
if not from_date:
|
||||||
if timegrain in ("Monthly", "Quarterly"):
|
if timegrain in ('Monthly', 'Quarterly'):
|
||||||
from_date = get_from_date_from_timespan(to_date, timespan)
|
from_date = get_from_date_from_timespan(to_date, timespan)
|
||||||
|
|
||||||
# fetch dates to plot
|
# fetch dates to plot
|
||||||
@@ -69,14 +55,16 @@ def get(
|
|||||||
result = build_result(account, dates, gl_entries)
|
result = build_result(account, dates, gl_entries)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"labels": [formatdate(r[0].strftime("%Y-%m-%d")) for r in result],
|
"labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result],
|
||||||
"datasets": [{"name": account, "values": [r[1] for r in result]}],
|
"datasets": [{
|
||||||
|
"name": account,
|
||||||
|
"values": [r[1] for r in result]
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_result(account, dates, gl_entries):
|
def build_result(account, dates, gl_entries):
|
||||||
result = [[getdate(date), 0.0] for date in dates]
|
result = [[getdate(date), 0.0] for date in dates]
|
||||||
root_type = frappe.db.get_value("Account", account, "root_type")
|
root_type = frappe.db.get_value('Account', account, 'root_type')
|
||||||
|
|
||||||
# start with the first date
|
# start with the first date
|
||||||
date_index = 0
|
date_index = 0
|
||||||
@@ -91,34 +79,30 @@ def build_result(account, dates, gl_entries):
|
|||||||
result[date_index][1] += entry.debit - entry.credit
|
result[date_index][1] += entry.debit - entry.credit
|
||||||
|
|
||||||
# if account type is credit, switch balances
|
# if account type is credit, switch balances
|
||||||
if root_type not in ("Asset", "Expense"):
|
if root_type not in ('Asset', 'Expense'):
|
||||||
for r in result:
|
for r in result:
|
||||||
r[1] = -1 * r[1]
|
r[1] = -1 * r[1]
|
||||||
|
|
||||||
# for balance sheet accounts, the totals are cumulative
|
# for balance sheet accounts, the totals are cumulative
|
||||||
if root_type in ("Asset", "Liability", "Equity"):
|
if root_type in ('Asset', 'Liability', 'Equity'):
|
||||||
for i, r in enumerate(result):
|
for i, r in enumerate(result):
|
||||||
if i > 0:
|
if i > 0:
|
||||||
r[1] = r[1] + result[i - 1][1]
|
r[1] = r[1] + result[i-1][1]
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_gl_entries(account, to_date):
|
def get_gl_entries(account, to_date):
|
||||||
child_accounts = get_descendants_of("Account", account, ignore_permissions=True)
|
child_accounts = get_descendants_of('Account', account, ignore_permissions=True)
|
||||||
child_accounts.append(account)
|
child_accounts.append(account)
|
||||||
|
|
||||||
return frappe.db.get_all(
|
return frappe.db.get_all('GL Entry',
|
||||||
"GL Entry",
|
fields = ['posting_date', 'debit', 'credit'],
|
||||||
fields=["posting_date", "debit", "credit"],
|
filters = [
|
||||||
filters=[
|
dict(posting_date = ('<', to_date)),
|
||||||
dict(posting_date=("<", to_date)),
|
dict(account = ('in', child_accounts)),
|
||||||
dict(account=("in", child_accounts)),
|
dict(voucher_type = ('!=', 'Period Closing Voucher'))
|
||||||
dict(voucher_type=("!=", "Period Closing Voucher")),
|
|
||||||
],
|
],
|
||||||
order_by="posting_date asc",
|
order_by = 'posting_date asc')
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dates_from_timegrain(from_date, to_date, timegrain):
|
def get_dates_from_timegrain(from_date, to_date, timegrain):
|
||||||
days = months = years = 0
|
days = months = years = 0
|
||||||
@@ -133,8 +117,6 @@ def get_dates_from_timegrain(from_date, to_date, timegrain):
|
|||||||
|
|
||||||
dates = [get_period_ending(from_date, timegrain)]
|
dates = [get_period_ending(from_date, timegrain)]
|
||||||
while getdate(dates[-1]) < getdate(to_date):
|
while getdate(dates[-1]) < getdate(to_date):
|
||||||
date = get_period_ending(
|
date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain)
|
||||||
add_to_date(dates[-1], years=years, months=months, days=days), timegrain
|
|
||||||
)
|
|
||||||
dates.append(date)
|
dates.append(date)
|
||||||
return dates
|
return dates
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.email import sendmail_to_system_managers
|
from frappe.email import sendmail_to_system_managers
|
||||||
@@ -22,23 +24,20 @@ from erpnext.accounts.utils import get_account_currency
|
|||||||
|
|
||||||
|
|
||||||
def validate_service_stop_date(doc):
|
def validate_service_stop_date(doc):
|
||||||
"""Validates service_stop_date for Purchase Invoice and Sales Invoice"""
|
''' Validates service_stop_date for Purchase Invoice and Sales Invoice '''
|
||||||
|
|
||||||
enable_check = (
|
enable_check = "enable_deferred_revenue" \
|
||||||
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
|
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
|
||||||
)
|
|
||||||
|
|
||||||
old_stop_dates = {}
|
old_stop_dates = {}
|
||||||
old_doc = frappe.db.get_all(
|
old_doc = frappe.db.get_all("{0} Item".format(doc.doctype),
|
||||||
"{0} Item".format(doc.doctype), {"parent": doc.name}, ["name", "service_stop_date"]
|
{"parent": doc.name}, ["name", "service_stop_date"])
|
||||||
)
|
|
||||||
|
|
||||||
for d in old_doc:
|
for d in old_doc:
|
||||||
old_stop_dates[d.name] = d.service_stop_date or ""
|
old_stop_dates[d.name] = d.service_stop_date or ""
|
||||||
|
|
||||||
for item in doc.items:
|
for item in doc.items:
|
||||||
if not item.get(enable_check):
|
if not item.get(enable_check): continue
|
||||||
continue
|
|
||||||
|
|
||||||
if item.service_stop_date:
|
if item.service_stop_date:
|
||||||
if date_diff(item.service_stop_date, item.service_start_date) < 0:
|
if date_diff(item.service_stop_date, item.service_start_date) < 0:
|
||||||
@@ -47,31 +46,21 @@ def validate_service_stop_date(doc):
|
|||||||
if date_diff(item.service_stop_date, item.service_end_date) > 0:
|
if date_diff(item.service_stop_date, item.service_end_date) > 0:
|
||||||
frappe.throw(_("Service Stop Date cannot be after Service End Date"))
|
frappe.throw(_("Service Stop Date cannot be after Service End Date"))
|
||||||
|
|
||||||
if (
|
if old_stop_dates and old_stop_dates.get(item.name) and item.service_stop_date!=old_stop_dates.get(item.name):
|
||||||
old_stop_dates
|
|
||||||
and old_stop_dates.get(item.name)
|
|
||||||
and item.service_stop_date != old_stop_dates.get(item.name)
|
|
||||||
):
|
|
||||||
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
|
frappe.throw(_("Cannot change Service Stop Date for item in row {0}").format(item.idx))
|
||||||
|
|
||||||
|
|
||||||
def build_conditions(process_type, account, company):
|
def build_conditions(process_type, account, company):
|
||||||
conditions = ""
|
conditions=''
|
||||||
deferred_account = (
|
deferred_account = "item.deferred_revenue_account" if process_type=="Income" else "item.deferred_expense_account"
|
||||||
"item.deferred_revenue_account" if process_type == "Income" else "item.deferred_expense_account"
|
|
||||||
)
|
|
||||||
|
|
||||||
if account:
|
if account:
|
||||||
conditions += "AND %s='%s'" % (deferred_account, account)
|
conditions += "AND %s='%s'"%(deferred_account, account)
|
||||||
elif company:
|
elif company:
|
||||||
conditions += f"AND p.company = {frappe.db.escape(company)}"
|
conditions += f"AND p.company = {frappe.db.escape(company)}"
|
||||||
|
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
|
def convert_deferred_expense_to_expense(deferred_process, start_date=None, end_date=None, conditions=''):
|
||||||
def convert_deferred_expense_to_expense(
|
|
||||||
deferred_process, start_date=None, end_date=None, conditions=""
|
|
||||||
):
|
|
||||||
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
||||||
|
|
||||||
if not start_date:
|
if not start_date:
|
||||||
@@ -80,19 +69,14 @@ def convert_deferred_expense_to_expense(
|
|||||||
end_date = add_days(today(), -1)
|
end_date = add_days(today(), -1)
|
||||||
|
|
||||||
# check for the purchase invoice for which GL entries has to be done
|
# check for the purchase invoice for which GL entries has to be done
|
||||||
invoices = frappe.db.sql_list(
|
invoices = frappe.db.sql_list('''
|
||||||
"""
|
|
||||||
select distinct item.parent
|
select distinct item.parent
|
||||||
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
|
from `tabPurchase Invoice Item` item, `tabPurchase Invoice` p
|
||||||
where item.service_start_date<=%s and item.service_end_date>=%s
|
where item.service_start_date<=%s and item.service_end_date>=%s
|
||||||
and item.enable_deferred_expense = 1 and item.parent=p.name
|
and item.enable_deferred_expense = 1 and item.parent=p.name
|
||||||
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
|
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
|
||||||
{0}
|
{0}
|
||||||
""".format(
|
'''.format(conditions), (end_date, start_date)) #nosec
|
||||||
conditions
|
|
||||||
),
|
|
||||||
(end_date, start_date),
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
# For each invoice, book deferred expense
|
# For each invoice, book deferred expense
|
||||||
for invoice in invoices:
|
for invoice in invoices:
|
||||||
@@ -102,10 +86,7 @@ def convert_deferred_expense_to_expense(
|
|||||||
if frappe.flags.deferred_accounting_error:
|
if frappe.flags.deferred_accounting_error:
|
||||||
send_mail(deferred_process)
|
send_mail(deferred_process)
|
||||||
|
|
||||||
|
def convert_deferred_revenue_to_income(deferred_process, start_date=None, end_date=None, conditions=''):
|
||||||
def convert_deferred_revenue_to_income(
|
|
||||||
deferred_process, start_date=None, end_date=None, conditions=""
|
|
||||||
):
|
|
||||||
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
# book the expense/income on the last day, but it will be trigger on the 1st of month at 12:00 AM
|
||||||
|
|
||||||
if not start_date:
|
if not start_date:
|
||||||
@@ -114,19 +95,14 @@ def convert_deferred_revenue_to_income(
|
|||||||
end_date = add_days(today(), -1)
|
end_date = add_days(today(), -1)
|
||||||
|
|
||||||
# check for the sales invoice for which GL entries has to be done
|
# check for the sales invoice for which GL entries has to be done
|
||||||
invoices = frappe.db.sql_list(
|
invoices = frappe.db.sql_list('''
|
||||||
"""
|
|
||||||
select distinct item.parent
|
select distinct item.parent
|
||||||
from `tabSales Invoice Item` item, `tabSales Invoice` p
|
from `tabSales Invoice Item` item, `tabSales Invoice` p
|
||||||
where item.service_start_date<=%s and item.service_end_date>=%s
|
where item.service_start_date<=%s and item.service_end_date>=%s
|
||||||
and item.enable_deferred_revenue = 1 and item.parent=p.name
|
and item.enable_deferred_revenue = 1 and item.parent=p.name
|
||||||
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
|
and item.docstatus = 1 and ifnull(item.amount, 0) > 0
|
||||||
{0}
|
{0}
|
||||||
""".format(
|
'''.format(conditions), (end_date, start_date)) #nosec
|
||||||
conditions
|
|
||||||
),
|
|
||||||
(end_date, start_date),
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
for invoice in invoices:
|
for invoice in invoices:
|
||||||
doc = frappe.get_doc("Sales Invoice", invoice)
|
doc = frappe.get_doc("Sales Invoice", invoice)
|
||||||
@@ -135,43 +111,30 @@ def convert_deferred_revenue_to_income(
|
|||||||
if frappe.flags.deferred_accounting_error:
|
if frappe.flags.deferred_accounting_error:
|
||||||
send_mail(deferred_process)
|
send_mail(deferred_process)
|
||||||
|
|
||||||
|
|
||||||
def get_booking_dates(doc, item, posting_date=None):
|
def get_booking_dates(doc, item, posting_date=None):
|
||||||
if not posting_date:
|
if not posting_date:
|
||||||
posting_date = add_days(today(), -1)
|
posting_date = add_days(today(), -1)
|
||||||
|
|
||||||
last_gl_entry = False
|
last_gl_entry = False
|
||||||
|
|
||||||
deferred_account = (
|
deferred_account = "deferred_revenue_account" if doc.doctype=="Sales Invoice" else "deferred_expense_account"
|
||||||
"deferred_revenue_account" if doc.doctype == "Sales Invoice" else "deferred_expense_account"
|
|
||||||
)
|
|
||||||
|
|
||||||
prev_gl_entry = frappe.db.sql(
|
prev_gl_entry = frappe.db.sql('''
|
||||||
"""
|
|
||||||
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
|
select name, posting_date from `tabGL Entry` where company=%s and account=%s and
|
||||||
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
||||||
and is_cancelled = 0
|
|
||||||
order by posting_date desc limit 1
|
order by posting_date desc limit 1
|
||||||
""",
|
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
|
||||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
prev_gl_via_je = frappe.db.sql(
|
prev_gl_via_je = frappe.db.sql('''
|
||||||
"""
|
|
||||||
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
|
SELECT p.name, p.posting_date FROM `tabJournal Entry` p, `tabJournal Entry Account` c
|
||||||
WHERE p.name = c.parent and p.company=%s and c.account=%s
|
WHERE p.name = c.parent and p.company=%s and c.account=%s
|
||||||
and c.reference_type=%s and c.reference_name=%s
|
and c.reference_type=%s and c.reference_name=%s
|
||||||
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
|
and c.reference_detail_no=%s and c.docstatus < 2 order by posting_date desc limit 1
|
||||||
""",
|
''', (doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
|
||||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if prev_gl_via_je:
|
if prev_gl_via_je:
|
||||||
if (not prev_gl_entry) or (
|
if (not prev_gl_entry) or (prev_gl_entry and
|
||||||
prev_gl_entry and prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date
|
prev_gl_entry[0].posting_date < prev_gl_via_je[0].posting_date):
|
||||||
):
|
|
||||||
prev_gl_entry = prev_gl_via_je
|
prev_gl_entry = prev_gl_via_je
|
||||||
|
|
||||||
if prev_gl_entry:
|
if prev_gl_entry:
|
||||||
@@ -195,94 +158,66 @@ def get_booking_dates(doc, item, posting_date=None):
|
|||||||
else:
|
else:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
|
||||||
|
def calculate_monthly_amount(doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency):
|
||||||
def calculate_monthly_amount(
|
|
||||||
doc, item, last_gl_entry, start_date, end_date, total_days, total_booking_days, account_currency
|
|
||||||
):
|
|
||||||
amount, base_amount = 0, 0
|
amount, base_amount = 0, 0
|
||||||
|
|
||||||
if not last_gl_entry:
|
if not last_gl_entry:
|
||||||
total_months = (
|
total_months = (item.service_end_date.year - item.service_start_date.year) * 12 + \
|
||||||
(item.service_end_date.year - item.service_start_date.year) * 12
|
(item.service_end_date.month - item.service_start_date.month) + 1
|
||||||
+ (item.service_end_date.month - item.service_start_date.month)
|
|
||||||
+ 1
|
|
||||||
)
|
|
||||||
|
|
||||||
prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) / flt(
|
prorate_factor = flt(date_diff(item.service_end_date, item.service_start_date)) \
|
||||||
date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date))
|
/ flt(date_diff(get_last_day(item.service_end_date), get_first_day(item.service_start_date)))
|
||||||
)
|
|
||||||
|
|
||||||
actual_months = rounded(total_months * prorate_factor, 1)
|
actual_months = rounded(total_months * prorate_factor, 1)
|
||||||
|
|
||||||
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
|
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
|
||||||
doc, item
|
|
||||||
)
|
|
||||||
base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
|
base_amount = flt(item.base_net_amount / actual_months, item.precision("base_net_amount"))
|
||||||
|
|
||||||
if base_amount + already_booked_amount > item.base_net_amount:
|
if base_amount + already_booked_amount > item.base_net_amount:
|
||||||
base_amount = item.base_net_amount - already_booked_amount
|
base_amount = item.base_net_amount - already_booked_amount
|
||||||
|
|
||||||
if account_currency == doc.company_currency:
|
if account_currency==doc.company_currency:
|
||||||
amount = base_amount
|
amount = base_amount
|
||||||
else:
|
else:
|
||||||
amount = flt(item.net_amount / actual_months, item.precision("net_amount"))
|
amount = flt(item.net_amount/actual_months, item.precision("net_amount"))
|
||||||
if amount + already_booked_amount_in_account_currency > item.net_amount:
|
if amount + already_booked_amount_in_account_currency > item.net_amount:
|
||||||
amount = item.net_amount - already_booked_amount_in_account_currency
|
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 not (get_first_day(start_date) == start_date and get_last_day(end_date) == end_date):
|
||||||
partial_month = flt(date_diff(end_date, start_date)) / flt(
|
partial_month = flt(date_diff(end_date, start_date)) \
|
||||||
date_diff(get_last_day(end_date), get_first_day(start_date))
|
/ flt(date_diff(get_last_day(end_date), get_first_day(start_date)))
|
||||||
)
|
|
||||||
|
|
||||||
base_amount = rounded(partial_month, 1) * base_amount
|
base_amount = rounded(partial_month, 1) * base_amount
|
||||||
amount = rounded(partial_month, 1) * amount
|
amount = rounded(partial_month, 1) * amount
|
||||||
else:
|
else:
|
||||||
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
|
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
|
||||||
doc, item
|
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
|
||||||
)
|
if account_currency==doc.company_currency:
|
||||||
base_amount = flt(
|
|
||||||
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
|
|
||||||
)
|
|
||||||
if account_currency == doc.company_currency:
|
|
||||||
amount = base_amount
|
amount = base_amount
|
||||||
else:
|
else:
|
||||||
amount = flt(
|
amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
|
||||||
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
|
|
||||||
)
|
|
||||||
|
|
||||||
return amount, base_amount
|
return amount, base_amount
|
||||||
|
|
||||||
|
|
||||||
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
|
def calculate_amount(doc, item, last_gl_entry, total_days, total_booking_days, account_currency):
|
||||||
amount, base_amount = 0, 0
|
amount, base_amount = 0, 0
|
||||||
if not last_gl_entry:
|
if not last_gl_entry:
|
||||||
base_amount = flt(
|
base_amount = flt(item.base_net_amount*total_booking_days/flt(total_days), item.precision("base_net_amount"))
|
||||||
item.base_net_amount * total_booking_days / flt(total_days), item.precision("base_net_amount")
|
if account_currency==doc.company_currency:
|
||||||
)
|
|
||||||
if account_currency == doc.company_currency:
|
|
||||||
amount = base_amount
|
amount = base_amount
|
||||||
else:
|
else:
|
||||||
amount = flt(
|
amount = flt(item.net_amount*total_booking_days/flt(total_days), item.precision("net_amount"))
|
||||||
item.net_amount * total_booking_days / flt(total_days), item.precision("net_amount")
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(
|
already_booked_amount, already_booked_amount_in_account_currency = get_already_booked_amount(doc, item)
|
||||||
doc, item
|
|
||||||
)
|
|
||||||
|
|
||||||
base_amount = flt(
|
base_amount = flt(item.base_net_amount - already_booked_amount, item.precision("base_net_amount"))
|
||||||
item.base_net_amount - already_booked_amount, item.precision("base_net_amount")
|
if account_currency==doc.company_currency:
|
||||||
)
|
|
||||||
if account_currency == doc.company_currency:
|
|
||||||
amount = base_amount
|
amount = base_amount
|
||||||
else:
|
else:
|
||||||
amount = flt(
|
amount = flt(item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount"))
|
||||||
item.net_amount - already_booked_amount_in_account_currency, item.precision("net_amount")
|
|
||||||
)
|
|
||||||
|
|
||||||
return amount, base_amount
|
return amount, base_amount
|
||||||
|
|
||||||
|
|
||||||
def get_already_booked_amount(doc, item):
|
def get_already_booked_amount(doc, item):
|
||||||
if doc.doctype == "Sales Invoice":
|
if doc.doctype == "Sales Invoice":
|
||||||
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
|
total_credit_debit, total_credit_debit_currency = "debit", "debit_in_account_currency"
|
||||||
@@ -291,31 +226,20 @@ def get_already_booked_amount(doc, item):
|
|||||||
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
|
total_credit_debit, total_credit_debit_currency = "credit", "credit_in_account_currency"
|
||||||
deferred_account = "deferred_expense_account"
|
deferred_account = "deferred_expense_account"
|
||||||
|
|
||||||
gl_entries_details = frappe.db.sql(
|
gl_entries_details = frappe.db.sql('''
|
||||||
"""
|
|
||||||
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
|
select sum({0}) as total_credit, sum({1}) as total_credit_in_account_currency, voucher_detail_no
|
||||||
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
from `tabGL Entry` where company=%s and account=%s and voucher_type=%s and voucher_no=%s and voucher_detail_no=%s
|
||||||
and is_cancelled = 0
|
|
||||||
group by voucher_detail_no
|
group by voucher_detail_no
|
||||||
""".format(
|
'''.format(total_credit_debit, total_credit_debit_currency),
|
||||||
total_credit_debit, total_credit_debit_currency
|
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
|
||||||
),
|
|
||||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
journal_entry_details = frappe.db.sql(
|
journal_entry_details = frappe.db.sql('''
|
||||||
"""
|
|
||||||
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
|
SELECT sum(c.{0}) as total_credit, sum(c.{1}) as total_credit_in_account_currency, reference_detail_no
|
||||||
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
|
FROM `tabJournal Entry` p , `tabJournal Entry Account` c WHERE p.name = c.parent and
|
||||||
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
|
p.company = %s and c.account=%s and c.reference_type=%s and c.reference_name=%s and c.reference_detail_no=%s
|
||||||
and p.docstatus < 2 group by reference_detail_no
|
and p.docstatus < 2 group by reference_detail_no
|
||||||
""".format(
|
'''.format(total_credit_debit, total_credit_debit_currency),
|
||||||
total_credit_debit, total_credit_debit_currency
|
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name), as_dict=True)
|
||||||
),
|
|
||||||
(doc.company, item.get(deferred_account), doc.doctype, doc.name, item.name),
|
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
|
already_booked_amount = gl_entries_details[0].total_credit if gl_entries_details else 0
|
||||||
already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
|
already_booked_amount += journal_entry_details[0].total_credit if journal_entry_details else 0
|
||||||
@@ -323,31 +247,20 @@ def get_already_booked_amount(doc, item):
|
|||||||
if doc.currency == doc.company_currency:
|
if doc.currency == doc.company_currency:
|
||||||
already_booked_amount_in_account_currency = already_booked_amount
|
already_booked_amount_in_account_currency = already_booked_amount
|
||||||
else:
|
else:
|
||||||
already_booked_amount_in_account_currency = (
|
already_booked_amount_in_account_currency = gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
|
||||||
gl_entries_details[0].total_credit_in_account_currency if gl_entries_details else 0
|
already_booked_amount_in_account_currency += journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
|
||||||
)
|
|
||||||
already_booked_amount_in_account_currency += (
|
|
||||||
journal_entry_details[0].total_credit_in_account_currency if journal_entry_details else 0
|
|
||||||
)
|
|
||||||
|
|
||||||
return already_booked_amount, already_booked_amount_in_account_currency
|
return already_booked_amount, already_booked_amount_in_account_currency
|
||||||
|
|
||||||
|
|
||||||
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||||
enable_check = (
|
enable_check = "enable_deferred_revenue" \
|
||||||
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
|
if doc.doctype=="Sales Invoice" else "enable_deferred_expense"
|
||||||
)
|
|
||||||
|
|
||||||
accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
|
def _book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on):
|
||||||
|
|
||||||
def _book_deferred_revenue_or_expense(
|
|
||||||
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
|
|
||||||
):
|
|
||||||
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
|
start_date, end_date, last_gl_entry = get_booking_dates(doc, item, posting_date=posting_date)
|
||||||
if not (start_date and end_date):
|
if not (start_date and end_date): return
|
||||||
return
|
|
||||||
|
|
||||||
account_currency = get_account_currency(item.expense_account or item.income_account)
|
account_currency = get_account_currency(item.expense_account)
|
||||||
if doc.doctype == "Sales Invoice":
|
if doc.doctype == "Sales Invoice":
|
||||||
against, project = doc.customer, doc.project
|
against, project = doc.customer, doc.project
|
||||||
credit_account, debit_account = item.income_account, item.deferred_revenue_account
|
credit_account, debit_account = item.income_account, item.deferred_revenue_account
|
||||||
@@ -358,180 +271,103 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
|||||||
total_days = date_diff(item.service_end_date, item.service_start_date) + 1
|
total_days = date_diff(item.service_end_date, item.service_start_date) + 1
|
||||||
total_booking_days = date_diff(end_date, start_date) + 1
|
total_booking_days = date_diff(end_date, start_date) + 1
|
||||||
|
|
||||||
if book_deferred_entries_based_on == "Months":
|
if book_deferred_entries_based_on == 'Months':
|
||||||
amount, base_amount = calculate_monthly_amount(
|
amount, base_amount = calculate_monthly_amount(doc, item, last_gl_entry,
|
||||||
doc,
|
start_date, end_date, total_days, total_booking_days, account_currency)
|
||||||
item,
|
|
||||||
last_gl_entry,
|
|
||||||
start_date,
|
|
||||||
end_date,
|
|
||||||
total_days,
|
|
||||||
total_booking_days,
|
|
||||||
account_currency,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
amount, base_amount = calculate_amount(
|
amount, base_amount = calculate_amount(doc, item, last_gl_entry,
|
||||||
doc, item, last_gl_entry, total_days, total_booking_days, account_currency
|
total_days, total_booking_days, account_currency)
|
||||||
)
|
|
||||||
|
|
||||||
if not amount:
|
if not amount:
|
||||||
return
|
return
|
||||||
|
|
||||||
# check if books nor frozen till endate:
|
|
||||||
if accounts_frozen_upto and (end_date) <= getdate(accounts_frozen_upto):
|
|
||||||
end_date = get_last_day(add_days(accounts_frozen_upto, 1))
|
|
||||||
|
|
||||||
if via_journal_entry:
|
if via_journal_entry:
|
||||||
book_revenue_via_journal_entry(
|
book_revenue_via_journal_entry(doc, credit_account, debit_account, against, amount,
|
||||||
doc,
|
base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process, submit_journal_entry)
|
||||||
credit_account,
|
|
||||||
debit_account,
|
|
||||||
against,
|
|
||||||
amount,
|
|
||||||
base_amount,
|
|
||||||
end_date,
|
|
||||||
project,
|
|
||||||
account_currency,
|
|
||||||
item.cost_center,
|
|
||||||
item,
|
|
||||||
deferred_process,
|
|
||||||
submit_journal_entry,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
make_gl_entries(
|
make_gl_entries(doc, credit_account, debit_account, against,
|
||||||
doc,
|
amount, base_amount, end_date, project, account_currency, item.cost_center, item, deferred_process)
|
||||||
credit_account,
|
|
||||||
debit_account,
|
|
||||||
against,
|
|
||||||
amount,
|
|
||||||
base_amount,
|
|
||||||
end_date,
|
|
||||||
project,
|
|
||||||
account_currency,
|
|
||||||
item.cost_center,
|
|
||||||
item,
|
|
||||||
deferred_process,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
|
# Returned in case of any errors because it tries to submit the same record again and again in case of errors
|
||||||
if frappe.flags.deferred_accounting_error:
|
if frappe.flags.deferred_accounting_error:
|
||||||
return
|
return
|
||||||
|
|
||||||
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
|
if getdate(end_date) < getdate(posting_date) and not last_gl_entry:
|
||||||
_book_deferred_revenue_or_expense(
|
_book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
|
||||||
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
|
|
||||||
)
|
|
||||||
|
|
||||||
via_journal_entry = cint(
|
via_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_via_journal_entry'))
|
||||||
frappe.db.get_singles_value("Accounts Settings", "book_deferred_entries_via_journal_entry")
|
submit_journal_entry = cint(frappe.db.get_singles_value('Accounts Settings', 'submit_journal_entries'))
|
||||||
)
|
book_deferred_entries_based_on = frappe.db.get_singles_value('Accounts Settings', 'book_deferred_entries_based_on')
|
||||||
submit_journal_entry = cint(
|
|
||||||
frappe.db.get_singles_value("Accounts Settings", "submit_journal_entries")
|
|
||||||
)
|
|
||||||
book_deferred_entries_based_on = frappe.db.get_singles_value(
|
|
||||||
"Accounts Settings", "book_deferred_entries_based_on"
|
|
||||||
)
|
|
||||||
|
|
||||||
for item in doc.get("items"):
|
for item in doc.get('items'):
|
||||||
if item.get(enable_check):
|
if item.get(enable_check):
|
||||||
_book_deferred_revenue_or_expense(
|
_book_deferred_revenue_or_expense(item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on)
|
||||||
item, via_journal_entry, submit_journal_entry, book_deferred_entries_based_on
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def process_deferred_accounting(posting_date=None):
|
def process_deferred_accounting(posting_date=None):
|
||||||
"""Converts deferred income/expense into income/expense
|
''' Converts deferred income/expense into income/expense
|
||||||
Executed via background jobs on every month end"""
|
Executed via background jobs on every month end '''
|
||||||
|
|
||||||
if not posting_date:
|
if not posting_date:
|
||||||
posting_date = today()
|
posting_date = today()
|
||||||
|
|
||||||
if not cint(
|
if not cint(frappe.db.get_singles_value('Accounts Settings', 'automatically_process_deferred_accounting_entry')):
|
||||||
frappe.db.get_singles_value(
|
|
||||||
"Accounts Settings", "automatically_process_deferred_accounting_entry"
|
|
||||||
)
|
|
||||||
):
|
|
||||||
return
|
return
|
||||||
|
|
||||||
start_date = add_months(today(), -1)
|
start_date = add_months(today(), -1)
|
||||||
end_date = add_days(today(), -1)
|
end_date = add_days(today(), -1)
|
||||||
|
|
||||||
companies = frappe.get_all("Company")
|
companies = frappe.get_all('Company')
|
||||||
|
|
||||||
for company in companies:
|
for company in companies:
|
||||||
for record_type in ("Income", "Expense"):
|
for record_type in ('Income', 'Expense'):
|
||||||
doc = frappe.get_doc(
|
doc = frappe.get_doc(dict(
|
||||||
dict(
|
doctype='Process Deferred Accounting',
|
||||||
doctype="Process Deferred Accounting",
|
|
||||||
company=company.name,
|
company=company.name,
|
||||||
posting_date=posting_date,
|
posting_date=posting_date,
|
||||||
start_date=start_date,
|
start_date=start_date,
|
||||||
end_date=end_date,
|
end_date=end_date,
|
||||||
type=record_type,
|
type=record_type
|
||||||
)
|
))
|
||||||
)
|
|
||||||
|
|
||||||
doc.insert()
|
doc.insert()
|
||||||
doc.submit()
|
doc.submit()
|
||||||
|
|
||||||
|
def make_gl_entries(doc, credit_account, debit_account, against,
|
||||||
def make_gl_entries(
|
amount, base_amount, posting_date, project, account_currency, cost_center, item, deferred_process=None):
|
||||||
doc,
|
|
||||||
credit_account,
|
|
||||||
debit_account,
|
|
||||||
against,
|
|
||||||
amount,
|
|
||||||
base_amount,
|
|
||||||
posting_date,
|
|
||||||
project,
|
|
||||||
account_currency,
|
|
||||||
cost_center,
|
|
||||||
item,
|
|
||||||
deferred_process=None,
|
|
||||||
):
|
|
||||||
# GL Entry for crediting the amount in the deferred expense
|
# GL Entry for crediting the amount in the deferred expense
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
|
|
||||||
if amount == 0:
|
if amount == 0: return
|
||||||
return
|
|
||||||
|
|
||||||
gl_entries = []
|
gl_entries = []
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
doc.get_gl_dict(
|
doc.get_gl_dict({
|
||||||
{
|
|
||||||
"account": credit_account,
|
"account": credit_account,
|
||||||
"against": against,
|
"against": against,
|
||||||
"credit": base_amount,
|
"credit": base_amount,
|
||||||
"credit_in_account_currency": amount,
|
"credit_in_account_currency": amount,
|
||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
"voucher_detail_no": item.name,
|
"voucher_detail_no": item.name,
|
||||||
"posting_date": posting_date,
|
'posting_date': posting_date,
|
||||||
"project": project,
|
'project': project,
|
||||||
"against_voucher_type": "Process Deferred Accounting",
|
'against_voucher_type': 'Process Deferred Accounting',
|
||||||
"against_voucher": deferred_process,
|
'against_voucher': deferred_process
|
||||||
},
|
}, account_currency, item=item)
|
||||||
account_currency,
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
# GL Entry to debit the amount from the expense
|
# GL Entry to debit the amount from the expense
|
||||||
gl_entries.append(
|
gl_entries.append(
|
||||||
doc.get_gl_dict(
|
doc.get_gl_dict({
|
||||||
{
|
|
||||||
"account": debit_account,
|
"account": debit_account,
|
||||||
"against": against,
|
"against": against,
|
||||||
"debit": base_amount,
|
"debit": base_amount,
|
||||||
"debit_in_account_currency": amount,
|
"debit_in_account_currency": amount,
|
||||||
"cost_center": cost_center,
|
"cost_center": cost_center,
|
||||||
"voucher_detail_no": item.name,
|
"voucher_detail_no": item.name,
|
||||||
"posting_date": posting_date,
|
'posting_date': posting_date,
|
||||||
"project": project,
|
'project': project,
|
||||||
"against_voucher_type": "Process Deferred Accounting",
|
'against_voucher_type': 'Process Deferred Accounting',
|
||||||
"against_voucher": deferred_process,
|
'against_voucher': deferred_process
|
||||||
},
|
}, account_currency, item=item)
|
||||||
account_currency,
|
|
||||||
item=item,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if gl_entries:
|
if gl_entries:
|
||||||
@@ -540,124 +376,95 @@ def make_gl_entries(
|
|||||||
frappe.db.commit()
|
frappe.db.commit()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
traceback = frappe.get_traceback()
|
|
||||||
frappe.log_error(
|
|
||||||
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
|
|
||||||
message=traceback,
|
|
||||||
)
|
|
||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback()
|
||||||
frappe.log_error(
|
frappe.log_error(message=traceback)
|
||||||
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
|
|
||||||
message=traceback,
|
|
||||||
)
|
|
||||||
frappe.flags.deferred_accounting_error = True
|
|
||||||
|
|
||||||
|
frappe.flags.deferred_accounting_error = True
|
||||||
|
|
||||||
def send_mail(deferred_process):
|
def send_mail(deferred_process):
|
||||||
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
|
title = _("Error while processing deferred accounting for {0}").format(deferred_process)
|
||||||
link = get_link_to_form("Process Deferred Accounting", deferred_process)
|
link = get_link_to_form('Process Deferred Accounting', deferred_process)
|
||||||
content = _("Deferred accounting failed for some invoices:") + "\n"
|
content = _("Deferred accounting failed for some invoices:") + "\n"
|
||||||
content += _(
|
content += _("Please check Process Deferred Accounting {0} and submit manually after resolving errors.").format(link)
|
||||||
"Please check Process Deferred Accounting {0} and submit manually after resolving errors."
|
|
||||||
).format(link)
|
|
||||||
sendmail_to_system_managers(title, content)
|
sendmail_to_system_managers(title, content)
|
||||||
|
|
||||||
|
def book_revenue_via_journal_entry(doc, credit_account, debit_account, against,
|
||||||
|
amount, base_amount, posting_date, project, account_currency, cost_center, item,
|
||||||
|
deferred_process=None, submit='No'):
|
||||||
|
|
||||||
def book_revenue_via_journal_entry(
|
if amount == 0: return
|
||||||
doc,
|
|
||||||
credit_account,
|
|
||||||
debit_account,
|
|
||||||
against,
|
|
||||||
amount,
|
|
||||||
base_amount,
|
|
||||||
posting_date,
|
|
||||||
project,
|
|
||||||
account_currency,
|
|
||||||
cost_center,
|
|
||||||
item,
|
|
||||||
deferred_process=None,
|
|
||||||
submit="No",
|
|
||||||
):
|
|
||||||
|
|
||||||
if amount == 0:
|
journal_entry = frappe.new_doc('Journal Entry')
|
||||||
return
|
|
||||||
|
|
||||||
journal_entry = frappe.new_doc("Journal Entry")
|
|
||||||
journal_entry.posting_date = posting_date
|
journal_entry.posting_date = posting_date
|
||||||
journal_entry.company = doc.company
|
journal_entry.company = doc.company
|
||||||
journal_entry.voucher_type = (
|
journal_entry.voucher_type = 'Deferred Revenue' if doc.doctype == 'Sales Invoice' \
|
||||||
"Deferred Revenue" if doc.doctype == "Sales Invoice" else "Deferred Expense"
|
else 'Deferred Expense'
|
||||||
)
|
|
||||||
|
|
||||||
debit_entry = {
|
debit_entry = {
|
||||||
"account": credit_account,
|
'account': credit_account,
|
||||||
"credit": base_amount,
|
'credit': base_amount,
|
||||||
"credit_in_account_currency": amount,
|
'credit_in_account_currency': amount,
|
||||||
"account_currency": account_currency,
|
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
|
||||||
"reference_name": doc.name,
|
'party': against,
|
||||||
"reference_type": doc.doctype,
|
'account_currency': account_currency,
|
||||||
"reference_detail_no": item.name,
|
'reference_name': doc.name,
|
||||||
"cost_center": cost_center,
|
'reference_type': doc.doctype,
|
||||||
"project": project,
|
'reference_detail_no': item.name,
|
||||||
|
'cost_center': cost_center,
|
||||||
|
'project': project,
|
||||||
}
|
}
|
||||||
|
|
||||||
credit_entry = {
|
credit_entry = {
|
||||||
"account": debit_account,
|
'account': debit_account,
|
||||||
"debit": base_amount,
|
'debit': base_amount,
|
||||||
"debit_in_account_currency": amount,
|
'debit_in_account_currency': amount,
|
||||||
"account_currency": account_currency,
|
'party_type': 'Customer' if doc.doctype == 'Sales Invoice' else 'Supplier',
|
||||||
"reference_name": doc.name,
|
'party': against,
|
||||||
"reference_type": doc.doctype,
|
'account_currency': account_currency,
|
||||||
"reference_detail_no": item.name,
|
'reference_name': doc.name,
|
||||||
"cost_center": cost_center,
|
'reference_type': doc.doctype,
|
||||||
"project": project,
|
'reference_detail_no': item.name,
|
||||||
|
'cost_center': cost_center,
|
||||||
|
'project': project,
|
||||||
}
|
}
|
||||||
|
|
||||||
for dimension in get_accounting_dimensions():
|
for dimension in get_accounting_dimensions():
|
||||||
debit_entry.update({dimension: item.get(dimension)})
|
debit_entry.update({
|
||||||
|
dimension: item.get(dimension)
|
||||||
|
})
|
||||||
|
|
||||||
credit_entry.update({dimension: item.get(dimension)})
|
credit_entry.update({
|
||||||
|
dimension: item.get(dimension)
|
||||||
|
})
|
||||||
|
|
||||||
journal_entry.append("accounts", debit_entry)
|
journal_entry.append('accounts', debit_entry)
|
||||||
journal_entry.append("accounts", credit_entry)
|
journal_entry.append('accounts', credit_entry)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
journal_entry.save()
|
journal_entry.save()
|
||||||
|
|
||||||
if submit:
|
if submit:
|
||||||
journal_entry.submit()
|
journal_entry.submit()
|
||||||
|
|
||||||
frappe.db.commit()
|
|
||||||
except Exception:
|
except Exception:
|
||||||
frappe.db.rollback()
|
frappe.db.rollback()
|
||||||
traceback = frappe.get_traceback()
|
traceback = frappe.get_traceback()
|
||||||
frappe.log_error(
|
frappe.log_error(message=traceback)
|
||||||
title=_("Error while processing deferred accounting for Invoice {0}").format(doc.name),
|
|
||||||
message=traceback,
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.flags.deferred_accounting_error = True
|
frappe.flags.deferred_accounting_error = True
|
||||||
|
|
||||||
|
|
||||||
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
|
def get_deferred_booking_accounts(doctype, voucher_detail_no, dr_or_cr):
|
||||||
|
|
||||||
if doctype == "Sales Invoice":
|
if doctype == 'Sales Invoice':
|
||||||
credit_account, debit_account = frappe.db.get_value(
|
credit_account, debit_account = frappe.db.get_value('Sales Invoice Item', {'name': voucher_detail_no},
|
||||||
"Sales Invoice Item",
|
['income_account', 'deferred_revenue_account'])
|
||||||
{"name": voucher_detail_no},
|
|
||||||
["income_account", "deferred_revenue_account"],
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
credit_account, debit_account = frappe.db.get_value(
|
credit_account, debit_account = frappe.db.get_value('Purchase Invoice Item', {'name': voucher_detail_no},
|
||||||
"Purchase Invoice Item",
|
['deferred_expense_account', 'expense_account'])
|
||||||
{"name": voucher_detail_no},
|
|
||||||
["deferred_expense_account", "expense_account"],
|
|
||||||
)
|
|
||||||
|
|
||||||
if dr_or_cr == "Debit":
|
if dr_or_cr == 'Debit':
|
||||||
return debit_account
|
return debit_account
|
||||||
else:
|
else:
|
||||||
return credit_account
|
return credit_account
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|||||||
@@ -43,12 +43,12 @@ frappe.ui.form.on('Account', {
|
|||||||
frm.trigger('add_toolbar_buttons');
|
frm.trigger('add_toolbar_buttons');
|
||||||
}
|
}
|
||||||
if (frm.has_perm('write')) {
|
if (frm.has_perm('write')) {
|
||||||
frm.add_custom_button(__('Merge Account'), function () {
|
|
||||||
frm.trigger("merge_account");
|
|
||||||
}, __('Actions'));
|
|
||||||
frm.add_custom_button(__('Update Account Name / Number'), function () {
|
frm.add_custom_button(__('Update Account Name / Number'), function () {
|
||||||
frm.trigger("update_account_number");
|
frm.trigger("update_account_number");
|
||||||
}, __('Actions'));
|
});
|
||||||
|
frm.add_custom_button(__('Merge Account'), function () {
|
||||||
|
frm.trigger("merge_account");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -59,12 +59,11 @@ frappe.ui.form.on('Account', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
add_toolbar_buttons: function(frm) {
|
add_toolbar_buttons: function(frm) {
|
||||||
frm.add_custom_button(__('Chart of Accounts'), () => {
|
frm.add_custom_button(__('Chart of Accounts'),
|
||||||
frappe.set_route("Tree", "Account");
|
function () { frappe.set_route("Tree", "Account"); });
|
||||||
}, __('View'));
|
|
||||||
|
|
||||||
if (frm.doc.is_group == 1) {
|
if (frm.doc.is_group == 1) {
|
||||||
frm.add_custom_button(__('Convert to Non-Group'), function () {
|
frm.add_custom_button(__('Group to Non-Group'), function () {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
method: 'convert_group_to_ledger',
|
method: 'convert_group_to_ledger',
|
||||||
@@ -72,11 +71,10 @@ frappe.ui.form.on('Account', {
|
|||||||
frm.refresh();
|
frm.refresh();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, __('Actions'));
|
});
|
||||||
|
|
||||||
} else if (cint(frm.doc.is_group) == 0
|
} else if (cint(frm.doc.is_group) == 0
|
||||||
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
|
&& frappe.boot.user.can_read.indexOf("GL Entry") !== -1) {
|
||||||
frm.add_custom_button(__('General Ledger'), function () {
|
frm.add_custom_button(__('Ledger'), function () {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
"account": frm.doc.name,
|
"account": frm.doc.name,
|
||||||
"from_date": frappe.sys_defaults.year_start_date,
|
"from_date": frappe.sys_defaults.year_start_date,
|
||||||
@@ -84,9 +82,9 @@ frappe.ui.form.on('Account', {
|
|||||||
"company": frm.doc.company
|
"company": frm.doc.company
|
||||||
};
|
};
|
||||||
frappe.set_route("query-report", "General Ledger");
|
frappe.set_route("query-report", "General Ledger");
|
||||||
}, __('View'));
|
});
|
||||||
|
|
||||||
frm.add_custom_button(__('Convert to Group'), function () {
|
frm.add_custom_button(__('Non-Group to Group'), function () {
|
||||||
return frappe.call({
|
return frappe.call({
|
||||||
doc: frm.doc,
|
doc: frm.doc,
|
||||||
method: 'convert_ledger_to_group',
|
method: 'convert_ledger_to_group',
|
||||||
@@ -94,7 +92,7 @@ frappe.ui.form.on('Account', {
|
|||||||
frm.refresh();
|
frm.refresh();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, __('Actions'));
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, throw
|
from frappe import _, throw
|
||||||
@@ -10,17 +11,11 @@ from frappe.utils.nestedset import NestedSet, get_ancestors_of, get_descendants_
|
|||||||
import erpnext
|
import erpnext
|
||||||
|
|
||||||
|
|
||||||
class RootNotEditable(frappe.ValidationError):
|
class RootNotEditable(frappe.ValidationError): pass
|
||||||
pass
|
class BalanceMismatchError(frappe.ValidationError): pass
|
||||||
|
|
||||||
|
|
||||||
class BalanceMismatchError(frappe.ValidationError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class Account(NestedSet):
|
class Account(NestedSet):
|
||||||
nsm_parent_field = "parent_account"
|
nsm_parent_field = 'parent_account'
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
if frappe.local.flags.ignore_update_nsm:
|
if frappe.local.flags.ignore_update_nsm:
|
||||||
return
|
return
|
||||||
@@ -28,20 +23,17 @@ class Account(NestedSet):
|
|||||||
super(Account, self).on_update()
|
super(Account, self).on_update()
|
||||||
|
|
||||||
def onload(self):
|
def onload(self):
|
||||||
frozen_accounts_modifier = frappe.db.get_value(
|
frozen_accounts_modifier = frappe.db.get_value("Accounts Settings", "Accounts Settings",
|
||||||
"Accounts Settings", "Accounts Settings", "frozen_accounts_modifier"
|
"frozen_accounts_modifier")
|
||||||
)
|
|
||||||
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
|
if not frozen_accounts_modifier or frozen_accounts_modifier in frappe.get_roles():
|
||||||
self.set_onload("can_freeze_account", True)
|
self.set_onload("can_freeze_account", True)
|
||||||
|
|
||||||
def autoname(self):
|
def autoname(self):
|
||||||
from erpnext.accounts.utils import get_autoname_with_number
|
from erpnext.accounts.utils import get_autoname_with_number
|
||||||
|
|
||||||
self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company)
|
self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
from erpnext.accounts.utils import validate_field_number
|
from erpnext.accounts.utils import validate_field_number
|
||||||
|
|
||||||
if frappe.local.flags.allow_unverified_charts:
|
if frappe.local.flags.allow_unverified_charts:
|
||||||
return
|
return
|
||||||
self.validate_parent()
|
self.validate_parent()
|
||||||
@@ -58,33 +50,22 @@ class Account(NestedSet):
|
|||||||
def validate_parent(self):
|
def validate_parent(self):
|
||||||
"""Fetch Parent Details and validate parent account"""
|
"""Fetch Parent Details and validate parent account"""
|
||||||
if self.parent_account:
|
if self.parent_account:
|
||||||
par = frappe.db.get_value(
|
par = frappe.db.get_value("Account", self.parent_account,
|
||||||
"Account", self.parent_account, ["name", "is_group", "company"], as_dict=1
|
["name", "is_group", "company"], as_dict=1)
|
||||||
)
|
|
||||||
if not par:
|
if not par:
|
||||||
throw(
|
throw(_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account))
|
||||||
_("Account {0}: Parent account {1} does not exist").format(self.name, self.parent_account)
|
|
||||||
)
|
|
||||||
elif par.name == self.name:
|
elif par.name == self.name:
|
||||||
throw(_("Account {0}: You can not assign itself as parent account").format(self.name))
|
throw(_("Account {0}: You can not assign itself as parent account").format(self.name))
|
||||||
elif not par.is_group:
|
elif not par.is_group:
|
||||||
throw(
|
throw(_("Account {0}: Parent account {1} can not be a ledger").format(self.name, self.parent_account))
|
||||||
_("Account {0}: Parent account {1} can not be a ledger").format(
|
|
||||||
self.name, self.parent_account
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif par.company != self.company:
|
elif par.company != self.company:
|
||||||
throw(
|
throw(_("Account {0}: Parent account {1} does not belong to company: {2}")
|
||||||
_("Account {0}: Parent account {1} does not belong to company: {2}").format(
|
.format(self.name, self.parent_account, self.company))
|
||||||
self.name, self.parent_account, self.company
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def set_root_and_report_type(self):
|
def set_root_and_report_type(self):
|
||||||
if self.parent_account:
|
if self.parent_account:
|
||||||
par = frappe.db.get_value(
|
par = frappe.db.get_value("Account", self.parent_account,
|
||||||
"Account", self.parent_account, ["report_type", "root_type"], as_dict=1
|
["report_type", "root_type"], as_dict=1)
|
||||||
)
|
|
||||||
|
|
||||||
if par.report_type:
|
if par.report_type:
|
||||||
self.report_type = par.report_type
|
self.report_type = par.report_type
|
||||||
@@ -95,20 +76,15 @@ class Account(NestedSet):
|
|||||||
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
|
db_value = frappe.db.get_value("Account", self.name, ["report_type", "root_type"], as_dict=1)
|
||||||
if db_value:
|
if db_value:
|
||||||
if self.report_type != db_value.report_type:
|
if self.report_type != db_value.report_type:
|
||||||
frappe.db.sql(
|
frappe.db.sql("update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
|
||||||
"update `tabAccount` set report_type=%s where lft > %s and rgt < %s",
|
(self.report_type, self.lft, self.rgt))
|
||||||
(self.report_type, self.lft, self.rgt),
|
|
||||||
)
|
|
||||||
if self.root_type != db_value.root_type:
|
if self.root_type != db_value.root_type:
|
||||||
frappe.db.sql(
|
frappe.db.sql("update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
|
||||||
"update `tabAccount` set root_type=%s where lft > %s and rgt < %s",
|
(self.root_type, self.lft, self.rgt))
|
||||||
(self.root_type, self.lft, self.rgt),
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.root_type and not self.report_type:
|
if self.root_type and not self.report_type:
|
||||||
self.report_type = (
|
self.report_type = "Balance Sheet" \
|
||||||
"Balance Sheet" if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
|
if self.root_type in ("Asset", "Liability", "Equity") else "Profit and Loss"
|
||||||
)
|
|
||||||
|
|
||||||
def validate_root_details(self):
|
def validate_root_details(self):
|
||||||
# does not exists parent
|
# does not exists parent
|
||||||
@@ -121,26 +97,21 @@ class Account(NestedSet):
|
|||||||
|
|
||||||
def validate_root_company_and_sync_account_to_children(self):
|
def validate_root_company_and_sync_account_to_children(self):
|
||||||
# ignore validation while creating new compnay or while syncing to child companies
|
# ignore validation while creating new compnay or while syncing to child companies
|
||||||
if (
|
if frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation:
|
||||||
frappe.local.flags.ignore_root_company_validation or self.flags.ignore_root_company_validation
|
|
||||||
):
|
|
||||||
return
|
return
|
||||||
ancestors = get_root_company(self.company)
|
ancestors = get_root_company(self.company)
|
||||||
if ancestors:
|
if ancestors:
|
||||||
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
|
if frappe.get_value("Company", self.company, "allow_account_creation_against_child_company"):
|
||||||
return
|
return
|
||||||
if not frappe.db.get_value(
|
if not frappe.db.get_value("Account",
|
||||||
"Account", {"account_name": self.account_name, "company": ancestors[0]}, "name"
|
{'account_name': self.account_name, 'company': ancestors[0]}, 'name'):
|
||||||
):
|
|
||||||
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
|
frappe.throw(_("Please add the account to root level Company - {}").format(ancestors[0]))
|
||||||
elif self.parent_account:
|
elif self.parent_account:
|
||||||
descendants = get_descendants_of("Company", self.company)
|
descendants = get_descendants_of('Company', self.company)
|
||||||
if not descendants:
|
if not descendants: return
|
||||||
return
|
|
||||||
parent_acc_name_map = {}
|
parent_acc_name_map = {}
|
||||||
parent_acc_name, parent_acc_number = frappe.db.get_value(
|
parent_acc_name, parent_acc_number = frappe.db.get_value('Account', self.parent_account, \
|
||||||
"Account", self.parent_account, ["account_name", "account_number"]
|
["account_name", "account_number"])
|
||||||
)
|
|
||||||
filters = {
|
filters = {
|
||||||
"company": ["in", descendants],
|
"company": ["in", descendants],
|
||||||
"account_name": parent_acc_name,
|
"account_name": parent_acc_name,
|
||||||
@@ -148,13 +119,10 @@ class Account(NestedSet):
|
|||||||
if parent_acc_number:
|
if parent_acc_number:
|
||||||
filters["account_number"] = parent_acc_number
|
filters["account_number"] = parent_acc_number
|
||||||
|
|
||||||
for d in frappe.db.get_values(
|
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
|
||||||
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
|
|
||||||
):
|
|
||||||
parent_acc_name_map[d["company"]] = d["name"]
|
parent_acc_name_map[d["company"]] = d["name"]
|
||||||
|
|
||||||
if not parent_acc_name_map:
|
if not parent_acc_name_map: return
|
||||||
return
|
|
||||||
|
|
||||||
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
self.create_account_for_child_company(parent_acc_name_map, descendants, parent_acc_name)
|
||||||
|
|
||||||
@@ -175,38 +143,26 @@ class Account(NestedSet):
|
|||||||
def validate_frozen_accounts_modifier(self):
|
def validate_frozen_accounts_modifier(self):
|
||||||
old_value = frappe.db.get_value("Account", self.name, "freeze_account")
|
old_value = frappe.db.get_value("Account", self.name, "freeze_account")
|
||||||
if old_value and old_value != self.freeze_account:
|
if old_value and old_value != self.freeze_account:
|
||||||
frozen_accounts_modifier = frappe.db.get_value(
|
frozen_accounts_modifier = frappe.db.get_value('Accounts Settings', None, 'frozen_accounts_modifier')
|
||||||
"Accounts Settings", None, "frozen_accounts_modifier"
|
if not frozen_accounts_modifier or \
|
||||||
)
|
frozen_accounts_modifier not in frappe.get_roles():
|
||||||
if not frozen_accounts_modifier or frozen_accounts_modifier not in frappe.get_roles():
|
|
||||||
throw(_("You are not authorized to set Frozen value"))
|
throw(_("You are not authorized to set Frozen value"))
|
||||||
|
|
||||||
def validate_balance_must_be_debit_or_credit(self):
|
def validate_balance_must_be_debit_or_credit(self):
|
||||||
from erpnext.accounts.utils import get_balance_on
|
from erpnext.accounts.utils import get_balance_on
|
||||||
|
|
||||||
if not self.get("__islocal") and self.balance_must_be:
|
if not self.get("__islocal") and self.balance_must_be:
|
||||||
account_balance = get_balance_on(self.name)
|
account_balance = get_balance_on(self.name)
|
||||||
|
|
||||||
if account_balance > 0 and self.balance_must_be == "Credit":
|
if account_balance > 0 and self.balance_must_be == "Credit":
|
||||||
frappe.throw(
|
frappe.throw(_("Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"))
|
||||||
_(
|
|
||||||
"Account balance already in Debit, you are not allowed to set 'Balance Must Be' as 'Credit'"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif account_balance < 0 and self.balance_must_be == "Debit":
|
elif account_balance < 0 and self.balance_must_be == "Debit":
|
||||||
frappe.throw(
|
frappe.throw(_("Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"))
|
||||||
_(
|
|
||||||
"Account balance already in Credit, you are not allowed to set 'Balance Must Be' as 'Debit'"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_account_currency(self):
|
def validate_account_currency(self):
|
||||||
if not self.account_currency:
|
if not self.account_currency:
|
||||||
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
self.account_currency = frappe.get_cached_value('Company', self.company, "default_currency")
|
||||||
|
|
||||||
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
|
elif self.account_currency != frappe.db.get_value("Account", self.name, "account_currency"):
|
||||||
|
|
||||||
if gl_currency and self.account_currency != gl_currency:
|
|
||||||
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
||||||
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
|
frappe.throw(_("Currency can not be changed after making entries using some other currency"))
|
||||||
|
|
||||||
@@ -215,52 +171,45 @@ class Account(NestedSet):
|
|||||||
company_bold = frappe.bold(company)
|
company_bold = frappe.bold(company)
|
||||||
parent_acc_name_bold = frappe.bold(parent_acc_name)
|
parent_acc_name_bold = frappe.bold(parent_acc_name)
|
||||||
if not parent_acc_name_map.get(company):
|
if not parent_acc_name_map.get(company):
|
||||||
frappe.throw(
|
frappe.throw(_("While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA")
|
||||||
_(
|
.format(company_bold, parent_acc_name_bold), title=_("Account Not Found"))
|
||||||
"While creating account for Child Company {0}, parent account {1} not found. Please create the parent account in corresponding COA"
|
|
||||||
).format(company_bold, parent_acc_name_bold),
|
|
||||||
title=_("Account Not Found"),
|
|
||||||
)
|
|
||||||
|
|
||||||
# validate if parent of child company account to be added is a group
|
# validate if parent of child company account to be added is a group
|
||||||
if frappe.db.get_value("Account", self.parent_account, "is_group") and not frappe.db.get_value(
|
if (frappe.db.get_value("Account", self.parent_account, "is_group")
|
||||||
"Account", parent_acc_name_map[company], "is_group"
|
and not frappe.db.get_value("Account", parent_acc_name_map[company], "is_group")):
|
||||||
):
|
msg = _("While creating account for Child Company {0}, parent account {1} found as a ledger account.").format(company_bold, parent_acc_name_bold)
|
||||||
msg = _(
|
|
||||||
"While creating account for Child Company {0}, parent account {1} found as a ledger account."
|
|
||||||
).format(company_bold, parent_acc_name_bold)
|
|
||||||
msg += "<br><br>"
|
msg += "<br><br>"
|
||||||
msg += _(
|
msg += _("Please convert the parent account in corresponding child company to a group account.")
|
||||||
"Please convert the parent account in corresponding child company to a group account."
|
|
||||||
)
|
|
||||||
frappe.throw(msg, title=_("Invalid Parent Account"))
|
frappe.throw(msg, title=_("Invalid Parent Account"))
|
||||||
|
|
||||||
filters = {"account_name": self.account_name, "company": company}
|
filters = {
|
||||||
|
"account_name": self.account_name,
|
||||||
|
"company": company
|
||||||
|
}
|
||||||
|
|
||||||
if self.account_number:
|
if self.account_number:
|
||||||
filters["account_number"] = self.account_number
|
filters["account_number"] = self.account_number
|
||||||
|
|
||||||
child_account = frappe.db.get_value("Account", filters, "name")
|
child_account = frappe.db.get_value("Account", filters, 'name')
|
||||||
if not child_account:
|
if not child_account:
|
||||||
doc = frappe.copy_doc(self)
|
doc = frappe.copy_doc(self)
|
||||||
doc.flags.ignore_root_company_validation = True
|
doc.flags.ignore_root_company_validation = True
|
||||||
doc.update(
|
doc.update({
|
||||||
{
|
|
||||||
"company": company,
|
"company": company,
|
||||||
# parent account's currency should be passed down to child account's curreny
|
# parent account's currency should be passed down to child account's curreny
|
||||||
# if it is None, it picks it up from default company currency, which might be unintended
|
# if it is None, it picks it up from default company currency, which might be unintended
|
||||||
"account_currency": erpnext.get_company_currency(company),
|
"account_currency": erpnext.get_company_currency(company),
|
||||||
"parent_account": parent_acc_name_map[company],
|
"parent_account": parent_acc_name_map[company]
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
frappe.msgprint(_("Account {0} is added in the child company {1}").format(doc.name, company))
|
frappe.msgprint(_("Account {0} is added in the child company {1}")
|
||||||
|
.format(doc.name, company))
|
||||||
elif child_account:
|
elif child_account:
|
||||||
# update the parent company's value in child companies
|
# update the parent company's value in child companies
|
||||||
doc = frappe.get_doc("Account", child_account)
|
doc = frappe.get_doc("Account", child_account)
|
||||||
parent_value_changed = False
|
parent_value_changed = False
|
||||||
for field in ["account_type", "freeze_account", "balance_must_be"]:
|
for field in ['account_type', 'freeze_account', 'balance_must_be']:
|
||||||
if doc.get(field) != self.get(field):
|
if doc.get(field) != self.get(field):
|
||||||
parent_value_changed = True
|
parent_value_changed = True
|
||||||
doc.set(field, self.get(field))
|
doc.set(field, self.get(field))
|
||||||
@@ -295,11 +244,8 @@ class Account(NestedSet):
|
|||||||
return frappe.db.get_value("GL Entry", {"account": self.name})
|
return frappe.db.get_value("GL Entry", {"account": self.name})
|
||||||
|
|
||||||
def check_if_child_exists(self):
|
def check_if_child_exists(self):
|
||||||
return frappe.db.sql(
|
return frappe.db.sql("""select name from `tabAccount` where parent_account = %s
|
||||||
"""select name from `tabAccount` where parent_account = %s
|
and docstatus != 2""", self.name)
|
||||||
and docstatus != 2""",
|
|
||||||
self.name,
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_mandatory(self):
|
def validate_mandatory(self):
|
||||||
if not self.root_type:
|
if not self.root_type:
|
||||||
@@ -315,99 +261,73 @@ class Account(NestedSet):
|
|||||||
|
|
||||||
super(Account, self).on_trash(True)
|
super(Account, self).on_trash(True)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@frappe.validate_and_sanitize_search_inputs
|
@frappe.validate_and_sanitize_search_inputs
|
||||||
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
def get_parent_account(doctype, txt, searchfield, start, page_len, filters):
|
||||||
return frappe.db.sql(
|
return frappe.db.sql("""select name from tabAccount
|
||||||
"""select name from tabAccount
|
|
||||||
where is_group = 1 and docstatus != 2 and company = %s
|
where is_group = 1 and docstatus != 2 and company = %s
|
||||||
and %s like %s order by name limit %s, %s"""
|
and %s like %s order by name limit %s, %s""" %
|
||||||
% ("%s", searchfield, "%s", "%s", "%s"),
|
("%s", searchfield, "%s", "%s", "%s"),
|
||||||
(filters["company"], "%%%s%%" % txt, start, page_len),
|
(filters["company"], "%%%s%%" % txt, start, page_len), as_list=1)
|
||||||
as_list=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_account_currency(account):
|
def get_account_currency(account):
|
||||||
"""Helper function to get account currency"""
|
"""Helper function to get account currency"""
|
||||||
if not account:
|
if not account:
|
||||||
return
|
return
|
||||||
|
|
||||||
def generator():
|
def generator():
|
||||||
account_currency, company = frappe.get_cached_value(
|
account_currency, company = frappe.get_cached_value("Account", account, ["account_currency", "company"])
|
||||||
"Account", account, ["account_currency", "company"]
|
|
||||||
)
|
|
||||||
if not account_currency:
|
if not account_currency:
|
||||||
account_currency = frappe.get_cached_value("Company", company, "default_currency")
|
account_currency = frappe.get_cached_value('Company', company, "default_currency")
|
||||||
|
|
||||||
return account_currency
|
return account_currency
|
||||||
|
|
||||||
return frappe.local_cache("account_currency", account, generator)
|
return frappe.local_cache("account_currency", account, generator)
|
||||||
|
|
||||||
|
|
||||||
def on_doctype_update():
|
def on_doctype_update():
|
||||||
frappe.db.add_index("Account", ["lft", "rgt"])
|
frappe.db.add_index("Account", ["lft", "rgt"])
|
||||||
|
|
||||||
|
|
||||||
def get_account_autoname(account_number, account_name, company):
|
def get_account_autoname(account_number, account_name, company):
|
||||||
# first validate if company exists
|
# first validate if company exists
|
||||||
company = frappe.get_cached_value("Company", company, ["abbr", "name"], as_dict=True)
|
company = frappe.get_cached_value('Company', company, ["abbr", "name"], as_dict=True)
|
||||||
if not company:
|
if not company:
|
||||||
frappe.throw(_("Company {0} does not exist").format(company))
|
frappe.throw(_('Company {0} does not exist').format(company))
|
||||||
|
|
||||||
parts = [account_name.strip(), company.abbr]
|
parts = [account_name.strip(), company.abbr]
|
||||||
if cstr(account_number).strip():
|
if cstr(account_number).strip():
|
||||||
parts.insert(0, cstr(account_number).strip())
|
parts.insert(0, cstr(account_number).strip())
|
||||||
return " - ".join(parts)
|
return ' - '.join(parts)
|
||||||
|
|
||||||
|
|
||||||
def validate_account_number(name, account_number, company):
|
def validate_account_number(name, account_number, company):
|
||||||
if account_number:
|
if account_number:
|
||||||
account_with_same_number = frappe.db.get_value(
|
account_with_same_number = frappe.db.get_value("Account",
|
||||||
"Account", {"account_number": account_number, "company": company, "name": ["!=", name]}
|
{"account_number": account_number, "company": company, "name": ["!=", name]})
|
||||||
)
|
|
||||||
if account_with_same_number:
|
if account_with_same_number:
|
||||||
frappe.throw(
|
frappe.throw(_("Account Number {0} already used in account {1}")
|
||||||
_("Account Number {0} already used in account {1}").format(
|
.format(account_number, account_with_same_number))
|
||||||
account_number, account_with_same_number
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
def update_account_number(name, account_name, account_number=None, from_descendant=False):
|
||||||
account = frappe.db.get_value("Account", name, "company", as_dict=True)
|
account = frappe.db.get_value("Account", name, "company", as_dict=True)
|
||||||
if not account:
|
if not account: return
|
||||||
return
|
|
||||||
|
|
||||||
old_acc_name, old_acc_number = frappe.db.get_value(
|
old_acc_name, old_acc_number = frappe.db.get_value('Account', name, \
|
||||||
"Account", name, ["account_name", "account_number"]
|
["account_name", "account_number"])
|
||||||
)
|
|
||||||
|
|
||||||
# check if account exists in parent company
|
# check if account exists in parent company
|
||||||
ancestors = get_ancestors_of("Company", account.company)
|
ancestors = get_ancestors_of("Company", account.company)
|
||||||
allow_independent_account_creation = frappe.get_value(
|
allow_independent_account_creation = frappe.get_value("Company", account.company, "allow_account_creation_against_child_company")
|
||||||
"Company", account.company, "allow_account_creation_against_child_company"
|
|
||||||
)
|
|
||||||
|
|
||||||
if ancestors and not allow_independent_account_creation:
|
if ancestors and not allow_independent_account_creation:
|
||||||
for ancestor in ancestors:
|
for ancestor in ancestors:
|
||||||
if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
|
if frappe.db.get_value("Account", {'account_name': old_acc_name, 'company': ancestor}, 'name'):
|
||||||
# same account in parent company exists
|
# same account in parent company exists
|
||||||
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
allow_child_account_creation = _("Allow Account Creation Against Child Company")
|
||||||
|
|
||||||
message = _("Account {0} exists in parent company {1}.").format(
|
message = _("Account {0} exists in parent company {1}.").format(frappe.bold(old_acc_name), frappe.bold(ancestor))
|
||||||
frappe.bold(old_acc_name), frappe.bold(ancestor)
|
|
||||||
)
|
|
||||||
message += "<br>"
|
message += "<br>"
|
||||||
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(
|
message += _("Renaming it is only allowed via parent company {0}, to avoid mismatch.").format(frappe.bold(ancestor))
|
||||||
frappe.bold(ancestor)
|
|
||||||
)
|
|
||||||
message += "<br><br>"
|
message += "<br><br>"
|
||||||
message += _("To overrule this, enable '{0}' in company {1}").format(
|
message += _("To overrule this, enable '{0}' in company {1}").format(allow_child_account_creation, frappe.bold(account.company))
|
||||||
allow_child_account_creation, frappe.bold(account.company)
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.throw(message, title=_("Rename Not Allowed"))
|
frappe.throw(message, title=_("Rename Not Allowed"))
|
||||||
|
|
||||||
@@ -420,53 +340,42 @@ def update_account_number(name, account_name, account_number=None, from_descenda
|
|||||||
|
|
||||||
if not from_descendant:
|
if not from_descendant:
|
||||||
# Update and rename in child company accounts as well
|
# Update and rename in child company accounts as well
|
||||||
descendants = get_descendants_of("Company", account.company)
|
descendants = get_descendants_of('Company', account.company)
|
||||||
if descendants:
|
if descendants:
|
||||||
sync_update_account_number_in_child(
|
sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number, old_acc_number)
|
||||||
descendants, old_acc_name, account_name, account_number, old_acc_number
|
|
||||||
)
|
|
||||||
|
|
||||||
new_name = get_account_autoname(account_number, account_name, account.company)
|
new_name = get_account_autoname(account_number, account_name, account.company)
|
||||||
if name != new_name:
|
if name != new_name:
|
||||||
frappe.rename_doc("Account", name, new_name, force=1)
|
frappe.rename_doc("Account", name, new_name, force=1)
|
||||||
return new_name
|
return new_name
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def merge_account(old, new, is_group, root_type, company):
|
def merge_account(old, new, is_group, root_type, company):
|
||||||
# Validate properties before merging
|
# Validate properties before merging
|
||||||
if not frappe.db.exists("Account", new):
|
if not frappe.db.exists("Account", new):
|
||||||
throw(_("Account {0} does not exist").format(new))
|
throw(_("Account {0} does not exist").format(new))
|
||||||
|
|
||||||
val = list(frappe.db.get_value("Account", new, ["is_group", "root_type", "company"]))
|
val = list(frappe.db.get_value("Account", new,
|
||||||
|
["is_group", "root_type", "company"]))
|
||||||
|
|
||||||
if val != [cint(is_group), root_type, company]:
|
if val != [cint(is_group), root_type, company]:
|
||||||
throw(
|
throw(_("""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""))
|
||||||
_(
|
|
||||||
"""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
|
if is_group and frappe.db.get_value("Account", new, "parent_account") == old:
|
||||||
frappe.db.set_value(
|
frappe.db.set_value("Account", new, "parent_account",
|
||||||
"Account", new, "parent_account", frappe.db.get_value("Account", old, "parent_account")
|
frappe.db.get_value("Account", old, "parent_account"))
|
||||||
)
|
|
||||||
|
|
||||||
frappe.rename_doc("Account", old, new, merge=1, force=1)
|
frappe.rename_doc("Account", old, new, merge=1, force=1)
|
||||||
|
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_root_company(company):
|
def get_root_company(company):
|
||||||
# return the topmost company in the hierarchy
|
# return the topmost company in the hierarchy
|
||||||
ancestors = get_ancestors_of("Company", company, "lft asc")
|
ancestors = get_ancestors_of('Company', company, "lft asc")
|
||||||
return [ancestors[0]] if ancestors else []
|
return [ancestors[0]] if ancestors else []
|
||||||
|
|
||||||
|
def sync_update_account_number_in_child(descendants, old_acc_name, account_name, account_number=None, old_acc_number=None):
|
||||||
def sync_update_account_number_in_child(
|
|
||||||
descendants, old_acc_name, account_name, account_number=None, old_acc_number=None
|
|
||||||
):
|
|
||||||
filters = {
|
filters = {
|
||||||
"company": ["in", descendants],
|
"company": ["in", descendants],
|
||||||
"account_name": old_acc_name,
|
"account_name": old_acc_name,
|
||||||
@@ -474,7 +383,5 @@ def sync_update_account_number_in_child(
|
|||||||
if old_acc_number:
|
if old_acc_number:
|
||||||
filters["account_number"] = old_acc_number
|
filters["account_number"] = old_acc_number
|
||||||
|
|
||||||
for d in frappe.db.get_values(
|
for d in frappe.db.get_values('Account', filters=filters, fieldname=["company", "name"], as_dict=True):
|
||||||
"Account", filters=filters, fieldname=["company", "name"], as_dict=True
|
|
||||||
):
|
|
||||||
update_account_number(d["name"], account_name, account_number, from_descendant=True)
|
update_account_number(d["name"], account_name, account_number, from_descendant=True)
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ frappe.treeview_settings["Account"] = {
|
|||||||
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
const format = (value, currency) => format_currency(Math.abs(value), currency);
|
||||||
|
|
||||||
if (account.balance!==undefined) {
|
if (account.balance!==undefined) {
|
||||||
node.parent && node.parent.find('.balance-area').remove();
|
|
||||||
$('<span class="balance-area pull-right">'
|
$('<span class="balance-area pull-right">'
|
||||||
+ (account.balance_in_account_currency ?
|
+ (account.balance_in_account_currency ?
|
||||||
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
(format(account.balance_in_account_currency, account.account_currency) + " / ") : "")
|
||||||
@@ -160,7 +159,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
let root_company = treeview.page.fields_dict.root_company.get_value();
|
let root_company = treeview.page.fields_dict.root_company.get_value();
|
||||||
|
|
||||||
if(root_company) {
|
if(root_company) {
|
||||||
frappe.throw(__("Please add the account to root level Company - {0}"), [root_company]);
|
frappe.throw(__("Please add the account to root level Company - ") + root_company);
|
||||||
} else {
|
} else {
|
||||||
treeview.new_node();
|
treeview.new_node();
|
||||||
}
|
}
|
||||||
@@ -176,7 +175,7 @@ frappe.treeview_settings["Account"] = {
|
|||||||
&& node.expandable && !node.hide_add;
|
&& node.expandable && !node.hide_add;
|
||||||
},
|
},
|
||||||
click: function() {
|
click: function() {
|
||||||
var me = frappe.views.trees['Account'];
|
var me = frappe.treeview_settings['Account'].treeview;
|
||||||
me.new_node();
|
me.new_node();
|
||||||
},
|
},
|
||||||
btnClass: "hidden-xs"
|
btnClass: "hidden-xs"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
@@ -7,44 +8,34 @@ import os
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe.utils import cstr
|
from frappe.utils import cstr
|
||||||
from frappe.utils.nestedset import rebuild_tree
|
from frappe.utils.nestedset import rebuild_tree
|
||||||
|
from six import iteritems
|
||||||
from unidecode import unidecode
|
from unidecode import unidecode
|
||||||
|
|
||||||
|
|
||||||
def create_charts(
|
def create_charts(company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None):
|
||||||
company, chart_template=None, existing_company=None, custom_chart=None, from_coa_importer=None
|
|
||||||
):
|
|
||||||
chart = custom_chart or get_chart(chart_template, existing_company)
|
chart = custom_chart or get_chart(chart_template, existing_company)
|
||||||
if chart:
|
if chart:
|
||||||
accounts = []
|
accounts = []
|
||||||
|
|
||||||
def _import_accounts(children, parent, root_type, root_account=False):
|
def _import_accounts(children, parent, root_type, root_account=False):
|
||||||
for account_name, child in children.items():
|
for account_name, child in iteritems(children):
|
||||||
if root_account:
|
if root_account:
|
||||||
root_type = child.get("root_type")
|
root_type = child.get("root_type")
|
||||||
|
|
||||||
if account_name not in [
|
if account_name not in ["account_name", "account_number", "account_type",
|
||||||
"account_name",
|
"root_type", "is_group", "tax_rate"]:
|
||||||
"account_number",
|
|
||||||
"account_type",
|
|
||||||
"root_type",
|
|
||||||
"is_group",
|
|
||||||
"tax_rate",
|
|
||||||
]:
|
|
||||||
|
|
||||||
account_number = cstr(child.get("account_number")).strip()
|
account_number = cstr(child.get("account_number")).strip()
|
||||||
account_name, account_name_in_db = add_suffix_if_duplicate(
|
account_name, account_name_in_db = add_suffix_if_duplicate(account_name,
|
||||||
account_name, account_number, accounts
|
account_number, accounts)
|
||||||
)
|
|
||||||
|
|
||||||
is_group = identify_is_group(child)
|
is_group = identify_is_group(child)
|
||||||
report_type = (
|
report_type = "Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] \
|
||||||
"Balance Sheet" if root_type in ["Asset", "Liability", "Equity"] else "Profit and Loss"
|
else "Profit and Loss"
|
||||||
)
|
|
||||||
|
|
||||||
account = frappe.get_doc(
|
account = frappe.get_doc({
|
||||||
{
|
|
||||||
"doctype": "Account",
|
"doctype": "Account",
|
||||||
"account_name": child.get("account_name") if from_coa_importer else account_name,
|
"account_name": child.get('account_name') if from_coa_importer else account_name,
|
||||||
"company": company,
|
"company": company,
|
||||||
"parent_account": parent,
|
"parent_account": parent,
|
||||||
"is_group": is_group,
|
"is_group": is_group,
|
||||||
@@ -52,11 +43,9 @@ def create_charts(
|
|||||||
"report_type": report_type,
|
"report_type": report_type,
|
||||||
"account_number": account_number,
|
"account_number": account_number,
|
||||||
"account_type": child.get("account_type"),
|
"account_type": child.get("account_type"),
|
||||||
"account_currency": child.get("account_currency")
|
"account_currency": child.get('account_currency') or frappe.db.get_value('Company', company, "default_currency"),
|
||||||
or frappe.db.get_value("Company", company, "default_currency"),
|
"tax_rate": child.get("tax_rate")
|
||||||
"tax_rate": child.get("tax_rate"),
|
})
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if root_account or frappe.local.flags.allow_unverified_charts:
|
if root_account or frappe.local.flags.allow_unverified_charts:
|
||||||
account.flags.ignore_mandatory = True
|
account.flags.ignore_mandatory = True
|
||||||
@@ -76,10 +65,10 @@ def create_charts(
|
|||||||
rebuild_tree("Account", "parent_account")
|
rebuild_tree("Account", "parent_account")
|
||||||
frappe.local.flags.ignore_update_nsm = False
|
frappe.local.flags.ignore_update_nsm = False
|
||||||
|
|
||||||
|
|
||||||
def add_suffix_if_duplicate(account_name, account_number, accounts):
|
def add_suffix_if_duplicate(account_name, account_number, accounts):
|
||||||
if account_number:
|
if account_number:
|
||||||
account_name_in_db = unidecode(" - ".join([account_number, account_name.strip().lower()]))
|
account_name_in_db = unidecode(" - ".join([account_number,
|
||||||
|
account_name.strip().lower()]))
|
||||||
else:
|
else:
|
||||||
account_name_in_db = unidecode(account_name.strip().lower())
|
account_name_in_db = unidecode(account_name.strip().lower())
|
||||||
|
|
||||||
@@ -89,21 +78,16 @@ def add_suffix_if_duplicate(account_name, account_number, accounts):
|
|||||||
|
|
||||||
return account_name, account_name_in_db
|
return account_name, account_name_in_db
|
||||||
|
|
||||||
|
|
||||||
def identify_is_group(child):
|
def identify_is_group(child):
|
||||||
if child.get("is_group"):
|
if child.get("is_group"):
|
||||||
is_group = child.get("is_group")
|
is_group = child.get("is_group")
|
||||||
elif len(
|
elif len(set(child.keys()) - set(["account_type", "root_type", "is_group", "tax_rate", "account_number"])):
|
||||||
set(child.keys())
|
|
||||||
- set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])
|
|
||||||
):
|
|
||||||
is_group = 1
|
is_group = 1
|
||||||
else:
|
else:
|
||||||
is_group = 0
|
is_group = 0
|
||||||
|
|
||||||
return is_group
|
return is_group
|
||||||
|
|
||||||
|
|
||||||
def get_chart(chart_template, existing_company=None):
|
def get_chart(chart_template, existing_company=None):
|
||||||
chart = {}
|
chart = {}
|
||||||
if existing_company:
|
if existing_company:
|
||||||
@@ -113,13 +97,11 @@ def get_chart(chart_template, existing_company=None):
|
|||||||
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
|
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
|
||||||
standard_chart_of_accounts,
|
standard_chart_of_accounts,
|
||||||
)
|
)
|
||||||
|
|
||||||
return standard_chart_of_accounts.get()
|
return standard_chart_of_accounts.get()
|
||||||
elif chart_template == "Standard with Numbers":
|
elif chart_template == "Standard with Numbers":
|
||||||
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
|
from erpnext.accounts.doctype.account.chart_of_accounts.verified import (
|
||||||
standard_chart_of_accounts_with_account_number,
|
standard_chart_of_accounts_with_account_number,
|
||||||
)
|
)
|
||||||
|
|
||||||
return standard_chart_of_accounts_with_account_number.get()
|
return standard_chart_of_accounts_with_account_number.get()
|
||||||
else:
|
else:
|
||||||
folders = ("verified",)
|
folders = ("verified",)
|
||||||
@@ -135,7 +117,6 @@ def get_chart(chart_template, existing_company=None):
|
|||||||
if chart and json.loads(chart).get("name") == chart_template:
|
if chart and json.loads(chart).get("name") == chart_template:
|
||||||
return json.loads(chart).get("tree")
|
return json.loads(chart).get("tree")
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_charts_for_country(country, with_standard=False):
|
def get_charts_for_country(country, with_standard=False):
|
||||||
charts = []
|
charts = []
|
||||||
@@ -143,9 +124,8 @@ def get_charts_for_country(country, with_standard=False):
|
|||||||
def _get_chart_name(content):
|
def _get_chart_name(content):
|
||||||
if content:
|
if content:
|
||||||
content = json.loads(content)
|
content = json.loads(content)
|
||||||
if (
|
if (content and content.get("disabled", "No") == "No") \
|
||||||
content and content.get("disabled", "No") == "No"
|
or frappe.local.flags.allow_unverified_charts:
|
||||||
) or frappe.local.flags.allow_unverified_charts:
|
|
||||||
charts.append(content["name"])
|
charts.append(content["name"])
|
||||||
|
|
||||||
country_code = frappe.db.get_value("Country", country, "code")
|
country_code = frappe.db.get_value("Country", country, "code")
|
||||||
@@ -173,21 +153,11 @@ def get_charts_for_country(country, with_standard=False):
|
|||||||
|
|
||||||
|
|
||||||
def get_account_tree_from_existing_company(existing_company):
|
def get_account_tree_from_existing_company(existing_company):
|
||||||
all_accounts = frappe.get_all(
|
all_accounts = frappe.get_all('Account',
|
||||||
"Account",
|
filters={'company': existing_company},
|
||||||
filters={"company": existing_company},
|
fields = ["name", "account_name", "parent_account", "account_type",
|
||||||
fields=[
|
"is_group", "root_type", "tax_rate", "account_number"],
|
||||||
"name",
|
order_by="lft, rgt")
|
||||||
"account_name",
|
|
||||||
"parent_account",
|
|
||||||
"account_type",
|
|
||||||
"is_group",
|
|
||||||
"root_type",
|
|
||||||
"tax_rate",
|
|
||||||
"account_number",
|
|
||||||
],
|
|
||||||
order_by="lft, rgt",
|
|
||||||
)
|
|
||||||
|
|
||||||
account_tree = {}
|
account_tree = {}
|
||||||
|
|
||||||
@@ -196,7 +166,6 @@ def get_account_tree_from_existing_company(existing_company):
|
|||||||
build_account_tree(account_tree, None, all_accounts)
|
build_account_tree(account_tree, None, all_accounts)
|
||||||
return account_tree
|
return account_tree
|
||||||
|
|
||||||
|
|
||||||
def build_account_tree(tree, parent, all_accounts):
|
def build_account_tree(tree, parent, all_accounts):
|
||||||
# find children
|
# find children
|
||||||
parent_account = parent.name if parent else ""
|
parent_account = parent.name if parent else ""
|
||||||
@@ -225,29 +194,27 @@ def build_account_tree(tree, parent, all_accounts):
|
|||||||
# call recursively to build a subtree for current account
|
# call recursively to build a subtree for current account
|
||||||
build_account_tree(tree[child.account_name], child, all_accounts)
|
build_account_tree(tree[child.account_name], child, all_accounts)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def validate_bank_account(coa, bank_account):
|
def validate_bank_account(coa, bank_account):
|
||||||
accounts = []
|
accounts = []
|
||||||
chart = get_chart(coa)
|
chart = get_chart(coa)
|
||||||
|
|
||||||
if chart:
|
if chart:
|
||||||
|
|
||||||
def _get_account_names(account_master):
|
def _get_account_names(account_master):
|
||||||
for account_name, child in account_master.items():
|
for account_name, child in iteritems(account_master):
|
||||||
if account_name not in ["account_number", "account_type", "root_type", "is_group", "tax_rate"]:
|
if account_name not in ["account_number", "account_type",
|
||||||
|
"root_type", "is_group", "tax_rate"]:
|
||||||
accounts.append(account_name)
|
accounts.append(account_name)
|
||||||
|
|
||||||
_get_account_names(child)
|
_get_account_names(child)
|
||||||
|
|
||||||
_get_account_names(chart)
|
_get_account_names(chart)
|
||||||
|
|
||||||
return bank_account in accounts
|
return (bank_account in accounts)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
|
def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=False):
|
||||||
"""get chart template from its folder and parse the json to be rendered as tree"""
|
''' get chart template from its folder and parse the json to be rendered as tree '''
|
||||||
chart = chart_data or get_chart(chart_template)
|
chart = chart_data or get_chart(chart_template)
|
||||||
|
|
||||||
# if no template selected, return as it is
|
# if no template selected, return as it is
|
||||||
@@ -255,33 +222,22 @@ def build_tree_from_json(chart_template, chart_data=None, from_coa_importer=Fals
|
|||||||
return
|
return
|
||||||
|
|
||||||
accounts = []
|
accounts = []
|
||||||
|
|
||||||
def _import_accounts(children, parent):
|
def _import_accounts(children, parent):
|
||||||
"""recursively called to form a parent-child based list of dict from chart template"""
|
''' recursively called to form a parent-child based list of dict from chart template '''
|
||||||
for account_name, child in children.items():
|
for account_name, child in iteritems(children):
|
||||||
account = {}
|
account = {}
|
||||||
if account_name in [
|
if account_name in ["account_name", "account_number", "account_type",\
|
||||||
"account_name",
|
"root_type", "is_group", "tax_rate"]: continue
|
||||||
"account_number",
|
|
||||||
"account_type",
|
|
||||||
"root_type",
|
|
||||||
"is_group",
|
|
||||||
"tax_rate",
|
|
||||||
]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if from_coa_importer:
|
if from_coa_importer:
|
||||||
account_name = child["account_name"]
|
account_name = child['account_name']
|
||||||
|
|
||||||
account["parent_account"] = parent
|
account['parent_account'] = parent
|
||||||
account["expandable"] = True if identify_is_group(child) else False
|
account['expandable'] = True if identify_is_group(child) else False
|
||||||
account["value"] = (
|
account['value'] = (cstr(child.get('account_number')).strip() + ' - ' + account_name) \
|
||||||
(cstr(child.get("account_number")).strip() + " - " + account_name)
|
if child.get('account_number') else account_name
|
||||||
if child.get("account_number")
|
|
||||||
else account_name
|
|
||||||
)
|
|
||||||
accounts.append(account)
|
accounts.append(account)
|
||||||
_import_accounts(child, account["value"])
|
_import_accounts(child, account['value'])
|
||||||
|
|
||||||
_import_accounts(chart, None)
|
_import_accounts(chart, None)
|
||||||
return accounts
|
return accounts
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"""
|
"""
|
||||||
Import chart of accounts from OpenERP sources
|
Import chart of accounts from OpenERP sources
|
||||||
"""
|
"""
|
||||||
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import ast
|
import ast
|
||||||
import json
|
import json
|
||||||
@@ -12,6 +13,7 @@ from xml.etree import ElementTree as ET
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe.utils.csvutils import read_csv_content
|
from frappe.utils.csvutils import read_csv_content
|
||||||
|
from six import iteritems
|
||||||
|
|
||||||
path = "/Users/nabinhait/projects/odoo/addons"
|
path = "/Users/nabinhait/projects/odoo/addons"
|
||||||
|
|
||||||
@@ -20,7 +22,6 @@ charts = {}
|
|||||||
all_account_types = []
|
all_account_types = []
|
||||||
all_roots = {}
|
all_roots = {}
|
||||||
|
|
||||||
|
|
||||||
def go():
|
def go():
|
||||||
global accounts, charts
|
global accounts, charts
|
||||||
default_account_types = get_default_account_types()
|
default_account_types = get_default_account_types()
|
||||||
@@ -35,16 +36,14 @@ def go():
|
|||||||
accounts, charts = {}, {}
|
accounts, charts = {}, {}
|
||||||
country_path = os.path.join(path, country_dir)
|
country_path = os.path.join(path, country_dir)
|
||||||
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
|
manifest = ast.literal_eval(open(os.path.join(country_path, "__openerp__.py")).read())
|
||||||
data_files = (
|
data_files = manifest.get("data", []) + manifest.get("init_xml", []) + \
|
||||||
manifest.get("data", []) + manifest.get("init_xml", []) + manifest.get("update_xml", [])
|
manifest.get("update_xml", [])
|
||||||
)
|
|
||||||
files_path = [os.path.join(country_path, d) for d in data_files]
|
files_path = [os.path.join(country_path, d) for d in data_files]
|
||||||
xml_roots = get_xml_roots(files_path)
|
xml_roots = get_xml_roots(files_path)
|
||||||
csv_content = get_csv_contents(files_path)
|
csv_content = get_csv_contents(files_path)
|
||||||
prefix = country_dir if csv_content else None
|
prefix = country_dir if csv_content else None
|
||||||
account_types = get_account_types(
|
account_types = get_account_types(xml_roots.get("account.account.type", []),
|
||||||
xml_roots.get("account.account.type", []), csv_content.get("account.account.type", []), prefix
|
csv_content.get("account.account.type", []), prefix)
|
||||||
)
|
|
||||||
account_types.update(default_account_types)
|
account_types.update(default_account_types)
|
||||||
|
|
||||||
if xml_roots:
|
if xml_roots:
|
||||||
@@ -57,15 +56,12 @@ def go():
|
|||||||
|
|
||||||
create_all_roots_file()
|
create_all_roots_file()
|
||||||
|
|
||||||
|
|
||||||
def get_default_account_types():
|
def get_default_account_types():
|
||||||
default_types_root = []
|
default_types_root = []
|
||||||
default_types_root.append(
|
default_types_root.append(ET.parse(os.path.join(path, "account", "data",
|
||||||
ET.parse(os.path.join(path, "account", "data", "data_account_type.xml")).getroot()
|
"data_account_type.xml")).getroot())
|
||||||
)
|
|
||||||
return get_account_types(default_types_root, None, prefix="account")
|
return get_account_types(default_types_root, None, prefix="account")
|
||||||
|
|
||||||
|
|
||||||
def get_xml_roots(files_path):
|
def get_xml_roots(files_path):
|
||||||
xml_roots = frappe._dict()
|
xml_roots = frappe._dict()
|
||||||
for filepath in files_path:
|
for filepath in files_path:
|
||||||
@@ -74,69 +70,64 @@ def get_xml_roots(files_path):
|
|||||||
tree = ET.parse(filepath)
|
tree = ET.parse(filepath)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
for node in root[0].findall("record"):
|
for node in root[0].findall("record"):
|
||||||
if node.get("model") in [
|
if node.get("model") in ["account.account.template",
|
||||||
"account.account.template",
|
"account.chart.template", "account.account.type"]:
|
||||||
"account.chart.template",
|
|
||||||
"account.account.type",
|
|
||||||
]:
|
|
||||||
xml_roots.setdefault(node.get("model"), []).append(root)
|
xml_roots.setdefault(node.get("model"), []).append(root)
|
||||||
break
|
break
|
||||||
return xml_roots
|
return xml_roots
|
||||||
|
|
||||||
|
|
||||||
def get_csv_contents(files_path):
|
def get_csv_contents(files_path):
|
||||||
csv_content = {}
|
csv_content = {}
|
||||||
for filepath in files_path:
|
for filepath in files_path:
|
||||||
fname = os.path.basename(filepath)
|
fname = os.path.basename(filepath)
|
||||||
for file_type in ["account.account.template", "account.account.type", "account.chart.template"]:
|
for file_type in ["account.account.template", "account.account.type",
|
||||||
|
"account.chart.template"]:
|
||||||
if fname.startswith(file_type) and fname.endswith(".csv"):
|
if fname.startswith(file_type) and fname.endswith(".csv"):
|
||||||
with open(filepath, "r") as csvfile:
|
with open(filepath, "r") as csvfile:
|
||||||
try:
|
try:
|
||||||
csv_content.setdefault(file_type, []).append(read_csv_content(csvfile.read()))
|
csv_content.setdefault(file_type, [])\
|
||||||
|
.append(read_csv_content(csvfile.read()))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
continue
|
continue
|
||||||
return csv_content
|
return csv_content
|
||||||
|
|
||||||
|
|
||||||
def get_account_types(root_list, csv_content, prefix=None):
|
def get_account_types(root_list, csv_content, prefix=None):
|
||||||
types = {}
|
types = {}
|
||||||
account_type_map = {
|
account_type_map = {
|
||||||
"cash": "Cash",
|
'cash': 'Cash',
|
||||||
"bank": "Bank",
|
'bank': 'Bank',
|
||||||
"tr_cash": "Cash",
|
'tr_cash': 'Cash',
|
||||||
"tr_bank": "Bank",
|
'tr_bank': 'Bank',
|
||||||
"receivable": "Receivable",
|
'receivable': 'Receivable',
|
||||||
"tr_receivable": "Receivable",
|
'tr_receivable': 'Receivable',
|
||||||
"account rec": "Receivable",
|
'account rec': 'Receivable',
|
||||||
"payable": "Payable",
|
'payable': 'Payable',
|
||||||
"tr_payable": "Payable",
|
'tr_payable': 'Payable',
|
||||||
"equity": "Equity",
|
'equity': 'Equity',
|
||||||
"stocks": "Stock",
|
'stocks': 'Stock',
|
||||||
"stock": "Stock",
|
'stock': 'Stock',
|
||||||
"tax": "Tax",
|
'tax': 'Tax',
|
||||||
"tr_tax": "Tax",
|
'tr_tax': 'Tax',
|
||||||
"tax-out": "Tax",
|
'tax-out': 'Tax',
|
||||||
"tax-in": "Tax",
|
'tax-in': 'Tax',
|
||||||
"charges_personnel": "Chargeable",
|
'charges_personnel': 'Chargeable',
|
||||||
"fixed asset": "Fixed Asset",
|
'fixed asset': 'Fixed Asset',
|
||||||
"cogs": "Cost of Goods Sold",
|
'cogs': 'Cost of Goods Sold',
|
||||||
|
|
||||||
}
|
}
|
||||||
for root in root_list:
|
for root in root_list:
|
||||||
for node in root[0].findall("record"):
|
for node in root[0].findall("record"):
|
||||||
if node.get("model") == "account.account.type":
|
if node.get("model")=="account.account.type":
|
||||||
data = {}
|
data = {}
|
||||||
for field in node.findall("field"):
|
for field in node.findall("field"):
|
||||||
if (
|
if field.get("name")=="code" and field.text.lower() != "none" \
|
||||||
field.get("name") == "code"
|
and account_type_map.get(field.text):
|
||||||
and field.text.lower() != "none"
|
|
||||||
and account_type_map.get(field.text)
|
|
||||||
):
|
|
||||||
data["account_type"] = account_type_map[field.text]
|
data["account_type"] = account_type_map[field.text]
|
||||||
|
|
||||||
node_id = prefix + "." + node.get("id") if prefix else node.get("id")
|
node_id = prefix + "." + node.get("id") if prefix else node.get("id")
|
||||||
types[node_id] = data
|
types[node_id] = data
|
||||||
|
|
||||||
if csv_content and csv_content[0][0] == "id":
|
if csv_content and csv_content[0][0]=="id":
|
||||||
for row in csv_content[1:]:
|
for row in csv_content[1:]:
|
||||||
row_dict = dict(zip(csv_content[0], row))
|
row_dict = dict(zip(csv_content[0], row))
|
||||||
data = {}
|
data = {}
|
||||||
@@ -147,22 +138,21 @@ def get_account_types(root_list, csv_content, prefix=None):
|
|||||||
types[node_id] = data
|
types[node_id] = data
|
||||||
return types
|
return types
|
||||||
|
|
||||||
|
|
||||||
def make_maps_for_xml(xml_roots, account_types, country_dir):
|
def make_maps_for_xml(xml_roots, account_types, country_dir):
|
||||||
"""make maps for `charts` and `accounts`"""
|
"""make maps for `charts` and `accounts`"""
|
||||||
for model, root_list in xml_roots.items():
|
for model, root_list in iteritems(xml_roots):
|
||||||
for root in root_list:
|
for root in root_list:
|
||||||
for node in root[0].findall("record"):
|
for node in root[0].findall("record"):
|
||||||
if node.get("model") == "account.account.template":
|
if node.get("model")=="account.account.template":
|
||||||
data = {}
|
data = {}
|
||||||
for field in node.findall("field"):
|
for field in node.findall("field"):
|
||||||
if field.get("name") == "name":
|
if field.get("name")=="name":
|
||||||
data["name"] = field.text
|
data["name"] = field.text
|
||||||
if field.get("name") == "parent_id":
|
if field.get("name")=="parent_id":
|
||||||
parent_id = field.get("ref") or field.get("eval")
|
parent_id = field.get("ref") or field.get("eval")
|
||||||
data["parent_id"] = parent_id
|
data["parent_id"] = parent_id
|
||||||
|
|
||||||
if field.get("name") == "user_type":
|
if field.get("name")=="user_type":
|
||||||
value = field.get("ref")
|
value = field.get("ref")
|
||||||
if account_types.get(value, {}).get("account_type"):
|
if account_types.get(value, {}).get("account_type"):
|
||||||
data["account_type"] = account_types[value]["account_type"]
|
data["account_type"] = account_types[value]["account_type"]
|
||||||
@@ -172,17 +162,16 @@ def make_maps_for_xml(xml_roots, account_types, country_dir):
|
|||||||
data["children"] = []
|
data["children"] = []
|
||||||
accounts[node.get("id")] = data
|
accounts[node.get("id")] = data
|
||||||
|
|
||||||
if node.get("model") == "account.chart.template":
|
if node.get("model")=="account.chart.template":
|
||||||
data = {}
|
data = {}
|
||||||
for field in node.findall("field"):
|
for field in node.findall("field"):
|
||||||
if field.get("name") == "name":
|
if field.get("name")=="name":
|
||||||
data["name"] = field.text
|
data["name"] = field.text
|
||||||
if field.get("name") == "account_root_id":
|
if field.get("name")=="account_root_id":
|
||||||
data["account_root_id"] = field.get("ref")
|
data["account_root_id"] = field.get("ref")
|
||||||
data["id"] = country_dir
|
data["id"] = country_dir
|
||||||
charts.setdefault(node.get("id"), {}).update(data)
|
charts.setdefault(node.get("id"), {}).update(data)
|
||||||
|
|
||||||
|
|
||||||
def make_maps_for_csv(csv_content, account_types, country_dir):
|
def make_maps_for_csv(csv_content, account_types, country_dir):
|
||||||
for content in csv_content.get("account.account.template", []):
|
for content in csv_content.get("account.account.template", []):
|
||||||
for row in content[1:]:
|
for row in content[1:]:
|
||||||
@@ -190,7 +179,7 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
|
|||||||
account = {
|
account = {
|
||||||
"name": data.get("name"),
|
"name": data.get("name"),
|
||||||
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
|
"parent_id": data.get("parent_id:id") or data.get("parent_id/id"),
|
||||||
"children": [],
|
"children": []
|
||||||
}
|
}
|
||||||
user_type = data.get("user_type/id") or data.get("user_type:id")
|
user_type = data.get("user_type/id") or data.get("user_type:id")
|
||||||
if account_types.get(user_type, {}).get("account_type"):
|
if account_types.get(user_type, {}).get("account_type"):
|
||||||
@@ -207,14 +196,12 @@ def make_maps_for_csv(csv_content, account_types, country_dir):
|
|||||||
for row in content[1:]:
|
for row in content[1:]:
|
||||||
if row:
|
if row:
|
||||||
data = dict(zip(content[0], row))
|
data = dict(zip(content[0], row))
|
||||||
charts.setdefault(data.get("id"), {}).update(
|
charts.setdefault(data.get("id"), {}).update({
|
||||||
{
|
"account_root_id": data.get("account_root_id:id") or \
|
||||||
"account_root_id": data.get("account_root_id:id") or data.get("account_root_id/id"),
|
data.get("account_root_id/id"),
|
||||||
"name": data.get("name"),
|
"name": data.get("name"),
|
||||||
"id": country_dir,
|
"id": country_dir
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def make_account_trees():
|
def make_account_trees():
|
||||||
"""build tree hierarchy"""
|
"""build tree hierarchy"""
|
||||||
@@ -233,7 +220,6 @@ def make_account_trees():
|
|||||||
if "children" in accounts[id] and not accounts[id].get("children"):
|
if "children" in accounts[id] and not accounts[id].get("children"):
|
||||||
del accounts[id]["children"]
|
del accounts[id]["children"]
|
||||||
|
|
||||||
|
|
||||||
def make_charts():
|
def make_charts():
|
||||||
"""write chart files in app/setup/doctype/company/charts"""
|
"""write chart files in app/setup/doctype/company/charts"""
|
||||||
for chart_id in charts:
|
for chart_id in charts:
|
||||||
@@ -252,38 +238,34 @@ def make_charts():
|
|||||||
chart["country_code"] = src["id"][5:]
|
chart["country_code"] = src["id"][5:]
|
||||||
chart["tree"] = accounts[src["account_root_id"]]
|
chart["tree"] = accounts[src["account_root_id"]]
|
||||||
|
|
||||||
|
|
||||||
for key, val in chart["tree"].items():
|
for key, val in chart["tree"].items():
|
||||||
if key in ["name", "parent_id"]:
|
if key in ["name", "parent_id"]:
|
||||||
chart["tree"].pop(key)
|
chart["tree"].pop(key)
|
||||||
if type(val) == dict:
|
if type(val) == dict:
|
||||||
val["root_type"] = ""
|
val["root_type"] = ""
|
||||||
if chart:
|
if chart:
|
||||||
fpath = os.path.join(
|
fpath = os.path.join("erpnext", "erpnext", "accounts", "doctype", "account",
|
||||||
"erpnext", "erpnext", "accounts", "doctype", "account", "chart_of_accounts", filename + ".json"
|
"chart_of_accounts", filename + ".json")
|
||||||
)
|
|
||||||
|
|
||||||
with open(fpath, "r") as chartfile:
|
with open(fpath, "r") as chartfile:
|
||||||
old_content = chartfile.read()
|
old_content = chartfile.read()
|
||||||
if not old_content or (
|
if not old_content or (json.loads(old_content).get("is_active", "No") == "No" \
|
||||||
json.loads(old_content).get("is_active", "No") == "No"
|
and json.loads(old_content).get("disabled", "No") == "No"):
|
||||||
and json.loads(old_content).get("disabled", "No") == "No"
|
|
||||||
):
|
|
||||||
with open(fpath, "w") as chartfile:
|
with open(fpath, "w") as chartfile:
|
||||||
chartfile.write(json.dumps(chart, indent=4, sort_keys=True))
|
chartfile.write(json.dumps(chart, indent=4, sort_keys=True))
|
||||||
|
|
||||||
all_roots.setdefault(filename, chart["tree"].keys())
|
all_roots.setdefault(filename, chart["tree"].keys())
|
||||||
|
|
||||||
|
|
||||||
def create_all_roots_file():
|
def create_all_roots_file():
|
||||||
with open("all_roots.txt", "w") as f:
|
with open('all_roots.txt', 'w') as f:
|
||||||
for filename, roots in sorted(all_roots.items()):
|
for filename, roots in sorted(all_roots.items()):
|
||||||
f.write(filename)
|
f.write(filename)
|
||||||
f.write("\n----------------------\n")
|
f.write('\n----------------------\n')
|
||||||
for r in sorted(roots):
|
for r in sorted(roots):
|
||||||
f.write(r.encode("utf-8"))
|
f.write(r.encode('utf-8'))
|
||||||
f.write("\n")
|
f.write('\n')
|
||||||
f.write("\n\n\n")
|
f.write('\n\n\n')
|
||||||
|
|
||||||
|
if __name__=="__main__":
|
||||||
if __name__ == "__main__":
|
|
||||||
go()
|
go()
|
||||||
|
|||||||
@@ -72,6 +72,7 @@
|
|||||||
"138-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 131)": {},
|
"138-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 131)": {},
|
||||||
"139-Subventions d'investissement inscrites au compte de r\u00e9sultat": {
|
"139-Subventions d'investissement inscrites au compte de r\u00e9sultat": {
|
||||||
"1391-Subventions d'\u00e9quipement": {
|
"1391-Subventions d'\u00e9quipement": {
|
||||||
|
"13911-Subventions d'\u00e9quipement": {
|
||||||
"13911-Etat": {},
|
"13911-Etat": {},
|
||||||
"13912-R\u00e9gions": {},
|
"13912-R\u00e9gions": {},
|
||||||
"13913-D\u00e9partements": {},
|
"13913-D\u00e9partements": {},
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
"13916-Entreprises publiques": {},
|
"13916-Entreprises publiques": {},
|
||||||
"13917-Entreprises et organismes priv\u00e9s": {},
|
"13917-Entreprises et organismes priv\u00e9s": {},
|
||||||
"13918-Autres": {}
|
"13918-Autres": {}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"1398-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 1391)": {}
|
"1398-Autres subventions d'investissement (m\u00eame ventilation que celle du compte 1391)": {}
|
||||||
}
|
}
|
||||||
@@ -534,8 +536,8 @@
|
|||||||
"3312-Produits en cours P2": {}
|
"3312-Produits en cours P2": {}
|
||||||
},
|
},
|
||||||
"335-Travaux en cours": {
|
"335-Travaux en cours": {
|
||||||
"3351-Travaux en cours T1": {},
|
"Travaux en cours T1": {},
|
||||||
"3352-Travaux en cours T2": {}
|
"Travaux en cours T2": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"34-En-cours de production de services": {
|
"34-En-cours de production de services": {
|
||||||
@@ -593,9 +595,7 @@
|
|||||||
"371-Marchandises (ou groupe) A": {},
|
"371-Marchandises (ou groupe) A": {},
|
||||||
"372-Marchandises (ou groupe) B": {}
|
"372-Marchandises (ou groupe) B": {}
|
||||||
},
|
},
|
||||||
"38-Stocks en voie d'acheminement, mis en d\u00e9p\u00f4t ou donn\u00e9s en consignation (en cas d'inventaire permanent en comptabilit\u00e9 g\u00e9n\u00e9rale)": {
|
"38-Stocks en voie d'acheminement, mis en d\u00e9p\u00f4t ou donn\u00e9s en consignation (en cas d'inventaire permanent en comptabilit\u00e9 g\u00e9n\u00e9rale)": {},
|
||||||
"account_type": "Stock"
|
|
||||||
},
|
|
||||||
"39-D\u00e9pr\u00e9ciations des stocks et en-cours": {
|
"39-D\u00e9pr\u00e9ciations des stocks et en-cours": {
|
||||||
"391-D\u00e9pr\u00e9ciations des mati\u00e8res premi\u00e8res (et fournitures)": {
|
"391-D\u00e9pr\u00e9ciations des mati\u00e8res premi\u00e8res (et fournitures)": {
|
||||||
"3911-Mati\u00e8res (ou groupe) A": {},
|
"3911-Mati\u00e8res (ou groupe) A": {},
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
@@ -9,61 +10,118 @@ def get():
|
|||||||
return {
|
return {
|
||||||
_("Application of Funds (Assets)"): {
|
_("Application of Funds (Assets)"): {
|
||||||
_("Current Assets"): {
|
_("Current Assets"): {
|
||||||
_("Accounts Receivable"): {_("Debtors"): {"account_type": "Receivable"}},
|
_("Accounts Receivable"): {
|
||||||
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1},
|
_("Debtors"): {
|
||||||
_("Cash In Hand"): {_("Cash"): {"account_type": "Cash"}, "account_type": "Cash"},
|
"account_type": "Receivable"
|
||||||
_("Loans and Advances (Assets)"): {
|
}
|
||||||
_("Employee Advances"): {},
|
},
|
||||||
|
_("Bank Accounts"): {
|
||||||
|
"account_type": "Bank",
|
||||||
|
"is_group": 1
|
||||||
|
},
|
||||||
|
_("Cash In Hand"): {
|
||||||
|
_("Cash"): {
|
||||||
|
"account_type": "Cash"
|
||||||
|
},
|
||||||
|
"account_type": "Cash"
|
||||||
|
},
|
||||||
|
_("Loans and Advances (Assets)"): {
|
||||||
|
_("Employee Advances"): {
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_("Securities and Deposits"): {
|
||||||
|
_("Earnest Money"): {}
|
||||||
},
|
},
|
||||||
_("Securities and Deposits"): {_("Earnest Money"): {}},
|
|
||||||
_("Stock Assets"): {
|
_("Stock Assets"): {
|
||||||
_("Stock In Hand"): {"account_type": "Stock"},
|
_("Stock In Hand"): {
|
||||||
|
"account_type": "Stock"
|
||||||
|
},
|
||||||
"account_type": "Stock",
|
"account_type": "Stock",
|
||||||
},
|
},
|
||||||
_("Tax Assets"): {"is_group": 1},
|
_("Tax Assets"): {
|
||||||
|
"is_group": 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_("Fixed Assets"): {
|
_("Fixed Assets"): {
|
||||||
_("Capital Equipments"): {"account_type": "Fixed Asset"},
|
_("Capital Equipments"): {
|
||||||
_("Electronic Equipments"): {"account_type": "Fixed Asset"},
|
"account_type": "Fixed Asset"
|
||||||
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset"},
|
},
|
||||||
_("Office Equipments"): {"account_type": "Fixed Asset"},
|
_("Electronic Equipments"): {
|
||||||
_("Plants and Machineries"): {"account_type": "Fixed Asset"},
|
"account_type": "Fixed Asset"
|
||||||
_("Buildings"): {"account_type": "Fixed Asset"},
|
},
|
||||||
_("Softwares"): {"account_type": "Fixed Asset"},
|
_("Furnitures and Fixtures"): {
|
||||||
_("Accumulated Depreciation"): {"account_type": "Accumulated Depreciation"},
|
"account_type": "Fixed Asset"
|
||||||
|
},
|
||||||
|
_("Office Equipments"): {
|
||||||
|
"account_type": "Fixed Asset"
|
||||||
|
},
|
||||||
|
_("Plants and Machineries"): {
|
||||||
|
"account_type": "Fixed Asset"
|
||||||
|
},
|
||||||
|
_("Buildings"): {
|
||||||
|
"account_type": "Fixed Asset"
|
||||||
|
},
|
||||||
|
_("Softwares"): {
|
||||||
|
"account_type": "Fixed Asset"
|
||||||
|
},
|
||||||
|
_("Accumulated Depreciation"): {
|
||||||
|
"account_type": "Accumulated Depreciation"
|
||||||
|
},
|
||||||
_("CWIP Account"): {
|
_("CWIP Account"): {
|
||||||
"account_type": "Capital Work in Progress",
|
"account_type": "Capital Work in Progress",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
_("Investments"): {
|
||||||
|
"is_group": 1
|
||||||
},
|
},
|
||||||
_("Investments"): {"is_group": 1},
|
_("Temporary Accounts"): {
|
||||||
_("Temporary Accounts"): {_("Temporary Opening"): {"account_type": "Temporary"}},
|
_("Temporary Opening"): {
|
||||||
"root_type": "Asset",
|
"account_type": "Temporary"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root_type": "Asset"
|
||||||
},
|
},
|
||||||
_("Expenses"): {
|
_("Expenses"): {
|
||||||
_("Direct Expenses"): {
|
_("Direct Expenses"): {
|
||||||
_("Stock Expenses"): {
|
_("Stock Expenses"): {
|
||||||
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold"},
|
_("Cost of Goods Sold"): {
|
||||||
|
"account_type": "Cost of Goods Sold"
|
||||||
|
},
|
||||||
_("Expenses Included In Asset Valuation"): {
|
_("Expenses Included In Asset Valuation"): {
|
||||||
"account_type": "Expenses Included In Asset Valuation"
|
"account_type": "Expenses Included In Asset Valuation"
|
||||||
},
|
},
|
||||||
_("Expenses Included In Valuation"): {"account_type": "Expenses Included In Valuation"},
|
_("Expenses Included In Valuation"): {
|
||||||
_("Stock Adjustment"): {"account_type": "Stock Adjustment"},
|
"account_type": "Expenses Included In Valuation"
|
||||||
|
},
|
||||||
|
_("Stock Adjustment"): {
|
||||||
|
"account_type": "Stock Adjustment"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
_("Indirect Expenses"): {
|
_("Indirect Expenses"): {
|
||||||
_("Administrative Expenses"): {},
|
_("Administrative Expenses"): {},
|
||||||
_("Commission on Sales"): {},
|
_("Commission on Sales"): {},
|
||||||
_("Depreciation"): {"account_type": "Depreciation"},
|
_("Depreciation"): {
|
||||||
|
"account_type": "Depreciation"
|
||||||
|
},
|
||||||
_("Entertainment Expenses"): {},
|
_("Entertainment Expenses"): {},
|
||||||
_("Freight and Forwarding Charges"): {"account_type": "Chargeable"},
|
_("Freight and Forwarding Charges"): {
|
||||||
|
"account_type": "Chargeable"
|
||||||
|
},
|
||||||
_("Legal Expenses"): {},
|
_("Legal Expenses"): {},
|
||||||
_("Marketing Expenses"): {"account_type": "Chargeable"},
|
_("Marketing Expenses"): {
|
||||||
_("Miscellaneous Expenses"): {"account_type": "Chargeable"},
|
"account_type": "Chargeable"
|
||||||
|
},
|
||||||
|
_("Miscellaneous Expenses"): {
|
||||||
|
"account_type": "Chargeable"
|
||||||
|
},
|
||||||
_("Office Maintenance Expenses"): {},
|
_("Office Maintenance Expenses"): {},
|
||||||
_("Office Rent"): {},
|
_("Office Rent"): {},
|
||||||
_("Postal Expenses"): {},
|
_("Postal Expenses"): {},
|
||||||
_("Print and Stationery"): {},
|
_("Print and Stationery"): {},
|
||||||
_("Round Off"): {"account_type": "Round Off"},
|
_("Round Off"): {
|
||||||
|
"account_type": "Round Off"
|
||||||
|
},
|
||||||
_("Salary"): {},
|
_("Salary"): {},
|
||||||
_("Sales Expenses"): {},
|
_("Sales Expenses"): {},
|
||||||
_("Telephone Expenses"): {},
|
_("Telephone Expenses"): {},
|
||||||
@@ -71,39 +129,61 @@ def get():
|
|||||||
_("Utility Expenses"): {},
|
_("Utility Expenses"): {},
|
||||||
_("Write Off"): {},
|
_("Write Off"): {},
|
||||||
_("Exchange Gain/Loss"): {},
|
_("Exchange Gain/Loss"): {},
|
||||||
_("Gain/Loss on Asset Disposal"): {},
|
_("Gain/Loss on Asset Disposal"): {}
|
||||||
},
|
},
|
||||||
"root_type": "Expense",
|
"root_type": "Expense"
|
||||||
},
|
},
|
||||||
_("Income"): {
|
_("Income"): {
|
||||||
_("Direct Income"): {_("Sales"): {}, _("Service"): {}},
|
_("Direct Income"): {
|
||||||
_("Indirect Income"): {"is_group": 1},
|
_("Sales"): {},
|
||||||
"root_type": "Income",
|
_("Service"): {}
|
||||||
|
},
|
||||||
|
_("Indirect Income"): {
|
||||||
|
"is_group": 1
|
||||||
|
},
|
||||||
|
"root_type": "Income"
|
||||||
},
|
},
|
||||||
_("Source of Funds (Liabilities)"): {
|
_("Source of Funds (Liabilities)"): {
|
||||||
_("Current Liabilities"): {
|
_("Current Liabilities"): {
|
||||||
_("Accounts Payable"): {
|
_("Accounts Payable"): {
|
||||||
_("Creditors"): {"account_type": "Payable"},
|
_("Creditors"): {
|
||||||
|
"account_type": "Payable"
|
||||||
|
},
|
||||||
_("Payroll Payable"): {},
|
_("Payroll Payable"): {},
|
||||||
},
|
},
|
||||||
_("Stock Liabilities"): {
|
_("Stock Liabilities"): {
|
||||||
_("Stock Received But Not Billed"): {"account_type": "Stock Received But Not Billed"},
|
_("Stock Received But Not Billed"): {
|
||||||
_("Asset Received But Not Billed"): {"account_type": "Asset Received But Not Billed"},
|
"account_type": "Stock Received But Not Billed"
|
||||||
|
},
|
||||||
|
_("Asset Received But Not Billed"): {
|
||||||
|
"account_type": "Asset Received But Not Billed"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_("Duties and Taxes"): {
|
||||||
|
"account_type": "Tax",
|
||||||
|
"is_group": 1
|
||||||
},
|
},
|
||||||
_("Duties and Taxes"): {"account_type": "Tax", "is_group": 1},
|
|
||||||
_("Loans (Liabilities)"): {
|
_("Loans (Liabilities)"): {
|
||||||
_("Secured Loans"): {},
|
_("Secured Loans"): {},
|
||||||
_("Unsecured Loans"): {},
|
_("Unsecured Loans"): {},
|
||||||
_("Bank Overdraft Account"): {},
|
_("Bank Overdraft Account"): {},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"root_type": "Liability",
|
"root_type": "Liability"
|
||||||
},
|
},
|
||||||
_("Equity"): {
|
_("Equity"): {
|
||||||
_("Capital Stock"): {"account_type": "Equity"},
|
_("Capital Stock"): {
|
||||||
_("Dividends Paid"): {"account_type": "Equity"},
|
"account_type": "Equity"
|
||||||
_("Opening Balance Equity"): {"account_type": "Equity"},
|
|
||||||
_("Retained Earnings"): {"account_type": "Equity"},
|
|
||||||
"root_type": "Equity",
|
|
||||||
},
|
},
|
||||||
|
_("Dividends Paid"): {
|
||||||
|
"account_type": "Equity"
|
||||||
|
},
|
||||||
|
_("Opening Balance Equity"): {
|
||||||
|
"account_type": "Equity"
|
||||||
|
},
|
||||||
|
_("Retained Earnings"): {
|
||||||
|
"account_type": "Equity"
|
||||||
|
},
|
||||||
|
"root_type": "Equity"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
@@ -10,149 +11,284 @@ def get():
|
|||||||
_("Application of Funds (Assets)"): {
|
_("Application of Funds (Assets)"): {
|
||||||
_("Current Assets"): {
|
_("Current Assets"): {
|
||||||
_("Accounts Receivable"): {
|
_("Accounts Receivable"): {
|
||||||
_("Debtors"): {"account_type": "Receivable", "account_number": "1310"},
|
_("Debtors"): {
|
||||||
"account_number": "1300",
|
"account_type": "Receivable",
|
||||||
|
"account_number": "1310"
|
||||||
|
},
|
||||||
|
"account_number": "1300"
|
||||||
|
},
|
||||||
|
_("Bank Accounts"): {
|
||||||
|
"account_type": "Bank",
|
||||||
|
"is_group": 1,
|
||||||
|
"account_number": "1200"
|
||||||
},
|
},
|
||||||
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1, "account_number": "1200"},
|
|
||||||
_("Cash In Hand"): {
|
_("Cash In Hand"): {
|
||||||
_("Cash"): {"account_type": "Cash", "account_number": "1110"},
|
_("Cash"): {
|
||||||
"account_type": "Cash",
|
"account_type": "Cash",
|
||||||
"account_number": "1100",
|
"account_number": "1110"
|
||||||
|
},
|
||||||
|
"account_type": "Cash",
|
||||||
|
"account_number": "1100"
|
||||||
},
|
},
|
||||||
_("Loans and Advances (Assets)"): {
|
_("Loans and Advances (Assets)"): {
|
||||||
_("Employee Advances"): {"account_number": "1610"},
|
_("Employee Advances"): {
|
||||||
"account_number": "1600",
|
"account_number": "1610"
|
||||||
|
},
|
||||||
|
"account_number": "1600"
|
||||||
},
|
},
|
||||||
_("Securities and Deposits"): {
|
_("Securities and Deposits"): {
|
||||||
_("Earnest Money"): {"account_number": "1651"},
|
_("Earnest Money"): {
|
||||||
"account_number": "1650",
|
"account_number": "1651"
|
||||||
|
},
|
||||||
|
"account_number": "1650"
|
||||||
},
|
},
|
||||||
_("Stock Assets"): {
|
_("Stock Assets"): {
|
||||||
_("Stock In Hand"): {"account_type": "Stock", "account_number": "1410"},
|
_("Stock In Hand"): {
|
||||||
"account_type": "Stock",
|
"account_type": "Stock",
|
||||||
"account_number": "1400",
|
"account_number": "1410"
|
||||||
},
|
},
|
||||||
_("Tax Assets"): {"is_group": 1, "account_number": "1500"},
|
"account_type": "Stock",
|
||||||
"account_number": "1100-1600",
|
"account_number": "1400"
|
||||||
|
},
|
||||||
|
_("Tax Assets"): {
|
||||||
|
"is_group": 1,
|
||||||
|
"account_number": "1500"
|
||||||
|
},
|
||||||
|
"account_number": "1100-1600"
|
||||||
},
|
},
|
||||||
_("Fixed Assets"): {
|
_("Fixed Assets"): {
|
||||||
_("Capital Equipments"): {"account_type": "Fixed Asset", "account_number": "1710"},
|
_("Capital Equipments"): {
|
||||||
_("Electronic Equipments"): {"account_type": "Fixed Asset", "account_number": "1720"},
|
"account_type": "Fixed Asset",
|
||||||
_("Furnitures and Fixtures"): {"account_type": "Fixed Asset", "account_number": "1730"},
|
"account_number": "1710"
|
||||||
_("Office Equipments"): {"account_type": "Fixed Asset", "account_number": "1740"},
|
},
|
||||||
_("Plants and Machineries"): {"account_type": "Fixed Asset", "account_number": "1750"},
|
_("Electronic Equipments"): {
|
||||||
_("Buildings"): {"account_type": "Fixed Asset", "account_number": "1760"},
|
"account_type": "Fixed Asset",
|
||||||
_("Softwares"): {"account_type": "Fixed Asset", "account_number": "1770"},
|
"account_number": "1720"
|
||||||
|
},
|
||||||
|
_("Furnitures and Fixtures"): {
|
||||||
|
"account_type": "Fixed Asset",
|
||||||
|
"account_number": "1730"
|
||||||
|
},
|
||||||
|
_("Office Equipments"): {
|
||||||
|
"account_type": "Fixed Asset",
|
||||||
|
"account_number": "1740"
|
||||||
|
},
|
||||||
|
_("Plants and Machineries"): {
|
||||||
|
"account_type": "Fixed Asset",
|
||||||
|
"account_number": "1750"
|
||||||
|
},
|
||||||
|
_("Buildings"): {
|
||||||
|
"account_type": "Fixed Asset",
|
||||||
|
"account_number": "1760"
|
||||||
|
},
|
||||||
|
_("Softwares"): {
|
||||||
|
"account_type": "Fixed Asset",
|
||||||
|
"account_number": "1770"
|
||||||
|
},
|
||||||
_("Accumulated Depreciation"): {
|
_("Accumulated Depreciation"): {
|
||||||
"account_type": "Accumulated Depreciation",
|
"account_type": "Accumulated Depreciation",
|
||||||
"account_number": "1780",
|
"account_number": "1780"
|
||||||
},
|
},
|
||||||
_("CWIP Account"): {"account_type": "Capital Work in Progress", "account_number": "1790"},
|
_("CWIP Account"): {
|
||||||
"account_number": "1700",
|
"account_type": "Capital Work in Progress",
|
||||||
|
"account_number": "1790"
|
||||||
|
},
|
||||||
|
"account_number": "1700"
|
||||||
|
},
|
||||||
|
_("Investments"): {
|
||||||
|
"is_group": 1,
|
||||||
|
"account_number": "1800"
|
||||||
},
|
},
|
||||||
_("Investments"): {"is_group": 1, "account_number": "1800"},
|
|
||||||
_("Temporary Accounts"): {
|
_("Temporary Accounts"): {
|
||||||
_("Temporary Opening"): {"account_type": "Temporary", "account_number": "1910"},
|
_("Temporary Opening"): {
|
||||||
"account_number": "1900",
|
"account_type": "Temporary",
|
||||||
|
"account_number": "1910"
|
||||||
|
},
|
||||||
|
"account_number": "1900"
|
||||||
},
|
},
|
||||||
"root_type": "Asset",
|
"root_type": "Asset",
|
||||||
"account_number": "1000",
|
"account_number": "1000"
|
||||||
},
|
},
|
||||||
_("Expenses"): {
|
_("Expenses"): {
|
||||||
_("Direct Expenses"): {
|
_("Direct Expenses"): {
|
||||||
_("Stock Expenses"): {
|
_("Stock Expenses"): {
|
||||||
_("Cost of Goods Sold"): {"account_type": "Cost of Goods Sold", "account_number": "5111"},
|
_("Cost of Goods Sold"): {
|
||||||
|
"account_type": "Cost of Goods Sold",
|
||||||
|
"account_number": "5111"
|
||||||
|
},
|
||||||
_("Expenses Included In Asset Valuation"): {
|
_("Expenses Included In Asset Valuation"): {
|
||||||
"account_type": "Expenses Included In Asset Valuation",
|
"account_type": "Expenses Included In Asset Valuation",
|
||||||
"account_number": "5112",
|
"account_number": "5112"
|
||||||
},
|
},
|
||||||
_("Expenses Included In Valuation"): {
|
_("Expenses Included In Valuation"): {
|
||||||
"account_type": "Expenses Included In Valuation",
|
"account_type": "Expenses Included In Valuation",
|
||||||
"account_number": "5118",
|
"account_number": "5118"
|
||||||
},
|
},
|
||||||
_("Stock Adjustment"): {"account_type": "Stock Adjustment", "account_number": "5119"},
|
_("Stock Adjustment"): {
|
||||||
"account_number": "5110",
|
"account_type": "Stock Adjustment",
|
||||||
|
"account_number": "5119"
|
||||||
},
|
},
|
||||||
"account_number": "5100",
|
"account_number": "5110"
|
||||||
|
},
|
||||||
|
"account_number": "5100"
|
||||||
},
|
},
|
||||||
_("Indirect Expenses"): {
|
_("Indirect Expenses"): {
|
||||||
_("Administrative Expenses"): {"account_number": "5201"},
|
_("Administrative Expenses"): {
|
||||||
_("Commission on Sales"): {"account_number": "5202"},
|
"account_number": "5201"
|
||||||
_("Depreciation"): {"account_type": "Depreciation", "account_number": "5203"},
|
},
|
||||||
_("Entertainment Expenses"): {"account_number": "5204"},
|
_("Commission on Sales"): {
|
||||||
_("Freight and Forwarding Charges"): {"account_type": "Chargeable", "account_number": "5205"},
|
"account_number": "5202"
|
||||||
_("Legal Expenses"): {"account_number": "5206"},
|
},
|
||||||
_("Marketing Expenses"): {"account_type": "Chargeable", "account_number": "5207"},
|
_("Depreciation"): {
|
||||||
_("Office Maintenance Expenses"): {"account_number": "5208"},
|
"account_type": "Depreciation",
|
||||||
_("Office Rent"): {"account_number": "5209"},
|
"account_number": "5203"
|
||||||
_("Postal Expenses"): {"account_number": "5210"},
|
},
|
||||||
_("Print and Stationery"): {"account_number": "5211"},
|
_("Entertainment Expenses"): {
|
||||||
_("Round Off"): {"account_type": "Round Off", "account_number": "5212"},
|
"account_number": "5204"
|
||||||
_("Salary"): {"account_number": "5213"},
|
},
|
||||||
_("Sales Expenses"): {"account_number": "5214"},
|
_("Freight and Forwarding Charges"): {
|
||||||
_("Telephone Expenses"): {"account_number": "5215"},
|
"account_type": "Chargeable",
|
||||||
_("Travel Expenses"): {"account_number": "5216"},
|
"account_number": "5205"
|
||||||
_("Utility Expenses"): {"account_number": "5217"},
|
},
|
||||||
_("Write Off"): {"account_number": "5218"},
|
_("Legal Expenses"): {
|
||||||
_("Exchange Gain/Loss"): {"account_number": "5219"},
|
"account_number": "5206"
|
||||||
_("Gain/Loss on Asset Disposal"): {"account_number": "5220"},
|
},
|
||||||
_("Miscellaneous Expenses"): {"account_type": "Chargeable", "account_number": "5221"},
|
_("Marketing Expenses"): {
|
||||||
"account_number": "5200",
|
"account_type": "Chargeable",
|
||||||
|
"account_number": "5207"
|
||||||
|
},
|
||||||
|
_("Office Maintenance Expenses"): {
|
||||||
|
"account_number": "5208"
|
||||||
|
},
|
||||||
|
_("Office Rent"): {
|
||||||
|
"account_number": "5209"
|
||||||
|
},
|
||||||
|
_("Postal Expenses"): {
|
||||||
|
"account_number": "5210"
|
||||||
|
},
|
||||||
|
_("Print and Stationery"): {
|
||||||
|
"account_number": "5211"
|
||||||
|
},
|
||||||
|
_("Round Off"): {
|
||||||
|
"account_type": "Round Off",
|
||||||
|
"account_number": "5212"
|
||||||
|
},
|
||||||
|
_("Salary"): {
|
||||||
|
"account_number": "5213"
|
||||||
|
},
|
||||||
|
_("Sales Expenses"): {
|
||||||
|
"account_number": "5214"
|
||||||
|
},
|
||||||
|
_("Telephone Expenses"): {
|
||||||
|
"account_number": "5215"
|
||||||
|
},
|
||||||
|
_("Travel Expenses"): {
|
||||||
|
"account_number": "5216"
|
||||||
|
},
|
||||||
|
_("Utility Expenses"): {
|
||||||
|
"account_number": "5217"
|
||||||
|
},
|
||||||
|
_("Write Off"): {
|
||||||
|
"account_number": "5218"
|
||||||
|
},
|
||||||
|
_("Exchange Gain/Loss"): {
|
||||||
|
"account_number": "5219"
|
||||||
|
},
|
||||||
|
_("Gain/Loss on Asset Disposal"): {
|
||||||
|
"account_number": "5220"
|
||||||
|
},
|
||||||
|
_("Miscellaneous Expenses"): {
|
||||||
|
"account_type": "Chargeable",
|
||||||
|
"account_number": "5221"
|
||||||
|
},
|
||||||
|
"account_number": "5200"
|
||||||
},
|
},
|
||||||
"root_type": "Expense",
|
"root_type": "Expense",
|
||||||
"account_number": "5000",
|
"account_number": "5000"
|
||||||
},
|
},
|
||||||
_("Income"): {
|
_("Income"): {
|
||||||
_("Direct Income"): {
|
_("Direct Income"): {
|
||||||
_("Sales"): {"account_number": "4110"},
|
_("Sales"): {
|
||||||
_("Service"): {"account_number": "4120"},
|
"account_number": "4110"
|
||||||
"account_number": "4100",
|
},
|
||||||
|
_("Service"): {
|
||||||
|
"account_number": "4120"
|
||||||
|
},
|
||||||
|
"account_number": "4100"
|
||||||
|
},
|
||||||
|
_("Indirect Income"): {
|
||||||
|
"is_group": 1,
|
||||||
|
"account_number": "4200"
|
||||||
},
|
},
|
||||||
_("Indirect Income"): {"is_group": 1, "account_number": "4200"},
|
|
||||||
"root_type": "Income",
|
"root_type": "Income",
|
||||||
"account_number": "4000",
|
"account_number": "4000"
|
||||||
},
|
},
|
||||||
_("Source of Funds (Liabilities)"): {
|
_("Source of Funds (Liabilities)"): {
|
||||||
_("Current Liabilities"): {
|
_("Current Liabilities"): {
|
||||||
_("Accounts Payable"): {
|
_("Accounts Payable"): {
|
||||||
_("Creditors"): {"account_type": "Payable", "account_number": "2110"},
|
_("Creditors"): {
|
||||||
_("Payroll Payable"): {"account_number": "2120"},
|
"account_type": "Payable",
|
||||||
"account_number": "2100",
|
"account_number": "2110"
|
||||||
|
},
|
||||||
|
_("Payroll Payable"): {
|
||||||
|
"account_number": "2120"
|
||||||
|
},
|
||||||
|
"account_number": "2100"
|
||||||
},
|
},
|
||||||
_("Stock Liabilities"): {
|
_("Stock Liabilities"): {
|
||||||
_("Stock Received But Not Billed"): {
|
_("Stock Received But Not Billed"): {
|
||||||
"account_type": "Stock Received But Not Billed",
|
"account_type": "Stock Received But Not Billed",
|
||||||
"account_number": "2210",
|
"account_number": "2210"
|
||||||
},
|
},
|
||||||
_("Asset Received But Not Billed"): {
|
_("Asset Received But Not Billed"): {
|
||||||
"account_type": "Asset Received But Not Billed",
|
"account_type": "Asset Received But Not Billed",
|
||||||
"account_number": "2211",
|
"account_number": "2211"
|
||||||
},
|
},
|
||||||
"account_number": "2200",
|
"account_number": "2200"
|
||||||
},
|
},
|
||||||
_("Duties and Taxes"): {
|
_("Duties and Taxes"): {
|
||||||
_("TDS Payable"): {"account_number": "2310"},
|
_("TDS Payable"): {
|
||||||
|
"account_number": "2310"
|
||||||
|
},
|
||||||
"account_type": "Tax",
|
"account_type": "Tax",
|
||||||
"is_group": 1,
|
"is_group": 1,
|
||||||
"account_number": "2300",
|
"account_number": "2300"
|
||||||
},
|
},
|
||||||
_("Loans (Liabilities)"): {
|
_("Loans (Liabilities)"): {
|
||||||
_("Secured Loans"): {"account_number": "2410"},
|
_("Secured Loans"): {
|
||||||
_("Unsecured Loans"): {"account_number": "2420"},
|
"account_number": "2410"
|
||||||
_("Bank Overdraft Account"): {"account_number": "2430"},
|
|
||||||
"account_number": "2400",
|
|
||||||
},
|
},
|
||||||
"account_number": "2100-2400",
|
_("Unsecured Loans"): {
|
||||||
|
"account_number": "2420"
|
||||||
|
},
|
||||||
|
_("Bank Overdraft Account"): {
|
||||||
|
"account_number": "2430"
|
||||||
|
},
|
||||||
|
"account_number": "2400"
|
||||||
|
},
|
||||||
|
"account_number": "2100-2400"
|
||||||
},
|
},
|
||||||
"root_type": "Liability",
|
"root_type": "Liability",
|
||||||
"account_number": "2000",
|
"account_number": "2000"
|
||||||
},
|
},
|
||||||
_("Equity"): {
|
_("Equity"): {
|
||||||
_("Capital Stock"): {"account_type": "Equity", "account_number": "3100"},
|
_("Capital Stock"): {
|
||||||
_("Dividends Paid"): {"account_type": "Equity", "account_number": "3200"},
|
"account_type": "Equity",
|
||||||
_("Opening Balance Equity"): {"account_type": "Equity", "account_number": "3300"},
|
"account_number": "3100"
|
||||||
_("Retained Earnings"): {"account_type": "Equity", "account_number": "3400"},
|
|
||||||
"root_type": "Equity",
|
|
||||||
"account_number": "3000",
|
|
||||||
},
|
},
|
||||||
|
_("Dividends Paid"): {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"account_number": "3200"
|
||||||
|
},
|
||||||
|
_("Opening Balance Equity"): {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"account_number": "3300"
|
||||||
|
},
|
||||||
|
_("Retained Earnings"): {
|
||||||
|
"account_type": "Equity",
|
||||||
|
"account_number": "3400"
|
||||||
|
},
|
||||||
|
"root_type": "Equity",
|
||||||
|
"account_number": "3000"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@@ -20,9 +21,8 @@ class TestAccount(unittest.TestCase):
|
|||||||
acc.company = "_Test Company"
|
acc.company = "_Test Company"
|
||||||
acc.insert()
|
acc.insert()
|
||||||
|
|
||||||
account_number, account_name = frappe.db.get_value(
|
account_number, account_name = frappe.db.get_value("Account", "1210 - Debtors - _TC",
|
||||||
"Account", "1210 - Debtors - _TC", ["account_number", "account_name"]
|
["account_number", "account_name"])
|
||||||
)
|
|
||||||
self.assertEqual(account_number, "1210")
|
self.assertEqual(account_number, "1210")
|
||||||
self.assertEqual(account_name, "Debtors")
|
self.assertEqual(account_name, "Debtors")
|
||||||
|
|
||||||
@@ -31,12 +31,8 @@ class TestAccount(unittest.TestCase):
|
|||||||
|
|
||||||
update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number)
|
update_account_number("1210 - Debtors - _TC", new_account_name, new_account_number)
|
||||||
|
|
||||||
new_acc = frappe.db.get_value(
|
new_acc = frappe.db.get_value("Account", "1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
|
||||||
"Account",
|
["account_name", "account_number"], as_dict=1)
|
||||||
"1211-11-4 - 6 - - Debtors 1 - Test - - _TC",
|
|
||||||
["account_name", "account_number"],
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
self.assertEqual(new_acc.account_name, "Debtors 1 - Test -")
|
self.assertEqual(new_acc.account_name, "Debtors 1 - Test -")
|
||||||
self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -")
|
self.assertEqual(new_acc.account_number, "1211-11-4 - 6 -")
|
||||||
@@ -84,9 +80,7 @@ class TestAccount(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(parent, "Securities and Deposits - _TC")
|
self.assertEqual(parent, "Securities and Deposits - _TC")
|
||||||
|
|
||||||
merge_account(
|
merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company)
|
||||||
"Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company
|
|
||||||
)
|
|
||||||
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
|
parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
|
||||||
|
|
||||||
# Parent account of the child account changes after merging
|
# Parent account of the child account changes after merging
|
||||||
@@ -98,28 +92,14 @@ class TestAccount(unittest.TestCase):
|
|||||||
doc = frappe.get_doc("Account", "Current Assets - _TC")
|
doc = frappe.get_doc("Account", "Current Assets - _TC")
|
||||||
|
|
||||||
# Raise error as is_group property doesn't match
|
# Raise error as is_group property doesn't match
|
||||||
self.assertRaises(
|
self.assertRaises(frappe.ValidationError, merge_account, "Current Assets - _TC",\
|
||||||
frappe.ValidationError,
|
"Accumulated Depreciation - _TC", doc.is_group, doc.root_type, doc.company)
|
||||||
merge_account,
|
|
||||||
"Current Assets - _TC",
|
|
||||||
"Accumulated Depreciation - _TC",
|
|
||||||
doc.is_group,
|
|
||||||
doc.root_type,
|
|
||||||
doc.company,
|
|
||||||
)
|
|
||||||
|
|
||||||
doc = frappe.get_doc("Account", "Capital Stock - _TC")
|
doc = frappe.get_doc("Account", "Capital Stock - _TC")
|
||||||
|
|
||||||
# Raise error as root_type property doesn't match
|
# Raise error as root_type property doesn't match
|
||||||
self.assertRaises(
|
self.assertRaises(frappe.ValidationError, merge_account, "Capital Stock - _TC",\
|
||||||
frappe.ValidationError,
|
"Softwares - _TC", doc.is_group, doc.root_type, doc.company)
|
||||||
merge_account,
|
|
||||||
"Capital Stock - _TC",
|
|
||||||
"Softwares - _TC",
|
|
||||||
doc.is_group,
|
|
||||||
doc.root_type,
|
|
||||||
doc.company,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_account_sync(self):
|
def test_account_sync(self):
|
||||||
frappe.local.flags.pop("ignore_root_company_validation", None)
|
frappe.local.flags.pop("ignore_root_company_validation", None)
|
||||||
@@ -130,12 +110,8 @@ class TestAccount(unittest.TestCase):
|
|||||||
acc.company = "_Test Company 3"
|
acc.company = "_Test Company 3"
|
||||||
acc.insert()
|
acc.insert()
|
||||||
|
|
||||||
acc_tc_4 = frappe.db.get_value(
|
acc_tc_4 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 4"})
|
||||||
"Account", {"account_name": "Test Sync Account", "company": "_Test Company 4"}
|
acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Sync Account", "company": "_Test Company 5"})
|
||||||
)
|
|
||||||
acc_tc_5 = frappe.db.get_value(
|
|
||||||
"Account", {"account_name": "Test Sync Account", "company": "_Test Company 5"}
|
|
||||||
)
|
|
||||||
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
|
self.assertEqual(acc_tc_4, "Test Sync Account - _TC4")
|
||||||
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
|
self.assertEqual(acc_tc_5, "Test Sync Account - _TC5")
|
||||||
|
|
||||||
@@ -163,26 +139,8 @@ class TestAccount(unittest.TestCase):
|
|||||||
update_account_number(acc.name, "Test Rename Sync Account", "1234")
|
update_account_number(acc.name, "Test Rename Sync Account", "1234")
|
||||||
|
|
||||||
# Check if renamed in children
|
# Check if renamed in children
|
||||||
self.assertTrue(
|
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 4", "account_number": "1234"}))
|
||||||
frappe.db.exists(
|
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Rename Sync Account", "company": "_Test Company 5", "account_number": "1234"}))
|
||||||
"Account",
|
|
||||||
{
|
|
||||||
"account_name": "Test Rename Sync Account",
|
|
||||||
"company": "_Test Company 4",
|
|
||||||
"account_number": "1234",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.assertTrue(
|
|
||||||
frappe.db.exists(
|
|
||||||
"Account",
|
|
||||||
{
|
|
||||||
"account_name": "Test Rename Sync Account",
|
|
||||||
"company": "_Test Company 5",
|
|
||||||
"account_number": "1234",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3")
|
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC3")
|
||||||
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
|
frappe.delete_doc("Account", "1234 - Test Rename Sync Account - _TC4")
|
||||||
@@ -198,71 +156,25 @@ class TestAccount(unittest.TestCase):
|
|||||||
acc.company = "_Test Company 3"
|
acc.company = "_Test Company 3"
|
||||||
acc.insert()
|
acc.insert()
|
||||||
|
|
||||||
self.assertTrue(
|
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 4"}))
|
||||||
frappe.db.exists(
|
self.assertTrue(frappe.db.exists("Account", {'account_name': "Test Group Account", "company": "_Test Company 5"}))
|
||||||
"Account", {"account_name": "Test Group Account", "company": "_Test Company 4"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.assertTrue(
|
|
||||||
frappe.db.exists(
|
|
||||||
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Try renaming child company account
|
# Try renaming child company account
|
||||||
acc_tc_5 = frappe.db.get_value(
|
acc_tc_5 = frappe.db.get_value('Account', {'account_name': "Test Group Account", "company": "_Test Company 5"})
|
||||||
"Account", {"account_name": "Test Group Account", "company": "_Test Company 5"}
|
self.assertRaises(frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account")
|
||||||
)
|
|
||||||
self.assertRaises(
|
|
||||||
frappe.ValidationError, update_account_number, acc_tc_5, "Test Modified Account"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Rename child company account with allow_account_creation_against_child_company enabled
|
# Rename child company account with allow_account_creation_against_child_company enabled
|
||||||
frappe.db.set_value(
|
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 1)
|
||||||
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 1
|
|
||||||
)
|
|
||||||
|
|
||||||
update_account_number(acc_tc_5, "Test Modified Account")
|
update_account_number(acc_tc_5, "Test Modified Account")
|
||||||
self.assertTrue(
|
self.assertTrue(frappe.db.exists("Account", {'name': "Test Modified Account - _TC5", "company": "_Test Company 5"}))
|
||||||
frappe.db.exists(
|
|
||||||
"Account", {"name": "Test Modified Account - _TC5", "company": "_Test Company 5"}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.db.set_value(
|
frappe.db.set_value("Company", "_Test Company 5", "allow_account_creation_against_child_company", 0)
|
||||||
"Company", "_Test Company 5", "allow_account_creation_against_child_company", 0
|
|
||||||
)
|
|
||||||
|
|
||||||
to_delete = [
|
to_delete = ["Test Group Account - _TC3", "Test Group Account - _TC4", "Test Modified Account - _TC5"]
|
||||||
"Test Group Account - _TC3",
|
|
||||||
"Test Group Account - _TC4",
|
|
||||||
"Test Modified Account - _TC5",
|
|
||||||
]
|
|
||||||
for doc in to_delete:
|
for doc in to_delete:
|
||||||
frappe.delete_doc("Account", doc)
|
frappe.delete_doc("Account", doc)
|
||||||
|
|
||||||
def test_validate_account_currency(self):
|
|
||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
|
||||||
|
|
||||||
if not frappe.db.get_value("Account", "Test Currency Account - _TC"):
|
|
||||||
acc = frappe.new_doc("Account")
|
|
||||||
acc.account_name = "Test Currency Account"
|
|
||||||
acc.parent_account = "Tax Assets - _TC"
|
|
||||||
acc.company = "_Test Company"
|
|
||||||
acc.insert()
|
|
||||||
else:
|
|
||||||
acc = frappe.get_doc("Account", "Test Currency Account - _TC")
|
|
||||||
|
|
||||||
self.assertEqual(acc.account_currency, "INR")
|
|
||||||
|
|
||||||
# Make a JV against this account
|
|
||||||
make_journal_entry(
|
|
||||||
"Test Currency Account - _TC", "Miscellaneous Expenses - _TC", 100, submit=True
|
|
||||||
)
|
|
||||||
|
|
||||||
acc.account_currency = "USD"
|
|
||||||
self.assertRaises(frappe.ValidationError, acc.save)
|
|
||||||
|
|
||||||
|
|
||||||
def _make_test_records(verbose=None):
|
def _make_test_records(verbose=None):
|
||||||
from frappe.test_runner import make_test_objects
|
from frappe.test_runner import make_test_objects
|
||||||
@@ -273,16 +185,20 @@ def _make_test_records(verbose=None):
|
|||||||
["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"],
|
["_Test Bank USD", "Bank Accounts", 0, "Bank", "USD"],
|
||||||
["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"],
|
["_Test Bank EUR", "Bank Accounts", 0, "Bank", "EUR"],
|
||||||
["_Test Cash", "Cash In Hand", 0, "Cash", None],
|
["_Test Cash", "Cash In Hand", 0, "Cash", None],
|
||||||
|
|
||||||
["_Test Account Stock Expenses", "Direct Expenses", 1, None, None],
|
["_Test Account Stock Expenses", "Direct Expenses", 1, None, None],
|
||||||
["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
|
["_Test Account Shipping Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
|
||||||
["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None],
|
["_Test Account Customs Duty", "_Test Account Stock Expenses", 0, "Tax", None],
|
||||||
["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
|
["_Test Account Insurance Charges", "_Test Account Stock Expenses", 0, "Chargeable", None],
|
||||||
["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None],
|
["_Test Account Stock Adjustment", "_Test Account Stock Expenses", 0, "Stock Adjustment", None],
|
||||||
["_Test Employee Advance", "Current Liabilities", 0, None, None],
|
["_Test Employee Advance", "Current Liabilities", 0, None, None],
|
||||||
|
|
||||||
["_Test Account Tax Assets", "Current Assets", 1, None, None],
|
["_Test Account Tax Assets", "Current Assets", 1, None, None],
|
||||||
["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None],
|
["_Test Account VAT", "_Test Account Tax Assets", 0, "Tax", None],
|
||||||
["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None],
|
["_Test Account Service Tax", "_Test Account Tax Assets", 0, "Tax", None],
|
||||||
|
|
||||||
["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None],
|
["_Test Account Reserves and Surplus", "Current Liabilities", 0, None, None],
|
||||||
|
|
||||||
["_Test Account Cost for Goods Sold", "Expenses", 0, None, None],
|
["_Test Account Cost for Goods Sold", "Expenses", 0, None, None],
|
||||||
["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None],
|
["_Test Account Excise Duty", "_Test Account Tax Assets", 0, "Tax", None],
|
||||||
["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
|
["_Test Account Education Cess", "_Test Account Tax Assets", 0, "Tax", None],
|
||||||
@@ -291,45 +207,38 @@ def _make_test_records(verbose=None):
|
|||||||
["_Test Account Discount", "Direct Expenses", 0, None, None],
|
["_Test Account Discount", "Direct Expenses", 0, None, None],
|
||||||
["_Test Write Off", "Indirect Expenses", 0, None, None],
|
["_Test Write Off", "Indirect Expenses", 0, None, None],
|
||||||
["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None],
|
["_Test Exchange Gain/Loss", "Indirect Expenses", 0, None, None],
|
||||||
|
|
||||||
["_Test Account Sales", "Direct Income", 0, None, None],
|
["_Test Account Sales", "Direct Income", 0, None, None],
|
||||||
|
|
||||||
# related to Account Inventory Integration
|
# related to Account Inventory Integration
|
||||||
["_Test Account Stock In Hand", "Current Assets", 0, None, None],
|
["_Test Account Stock In Hand", "Current Assets", 0, None, None],
|
||||||
|
|
||||||
# fixed asset depreciation
|
# fixed asset depreciation
|
||||||
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
|
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
|
||||||
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
|
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
|
||||||
["_Test Depreciations", "Expenses", 0, None, None],
|
["_Test Depreciations", "Expenses", 0, None, None],
|
||||||
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
|
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
|
||||||
|
|
||||||
# Receivable / Payable Account
|
# Receivable / Payable Account
|
||||||
["_Test Receivable", "Current Assets", 0, "Receivable", None],
|
["_Test Receivable", "Current Assets", 0, "Receivable", None],
|
||||||
["_Test Payable", "Current Liabilities", 0, "Payable", None],
|
["_Test Payable", "Current Liabilities", 0, "Payable", None],
|
||||||
["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"],
|
["_Test Receivable USD", "Current Assets", 0, "Receivable", "USD"],
|
||||||
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"],
|
["_Test Payable USD", "Current Liabilities", 0, "Payable", "USD"]
|
||||||
]
|
]
|
||||||
|
|
||||||
for company, abbr in [
|
for company, abbr in [["_Test Company", "_TC"], ["_Test Company 1", "_TC1"], ["_Test Company with perpetual inventory", "TCP1"]]:
|
||||||
["_Test Company", "_TC"],
|
test_objects = make_test_objects("Account", [{
|
||||||
["_Test Company 1", "_TC1"],
|
|
||||||
["_Test Company with perpetual inventory", "TCP1"],
|
|
||||||
]:
|
|
||||||
test_objects = make_test_objects(
|
|
||||||
"Account",
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"doctype": "Account",
|
"doctype": "Account",
|
||||||
"account_name": account_name,
|
"account_name": account_name,
|
||||||
"parent_account": parent_account + " - " + abbr,
|
"parent_account": parent_account + " - " + abbr,
|
||||||
"company": company,
|
"company": company,
|
||||||
"is_group": is_group,
|
"is_group": is_group,
|
||||||
"account_type": account_type,
|
"account_type": account_type,
|
||||||
"account_currency": currency,
|
"account_currency": currency
|
||||||
}
|
} for account_name, parent_account, is_group, account_type, currency in accounts])
|
||||||
for account_name, parent_account, is_group, account_type, currency in accounts
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
return test_objects
|
return test_objects
|
||||||
|
|
||||||
|
|
||||||
def get_inventory_account(company, warehouse=None):
|
def get_inventory_account(company, warehouse=None):
|
||||||
account = None
|
account = None
|
||||||
if warehouse:
|
if warehouse:
|
||||||
@@ -339,24 +248,19 @@ def get_inventory_account(company, warehouse=None):
|
|||||||
|
|
||||||
return account
|
return account
|
||||||
|
|
||||||
|
|
||||||
def create_account(**kwargs):
|
def create_account(**kwargs):
|
||||||
account = frappe.db.get_value(
|
account = frappe.db.get_value("Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")})
|
||||||
"Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")}
|
|
||||||
)
|
|
||||||
if account:
|
if account:
|
||||||
return account
|
return account
|
||||||
else:
|
else:
|
||||||
account = frappe.get_doc(
|
account = frappe.get_doc(dict(
|
||||||
dict(
|
doctype = "Account",
|
||||||
doctype="Account",
|
account_name = kwargs.get('account_name'),
|
||||||
account_name=kwargs.get("account_name"),
|
account_type = kwargs.get('account_type'),
|
||||||
account_type=kwargs.get("account_type"),
|
parent_account = kwargs.get('parent_account'),
|
||||||
parent_account=kwargs.get("parent_account"),
|
company = kwargs.get('company'),
|
||||||
company=kwargs.get("company"),
|
account_currency = kwargs.get('account_currency')
|
||||||
account_currency=kwargs.get("account_currency"),
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
account.save()
|
account.save()
|
||||||
return account.name
|
return account.name
|
||||||
|
|||||||
29
erpnext/accounts/doctype/account/tests/test_account.js
Normal file
29
erpnext/accounts/doctype/account/tests/test_account.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
QUnit.module('accounts');
|
||||||
|
|
||||||
|
QUnit.test("test account", function(assert) {
|
||||||
|
assert.expect(4);
|
||||||
|
let done = assert.async();
|
||||||
|
frappe.run_serially([
|
||||||
|
() => frappe.set_route('Tree', 'Account'),
|
||||||
|
() => frappe.timeout(3),
|
||||||
|
() => frappe.click_button('Expand All'),
|
||||||
|
() => frappe.timeout(1),
|
||||||
|
() => frappe.click_link('Debtors'),
|
||||||
|
() => frappe.click_button('Edit'),
|
||||||
|
() => frappe.timeout(1),
|
||||||
|
() => {
|
||||||
|
assert.ok(cur_frm.doc.root_type=='Asset');
|
||||||
|
assert.ok(cur_frm.doc.report_type=='Balance Sheet');
|
||||||
|
assert.ok(cur_frm.doc.account_type=='Receivable');
|
||||||
|
},
|
||||||
|
() => frappe.click_button('Ledger'),
|
||||||
|
() => frappe.timeout(1),
|
||||||
|
() => {
|
||||||
|
// check if general ledger report shown
|
||||||
|
assert.deepEqual(frappe.get_route(), ['query-report', 'General Ledger']);
|
||||||
|
window.history.back();
|
||||||
|
return frappe.timeout(1);
|
||||||
|
},
|
||||||
|
() => done()
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
QUnit.module('accounts');
|
||||||
|
|
||||||
|
QUnit.test("test account with number", function(assert) {
|
||||||
|
assert.expect(7);
|
||||||
|
let done = assert.async();
|
||||||
|
frappe.run_serially([
|
||||||
|
() => frappe.set_route('Tree', 'Account'),
|
||||||
|
() => frappe.click_link('Income'),
|
||||||
|
() => frappe.click_button('Add Child'),
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => {
|
||||||
|
cur_dialog.fields_dict.account_name.$input.val("Test Income");
|
||||||
|
cur_dialog.fields_dict.account_number.$input.val("4010");
|
||||||
|
},
|
||||||
|
() => frappe.click_button('Create New'),
|
||||||
|
() => frappe.timeout(1),
|
||||||
|
() => {
|
||||||
|
assert.ok($('a:contains("4010 - Test Income"):visible').length!=0, "Account created with number");
|
||||||
|
},
|
||||||
|
() => frappe.click_link('4010 - Test Income'),
|
||||||
|
() => frappe.click_button('Edit'),
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => frappe.click_button('Update Account Number'),
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => {
|
||||||
|
cur_dialog.fields_dict.account_number.$input.val("4020");
|
||||||
|
},
|
||||||
|
() => frappe.timeout(1),
|
||||||
|
() => cur_dialog.primary_action(),
|
||||||
|
() => frappe.timeout(1),
|
||||||
|
() => cur_frm.refresh_fields(),
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => {
|
||||||
|
var abbr = frappe.get_abbr(frappe.defaults.get_default("Company"));
|
||||||
|
var new_account = "4020 - Test Income - " + abbr;
|
||||||
|
assert.ok(cur_frm.doc.name==new_account, "Account renamed");
|
||||||
|
assert.ok(cur_frm.doc.account_name=="Test Income", "account name remained same");
|
||||||
|
assert.ok(cur_frm.doc.account_number=="4020", "Account number updated to 4020");
|
||||||
|
},
|
||||||
|
() => frappe.timeout(1),
|
||||||
|
() => frappe.click_button('Menu'),
|
||||||
|
() => frappe.click_link('Rename'),
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => {
|
||||||
|
cur_dialog.fields_dict.new_name.$input.val("4030 - Test Income");
|
||||||
|
},
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => frappe.click_button("Rename"),
|
||||||
|
() => frappe.timeout(2),
|
||||||
|
() => {
|
||||||
|
assert.ok(cur_frm.doc.account_name=="Test Income", "account name remained same");
|
||||||
|
assert.ok(cur_frm.doc.account_number=="4030", "Account number updated to 4030");
|
||||||
|
},
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => frappe.click_button('Chart of Accounts'),
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => frappe.click_button('Menu'),
|
||||||
|
() => frappe.click_link('Refresh'),
|
||||||
|
() => frappe.click_button('Expand All'),
|
||||||
|
() => frappe.click_link('4030 - Test Income'),
|
||||||
|
() => frappe.click_button('Delete'),
|
||||||
|
() => frappe.click_button('Yes'),
|
||||||
|
() => frappe.timeout(.5),
|
||||||
|
() => {
|
||||||
|
assert.ok($('a:contains("4030 - Test Account"):visible').length==0, "Account deleted");
|
||||||
|
},
|
||||||
|
() => done()
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
QUnit.module('accounts');
|
||||||
|
QUnit.test("test account", assert => {
|
||||||
|
assert.expect(3);
|
||||||
|
let done = assert.async();
|
||||||
|
frappe.run_serially([
|
||||||
|
() => frappe.set_route('Tree', 'Account'),
|
||||||
|
() => frappe.click_button('Expand All'),
|
||||||
|
() => frappe.click_link('Duties and Taxes - '+ frappe.get_abbr(frappe.defaults.get_default("Company"))),
|
||||||
|
() => {
|
||||||
|
if($('a:contains("CGST"):visible').length == 0){
|
||||||
|
return frappe.map_tax.make('CGST', 9);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if($('a:contains("SGST"):visible').length == 0){
|
||||||
|
return frappe.map_tax.make('SGST', 9);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
if($('a:contains("IGST"):visible').length == 0){
|
||||||
|
return frappe.map_tax.make('IGST', 18);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
assert.ok($('a:contains("CGST"):visible').length!=0, "CGST Checked");
|
||||||
|
assert.ok($('a:contains("SGST"):visible').length!=0, "SGST Checked");
|
||||||
|
assert.ok($('a:contains("IGST"):visible').length!=0, "IGST Checked");
|
||||||
|
},
|
||||||
|
() => done()
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
frappe.map_tax = {
|
||||||
|
make:function(text,rate){
|
||||||
|
return frappe.run_serially([
|
||||||
|
() => frappe.click_button('Add Child'),
|
||||||
|
() => frappe.timeout(0.2),
|
||||||
|
() => cur_dialog.set_value('account_name',text),
|
||||||
|
() => cur_dialog.set_value('account_type','Tax'),
|
||||||
|
() => cur_dialog.set_value('tax_rate',rate),
|
||||||
|
() => cur_dialog.set_value('account_currency','INR'),
|
||||||
|
() => frappe.click_button('Create New'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -17,21 +19,13 @@ class AccountingDimension(Document):
|
|||||||
self.set_fieldname_and_label()
|
self.set_fieldname_and_label()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
if self.document_type in core_doctypes_list + (
|
if self.document_type in core_doctypes_list + ('Accounting Dimension', 'Project',
|
||||||
"Accounting Dimension",
|
'Cost Center', 'Accounting Dimension Detail', 'Company', 'Account') :
|
||||||
"Project",
|
|
||||||
"Cost Center",
|
|
||||||
"Accounting Dimension Detail",
|
|
||||||
"Company",
|
|
||||||
"Account",
|
|
||||||
):
|
|
||||||
|
|
||||||
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
msg = _("Not allowed to create accounting dimension for {0}").format(self.document_type)
|
||||||
frappe.throw(msg)
|
frappe.throw(msg)
|
||||||
|
|
||||||
exists = frappe.db.get_value(
|
exists = frappe.db.get_value("Accounting Dimension", {'document_type': self.document_type}, ['name'])
|
||||||
"Accounting Dimension", {"document_type": self.document_type}, ["name"]
|
|
||||||
)
|
|
||||||
|
|
||||||
if exists and self.is_new():
|
if exists and self.is_new():
|
||||||
frappe.throw(_("Document Type already used as a dimension"))
|
frappe.throw(_("Document Type already used as a dimension"))
|
||||||
@@ -50,13 +44,13 @@ class AccountingDimension(Document):
|
|||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
make_dimension_in_accounting_doctypes(doc=self)
|
make_dimension_in_accounting_doctypes(doc=self)
|
||||||
else:
|
else:
|
||||||
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue="long")
|
frappe.enqueue(make_dimension_in_accounting_doctypes, doc=self, queue='long')
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
delete_accounting_dimension(doc=self)
|
delete_accounting_dimension(doc=self)
|
||||||
else:
|
else:
|
||||||
frappe.enqueue(delete_accounting_dimension, doc=self, queue="long")
|
frappe.enqueue(delete_accounting_dimension, doc=self, queue='long')
|
||||||
|
|
||||||
def set_fieldname_and_label(self):
|
def set_fieldname_and_label(self):
|
||||||
if not self.label:
|
if not self.label:
|
||||||
@@ -68,7 +62,6 @@ class AccountingDimension(Document):
|
|||||||
def on_update(self):
|
def on_update(self):
|
||||||
frappe.flags.accounting_dimensions = None
|
frappe.flags.accounting_dimensions = None
|
||||||
|
|
||||||
|
|
||||||
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||||
if not doclist:
|
if not doclist:
|
||||||
doclist = get_doctypes_with_dimensions()
|
doclist = get_doctypes_with_dimensions()
|
||||||
@@ -79,9 +72,9 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
|||||||
for doctype in doclist:
|
for doctype in doclist:
|
||||||
|
|
||||||
if (doc_count + 1) % 2 == 0:
|
if (doc_count + 1) % 2 == 0:
|
||||||
insert_after_field = "dimension_col_break"
|
insert_after_field = 'dimension_col_break'
|
||||||
else:
|
else:
|
||||||
insert_after_field = "accounting_dimensions_section"
|
insert_after_field = 'accounting_dimensions_section'
|
||||||
|
|
||||||
df = {
|
df = {
|
||||||
"fieldname": doc.fieldname,
|
"fieldname": doc.fieldname,
|
||||||
@@ -89,13 +82,13 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": doc.document_type,
|
"options": doc.document_type,
|
||||||
"insert_after": insert_after_field,
|
"insert_after": insert_after_field,
|
||||||
"owner": "Administrator",
|
"owner": "Administrator"
|
||||||
}
|
}
|
||||||
|
|
||||||
meta = frappe.get_meta(doctype, cached=False)
|
meta = frappe.get_meta(doctype, cached=False)
|
||||||
fieldnames = [d.fieldname for d in meta.get("fields")]
|
fieldnames = [d.fieldname for d in meta.get("fields")]
|
||||||
|
|
||||||
if df["fieldname"] not in fieldnames:
|
if df['fieldname'] not in fieldnames:
|
||||||
if doctype == "Budget":
|
if doctype == "Budget":
|
||||||
add_dimension_to_budget_doctype(df.copy(), doc)
|
add_dimension_to_budget_doctype(df.copy(), doc)
|
||||||
else:
|
else:
|
||||||
@@ -103,17 +96,14 @@ def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
|||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
frappe.publish_progress(count * 100 / len(doclist), title=_("Creating Dimensions..."))
|
frappe.publish_progress(count*100/len(doclist), title = _("Creating Dimensions..."))
|
||||||
frappe.clear_cache(doctype=doctype)
|
frappe.clear_cache(doctype=doctype)
|
||||||
|
|
||||||
|
|
||||||
def add_dimension_to_budget_doctype(df, doc):
|
def add_dimension_to_budget_doctype(df, doc):
|
||||||
df.update(
|
df.update({
|
||||||
{
|
|
||||||
"insert_after": "cost_center",
|
"insert_after": "cost_center",
|
||||||
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type),
|
"depends_on": "eval:doc.budget_against == '{0}'".format(doc.document_type)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
create_custom_field("Budget", df)
|
create_custom_field("Budget", df)
|
||||||
|
|
||||||
@@ -124,44 +114,36 @@ def add_dimension_to_budget_doctype(df, doc):
|
|||||||
property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type
|
property_setter_doc.value = property_setter_doc.value + "\n" + doc.document_type
|
||||||
property_setter_doc.save()
|
property_setter_doc.save()
|
||||||
|
|
||||||
frappe.clear_cache(doctype="Budget")
|
frappe.clear_cache(doctype='Budget')
|
||||||
else:
|
else:
|
||||||
frappe.get_doc(
|
frappe.get_doc({
|
||||||
{
|
|
||||||
"doctype": "Property Setter",
|
"doctype": "Property Setter",
|
||||||
"doctype_or_field": "DocField",
|
"doctype_or_field": "DocField",
|
||||||
"doc_type": "Budget",
|
"doc_type": "Budget",
|
||||||
"field_name": "budget_against",
|
"field_name": "budget_against",
|
||||||
"property": "options",
|
"property": "options",
|
||||||
"property_type": "Text",
|
"property_type": "Text",
|
||||||
"value": "\nCost Center\nProject\n" + doc.document_type,
|
"value": "\nCost Center\nProject\n" + doc.document_type
|
||||||
}
|
}).insert(ignore_permissions=True)
|
||||||
).insert(ignore_permissions=True)
|
|
||||||
|
|
||||||
|
|
||||||
def delete_accounting_dimension(doc):
|
def delete_accounting_dimension(doc):
|
||||||
doclist = get_doctypes_with_dimensions()
|
doclist = get_doctypes_with_dimensions()
|
||||||
|
|
||||||
frappe.db.sql(
|
frappe.db.sql("""
|
||||||
"""
|
|
||||||
DELETE FROM `tabCustom Field`
|
DELETE FROM `tabCustom Field`
|
||||||
WHERE fieldname = %s
|
WHERE fieldname = %s
|
||||||
AND dt IN (%s)"""
|
AND dt IN (%s)""" % #nosec
|
||||||
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
|
('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
|
||||||
tuple([doc.fieldname] + doclist),
|
|
||||||
)
|
|
||||||
|
|
||||||
frappe.db.sql(
|
frappe.db.sql("""
|
||||||
"""
|
|
||||||
DELETE FROM `tabProperty Setter`
|
DELETE FROM `tabProperty Setter`
|
||||||
WHERE field_name = %s
|
WHERE field_name = %s
|
||||||
AND doc_type IN (%s)"""
|
AND doc_type IN (%s)""" % #nosec
|
||||||
% ("%s", ", ".join(["%s"] * len(doclist))), # nosec
|
('%s', ', '.join(['%s']* len(doclist))), tuple([doc.fieldname] + doclist))
|
||||||
tuple([doc.fieldname] + doclist),
|
|
||||||
)
|
|
||||||
|
|
||||||
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
|
budget_against_property = frappe.get_doc("Property Setter", "Budget-budget_against-options")
|
||||||
value_list = budget_against_property.value.split("\n")[3:]
|
value_list = budget_against_property.value.split('\n')[3:]
|
||||||
|
|
||||||
if doc.document_type in value_list:
|
if doc.document_type in value_list:
|
||||||
value_list.remove(doc.document_type)
|
value_list.remove(doc.document_type)
|
||||||
@@ -172,7 +154,6 @@ def delete_accounting_dimension(doc):
|
|||||||
for doctype in doclist:
|
for doctype in doclist:
|
||||||
frappe.clear_cache(doctype=doctype)
|
frappe.clear_cache(doctype=doctype)
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def disable_dimension(doc):
|
def disable_dimension(doc):
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
@@ -180,11 +161,10 @@ def disable_dimension(doc):
|
|||||||
else:
|
else:
|
||||||
frappe.enqueue(toggle_disabling, doc=doc)
|
frappe.enqueue(toggle_disabling, doc=doc)
|
||||||
|
|
||||||
|
|
||||||
def toggle_disabling(doc):
|
def toggle_disabling(doc):
|
||||||
doc = json.loads(doc)
|
doc = json.loads(doc)
|
||||||
|
|
||||||
if doc.get("disabled"):
|
if doc.get('disabled'):
|
||||||
df = {"read_only": 1}
|
df = {"read_only": 1}
|
||||||
else:
|
else:
|
||||||
df = {"read_only": 0}
|
df = {"read_only": 0}
|
||||||
@@ -192,7 +172,7 @@ def toggle_disabling(doc):
|
|||||||
doclist = get_doctypes_with_dimensions()
|
doclist = get_doctypes_with_dimensions()
|
||||||
|
|
||||||
for doctype in doclist:
|
for doctype in doclist:
|
||||||
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get("fieldname")})
|
field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": doc.get('fieldname')})
|
||||||
if field:
|
if field:
|
||||||
custom_field = frappe.get_doc("Custom Field", field)
|
custom_field = frappe.get_doc("Custom Field", field)
|
||||||
custom_field.update(df)
|
custom_field.update(df)
|
||||||
@@ -200,34 +180,26 @@ def toggle_disabling(doc):
|
|||||||
|
|
||||||
frappe.clear_cache(doctype=doctype)
|
frappe.clear_cache(doctype=doctype)
|
||||||
|
|
||||||
|
|
||||||
def get_doctypes_with_dimensions():
|
def get_doctypes_with_dimensions():
|
||||||
return frappe.get_hooks("accounting_dimension_doctypes")
|
return frappe.get_hooks("accounting_dimension_doctypes")
|
||||||
|
|
||||||
|
|
||||||
def get_accounting_dimensions(as_list=True):
|
def get_accounting_dimensions(as_list=True):
|
||||||
if frappe.flags.accounting_dimensions is None:
|
if frappe.flags.accounting_dimensions is None:
|
||||||
frappe.flags.accounting_dimensions = frappe.get_all(
|
frappe.flags.accounting_dimensions = frappe.get_all("Accounting Dimension",
|
||||||
"Accounting Dimension", fields=["label", "fieldname", "disabled", "document_type"]
|
fields=["label", "fieldname", "disabled", "document_type"])
|
||||||
)
|
|
||||||
|
|
||||||
if as_list:
|
if as_list:
|
||||||
return [d.fieldname for d in frappe.flags.accounting_dimensions]
|
return [d.fieldname for d in frappe.flags.accounting_dimensions]
|
||||||
else:
|
else:
|
||||||
return frappe.flags.accounting_dimensions
|
return frappe.flags.accounting_dimensions
|
||||||
|
|
||||||
|
|
||||||
def get_checks_for_pl_and_bs_accounts():
|
def get_checks_for_pl_and_bs_accounts():
|
||||||
dimensions = frappe.db.sql(
|
dimensions = frappe.db.sql("""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
||||||
"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
|
|
||||||
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
|
FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
|
||||||
WHERE p.name = c.parent""",
|
WHERE p.name = c.parent""", as_dict=1)
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
return dimensions
|
return dimensions
|
||||||
|
|
||||||
|
|
||||||
def get_dimension_with_children(doctype, dimension):
|
def get_dimension_with_children(doctype, dimension):
|
||||||
|
|
||||||
if isinstance(dimension, list):
|
if isinstance(dimension, list):
|
||||||
@@ -235,39 +207,34 @@ def get_dimension_with_children(doctype, dimension):
|
|||||||
|
|
||||||
all_dimensions = []
|
all_dimensions = []
|
||||||
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
|
lft, rgt = frappe.db.get_value(doctype, dimension, ["lft", "rgt"])
|
||||||
children = frappe.get_all(
|
children = frappe.get_all(doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft")
|
||||||
doctype, filters={"lft": [">=", lft], "rgt": ["<=", rgt]}, order_by="lft"
|
|
||||||
)
|
|
||||||
all_dimensions += [c.name for c in children]
|
all_dimensions += [c.name for c in children]
|
||||||
|
|
||||||
return all_dimensions
|
return all_dimensions
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_dimensions(with_cost_center_and_project=False):
|
def get_dimensions(with_cost_center_and_project=False):
|
||||||
dimension_filters = frappe.db.sql(
|
dimension_filters = frappe.db.sql("""
|
||||||
"""
|
|
||||||
SELECT label, fieldname, document_type
|
SELECT label, fieldname, document_type
|
||||||
FROM `tabAccounting Dimension`
|
FROM `tabAccounting Dimension`
|
||||||
WHERE disabled = 0
|
WHERE disabled = 0
|
||||||
""",
|
""", as_dict=1)
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
default_dimensions = frappe.db.sql(
|
default_dimensions = frappe.db.sql("""SELECT p.fieldname, c.company, c.default_dimension
|
||||||
"""SELECT p.fieldname, c.company, c.default_dimension
|
|
||||||
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
|
||||||
WHERE c.parent = p.name""",
|
WHERE c.parent = p.name""", as_dict=1)
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
if with_cost_center_and_project:
|
if with_cost_center_and_project:
|
||||||
dimension_filters.extend(
|
dimension_filters.extend([
|
||||||
[
|
{
|
||||||
{"fieldname": "cost_center", "document_type": "Cost Center"},
|
'fieldname': 'cost_center',
|
||||||
{"fieldname": "project", "document_type": "Project"},
|
'document_type': 'Cost Center'
|
||||||
]
|
},
|
||||||
)
|
{
|
||||||
|
'fieldname': 'project',
|
||||||
|
'document_type': 'Project'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
default_dimensions_map = {}
|
default_dimensions_map = {}
|
||||||
for dimension in default_dimensions:
|
for dimension in default_dimensions:
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@@ -8,8 +10,7 @@ import frappe
|
|||||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
|
||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
|
|
||||||
test_dependencies = ["Cost Center", "Location", "Warehouse", "Department"]
|
test_dependencies = ['Cost Center', 'Location', 'Warehouse', 'Department']
|
||||||
|
|
||||||
|
|
||||||
class TestAccountingDimension(unittest.TestCase):
|
class TestAccountingDimension(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -19,9 +20,7 @@ class TestAccountingDimension(unittest.TestCase):
|
|||||||
si = create_sales_invoice(do_not_save=1)
|
si = create_sales_invoice(do_not_save=1)
|
||||||
|
|
||||||
si.location = "Block 1"
|
si.location = "Block 1"
|
||||||
si.append(
|
si.append("items", {
|
||||||
"items",
|
|
||||||
{
|
|
||||||
"item_code": "_Test Item",
|
"item_code": "_Test Item",
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
"qty": 1,
|
"qty": 1,
|
||||||
@@ -30,16 +29,15 @@ class TestAccountingDimension(unittest.TestCase):
|
|||||||
"expense_account": "Cost of Goods Sold - _TC",
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
"department": "_Test Department - _TC",
|
"department": "_Test Department - _TC",
|
||||||
"location": "Block 1",
|
"location": "Block 1"
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"})
|
gle = frappe.get_doc("GL Entry", {"voucher_no": si.name, "account": "Sales - _TC"})
|
||||||
|
|
||||||
self.assertEqual(gle.get("department"), "_Test Department - _TC")
|
self.assertEqual(gle.get('department'), "_Test Department - _TC")
|
||||||
|
|
||||||
def test_dimension_against_journal_entry(self):
|
def test_dimension_against_journal_entry(self):
|
||||||
je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False)
|
je = make_journal_entry("Sales - _TC", "Sales Expenses - _TC", 500, save=False)
|
||||||
@@ -54,14 +52,12 @@ class TestAccountingDimension(unittest.TestCase):
|
|||||||
|
|
||||||
gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"})
|
gle = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales - _TC"})
|
||||||
gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"})
|
gle1 = frappe.get_doc("GL Entry", {"voucher_no": je.name, "account": "Sales Expenses - _TC"})
|
||||||
self.assertEqual(gle.get("department"), "_Test Department - _TC")
|
self.assertEqual(gle.get('department'), "_Test Department - _TC")
|
||||||
self.assertEqual(gle1.get("department"), "_Test Department - _TC")
|
self.assertEqual(gle1.get('department'), "_Test Department - _TC")
|
||||||
|
|
||||||
def test_mandatory(self):
|
def test_mandatory(self):
|
||||||
si = create_sales_invoice(do_not_save=1)
|
si = create_sales_invoice(do_not_save=1)
|
||||||
si.append(
|
si.append("items", {
|
||||||
"items",
|
|
||||||
{
|
|
||||||
"item_code": "_Test Item",
|
"item_code": "_Test Item",
|
||||||
"warehouse": "_Test Warehouse - _TC",
|
"warehouse": "_Test Warehouse - _TC",
|
||||||
"qty": 1,
|
"qty": 1,
|
||||||
@@ -69,9 +65,8 @@ class TestAccountingDimension(unittest.TestCase):
|
|||||||
"income_account": "Sales - _TC",
|
"income_account": "Sales - _TC",
|
||||||
"expense_account": "Cost of Goods Sold - _TC",
|
"expense_account": "Cost of Goods Sold - _TC",
|
||||||
"cost_center": "_Test Cost Center - _TC",
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
"location": "",
|
"location": ""
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
si.save()
|
si.save()
|
||||||
self.assertRaises(frappe.ValidationError, si.submit)
|
self.assertRaises(frappe.ValidationError, si.submit)
|
||||||
@@ -79,39 +74,31 @@ class TestAccountingDimension(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
disable_dimension()
|
disable_dimension()
|
||||||
|
|
||||||
|
|
||||||
def create_dimension():
|
def create_dimension():
|
||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
|
|
||||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
|
if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
|
||||||
frappe.get_doc(
|
frappe.get_doc({
|
||||||
{
|
|
||||||
"doctype": "Accounting Dimension",
|
"doctype": "Accounting Dimension",
|
||||||
"document_type": "Department",
|
"document_type": "Department",
|
||||||
}
|
}).insert()
|
||||||
).insert()
|
|
||||||
else:
|
else:
|
||||||
dimension = frappe.get_doc("Accounting Dimension", "Department")
|
dimension = frappe.get_doc("Accounting Dimension", "Department")
|
||||||
dimension.disabled = 0
|
dimension.disabled = 0
|
||||||
dimension.save()
|
dimension.save()
|
||||||
|
|
||||||
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
|
if not frappe.db.exists("Accounting Dimension", {"document_type": "Location"}):
|
||||||
dimension1 = frappe.get_doc(
|
dimension1 = frappe.get_doc({
|
||||||
{
|
|
||||||
"doctype": "Accounting Dimension",
|
"doctype": "Accounting Dimension",
|
||||||
"document_type": "Location",
|
"document_type": "Location",
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
dimension1.append(
|
dimension1.append("dimension_defaults", {
|
||||||
"dimension_defaults",
|
|
||||||
{
|
|
||||||
"company": "_Test Company",
|
"company": "_Test Company",
|
||||||
"reference_document": "Location",
|
"reference_document": "Location",
|
||||||
"default_dimension": "Block 1",
|
"default_dimension": "Block 1",
|
||||||
"mandatory_for_bs": 1,
|
"mandatory_for_bs": 1
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
dimension1.insert()
|
dimension1.insert()
|
||||||
dimension1.save()
|
dimension1.save()
|
||||||
@@ -120,7 +107,6 @@ def create_dimension():
|
|||||||
dimension1.disabled = 0
|
dimension1.disabled = 0
|
||||||
dimension1.save()
|
dimension1.save()
|
||||||
|
|
||||||
|
|
||||||
def disable_dimension():
|
def disable_dimension():
|
||||||
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
dimension1 = frappe.get_doc("Accounting Dimension", "Department")
|
||||||
dimension1.disabled = 1
|
dimension1.disabled = 1
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ frappe.ui.form.on('Accounting Dimension Filter', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let help_content =
|
let help_content =
|
||||||
`<table class="table table-bordered" style="background-color: var(--scrollbar-track-color);">
|
`<table class="table table-bordered" style="background-color: #f9f9f9;">
|
||||||
<tr><td>
|
<tr><td>
|
||||||
<p>
|
<p>
|
||||||
<i class="fa fa-hand-right"></i>
|
<i class="fa fa-hand-right"></i>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright, (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright, (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, scrub
|
from frappe import _, scrub
|
||||||
@@ -19,27 +21,17 @@ class AccountingDimensionFilter(Document):
|
|||||||
WHERE d.name = a.parent
|
WHERE d.name = a.parent
|
||||||
and d.name != %s
|
and d.name != %s
|
||||||
and d.accounting_dimension = %s
|
and d.accounting_dimension = %s
|
||||||
""",
|
""", (self.name, self.accounting_dimension), as_dict=1)
|
||||||
(self.name, self.accounting_dimension),
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
account_list = [d.account for d in accounts]
|
account_list = [d.account for d in accounts]
|
||||||
|
|
||||||
for account in self.get("accounts"):
|
for account in self.get('accounts'):
|
||||||
if account.applicable_on_account in account_list:
|
if account.applicable_on_account in account_list:
|
||||||
frappe.throw(
|
frappe.throw(_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
|
||||||
_("Row {0}: {1} account already applied for Accounting Dimension {2}").format(
|
account.idx, frappe.bold(account.applicable_on_account), frappe.bold(self.accounting_dimension)))
|
||||||
account.idx,
|
|
||||||
frappe.bold(account.applicable_on_account),
|
|
||||||
frappe.bold(self.accounting_dimension),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_dimension_filter_map():
|
def get_dimension_filter_map():
|
||||||
filters = frappe.db.sql(
|
filters = frappe.db.sql("""
|
||||||
"""
|
|
||||||
SELECT
|
SELECT
|
||||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||||
p.allow_or_restrict, a.is_mandatory
|
p.allow_or_restrict, a.is_mandatory
|
||||||
@@ -50,30 +42,22 @@ def get_dimension_filter_map():
|
|||||||
p.name = a.parent
|
p.name = a.parent
|
||||||
AND p.disabled = 0
|
AND p.disabled = 0
|
||||||
AND p.name = d.parent
|
AND p.name = d.parent
|
||||||
""",
|
""", as_dict=1)
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
dimension_filter_map = {}
|
dimension_filter_map = {}
|
||||||
|
|
||||||
for f in filters:
|
for f in filters:
|
||||||
f.fieldname = scrub(f.accounting_dimension)
|
f.fieldname = scrub(f.accounting_dimension)
|
||||||
|
|
||||||
build_map(
|
build_map(dimension_filter_map, f.fieldname, f.applicable_on_account, f.dimension_value,
|
||||||
dimension_filter_map,
|
f.allow_or_restrict, f.is_mandatory)
|
||||||
f.fieldname,
|
|
||||||
f.applicable_on_account,
|
|
||||||
f.dimension_value,
|
|
||||||
f.allow_or_restrict,
|
|
||||||
f.is_mandatory,
|
|
||||||
)
|
|
||||||
|
|
||||||
return dimension_filter_map
|
return dimension_filter_map
|
||||||
|
|
||||||
|
|
||||||
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
|
||||||
map_object.setdefault(
|
map_object.setdefault((dimension, account), {
|
||||||
(dimension, account),
|
'allowed_dimensions': [],
|
||||||
{"allowed_dimensions": [], "is_mandatory": is_mandatory, "allow_or_restrict": allow_or_restrict},
|
'is_mandatory': is_mandatory,
|
||||||
)
|
'allow_or_restrict': allow_or_restrict
|
||||||
map_object[(dimension, account)]["allowed_dimensions"].append(filter_value)
|
})
|
||||||
|
map_object[(dimension, account)]['allowed_dimensions'].append(filter_value)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@@ -12,8 +14,7 @@ from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension imp
|
|||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||||
|
|
||||||
test_dependencies = ["Location", "Cost Center", "Department"]
|
test_dependencies = ['Location', 'Cost Center', 'Department']
|
||||||
|
|
||||||
|
|
||||||
class TestAccountingDimensionFilter(unittest.TestCase):
|
class TestAccountingDimensionFilter(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -23,9 +24,9 @@ class TestAccountingDimensionFilter(unittest.TestCase):
|
|||||||
|
|
||||||
def test_allowed_dimension_validation(self):
|
def test_allowed_dimension_validation(self):
|
||||||
si = create_sales_invoice(do_not_save=1)
|
si = create_sales_invoice(do_not_save=1)
|
||||||
si.items[0].cost_center = "Main - _TC"
|
si.items[0].cost_center = 'Main - _TC'
|
||||||
si.department = "Accounts - _TC"
|
si.department = 'Accounts - _TC'
|
||||||
si.location = "Block 1"
|
si.location = 'Block 1'
|
||||||
si.save()
|
si.save()
|
||||||
|
|
||||||
self.assertRaises(InvalidAccountDimensionError, si.submit)
|
self.assertRaises(InvalidAccountDimensionError, si.submit)
|
||||||
@@ -33,12 +34,12 @@ class TestAccountingDimensionFilter(unittest.TestCase):
|
|||||||
|
|
||||||
def test_mandatory_dimension_validation(self):
|
def test_mandatory_dimension_validation(self):
|
||||||
si = create_sales_invoice(do_not_save=1)
|
si = create_sales_invoice(do_not_save=1)
|
||||||
si.department = ""
|
si.department = ''
|
||||||
si.location = "Block 1"
|
si.location = 'Block 1'
|
||||||
|
|
||||||
# Test with no department for Sales Account
|
# Test with no department for Sales Account
|
||||||
si.items[0].department = ""
|
si.items[0].department = ''
|
||||||
si.items[0].cost_center = "_Test Cost Center 2 - _TC"
|
si.items[0].cost_center = '_Test Cost Center 2 - _TC'
|
||||||
si.save()
|
si.save()
|
||||||
|
|
||||||
self.assertRaises(MandatoryAccountDimensionError, si.submit)
|
self.assertRaises(MandatoryAccountDimensionError, si.submit)
|
||||||
@@ -53,54 +54,53 @@ class TestAccountingDimensionFilter(unittest.TestCase):
|
|||||||
if si.docstatus == 1:
|
if si.docstatus == 1:
|
||||||
si.cancel()
|
si.cancel()
|
||||||
|
|
||||||
|
|
||||||
def create_accounting_dimension_filter():
|
def create_accounting_dimension_filter():
|
||||||
if not frappe.db.get_value(
|
if not frappe.db.get_value('Accounting Dimension Filter',
|
||||||
"Accounting Dimension Filter", {"accounting_dimension": "Cost Center"}
|
{'accounting_dimension': 'Cost Center'}):
|
||||||
):
|
frappe.get_doc({
|
||||||
frappe.get_doc(
|
'doctype': 'Accounting Dimension Filter',
|
||||||
{
|
'accounting_dimension': 'Cost Center',
|
||||||
"doctype": "Accounting Dimension Filter",
|
'allow_or_restrict': 'Allow',
|
||||||
"accounting_dimension": "Cost Center",
|
'company': '_Test Company',
|
||||||
"allow_or_restrict": "Allow",
|
'accounts': [{
|
||||||
"company": "_Test Company",
|
'applicable_on_account': 'Sales - _TC',
|
||||||
"accounts": [
|
}],
|
||||||
{
|
'dimensions': [{
|
||||||
"applicable_on_account": "Sales - _TC",
|
'accounting_dimension': 'Cost Center',
|
||||||
}
|
'dimension_value': '_Test Cost Center 2 - _TC'
|
||||||
],
|
}]
|
||||||
"dimensions": [
|
}).insert()
|
||||||
{"accounting_dimension": "Cost Center", "dimension_value": "_Test Cost Center 2 - _TC"}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
).insert()
|
|
||||||
else:
|
else:
|
||||||
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
|
||||||
doc.disabled = 0
|
doc.disabled = 0
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
if not frappe.db.get_value("Accounting Dimension Filter", {"accounting_dimension": "Department"}):
|
if not frappe.db.get_value('Accounting Dimension Filter',
|
||||||
frappe.get_doc(
|
{'accounting_dimension': 'Department'}):
|
||||||
{
|
frappe.get_doc({
|
||||||
"doctype": "Accounting Dimension Filter",
|
'doctype': 'Accounting Dimension Filter',
|
||||||
"accounting_dimension": "Department",
|
'accounting_dimension': 'Department',
|
||||||
"allow_or_restrict": "Allow",
|
'allow_or_restrict': 'Allow',
|
||||||
"company": "_Test Company",
|
'company': '_Test Company',
|
||||||
"accounts": [{"applicable_on_account": "Sales - _TC", "is_mandatory": 1}],
|
'accounts': [{
|
||||||
"dimensions": [{"accounting_dimension": "Department", "dimension_value": "Accounts - _TC"}],
|
'applicable_on_account': 'Sales - _TC',
|
||||||
}
|
'is_mandatory': 1
|
||||||
).insert()
|
}],
|
||||||
|
'dimensions': [{
|
||||||
|
'accounting_dimension': 'Department',
|
||||||
|
'dimension_value': 'Accounts - _TC'
|
||||||
|
}]
|
||||||
|
}).insert()
|
||||||
else:
|
else:
|
||||||
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
|
||||||
doc.disabled = 0
|
doc.disabled = 0
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
|
|
||||||
def disable_dimension_filter():
|
def disable_dimension_filter():
|
||||||
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Cost Center"})
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Cost Center'})
|
||||||
doc.disabled = 1
|
doc.disabled = 1
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
doc = frappe.get_doc("Accounting Dimension Filter", {"accounting_dimension": "Department"})
|
doc = frappe.get_doc('Accounting Dimension Filter', {'accounting_dimension': 'Department'})
|
||||||
doc.disabled = 1
|
doc.disabled = 1
|
||||||
doc.save()
|
doc.save()
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|
||||||
class OverlapError(frappe.ValidationError):
|
class OverlapError(frappe.ValidationError): pass
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class AccountingPeriod(Document):
|
class AccountingPeriod(Document):
|
||||||
def validate(self):
|
def validate(self):
|
||||||
@@ -19,12 +19,11 @@ class AccountingPeriod(Document):
|
|||||||
self.bootstrap_doctypes_for_closing()
|
self.bootstrap_doctypes_for_closing()
|
||||||
|
|
||||||
def autoname(self):
|
def autoname(self):
|
||||||
company_abbr = frappe.get_cached_value("Company", self.company, "abbr")
|
company_abbr = frappe.get_cached_value('Company', self.company, "abbr")
|
||||||
self.name = " - ".join([self.period_name, company_abbr])
|
self.name = " - ".join([self.period_name, company_abbr])
|
||||||
|
|
||||||
def validate_overlap(self):
|
def validate_overlap(self):
|
||||||
existing_accounting_period = frappe.db.sql(
|
existing_accounting_period = frappe.db.sql("""select name from `tabAccounting Period`
|
||||||
"""select name from `tabAccounting Period`
|
|
||||||
where (
|
where (
|
||||||
(%(start_date)s between start_date and end_date)
|
(%(start_date)s between start_date and end_date)
|
||||||
or (%(end_date)s between start_date and end_date)
|
or (%(end_date)s between start_date and end_date)
|
||||||
@@ -35,29 +34,18 @@ class AccountingPeriod(Document):
|
|||||||
"start_date": self.start_date,
|
"start_date": self.start_date,
|
||||||
"end_date": self.end_date,
|
"end_date": self.end_date,
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"company": self.company,
|
"company": self.company
|
||||||
},
|
}, as_dict=True)
|
||||||
as_dict=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(existing_accounting_period) > 0:
|
if len(existing_accounting_period) > 0:
|
||||||
frappe.throw(
|
frappe.throw(_("Accounting Period overlaps with {0}")
|
||||||
_("Accounting Period overlaps with {0}").format(existing_accounting_period[0].get("name")),
|
.format(existing_accounting_period[0].get("name")), OverlapError)
|
||||||
OverlapError,
|
|
||||||
)
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_doctypes_for_closing(self):
|
def get_doctypes_for_closing(self):
|
||||||
docs_for_closing = []
|
docs_for_closing = []
|
||||||
doctypes = [
|
doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
|
||||||
"Sales Invoice",
|
"Bank Clearance", "Asset", "Stock Entry"]
|
||||||
"Purchase Invoice",
|
|
||||||
"Journal Entry",
|
|
||||||
"Payroll Entry",
|
|
||||||
"Bank Clearance",
|
|
||||||
"Asset",
|
|
||||||
"Stock Entry",
|
|
||||||
]
|
|
||||||
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
|
closed_doctypes = [{"document_type": doctype, "closed": 1} for doctype in doctypes]
|
||||||
for closed_doctype in closed_doctypes:
|
for closed_doctype in closed_doctypes:
|
||||||
docs_for_closing.append(closed_doctype)
|
docs_for_closing.append(closed_doctype)
|
||||||
@@ -67,7 +55,7 @@ class AccountingPeriod(Document):
|
|||||||
def bootstrap_doctypes_for_closing(self):
|
def bootstrap_doctypes_for_closing(self):
|
||||||
if len(self.closed_documents) == 0:
|
if len(self.closed_documents) == 0:
|
||||||
for doctype_for_closing in self.get_doctypes_for_closing():
|
for doctype_for_closing in self.get_doctypes_for_closing():
|
||||||
self.append(
|
self.append('closed_documents', {
|
||||||
"closed_documents",
|
"document_type": doctype_for_closing.document_type,
|
||||||
{"document_type": doctype_for_closing.document_type, "closed": doctype_for_closing.closed},
|
"closed": doctype_for_closing.closed
|
||||||
)
|
})
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@@ -10,38 +12,29 @@ from erpnext.accounts.doctype.accounting_period.accounting_period import Overlap
|
|||||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||||
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
|
from erpnext.accounts.general_ledger import ClosedAccountingPeriod
|
||||||
|
|
||||||
test_dependencies = ["Item"]
|
test_dependencies = ['Item']
|
||||||
|
|
||||||
|
|
||||||
class TestAccountingPeriod(unittest.TestCase):
|
class TestAccountingPeriod(unittest.TestCase):
|
||||||
def test_overlap(self):
|
def test_overlap(self):
|
||||||
ap1 = create_accounting_period(
|
ap1 = create_accounting_period(start_date = "2018-04-01",
|
||||||
start_date="2018-04-01", end_date="2018-06-30", company="Wind Power LLC"
|
end_date = "2018-06-30", company = "Wind Power LLC")
|
||||||
)
|
|
||||||
ap1.save()
|
ap1.save()
|
||||||
|
|
||||||
ap2 = create_accounting_period(
|
ap2 = create_accounting_period(start_date = "2018-06-30",
|
||||||
start_date="2018-06-30",
|
end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
|
||||||
end_date="2018-07-10",
|
|
||||||
company="Wind Power LLC",
|
|
||||||
period_name="Test Accounting Period 1",
|
|
||||||
)
|
|
||||||
self.assertRaises(OverlapError, ap2.save)
|
self.assertRaises(OverlapError, ap2.save)
|
||||||
|
|
||||||
def test_accounting_period(self):
|
def test_accounting_period(self):
|
||||||
ap1 = create_accounting_period(period_name="Test Accounting Period 2")
|
ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
|
||||||
ap1.save()
|
ap1.save()
|
||||||
|
|
||||||
doc = create_sales_invoice(
|
doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
|
||||||
do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC"
|
|
||||||
)
|
|
||||||
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
self.assertRaises(ClosedAccountingPeriod, doc.submit)
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for d in frappe.get_all("Accounting Period"):
|
for d in frappe.get_all("Accounting Period"):
|
||||||
frappe.delete_doc("Accounting Period", d.name)
|
frappe.delete_doc("Accounting Period", d.name)
|
||||||
|
|
||||||
|
|
||||||
def create_accounting_period(**args):
|
def create_accounting_period(**args):
|
||||||
args = frappe._dict(args)
|
args = frappe._dict(args)
|
||||||
|
|
||||||
@@ -50,6 +43,8 @@ def create_accounting_period(**args):
|
|||||||
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
|
||||||
accounting_period.company = args.company or "_Test Company"
|
accounting_period.company = args.company or "_Test Company"
|
||||||
accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
|
accounting_period.period_name = args.period_name or "_Test_Period_Name_1"
|
||||||
accounting_period.append("closed_documents", {"document_type": "Sales Invoice", "closed": 1})
|
accounting_period.append("closed_documents", {
|
||||||
|
"document_type": 'Sales Invoice', "closed": 1
|
||||||
|
})
|
||||||
|
|
||||||
return accounting_period
|
return accounting_period
|
||||||
|
|||||||
@@ -6,3 +6,46 @@ frappe.ui.form.on('Accounts Settings', {
|
|||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
frappe.tour['Accounts Settings'] = [
|
||||||
|
{
|
||||||
|
fieldname: "acc_frozen_upto",
|
||||||
|
title: "Accounts Frozen Upto",
|
||||||
|
description: __("Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role."),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "frozen_accounts_modifier",
|
||||||
|
title: "Role Allowed to Set Frozen Accounts & Edit Frozen Entries",
|
||||||
|
description: __("Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "determine_address_tax_category_from",
|
||||||
|
title: "Determine Address Tax Category From",
|
||||||
|
description: __("Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "over_billing_allowance",
|
||||||
|
title: "Over Billing Allowance Percentage",
|
||||||
|
description: __("The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "credit_controller",
|
||||||
|
title: "Credit Controller",
|
||||||
|
description: __("Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "make_payment_via_journal_entry",
|
||||||
|
title: "Make Payment via Journal Entry",
|
||||||
|
description: __("When checked, if user proceeds to make payment from an invoice, the system will open a Journal Entry instead of a Payment Entry.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "unlink_payment_on_cancellation_of_invoice",
|
||||||
|
title: "Unlink Payment on Cancellation of Invoice",
|
||||||
|
description: __("If checked, system will unlink the payment against the respective invoice.")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fieldname: "unlink_advance_payment_on_cancelation_of_order",
|
||||||
|
title: "Unlink Advance Payment on Cancellation of Order",
|
||||||
|
description: __("Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|||||||
@@ -7,30 +7,35 @@
|
|||||||
"editable_grid": 1,
|
"editable_grid": 1,
|
||||||
"engine": "InnoDB",
|
"engine": "InnoDB",
|
||||||
"field_order": [
|
"field_order": [
|
||||||
"invoice_and_billing_tab",
|
"accounts_transactions_settings_section",
|
||||||
"enable_features_section",
|
"over_billing_allowance",
|
||||||
"unlink_payment_on_cancellation_of_invoice",
|
"role_allowed_to_over_bill",
|
||||||
"unlink_advance_payment_on_cancelation_of_order",
|
"make_payment_via_journal_entry",
|
||||||
"column_break_13",
|
"column_break_11",
|
||||||
"delete_linked_ledger_entries",
|
|
||||||
"invoicing_features_section",
|
|
||||||
"check_supplier_invoice_uniqueness",
|
"check_supplier_invoice_uniqueness",
|
||||||
|
"unlink_payment_on_cancellation_of_invoice",
|
||||||
"automatically_fetch_payment_terms",
|
"automatically_fetch_payment_terms",
|
||||||
"column_break_17",
|
"delete_linked_ledger_entries",
|
||||||
|
"book_asset_depreciation_entry_automatically",
|
||||||
|
"unlink_advance_payment_on_cancelation_of_order",
|
||||||
"enable_common_party_accounting",
|
"enable_common_party_accounting",
|
||||||
|
"post_change_gl_entries",
|
||||||
"enable_discount_accounting",
|
"enable_discount_accounting",
|
||||||
"report_setting_section",
|
"tax_settings_section",
|
||||||
"use_custom_cash_flow",
|
"determine_address_tax_category_from",
|
||||||
|
"column_break_19",
|
||||||
|
"add_taxes_from_item_tax_template",
|
||||||
|
"period_closing_settings_section",
|
||||||
|
"acc_frozen_upto",
|
||||||
|
"frozen_accounts_modifier",
|
||||||
|
"column_break_4",
|
||||||
|
"credit_controller",
|
||||||
"deferred_accounting_settings_section",
|
"deferred_accounting_settings_section",
|
||||||
"book_deferred_entries_based_on",
|
"book_deferred_entries_based_on",
|
||||||
"column_break_18",
|
"column_break_18",
|
||||||
"automatically_process_deferred_accounting_entry",
|
"automatically_process_deferred_accounting_entry",
|
||||||
"book_deferred_entries_via_journal_entry",
|
"book_deferred_entries_via_journal_entry",
|
||||||
"submit_journal_entries",
|
"submit_journal_entries",
|
||||||
"tax_settings_section",
|
|
||||||
"determine_address_tax_category_from",
|
|
||||||
"column_break_19",
|
|
||||||
"add_taxes_from_item_tax_template",
|
|
||||||
"print_settings",
|
"print_settings",
|
||||||
"show_inclusive_tax_in_print",
|
"show_inclusive_tax_in_print",
|
||||||
"column_break_12",
|
"column_break_12",
|
||||||
@@ -38,25 +43,8 @@
|
|||||||
"currency_exchange_section",
|
"currency_exchange_section",
|
||||||
"allow_stale",
|
"allow_stale",
|
||||||
"stale_days",
|
"stale_days",
|
||||||
"invoicing_settings_tab",
|
"report_settings_sb",
|
||||||
"accounts_transactions_settings_section",
|
"use_custom_cash_flow"
|
||||||
"over_billing_allowance",
|
|
||||||
"column_break_11",
|
|
||||||
"role_allowed_to_over_bill",
|
|
||||||
"credit_controller",
|
|
||||||
"make_payment_via_journal_entry",
|
|
||||||
"pos_tab",
|
|
||||||
"pos_setting_section",
|
|
||||||
"post_change_gl_entries",
|
|
||||||
"assets_tab",
|
|
||||||
"asset_settings_section",
|
|
||||||
"book_asset_depreciation_entry_automatically",
|
|
||||||
"closing_settings_tab",
|
|
||||||
"period_closing_settings_section",
|
|
||||||
"acc_frozen_upto",
|
|
||||||
"column_break_25",
|
|
||||||
"frozen_accounts_modifier",
|
|
||||||
"report_settings_sb"
|
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -83,15 +71,19 @@
|
|||||||
"options": "Billing Address\nShipping Address"
|
"options": "Billing Address\nShipping Address"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"fieldname": "column_break_4",
|
||||||
|
"fieldtype": "Column Break"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "This role is allowed to submit transactions that exceed credit limits",
|
||||||
"fieldname": "credit_controller",
|
"fieldname": "credit_controller",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Role allowed to bypass Credit Limit",
|
"label": "Credit Controller",
|
||||||
"options": "Role"
|
"options": "Role"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Enabling ensure each Sales Invoice has a unique value in Supplier Invoice No. field",
|
|
||||||
"fieldname": "check_supplier_invoice_uniqueness",
|
"fieldname": "check_supplier_invoice_uniqueness",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Check Supplier Invoice Number Uniqueness"
|
"label": "Check Supplier Invoice Number Uniqueness"
|
||||||
@@ -177,7 +169,7 @@
|
|||||||
"description": "Only select this if you have set up the Cash Flow Mapper documents",
|
"description": "Only select this if you have set up the Cash Flow Mapper documents",
|
||||||
"fieldname": "use_custom_cash_flow",
|
"fieldname": "use_custom_cash_flow",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Custom Cash Flow Format"
|
"label": "Use Custom Cash Flow Format"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
@@ -250,7 +242,7 @@
|
|||||||
{
|
{
|
||||||
"fieldname": "accounts_transactions_settings_section",
|
"fieldname": "accounts_transactions_settings_section",
|
||||||
"fieldtype": "Section Break",
|
"fieldtype": "Section Break",
|
||||||
"label": "Credit Limit Settings"
|
"label": "Transactions Settings"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_11",
|
"fieldname": "column_break_11",
|
||||||
@@ -281,72 +273,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"default": "0",
|
||||||
"description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
|
|
||||||
"fieldname": "enable_common_party_accounting",
|
"fieldname": "enable_common_party_accounting",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Common Party Accounting"
|
"label": "Enable Common Party Accounting"
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "enable_features_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Invoice Cancellation"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_13",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_25",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "asset_settings_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Asset Settings"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "invoicing_settings_tab",
|
|
||||||
"fieldtype": "Tab Break",
|
|
||||||
"label": "Credit Limits"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "assets_tab",
|
|
||||||
"fieldtype": "Tab Break",
|
|
||||||
"label": "Assets"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "closing_settings_tab",
|
|
||||||
"fieldtype": "Tab Break",
|
|
||||||
"label": "Accounts Closing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "pos_setting_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "POS Setting"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "invoice_and_billing_tab",
|
|
||||||
"fieldtype": "Tab Break",
|
|
||||||
"label": "Invoice and Billing"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "invoicing_features_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Invoicing Features"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "column_break_17",
|
|
||||||
"fieldtype": "Column Break"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "pos_tab",
|
|
||||||
"fieldtype": "Tab Break",
|
|
||||||
"label": "POS"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "report_setting_section",
|
|
||||||
"fieldtype": "Section Break",
|
|
||||||
"label": "Report Setting"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icon": "icon-cog",
|
"icon": "icon-cog",
|
||||||
@@ -354,7 +283,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2022-02-04 12:32:36.805652",
|
"modified": "2021-10-11 17:42:36.427699",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
@@ -381,6 +310,5 @@
|
|||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "ASC",
|
"sort_order": "ASC",
|
||||||
"states": [],
|
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@@ -10,116 +11,46 @@ from frappe.custom.doctype.property_setter.property_setter import make_property_
|
|||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import cint
|
from frappe.utils import cint
|
||||||
|
|
||||||
from erpnext.stock.utils import check_pending_reposting
|
|
||||||
|
|
||||||
|
|
||||||
class AccountsSettings(Document):
|
class AccountsSettings(Document):
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
frappe.clear_cache()
|
frappe.clear_cache()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
frappe.db.set_default(
|
frappe.db.set_default("add_taxes_from_item_tax_template",
|
||||||
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
|
self.get("add_taxes_from_item_tax_template", 0))
|
||||||
)
|
|
||||||
|
|
||||||
frappe.db.set_default(
|
|
||||||
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.validate_stale_days()
|
self.validate_stale_days()
|
||||||
self.enable_payment_schedule_in_print()
|
self.enable_payment_schedule_in_print()
|
||||||
self.toggle_discount_accounting_fields()
|
self.toggle_discount_accounting_fields()
|
||||||
self.validate_pending_reposts()
|
|
||||||
|
|
||||||
def validate_stale_days(self):
|
def validate_stale_days(self):
|
||||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||||
frappe.msgprint(
|
frappe.msgprint(
|
||||||
_("Stale Days should start from 1."), title="Error", indicator="red", raise_exception=1
|
_("Stale Days should start from 1."), title='Error', indicator='red',
|
||||||
)
|
raise_exception=1)
|
||||||
|
|
||||||
def enable_payment_schedule_in_print(self):
|
def enable_payment_schedule_in_print(self):
|
||||||
show_in_print = cint(self.show_payment_schedule_in_print)
|
show_in_print = cint(self.show_payment_schedule_in_print)
|
||||||
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
|
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
|
||||||
make_property_setter(
|
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
|
||||||
doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False
|
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
|
||||||
)
|
|
||||||
make_property_setter(
|
|
||||||
doctype,
|
|
||||||
"payment_schedule",
|
|
||||||
"print_hide",
|
|
||||||
0 if show_in_print else 1,
|
|
||||||
"Check",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def toggle_discount_accounting_fields(self):
|
def toggle_discount_accounting_fields(self):
|
||||||
enable_discount_accounting = cint(self.enable_discount_accounting)
|
enable_discount_accounting = cint(self.enable_discount_accounting)
|
||||||
|
|
||||||
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
|
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
|
||||||
make_property_setter(
|
make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
||||||
doctype,
|
|
||||||
"discount_account",
|
|
||||||
"hidden",
|
|
||||||
not (enable_discount_accounting),
|
|
||||||
"Check",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
if enable_discount_accounting:
|
if enable_discount_accounting:
|
||||||
make_property_setter(
|
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
|
||||||
doctype,
|
|
||||||
"discount_account",
|
|
||||||
"mandatory_depends_on",
|
|
||||||
"eval: doc.discount_amount",
|
|
||||||
"Code",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
make_property_setter(
|
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
|
||||||
doctype,
|
|
||||||
"discount_account",
|
|
||||||
"mandatory_depends_on",
|
|
||||||
"",
|
|
||||||
"Code",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||||
make_property_setter(
|
make_property_setter(doctype, "additional_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
||||||
doctype,
|
|
||||||
"additional_discount_account",
|
|
||||||
"hidden",
|
|
||||||
not (enable_discount_accounting),
|
|
||||||
"Check",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
if enable_discount_accounting:
|
if enable_discount_accounting:
|
||||||
make_property_setter(
|
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
|
||||||
doctype,
|
|
||||||
"additional_discount_account",
|
|
||||||
"mandatory_depends_on",
|
|
||||||
"eval: doc.discount_amount",
|
|
||||||
"Code",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
make_property_setter(
|
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
|
||||||
doctype,
|
|
||||||
"additional_discount_account",
|
|
||||||
"mandatory_depends_on",
|
|
||||||
"",
|
|
||||||
"Code",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
make_property_setter(
|
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
|
||||||
"Item",
|
|
||||||
"default_discount_account",
|
|
||||||
"hidden",
|
|
||||||
not (enable_discount_accounting),
|
|
||||||
"Check",
|
|
||||||
validate_fields_for_doctype=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def validate_pending_reposts(self):
|
|
||||||
if self.acc_frozen_upto:
|
|
||||||
check_pending_reposting(self.acc_frozen_upto)
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
QUnit.module('accounts');
|
||||||
|
|
||||||
|
QUnit.test("test: Accounts Settings doesn't allow negatives", function (assert) {
|
||||||
|
let done = assert.async();
|
||||||
|
|
||||||
|
assert.expect(2);
|
||||||
|
|
||||||
|
frappe.run_serially([
|
||||||
|
() => frappe.set_route('Form', 'Accounts Settings', 'Accounts Settings'),
|
||||||
|
() => frappe.timeout(2),
|
||||||
|
() => unchecked_if_checked(cur_frm, 'Allow Stale Exchange Rates', frappe.click_check),
|
||||||
|
() => cur_frm.set_value('stale_days', 0),
|
||||||
|
() => frappe.click_button('Save'),
|
||||||
|
() => frappe.timeout(2),
|
||||||
|
() => {
|
||||||
|
assert.ok(cur_dialog);
|
||||||
|
},
|
||||||
|
() => frappe.click_button('Close'),
|
||||||
|
() => cur_frm.set_value('stale_days', -1),
|
||||||
|
() => frappe.click_button('Save'),
|
||||||
|
() => frappe.timeout(2),
|
||||||
|
() => {
|
||||||
|
assert.ok(cur_dialog);
|
||||||
|
},
|
||||||
|
() => frappe.click_button('Close'),
|
||||||
|
() => done()
|
||||||
|
]);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
const unchecked_if_checked = function(frm, field_name, fn){
|
||||||
|
if (frm.doc.allow_stale) {
|
||||||
|
return fn(field_name);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
@@ -7,12 +9,12 @@ class TestAccountsSettings(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
# Just in case `save` method succeeds, we need to take things back to default so that other tests
|
# Just in case `save` method succeeds, we need to take things back to default so that other tests
|
||||||
# don't break
|
# don't break
|
||||||
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
|
||||||
cur_settings.allow_stale = 1
|
cur_settings.allow_stale = 1
|
||||||
cur_settings.save()
|
cur_settings.save()
|
||||||
|
|
||||||
def test_stale_days(self):
|
def test_stale_days(self):
|
||||||
cur_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
cur_settings = frappe.get_doc('Accounts Settings', 'Accounts Settings')
|
||||||
cur_settings.allow_stale = 0
|
cur_settings.allow_stale = 0
|
||||||
cur_settings.stale_days = 0
|
cur_settings.stale_days = 0
|
||||||
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
{
|
|
||||||
"actions": [],
|
|
||||||
"allow_rename": 1,
|
|
||||||
"creation": "2021-11-25 10:24:39.836195",
|
|
||||||
"doctype": "DocType",
|
|
||||||
"engine": "InnoDB",
|
|
||||||
"field_order": [
|
|
||||||
"reference_type",
|
|
||||||
"reference_name",
|
|
||||||
"reference_detail",
|
|
||||||
"account_head",
|
|
||||||
"allocated_amount"
|
|
||||||
],
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"fieldname": "reference_type",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Reference Type",
|
|
||||||
"options": "DocType"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "reference_name",
|
|
||||||
"fieldtype": "Dynamic Link",
|
|
||||||
"label": "Reference Name",
|
|
||||||
"options": "reference_type"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "reference_detail",
|
|
||||||
"fieldtype": "Data",
|
|
||||||
"label": "Reference Detail"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "account_head",
|
|
||||||
"fieldtype": "Link",
|
|
||||||
"label": "Account Head",
|
|
||||||
"options": "Account"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"fieldname": "allocated_amount",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"label": "Allocated Amount",
|
|
||||||
"options": "party_account_currency"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"index_web_pages_for_search": 1,
|
|
||||||
"istable": 1,
|
|
||||||
"links": [],
|
|
||||||
"modified": "2021-11-25 10:27:51.712286",
|
|
||||||
"modified_by": "Administrator",
|
|
||||||
"module": "Accounts",
|
|
||||||
"name": "Advance Tax",
|
|
||||||
"owner": "Administrator",
|
|
||||||
"permissions": [],
|
|
||||||
"sort_field": "modified",
|
|
||||||
"sort_order": "DESC"
|
|
||||||
}
|
|
||||||
@@ -25,7 +25,8 @@
|
|||||||
"allocated_amount",
|
"allocated_amount",
|
||||||
"column_break_13",
|
"column_break_13",
|
||||||
"base_tax_amount",
|
"base_tax_amount",
|
||||||
"base_total"
|
"base_total",
|
||||||
|
"base_allocated_amount"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -167,6 +168,12 @@
|
|||||||
"label": "Allocated Amount",
|
"label": "Allocated Amount",
|
||||||
"options": "currency"
|
"options": "currency"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "base_allocated_amount",
|
||||||
|
"fieldtype": "Currency",
|
||||||
|
"label": "Allocated Amount (Company Currency)",
|
||||||
|
"options": "Company:company:default_currency"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "account_head.account_currency",
|
"fetch_from": "account_head.account_currency",
|
||||||
"fieldname": "currency",
|
"fieldname": "currency",
|
||||||
@@ -179,7 +186,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-11-25 11:10:10.945027",
|
"modified": "2021-06-09 11:46:58.373170",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Advance Taxes and Charges",
|
"name": "Advance Taxes and Charges",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from frappe.contacts.address_and_contact import (
|
from frappe.contacts.address_and_contact import (
|
||||||
delete_contact_and_address,
|
delete_contact_and_address,
|
||||||
@@ -15,4 +17,4 @@ class Bank(Document):
|
|||||||
load_address_and_contact(self)
|
load_address_and_contact(self)
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
delete_contact_and_address("Bank", self.name)
|
delete_contact_and_address('Bank', self.name)
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
def get_data():
|
def get_data():
|
||||||
return {
|
return {
|
||||||
"fieldname": "bank",
|
'fieldname': 'bank',
|
||||||
"transactions": [{"label": _("Bank Details"), "items": ["Bank Account", "Bank Guarantee"]}],
|
'transactions': [
|
||||||
|
{
|
||||||
|
'label': _('Bank Details'),
|
||||||
|
'items': ['Bank Account', 'Bank Guarantee']
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
@@ -20,7 +22,7 @@ class BankAccount(Document):
|
|||||||
self.name = self.account_name + " - " + self.bank
|
self.name = self.account_name + " - " + self.bank
|
||||||
|
|
||||||
def on_trash(self):
|
def on_trash(self):
|
||||||
delete_contact_and_address("BankAccount", self.name)
|
delete_contact_and_address('BankAccount', self.name)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
self.validate_company()
|
self.validate_company()
|
||||||
@@ -31,9 +33,9 @@ class BankAccount(Document):
|
|||||||
frappe.throw(_("Company is manadatory for company account"))
|
frappe.throw(_("Company is manadatory for company account"))
|
||||||
|
|
||||||
def validate_iban(self):
|
def validate_iban(self):
|
||||||
"""
|
'''
|
||||||
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
|
Algorithm: https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
|
||||||
"""
|
'''
|
||||||
# IBAN field is optional
|
# IBAN field is optional
|
||||||
if not self.iban:
|
if not self.iban:
|
||||||
return
|
return
|
||||||
@@ -43,7 +45,7 @@ class BankAccount(Document):
|
|||||||
return str(9 + ord(c) - 64)
|
return str(9 + ord(c) - 64)
|
||||||
|
|
||||||
# remove whitespaces, upper case to get the right number from ord()
|
# remove whitespaces, upper case to get the right number from ord()
|
||||||
iban = "".join(self.iban.split(" ")).upper()
|
iban = ''.join(self.iban.split(' ')).upper()
|
||||||
|
|
||||||
# Move country code and checksum from the start to the end
|
# Move country code and checksum from the start to the end
|
||||||
flipped = iban[4:] + iban[:4]
|
flipped = iban[4:] + iban[:4]
|
||||||
@@ -52,12 +54,12 @@ class BankAccount(Document):
|
|||||||
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
|
encoded = [encode_char(c) if ord(c) >= 65 and ord(c) <= 90 else c for c in flipped]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
to_check = int("".join(encoded))
|
to_check = int(''.join(encoded))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
frappe.throw(_("IBAN is not valid"))
|
frappe.throw(_('IBAN is not valid'))
|
||||||
|
|
||||||
if to_check % 97 != 1:
|
if to_check % 97 != 1:
|
||||||
frappe.throw(_("IBAN is not valid"))
|
frappe.throw(_('IBAN is not valid'))
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -69,14 +71,12 @@ def make_bank_account(doctype, docname):
|
|||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_party_bank_account(party_type, party):
|
def get_party_bank_account(party_type, party):
|
||||||
return frappe.db.get_value(party_type, party, "default_bank_account")
|
return frappe.db.get_value(party_type,
|
||||||
|
party, 'default_bank_account')
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_bank_account_details(bank_account):
|
def get_bank_account_details(bank_account):
|
||||||
return frappe.db.get_value(
|
return frappe.db.get_value("Bank Account",
|
||||||
"Bank Account", bank_account, ["account", "bank", "bank_account_no"], as_dict=1
|
bank_account, ['account', 'bank', 'bank_account_no'], as_dict=1)
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
|
||||||
|
|
||||||
def get_data():
|
def get_data():
|
||||||
return {
|
return {
|
||||||
"fieldname": "bank_account",
|
'fieldname': 'bank_account',
|
||||||
"non_standard_fieldnames": {
|
'non_standard_fieldnames': {
|
||||||
"Customer": "default_bank_account",
|
'Customer': 'default_bank_account',
|
||||||
"Supplier": "default_bank_account",
|
'Supplier': 'default_bank_account',
|
||||||
},
|
},
|
||||||
"transactions": [
|
'transactions': [
|
||||||
{
|
{
|
||||||
"label": _("Payments"),
|
'label': _('Payments'),
|
||||||
"items": ["Payment Entry", "Payment Request", "Payment Order", "Payroll Entry"],
|
'items': ['Payment Entry', 'Payment Request', 'Payment Order', 'Payroll Entry']
|
||||||
},
|
},
|
||||||
{"label": _("Party"), "items": ["Customer", "Supplier"]},
|
{
|
||||||
{"items": ["Bank Guarantee"]},
|
'label': _('Party'),
|
||||||
{"items": ["Journal Entry"]},
|
'items': ['Customer', 'Supplier']
|
||||||
],
|
},
|
||||||
|
{
|
||||||
|
'items': ['Bank Guarantee']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'items': ['Journal Entry']
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
@@ -8,28 +10,28 @@ from frappe import ValidationError
|
|||||||
|
|
||||||
# test_records = frappe.get_test_records('Bank Account')
|
# test_records = frappe.get_test_records('Bank Account')
|
||||||
|
|
||||||
|
|
||||||
class TestBankAccount(unittest.TestCase):
|
class TestBankAccount(unittest.TestCase):
|
||||||
|
|
||||||
def test_validate_iban(self):
|
def test_validate_iban(self):
|
||||||
valid_ibans = [
|
valid_ibans = [
|
||||||
"GB82 WEST 1234 5698 7654 32",
|
'GB82 WEST 1234 5698 7654 32',
|
||||||
"DE91 1000 0000 0123 4567 89",
|
'DE91 1000 0000 0123 4567 89',
|
||||||
"FR76 3000 6000 0112 3456 7890 189",
|
'FR76 3000 6000 0112 3456 7890 189'
|
||||||
]
|
]
|
||||||
|
|
||||||
invalid_ibans = [
|
invalid_ibans = [
|
||||||
# wrong checksum (3rd place)
|
# wrong checksum (3rd place)
|
||||||
"GB72 WEST 1234 5698 7654 32",
|
'GB72 WEST 1234 5698 7654 32',
|
||||||
"DE81 1000 0000 0123 4567 89",
|
'DE81 1000 0000 0123 4567 89',
|
||||||
"FR66 3000 6000 0112 3456 7890 189",
|
'FR66 3000 6000 0112 3456 7890 189'
|
||||||
]
|
]
|
||||||
|
|
||||||
bank_account = frappe.get_doc({"doctype": "Bank Account"})
|
bank_account = frappe.get_doc({'doctype':'Bank Account'})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
bank_account.validate_iban()
|
bank_account.validate_iban()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
msg = "BankAccount.validate_iban() failed for empty IBAN"
|
msg = 'BankAccount.validate_iban() failed for empty IBAN'
|
||||||
self.fail(msg=msg)
|
self.fail(msg=msg)
|
||||||
|
|
||||||
for iban in valid_ibans:
|
for iban in valid_ibans:
|
||||||
@@ -37,11 +39,11 @@ class TestBankAccount(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
bank_account.validate_iban()
|
bank_account.validate_iban()
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
msg = "BankAccount.validate_iban() failed for valid IBAN {}".format(iban)
|
msg = 'BankAccount.validate_iban() failed for valid IBAN {}'.format(iban)
|
||||||
self.fail(msg=msg)
|
self.fail(msg=msg)
|
||||||
|
|
||||||
for not_iban in invalid_ibans:
|
for not_iban in invalid_ibans:
|
||||||
bank_account.iban = not_iban
|
bank_account.iban = not_iban
|
||||||
msg = "BankAccount.validate_iban() accepted invalid IBAN {}".format(not_iban)
|
msg = 'BankAccount.validate_iban() accepted invalid IBAN {}'.format(not_iban)
|
||||||
with self.assertRaises(ValidationError, msg=msg):
|
with self.assertRaises(ValidationError, msg=msg):
|
||||||
bank_account.validate_iban()
|
bank_account.validate_iban()
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
from frappe.utils import flt, fmt_money, getdate, nowdate
|
from frappe.utils import flt, fmt_money, getdate, nowdate
|
||||||
|
|
||||||
form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
|
form_grid_templates = {
|
||||||
|
"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"
|
||||||
|
}
|
||||||
|
|
||||||
class BankClearance(Document):
|
class BankClearance(Document):
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
@@ -23,8 +25,7 @@ class BankClearance(Document):
|
|||||||
if not self.include_reconciled_entries:
|
if not self.include_reconciled_entries:
|
||||||
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
|
condition = "and (clearance_date IS NULL or clearance_date='0000-00-00')"
|
||||||
|
|
||||||
journal_entries = frappe.db.sql(
|
journal_entries = frappe.db.sql("""
|
||||||
"""
|
|
||||||
select
|
select
|
||||||
"Journal Entry" as payment_document, t1.name as payment_entry,
|
"Journal Entry" as payment_document, t1.name as payment_entry,
|
||||||
t1.cheque_no as cheque_number, t1.cheque_date,
|
t1.cheque_no as cheque_number, t1.cheque_date,
|
||||||
@@ -38,18 +39,12 @@ class BankClearance(Document):
|
|||||||
and ifnull(t1.is_opening, 'No') = 'No' {condition}
|
and ifnull(t1.is_opening, 'No') = 'No' {condition}
|
||||||
group by t2.account, t1.name
|
group by t2.account, t1.name
|
||||||
order by t1.posting_date ASC, t1.name DESC
|
order by t1.posting_date ASC, t1.name DESC
|
||||||
""".format(
|
""".format(condition=condition), {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
|
||||||
condition=condition
|
|
||||||
),
|
|
||||||
{"account": self.account, "from": self.from_date, "to": self.to_date},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.bank_account:
|
if self.bank_account:
|
||||||
condition += "and bank_account = %(bank_account)s"
|
condition += 'and bank_account = %(bank_account)s'
|
||||||
|
|
||||||
payment_entries = frappe.db.sql(
|
payment_entries = frappe.db.sql("""
|
||||||
"""
|
|
||||||
select
|
select
|
||||||
"Payment Entry" as payment_document, name as payment_entry,
|
"Payment Entry" as payment_document, name as payment_entry,
|
||||||
reference_no as cheque_number, reference_date as cheque_date,
|
reference_no as cheque_number, reference_date as cheque_date,
|
||||||
@@ -64,22 +59,12 @@ class BankClearance(Document):
|
|||||||
{condition}
|
{condition}
|
||||||
order by
|
order by
|
||||||
posting_date ASC, name DESC
|
posting_date ASC, name DESC
|
||||||
""".format(
|
""".format(condition=condition), {"account": self.account, "from":self.from_date,
|
||||||
condition=condition
|
"to": self.to_date, "bank_account": self.bank_account}, as_dict=1)
|
||||||
),
|
|
||||||
{
|
|
||||||
"account": self.account,
|
|
||||||
"from": self.from_date,
|
|
||||||
"to": self.to_date,
|
|
||||||
"bank_account": self.bank_account,
|
|
||||||
},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
pos_sales_invoices, pos_purchase_invoices = [], []
|
pos_sales_invoices, pos_purchase_invoices = [], []
|
||||||
if self.include_pos_transactions:
|
if self.include_pos_transactions:
|
||||||
pos_sales_invoices = frappe.db.sql(
|
pos_sales_invoices = frappe.db.sql("""
|
||||||
"""
|
|
||||||
select
|
select
|
||||||
"Sales Invoice Payment" as payment_document, sip.name as payment_entry, sip.amount as debit,
|
"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,
|
si.posting_date, si.customer as against_account, sip.clearance_date,
|
||||||
@@ -90,13 +75,9 @@ class BankClearance(Document):
|
|||||||
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
|
and account.name = sip.account and si.posting_date >= %(from)s and si.posting_date <= %(to)s
|
||||||
order by
|
order by
|
||||||
si.posting_date ASC, si.name DESC
|
si.posting_date ASC, si.name DESC
|
||||||
""",
|
""", {"account":self.account, "from":self.from_date, "to":self.to_date}, as_dict=1)
|
||||||
{"account": self.account, "from": self.from_date, "to": self.to_date},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
pos_purchase_invoices = frappe.db.sql(
|
pos_purchase_invoices = frappe.db.sql("""
|
||||||
"""
|
|
||||||
select
|
select
|
||||||
"Purchase Invoice" as payment_document, pi.name as payment_entry, pi.paid_amount as credit,
|
"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,
|
pi.posting_date, pi.supplier as against_account, pi.clearance_date,
|
||||||
@@ -107,24 +88,18 @@ class BankClearance(Document):
|
|||||||
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
|
and pi.posting_date >= %(from)s and pi.posting_date <= %(to)s
|
||||||
order by
|
order by
|
||||||
pi.posting_date ASC, pi.name DESC
|
pi.posting_date ASC, pi.name DESC
|
||||||
""",
|
""", {"account": self.account, "from": self.from_date, "to": self.to_date}, as_dict=1)
|
||||||
{"account": self.account, "from": self.from_date, "to": self.to_date},
|
|
||||||
as_dict=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
entries = sorted(
|
entries = sorted(list(payment_entries) + list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
|
||||||
list(payment_entries)
|
key=lambda k: k['posting_date'] or getdate(nowdate()))
|
||||||
+ list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
|
|
||||||
key=lambda k: k["posting_date"] or getdate(nowdate()),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.set("payment_entries", [])
|
self.set('payment_entries', [])
|
||||||
self.total_amount = 0.0
|
self.total_amount = 0.0
|
||||||
|
|
||||||
for d in entries:
|
for d in entries:
|
||||||
row = self.append("payment_entries", {})
|
row = self.append('payment_entries', {})
|
||||||
|
|
||||||
amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
|
amount = flt(d.get('debit', 0)) - flt(d.get('credit', 0))
|
||||||
|
|
||||||
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
|
formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
|
||||||
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
|
d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
|
||||||
@@ -138,24 +113,21 @@ class BankClearance(Document):
|
|||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def update_clearance_date(self):
|
def update_clearance_date(self):
|
||||||
clearance_date_updated = False
|
clearance_date_updated = False
|
||||||
for d in self.get("payment_entries"):
|
for d in self.get('payment_entries'):
|
||||||
if d.clearance_date:
|
if d.clearance_date:
|
||||||
if not d.payment_document:
|
if not d.payment_document:
|
||||||
frappe.throw(_("Row #{0}: Payment document is required to complete the transaction"))
|
frappe.throw(_("Row #{0}: Payment document is required to complete the transaction"))
|
||||||
|
|
||||||
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
|
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
|
||||||
frappe.throw(
|
frappe.throw(_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}")
|
||||||
_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
|
.format(d.idx, d.clearance_date, d.cheque_date))
|
||||||
d.idx, d.clearance_date, d.cheque_date
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if d.clearance_date or self.include_reconciled_entries:
|
if d.clearance_date or self.include_reconciled_entries:
|
||||||
if not d.clearance_date:
|
if not d.clearance_date:
|
||||||
d.clearance_date = None
|
d.clearance_date = None
|
||||||
|
|
||||||
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
|
payment_entry = frappe.get_doc(d.payment_document, d.payment_entry)
|
||||||
payment_entry.db_set("clearance_date", d.clearance_date)
|
payment_entry.db_set('clearance_date', d.clearance_date)
|
||||||
|
|
||||||
clearance_date_updated = True
|
clearance_date_updated = True
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
# import frappe
|
# import frappe
|
||||||
import unittest
|
import unittest
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from __future__ import unicode_literals
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# License: GNU General Public License v3. See license.txt
|
# License: GNU General Public License v3. See license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from frappe.model.document import Document
|
from frappe.model.document import Document
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
|
||||||
# For license information, please see license.txt
|
# For license information, please see license.txt
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@@ -23,16 +25,10 @@ class BankGuarantee(Document):
|
|||||||
if not self.bank:
|
if not self.bank:
|
||||||
frappe.throw(_("Enter the name of the bank or lending institution before submittting."))
|
frappe.throw(_("Enter the name of the bank or lending institution before submittting."))
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_vouchar_detials(column_list, doctype, docname):
|
def get_vouchar_detials(column_list, doctype, docname):
|
||||||
column_list = json.loads(column_list)
|
column_list = json.loads(column_list)
|
||||||
for col in column_list:
|
for col in column_list:
|
||||||
sanitize_searchfield(col)
|
sanitize_searchfield(col)
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(''' select {columns} from `tab{doctype}` where name=%s'''
|
||||||
""" select {columns} from `tab{doctype}` where name=%s""".format(
|
.format(columns=", ".join(column_list), doctype=doctype), docname, as_dict=1)[0]
|
||||||
columns=", ".join(column_list), doctype=doctype
|
|
||||||
),
|
|
||||||
docname,
|
|
||||||
as_dict=1,
|
|
||||||
)[0]
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# See license.txt
|
# See license.txt
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user