mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-03 21:50:53 +00:00
Compare commits
487 Commits
copilot/re
...
coderabbit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6c8d5e038 | ||
|
|
a030ea6fde | ||
|
|
e5d25a7f04 | ||
|
|
40e86b6670 | ||
|
|
d0c9924c37 | ||
|
|
ede4faa152 | ||
|
|
05cf1dcab8 | ||
|
|
43a6dd5657 | ||
|
|
343ee9695b | ||
|
|
49e64f4e1c | ||
|
|
1e3db9f916 | ||
|
|
70ec977cb2 | ||
|
|
d6bbe43fa0 | ||
|
|
035b3cb61e | ||
|
|
97c36d1edc | ||
|
|
7170a1bd78 | ||
|
|
936f13eb20 | ||
|
|
ed51db3217 | ||
|
|
5bacb67d36 | ||
|
|
c919b1de38 | ||
|
|
46ab5e8e46 | ||
|
|
3960c01798 | ||
|
|
3c5071cefc | ||
|
|
6e4b90055f | ||
|
|
37ee560eae | ||
|
|
4b3000b071 | ||
|
|
ad6cb177e3 | ||
|
|
d256365f4a | ||
|
|
05fea7f66f | ||
|
|
7535931571 | ||
|
|
27915c9ce2 | ||
|
|
93b131f48a | ||
|
|
10d5463a40 | ||
|
|
017cc9d9f9 | ||
|
|
b691de0147 | ||
|
|
beabbb1fa2 | ||
|
|
65c3020d1b | ||
|
|
f003b3c378 | ||
|
|
a268316322 | ||
|
|
090dabeea5 | ||
|
|
edba9efb5e | ||
|
|
ad205546c3 | ||
|
|
47ee9ce0e2 | ||
|
|
aea70c5ec1 | ||
|
|
7532ab01d6 | ||
|
|
0209f0fe29 | ||
|
|
51fd15e2af | ||
|
|
7b5f69bae8 | ||
|
|
11d198fcd6 | ||
|
|
ad11914fca | ||
|
|
fbac8b032e | ||
|
|
04a2a52639 | ||
|
|
7dc8b74aa1 | ||
|
|
15047235cb | ||
|
|
020bdfb5bc | ||
|
|
3a85c38417 | ||
|
|
60ed4ada10 | ||
|
|
4b27bcd432 | ||
|
|
a2ae2c1a1a | ||
|
|
56e58ef301 | ||
|
|
7102036500 | ||
|
|
dfcbee9cc0 | ||
|
|
22dee50348 | ||
|
|
1ccc7365a7 | ||
|
|
a074d81754 | ||
|
|
c0149925ad | ||
|
|
d8d74236dd | ||
|
|
73bcfc4710 | ||
|
|
0c0f43f7f7 | ||
|
|
998f206da1 | ||
|
|
e7f6125df8 | ||
|
|
f00aeec9b4 | ||
|
|
83919119f8 | ||
|
|
9a79beda04 | ||
|
|
e6366e830c | ||
|
|
218c255543 | ||
|
|
167e9c5341 | ||
|
|
7cbd644782 | ||
|
|
d2e01e97f0 | ||
|
|
56f5df6847 | ||
|
|
cb696a8880 | ||
|
|
b3efb3084f | ||
|
|
4fe1b214c1 | ||
|
|
96c3fccb05 | ||
|
|
6a876de838 | ||
|
|
765487a087 | ||
|
|
d472888bf0 | ||
|
|
b83640fae7 | ||
|
|
c519cd0268 | ||
|
|
30263b26a5 | ||
|
|
3fe5b5c80d | ||
|
|
e8510287e3 | ||
|
|
310cca6939 | ||
|
|
e51b7155aa | ||
|
|
96ade0b821 | ||
|
|
b3db2981de | ||
|
|
0d7b2d812c | ||
|
|
f959b2c59a | ||
|
|
7549f1ba95 | ||
|
|
047343ca11 | ||
|
|
876c815bd8 | ||
|
|
8fd1d6aec8 | ||
|
|
589a393b5c | ||
|
|
19ae405742 | ||
|
|
f952b92d71 | ||
|
|
b567184dd7 | ||
|
|
c5b0787de6 | ||
|
|
3d0f649411 | ||
|
|
b54067e04d | ||
|
|
8d188cd32b | ||
|
|
5ebaee03da | ||
|
|
7ff31a1d91 | ||
|
|
1db9ce205f | ||
|
|
5a53c45321 | ||
|
|
050ea96cc6 | ||
|
|
fb6e0be5fe | ||
|
|
66fe1aa85d | ||
|
|
7ef8c81caf | ||
|
|
2f3d4ddc58 | ||
|
|
257f0c338c | ||
|
|
9406c07c42 | ||
|
|
1d35e2b261 | ||
|
|
0643beb079 | ||
|
|
22e0ca2d7e | ||
|
|
ce7be9fad5 | ||
|
|
6d3f6d73d0 | ||
|
|
8b445e04e5 | ||
|
|
b6312bca9c | ||
|
|
201a04c49a | ||
|
|
5e2c7a08d3 | ||
|
|
0da98e6769 | ||
|
|
da87f358c4 | ||
|
|
9de3b07223 | ||
|
|
d3cd887f5e | ||
|
|
d65cd605a1 | ||
|
|
6ec41fa47e | ||
|
|
d879a91165 | ||
|
|
d21cfae095 | ||
|
|
be5f2b6cf0 | ||
|
|
37b3a22825 | ||
|
|
bb307dec0a | ||
|
|
3bc58fb46f | ||
|
|
73b038084b | ||
|
|
e6133ad6d4 | ||
|
|
eeb6d0e9bf | ||
|
|
ca97f34092 | ||
|
|
ccb4b1fbc4 | ||
|
|
adec530792 | ||
|
|
784e338be4 | ||
|
|
b5e0b543f7 | ||
|
|
e87857cee0 | ||
|
|
02a9c54b74 | ||
|
|
8434efd11b | ||
|
|
671610db1e | ||
|
|
1c6bfc9af7 | ||
|
|
b9659a8741 | ||
|
|
e3beca400f | ||
|
|
22e9cb4cf4 | ||
|
|
500c44e3f5 | ||
|
|
5f00239bba | ||
|
|
b1704ccef1 | ||
|
|
d8ff1595a7 | ||
|
|
528de7fbd4 | ||
|
|
cecd07bbf4 | ||
|
|
e66b1a06f4 | ||
|
|
c15e96c460 | ||
|
|
78ac8232d8 | ||
|
|
f7004aa8c3 | ||
|
|
8379b39aaf | ||
|
|
8d4a179a8f | ||
|
|
058500e011 | ||
|
|
bf2ab32abf | ||
|
|
7e99148357 | ||
|
|
4e6d86d6f0 | ||
|
|
584c40a1b5 | ||
|
|
1d6d9c2040 | ||
|
|
4987b2fe26 | ||
|
|
7e7e83440f | ||
|
|
4cb4b34683 | ||
|
|
7662616721 | ||
|
|
9f4bf65768 | ||
|
|
7c96a08054 | ||
|
|
46e7fedff8 | ||
|
|
e8665864a4 | ||
|
|
b6e5b67676 | ||
|
|
ff9b936634 | ||
|
|
e9c009b564 | ||
|
|
17955337cc | ||
|
|
34fd4e7043 | ||
|
|
bf199cc2e0 | ||
|
|
e4741072a6 | ||
|
|
43d1d685c6 | ||
|
|
f23c11256c | ||
|
|
cda8a97f4a | ||
|
|
8e143d68b4 | ||
|
|
79a14f49e1 | ||
|
|
d3a480200e | ||
|
|
c1d50c492b | ||
|
|
d595a974ee | ||
|
|
b11f20596e | ||
|
|
c06a8568bd | ||
|
|
fc9bc36110 | ||
|
|
67def7dc13 | ||
|
|
cdd7b12279 | ||
|
|
3a995ba260 | ||
|
|
4d8d29b0df | ||
|
|
02cefa8bdb | ||
|
|
aef2e2794b | ||
|
|
069f28feeb | ||
|
|
62270af65b | ||
|
|
5139442205 | ||
|
|
5eb062c065 | ||
|
|
20f6e37b65 | ||
|
|
c5ce14dc14 | ||
|
|
b5d0e85b59 | ||
|
|
7db70742e8 | ||
|
|
bf430fce09 | ||
|
|
6bdaeb983d | ||
|
|
b0a04e202a | ||
|
|
721b29c8df | ||
|
|
f4f02458ef | ||
|
|
e5b93d85e6 | ||
|
|
e4a0bc2d5f | ||
|
|
c81dee137f | ||
|
|
8acf373e68 | ||
|
|
10a3f61689 | ||
|
|
3c13543c24 | ||
|
|
3906bf450e | ||
|
|
69597329e9 | ||
|
|
87be020c78 | ||
|
|
d0ba365aaa | ||
|
|
d82bf43684 | ||
|
|
cfa00829a8 | ||
|
|
190204a939 | ||
|
|
bc63c85daf | ||
|
|
dbab929016 | ||
|
|
1bfb62465f | ||
|
|
dd94e51d66 | ||
|
|
fd0ed04979 | ||
|
|
f111d97444 | ||
|
|
ae7aa19afc | ||
|
|
772847c0d3 | ||
|
|
ed1f1110c7 | ||
|
|
24c8cfe128 | ||
|
|
5fa9e0421c | ||
|
|
103e4aaa93 | ||
|
|
6729d3d1ca | ||
|
|
66425462ba | ||
|
|
e675c76628 | ||
|
|
64f391adf7 | ||
|
|
c0a85faa68 | ||
|
|
57624e1e33 | ||
|
|
3acc3e6ad1 | ||
|
|
2dcba90afc | ||
|
|
ede0dcd58a | ||
|
|
a7be255261 | ||
|
|
015529a321 | ||
|
|
f5aaa1139c | ||
|
|
d420ec0b22 | ||
|
|
20320c4a6c | ||
|
|
e0f6f2a600 | ||
|
|
6cd4ef694e | ||
|
|
e432a88ec1 | ||
|
|
8fdf6a9ac6 | ||
|
|
e5b02e81a9 | ||
|
|
a36331c393 | ||
|
|
b63d069635 | ||
|
|
feb4bf394a | ||
|
|
49f1688a51 | ||
|
|
fd6683e196 | ||
|
|
825e3717ca | ||
|
|
007258d657 | ||
|
|
5a47503611 | ||
|
|
07a69a073d | ||
|
|
f8f82ccf31 | ||
|
|
54cbe4222d | ||
|
|
f222d3a37b | ||
|
|
8ba71300db | ||
|
|
7a4cd3ac33 | ||
|
|
7f6e509e20 | ||
|
|
f786c16a7d | ||
|
|
53b13501a9 | ||
|
|
c84986d00e | ||
|
|
244319bf1d | ||
|
|
f6a4f696a1 | ||
|
|
afc5dda372 | ||
|
|
8d186d6b3f | ||
|
|
140d13cfb3 | ||
|
|
1296829b9c | ||
|
|
0950e67eea | ||
|
|
31a147126e | ||
|
|
72aa27a87f | ||
|
|
9cb5768fea | ||
|
|
01c560eb99 | ||
|
|
fa0ac8db4d | ||
|
|
57c6f4ffb6 | ||
|
|
84612d676b | ||
|
|
c7a81161c7 | ||
|
|
aac39b2671 | ||
|
|
028cd97ee1 | ||
|
|
c297282fa5 | ||
|
|
522e4887ca | ||
|
|
bd94deee62 | ||
|
|
344572cf87 | ||
|
|
fc9496a36b | ||
|
|
b204853193 | ||
|
|
2d5d03e63a | ||
|
|
cccd34b06a | ||
|
|
7ab1e1f677 | ||
|
|
daabb42ad7 | ||
|
|
3d4c8d6d35 | ||
|
|
247cc1d53e | ||
|
|
2eb448d4b4 | ||
|
|
7308021aa8 | ||
|
|
82b49f5d9d | ||
|
|
b9e8b2808a | ||
|
|
d4702ac232 | ||
|
|
f622996c48 | ||
|
|
b8b55754c8 | ||
|
|
7baa75faa5 | ||
|
|
6147f9c6a3 | ||
|
|
7c16db567b | ||
|
|
83bc8744bb | ||
|
|
fd5b84fe1a | ||
|
|
59f5ee7b63 | ||
|
|
4632ddc497 | ||
|
|
7bb0ec836f | ||
|
|
ca568a01f5 | ||
|
|
06fd0f8084 | ||
|
|
cc6cb5d1af | ||
|
|
6c6bf306d3 | ||
|
|
43e859b093 | ||
|
|
855dd4162f | ||
|
|
28a49a21ee | ||
|
|
50b30fce4a | ||
|
|
debd3c6886 | ||
|
|
83ddaf1696 | ||
|
|
f79e7dd806 | ||
|
|
9854b146a5 | ||
|
|
b93f920592 | ||
|
|
45fe7f2875 | ||
|
|
1fba6fc2f4 | ||
|
|
7e81be7b8c | ||
|
|
767bb534c0 | ||
|
|
349db6cfaf | ||
|
|
73e7c38bc7 | ||
|
|
7811c08e2b | ||
|
|
aefd36e050 | ||
|
|
87d00a373f | ||
|
|
86b0f67dbc | ||
|
|
410a4b20af | ||
|
|
f56a673baa | ||
|
|
24757465ce | ||
|
|
c57a43b3f4 | ||
|
|
e3fb7f4c47 | ||
|
|
8108fe4ca5 | ||
|
|
8ebd1fd029 | ||
|
|
a450f7a00d | ||
|
|
cb5926f59b | ||
|
|
0c08fb9303 | ||
|
|
732b04f37c | ||
|
|
4adeaedfde | ||
|
|
016170b08c | ||
|
|
566d1a1ee2 | ||
|
|
e1f9adf4e9 | ||
|
|
23b094f151 | ||
|
|
ae9c9f7570 | ||
|
|
1416ae3fb2 | ||
|
|
c249638ebb | ||
|
|
d24a68b206 | ||
|
|
1097d6eef1 | ||
|
|
a9b3839c3c | ||
|
|
2860d5a406 | ||
|
|
b8ecefa06c | ||
|
|
e7e6567792 | ||
|
|
292a51c160 | ||
|
|
469a1ade79 | ||
|
|
acb30dc6ac | ||
|
|
43cdca950f | ||
|
|
e2e2e78b65 | ||
|
|
0c43c07cf6 | ||
|
|
875bf5c8a9 | ||
|
|
b30ffc561b | ||
|
|
8fa047f96d | ||
|
|
4a30634942 | ||
|
|
9eeccb765d | ||
|
|
69e94248c1 | ||
|
|
a88fe2ecab | ||
|
|
9a2710b9d7 | ||
|
|
8fb8f32ad6 | ||
|
|
da899913b8 | ||
|
|
41a7e64772 | ||
|
|
aae0448e1f | ||
|
|
9f0f4d6709 | ||
|
|
7b4cb629de | ||
|
|
3ef66f65b9 | ||
|
|
70f1d3ca47 | ||
|
|
68ccb961f1 | ||
|
|
20dc93a4b7 | ||
|
|
bf791ae716 | ||
|
|
44d85b0764 | ||
|
|
b3526599dd | ||
|
|
bce4ec20e5 | ||
|
|
30d4d53171 | ||
|
|
9dfb0fdcbb | ||
|
|
6fc9636642 | ||
|
|
db9ce998e1 | ||
|
|
11db07c42e | ||
|
|
5f4185ede9 | ||
|
|
b6cb9d4799 | ||
|
|
7df349844a | ||
|
|
73daad0fc9 | ||
|
|
fa8e80c6a0 | ||
|
|
aebcd010ba | ||
|
|
8dacfdf287 | ||
|
|
50f73a5072 | ||
|
|
ae594e81f9 | ||
|
|
0050898762 | ||
|
|
734d553338 | ||
|
|
ca31a94c76 | ||
|
|
666c77dd91 | ||
|
|
20654acdd7 | ||
|
|
e8777d4f72 | ||
|
|
3ebff7e236 | ||
|
|
f44a181c81 | ||
|
|
4a8ed972d0 | ||
|
|
ca2ddd5d92 | ||
|
|
201dbad545 | ||
|
|
c44adc6d98 | ||
|
|
9580a3c9f7 | ||
|
|
941b70a185 | ||
|
|
232cc16bbb | ||
|
|
96829787b9 | ||
|
|
b55afe36c8 | ||
|
|
57d34ab146 | ||
|
|
c7069df4b4 | ||
|
|
46f8cfd228 | ||
|
|
dd18d78c10 | ||
|
|
6cb6cbb00b | ||
|
|
23c70332df | ||
|
|
278fd072c3 | ||
|
|
fc92458378 | ||
|
|
a35216a668 | ||
|
|
175d262b94 | ||
|
|
bd9f5fca08 | ||
|
|
f523c7889e | ||
|
|
ca00b46507 | ||
|
|
88dd869a11 | ||
|
|
4e04d5d1bc | ||
|
|
f2160a0629 | ||
|
|
9ca3d00eb7 | ||
|
|
6ade609dd6 | ||
|
|
e84c84975c | ||
|
|
c66f78c784 | ||
|
|
56192afe5f | ||
|
|
8af3dd0ba0 | ||
|
|
655b35fcdd | ||
|
|
ff0b37055b | ||
|
|
c87b5d3132 | ||
|
|
38a4642479 | ||
|
|
489a035637 | ||
|
|
2073cb0106 | ||
|
|
eda8a621c6 | ||
|
|
83ab480211 | ||
|
|
f122509816 | ||
|
|
50bb1ce31d | ||
|
|
2873dab98e | ||
|
|
9bbcbe0ac3 | ||
|
|
8310b5ca36 | ||
|
|
09bcadfbb9 | ||
|
|
250b9755f2 | ||
|
|
6ac88f9bbf | ||
|
|
4b07d4a1b9 | ||
|
|
1b530f7f92 | ||
|
|
8a8e1a2c4d | ||
|
|
352c585208 | ||
|
|
e98d8bf638 | ||
|
|
cfe2181bf8 | ||
|
|
a441b1c53a | ||
|
|
a5e5365ba9 | ||
|
|
6e8a6582a0 | ||
|
|
c0ac5f94b5 | ||
|
|
a55092d8da | ||
|
|
00c9e20df3 | ||
|
|
f48b90c600 | ||
|
|
62acc4aeb5 | ||
|
|
5f986e4032 |
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout Actions
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: "frappe/backport"
|
||||
path: ./actions
|
||||
|
||||
4
.github/workflows/docs-checker.yml
vendored
4
.github/workflows/docs-checker.yml
vendored
@@ -13,12 +13,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: 'Setup Environment'
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: 'Clone repo'
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Validate Docs
|
||||
env:
|
||||
|
||||
13
.github/workflows/generate-pot-file.yml
vendored
13
.github/workflows/generate-pot-file.yml
vendored
@@ -15,20 +15,25 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
branch: ["develop"]
|
||||
branch: ["develop", "version-16-hotfix"]
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ matrix.branch }}
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
python-version: "3.14"
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Run script to update POT file
|
||||
run: |
|
||||
|
||||
2
.github/workflows/initiate_release.yml
vendored
2
.github/workflows/initiate_release.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version: ["14", "15"]
|
||||
version: ["14", "15", "16"]
|
||||
|
||||
steps:
|
||||
- uses: octokit/request-action@v2.x
|
||||
|
||||
16
.github/workflows/linters.yml
vendored
16
.github/workflows/linters.yml
vendored
@@ -12,12 +12,12 @@ jobs:
|
||||
name: linters
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
- name: Set up Python 3.14
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.14'
|
||||
cache: pip
|
||||
|
||||
- name: Install and Run Pre-commit
|
||||
@@ -27,12 +27,12 @@ jobs:
|
||||
name: semgrep
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v4
|
||||
- name: Set up Python 3.14
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.10'
|
||||
python-version: '3.14'
|
||||
cache: pip
|
||||
|
||||
- name: Download Semgrep rules
|
||||
|
||||
25
.github/workflows/patch.yml
vendored
25
.github/workflows/patch.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mariadb:10.6
|
||||
image: mariadb:11.8
|
||||
env:
|
||||
MARIADB_ROOT_PASSWORD: 'root'
|
||||
ports:
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
@@ -49,14 +49,17 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
python-version: |
|
||||
3.11
|
||||
3.13
|
||||
3.14
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 24
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
@@ -110,8 +113,8 @@ jobs:
|
||||
jq 'del(.install_apps)' ~/frappe-bench/sites/test_site/site_config.json > tmp.json
|
||||
mv tmp.json ~/frappe-bench/sites/test_site/site_config.json
|
||||
|
||||
wget https://erpnext.com/files/v13-erpnext.sql.gz
|
||||
bench --site test_site --force restore ~/frappe-bench/v13-erpnext.sql.gz
|
||||
wget https://frappe.io/files/erpnext-v14.sql.gz
|
||||
bench --site test_site --force restore ~/frappe-bench/erpnext-v14.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
|
||||
@@ -132,15 +135,15 @@ jobs:
|
||||
# Resetup env and install apps
|
||||
pgrep honcho | xargs kill
|
||||
rm -rf ~/frappe-bench/env
|
||||
bench -v setup env
|
||||
bench -v setup env --python python$2
|
||||
bench pip install -e ./apps/erpnext
|
||||
bench start &>> ~/frappe-bench/bench_start.log &
|
||||
|
||||
bench --site test_site migrate
|
||||
}
|
||||
|
||||
update_to_version 14
|
||||
update_to_version 15
|
||||
update_to_version 15 3.13
|
||||
update_to_version 16 3.14
|
||||
|
||||
echo "Updating to latest version"
|
||||
git -C "apps/frappe" fetch --depth 1 upstream "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -13,12 +13,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Entire Repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Setup dependencies
|
||||
|
||||
12
.github/workflows/run-indinvidual-tests.yml
vendored
12
.github/workflows/run-indinvidual-tests.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- id: set-matrix
|
||||
run: |
|
||||
# Use grep and find to get the list of test files
|
||||
@@ -72,17 +72,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 24
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
|
||||
4
.github/workflows/semantic-commits.yml
vendored
4
.github/workflows/semantic-commits.yml
vendored
@@ -15,11 +15,11 @@ jobs:
|
||||
name: Check Commit Titles
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 200
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 18
|
||||
check-latest: true
|
||||
|
||||
13
.github/workflows/server-tests-mariadb.yml
vendored
13
.github/workflows/server-tests-mariadb.yml
vendored
@@ -7,6 +7,7 @@ on:
|
||||
paths-ignore:
|
||||
- '**.js'
|
||||
- '**.css'
|
||||
- '**.svg'
|
||||
- '**.md'
|
||||
- '**.html'
|
||||
- 'crowdin.yml'
|
||||
@@ -62,12 +63,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
@@ -78,9 +79,9 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 24
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
@@ -149,7 +150,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Clone
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
10
.github/workflows/server-tests-postgres.yml
vendored
10
.github/workflows/server-tests-postgres.yml
vendored
@@ -47,12 +47,12 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Clone
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.12'
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Check for valid Python & Merge Conflicts
|
||||
run: |
|
||||
@@ -63,9 +63,9 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 24
|
||||
check-latest: true
|
||||
|
||||
- name: Add to Hosts
|
||||
|
||||
@@ -50,13 +50,13 @@ pull_request_rules:
|
||||
- version-15-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
- name: backport to version-16-beta
|
||||
- name: backport to version-16-hotfix
|
||||
conditions:
|
||||
- label="backport version-16-beta"
|
||||
- label="backport version-16-hotfix"
|
||||
actions:
|
||||
backport:
|
||||
branches:
|
||||
- version-16-beta
|
||||
- version-16-hotfix
|
||||
assignees:
|
||||
- "{{ author }}"
|
||||
- name: Automatic merge on CI success and review
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "16.0.0-dev"
|
||||
__version__ = "17.0.0-dev"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -9,18 +9,20 @@
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"last_synced_on": "2020-07-22 12:19:59.879476",
|
||||
"modified": "2020-07-22 12:21:48.780513",
|
||||
"last_synced_on": "2026-01-02 13:01:24.037552",
|
||||
"modified": "2026-01-02 13:04:57.850305",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Balance",
|
||||
"number_of_groups": 0,
|
||||
"owner": "Administrator",
|
||||
"roles": [],
|
||||
"show_values_over_chart": 1,
|
||||
"source": "Account Balance Timeline",
|
||||
"time_interval": "Quarterly",
|
||||
"timeseries": 0,
|
||||
"time_interval": "Monthly",
|
||||
"timeseries": 1,
|
||||
"timespan": "Last Year",
|
||||
"type": "Line",
|
||||
"use_report_chart": 0,
|
||||
"y_axis": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"chart_name": "Profit and Loss",
|
||||
"chart_type": "Report",
|
||||
"creation": "2020-07-17 11:25:34.448572",
|
||||
"creation": "2025-04-01 20:38:16.986176",
|
||||
"docstatus": 0,
|
||||
"doctype": "Dashboard Chart",
|
||||
"dynamic_filters_json": "{\"company\":\"frappe.defaults.get_user_default(\\\"Company\\\")\",\"from_fiscal_year\":\"erpnext.utils.get_fiscal_year()\",\"to_fiscal_year\":\"erpnext.utils.get_fiscal_year()\"}",
|
||||
@@ -9,7 +9,7 @@
|
||||
"idx": 0,
|
||||
"is_public": 1,
|
||||
"is_standard": 1,
|
||||
"modified": "2023-07-19 13:08:56.470390",
|
||||
"modified": "2025-12-19 12:37:31.673782",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Profit and Loss",
|
||||
@@ -17,8 +17,9 @@
|
||||
"owner": "Administrator",
|
||||
"report_name": "Profit and Loss Statement",
|
||||
"roles": [],
|
||||
"show_values_over_chart": 1,
|
||||
"timeseries": 0,
|
||||
"type": "Bar",
|
||||
"type": "Line",
|
||||
"use_report_chart": 1,
|
||||
"y_axis": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ from frappe.utils import (
|
||||
cint,
|
||||
date_diff,
|
||||
flt,
|
||||
formatdate,
|
||||
get_first_day,
|
||||
get_last_day,
|
||||
get_link_to_form,
|
||||
@@ -450,14 +449,12 @@ def process_deferred_accounting(posting_date=None):
|
||||
for company in companies:
|
||||
for record_type in ("Income", "Expense"):
|
||||
doc = frappe.get_doc(
|
||||
dict(
|
||||
doctype="Process Deferred Accounting",
|
||||
company=company.name,
|
||||
posting_date=posting_date,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
type=record_type,
|
||||
)
|
||||
doctype="Process Deferred Accounting",
|
||||
company=company.name,
|
||||
posting_date=posting_date,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
type=record_type,
|
||||
)
|
||||
|
||||
doc.insert()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
@@ -415,15 +414,13 @@ def create_account(**kwargs):
|
||||
return account.name
|
||||
else:
|
||||
account = frappe.get_doc(
|
||||
dict(
|
||||
doctype="Account",
|
||||
is_group=kwargs.get("is_group", 0),
|
||||
account_name=kwargs.get("account_name"),
|
||||
account_type=kwargs.get("account_type"),
|
||||
parent_account=kwargs.get("parent_account"),
|
||||
company=kwargs.get("company"),
|
||||
account_currency=kwargs.get("account_currency"),
|
||||
)
|
||||
doctype="Account",
|
||||
is_group=kwargs.get("is_group", 0),
|
||||
account_name=kwargs.get("account_name"),
|
||||
account_type=kwargs.get("account_type"),
|
||||
parent_account=kwargs.get("parent_account"),
|
||||
company=kwargs.get("company"),
|
||||
account_currency=kwargs.get("account_currency"),
|
||||
)
|
||||
|
||||
account.save()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
@@ -37,6 +36,59 @@ class TestAccountingPeriod(IntegrationTestCase):
|
||||
doc = create_sales_invoice(do_not_save=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
|
||||
self.assertRaises(ClosedAccountingPeriod, doc.save)
|
||||
|
||||
def test_accounting_period_exempted_role(self):
|
||||
# Create Accounting Period with exempted role
|
||||
ap = create_accounting_period(
|
||||
period_name="Test Accounting Period Exempted",
|
||||
exempted_role="Accounts Manager",
|
||||
start_date="2025-12-01",
|
||||
end_date="2025-12-31",
|
||||
)
|
||||
ap.save()
|
||||
|
||||
# Create users
|
||||
users = frappe.get_all("User", filters={"email": ["like", "test%"]}, limit=1)
|
||||
user = None
|
||||
|
||||
if users[0].name:
|
||||
user = frappe.get_doc("User", users[0].name)
|
||||
else:
|
||||
user = frappe.get_doc(
|
||||
{
|
||||
"doctype": "User",
|
||||
"email": "test1@example.com",
|
||||
"first_name": "Test1",
|
||||
}
|
||||
)
|
||||
user.insert()
|
||||
|
||||
user.roles = []
|
||||
user.append("roles", {"role": "Accounts User"})
|
||||
|
||||
# ---- Non-exempted user should FAIL ----
|
||||
user.save(ignore_permissions=True)
|
||||
frappe.clear_cache(user=user.name)
|
||||
|
||||
frappe.set_user(user.name)
|
||||
posting_date = "2025-12-11"
|
||||
doc = create_sales_invoice(
|
||||
do_not_save=1,
|
||||
posting_date=posting_date,
|
||||
)
|
||||
|
||||
with self.assertRaises(frappe.ValidationError):
|
||||
doc.submit()
|
||||
|
||||
# ---- Exempted role should PASS ----
|
||||
user.append("roles", {"role": "Accounts Manager"})
|
||||
user.save(ignore_permissions=True)
|
||||
frappe.clear_cache(user=user.name)
|
||||
|
||||
doc = create_sales_invoice(do_not_save=1, posting_date=posting_date)
|
||||
|
||||
doc.submit() # Should not raise
|
||||
self.assertEqual(doc.docstatus, 1)
|
||||
|
||||
def tearDown(self):
|
||||
for d in frappe.get_all("Accounting Period"):
|
||||
frappe.delete_doc("Accounting Period", d.name)
|
||||
@@ -51,5 +103,6 @@ def create_accounting_period(**args):
|
||||
accounting_period.company = args.company or "_Test Company"
|
||||
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.exempted_role = args.exempted_role or ""
|
||||
|
||||
return accounting_period
|
||||
|
||||
@@ -64,10 +64,6 @@
|
||||
"role_allowed_to_over_bill",
|
||||
"credit_controller",
|
||||
"make_payment_via_journal_entry",
|
||||
"pos_tab",
|
||||
"pos_setting_section",
|
||||
"post_change_gl_entries",
|
||||
"column_break_xrnd",
|
||||
"assets_tab",
|
||||
"asset_settings_section",
|
||||
"calculate_depr_using_total_days",
|
||||
@@ -79,11 +75,6 @@
|
||||
"ignore_account_closing_balance",
|
||||
"use_legacy_controller_for_pcv",
|
||||
"column_break_25",
|
||||
"tab_break_dpet",
|
||||
"show_balance_in_coa",
|
||||
"banking_tab",
|
||||
"enable_party_matching",
|
||||
"enable_fuzzy_matching",
|
||||
"reports_tab",
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
@@ -91,13 +82,20 @@
|
||||
"receivable_payable_remarks_length",
|
||||
"accounts_receivable_payable_tuning_section",
|
||||
"receivable_payable_fetch_method",
|
||||
"default_ageing_range",
|
||||
"column_break_ntmi",
|
||||
"drop_ar_procedures",
|
||||
"legacy_section",
|
||||
"ignore_is_opening_check_for_reporting",
|
||||
"payment_request_settings",
|
||||
"tab_break_dpet",
|
||||
"chart_of_accounts_section",
|
||||
"show_balance_in_coa",
|
||||
"banking_section",
|
||||
"enable_party_matching",
|
||||
"enable_fuzzy_matching",
|
||||
"payment_request_section",
|
||||
"create_pr_in_draft_status",
|
||||
"budget_settings",
|
||||
"budget_section",
|
||||
"use_legacy_budget_controller"
|
||||
],
|
||||
"fields": [
|
||||
@@ -281,16 +279,9 @@
|
||||
"fieldname": "column_break_19",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, ledger entries will be posted for change amount in POS transactions",
|
||||
"fieldname": "post_change_gl_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create Ledger Entries for Change Amount"
|
||||
},
|
||||
{
|
||||
"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>",
|
||||
"description": "Learn about <a href=\"https://docs.frappe.io/erpnext/user/manual/en/common_party_accounting\">Common Party</a>",
|
||||
"fieldname": "enable_common_party_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Common Party Accounting"
|
||||
@@ -328,11 +319,6 @@
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Accounts Closing"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_setting_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "POS Setting"
|
||||
},
|
||||
{
|
||||
"fieldname": "invoice_and_billing_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
@@ -347,11 +333,6 @@
|
||||
"fieldname": "column_break_17",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "pos_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "POS"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Enabling this will allow creation of multi-currency invoices against single party account in company currency",
|
||||
@@ -362,7 +343,7 @@
|
||||
{
|
||||
"fieldname": "tab_break_dpet",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Chart Of Accounts"
|
||||
"label": "Others"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
@@ -406,11 +387,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Taxes as Table in Print"
|
||||
},
|
||||
{
|
||||
"fieldname": "banking_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Banking"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Auto match and set the Party in Bank Transactions",
|
||||
@@ -486,14 +462,9 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Calculate daily depreciation using total days in depreciation period"
|
||||
},
|
||||
{
|
||||
"description": "Payment Request created from Sales Order or Purchase Order will be in Draft status. When disabled document will be in unsaved state.",
|
||||
"fieldname": "payment_request_settings",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Payment Request"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Payment Requests made from Sales / Purchase Invoice will be put in Draft explicitly",
|
||||
"fieldname": "create_pr_in_draft_status",
|
||||
"fieldtype": "Check",
|
||||
"label": "Create in Draft Status"
|
||||
@@ -535,10 +506,6 @@
|
||||
"label": "Posting Date Inheritance for Exchange Gain / Loss",
|
||||
"options": "Invoice\nPayment\nReconciliation Date"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_xrnd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Buffered Cursor",
|
||||
"fieldname": "receivable_payable_fetch_method",
|
||||
@@ -578,11 +545,6 @@
|
||||
"label": "Role Allowed to Override Stop Action",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_settings",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Budget"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "If enabled, user will be alerted before resetting posting date to current date in relevant transactions",
|
||||
@@ -649,15 +611,42 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Role to Notify on Depreciation Failure",
|
||||
"options": "Role"
|
||||
},
|
||||
{
|
||||
"default": "30, 60, 90, 120",
|
||||
"fieldname": "default_ageing_range",
|
||||
"fieldtype": "Data",
|
||||
"label": "Default Ageing Range"
|
||||
},
|
||||
{
|
||||
"fieldname": "chart_of_accounts_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Chart Of Accounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "banking_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Banking"
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_request_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Payment Request"
|
||||
},
|
||||
{
|
||||
"fieldname": "budget_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Budget"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"hide_toolbar": 1,
|
||||
"icon": "icon-cog",
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-03 20:42:13.238050",
|
||||
"modified": "2026-01-11 18:30:45.968531",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -40,6 +40,7 @@ class AccountsSettings(Document):
|
||||
confirm_before_resetting_posting_date: DF.Check
|
||||
create_pr_in_draft_status: DF.Check
|
||||
credit_controller: DF.Link | None
|
||||
default_ageing_range: DF.Data | None
|
||||
delete_linked_ledger_entries: DF.Check
|
||||
determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
|
||||
enable_common_party_accounting: DF.Check
|
||||
@@ -56,7 +57,6 @@ class AccountsSettings(Document):
|
||||
make_payment_via_journal_entry: DF.Check
|
||||
merge_similar_account_heads: DF.Check
|
||||
over_billing_allowance: DF.Currency
|
||||
post_change_gl_entries: DF.Check
|
||||
receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor", "Raw SQL"]
|
||||
receivable_payable_remarks_length: DF.Int
|
||||
reconciliation_queue_size: DF.Int
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,57 +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": "2024-03-27 13:05:58.308002",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Tax",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AdvanceTax(Document):
|
||||
# begin: auto-generated types
|
||||
# This code is auto-generated. Do not modify anything in this block.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
account_head: DF.Link | None
|
||||
allocated_amount: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
reference_detail: DF.Data | None
|
||||
reference_name: DF.DynamicLink | None
|
||||
reference_type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
@@ -14,6 +14,7 @@
|
||||
"description",
|
||||
"included_in_paid_amount",
|
||||
"set_by_item_tax_template",
|
||||
"is_tax_withholding_account",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
@@ -25,7 +26,6 @@
|
||||
"net_amount",
|
||||
"tax_amount",
|
||||
"total",
|
||||
"allocated_amount",
|
||||
"column_break_13",
|
||||
"base_tax_amount",
|
||||
"base_net_amount",
|
||||
@@ -97,11 +97,11 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
"options": "Project"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_8",
|
||||
@@ -172,12 +172,6 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Considered In Paid Amount"
|
||||
},
|
||||
{
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Allocated Amount",
|
||||
"options": "currency"
|
||||
},
|
||||
{
|
||||
"fetch_from": "account_head.account_currency",
|
||||
"fieldname": "currency",
|
||||
@@ -213,18 +207,26 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1,
|
||||
"report_hide": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "is_tax_withholding_account",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Tax Withholding Account",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-22 19:16:22.346267",
|
||||
"modified": "2025-12-15 06:42:18.707671",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Taxes and Charges",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ class AdvanceTaxesandCharges(Document):
|
||||
|
||||
account_head: DF.Link
|
||||
add_deduct_tax: DF.Literal["Add", "Deduct"]
|
||||
allocated_amount: DF.Currency
|
||||
base_net_amount: DF.Currency
|
||||
base_tax_amount: DF.Currency
|
||||
base_total: DF.Currency
|
||||
@@ -28,10 +27,12 @@ class AdvanceTaxesandCharges(Document):
|
||||
currency: DF.Link | None
|
||||
description: DF.SmallText
|
||||
included_in_paid_amount: DF.Check
|
||||
is_tax_withholding_account: DF.Check
|
||||
net_amount: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
project: DF.Link | None
|
||||
rate: DF.Float
|
||||
row_id: DF.Data | None
|
||||
set_by_item_tax_template: DF.Check
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -42,8 +42,4 @@ frappe.ui.form.on("Bank Account", {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
is_company_account: function (frm) {
|
||||
frm.set_df_property("account", "reqd", frm.doc.is_company_account);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company Account",
|
||||
"mandatory_depends_on": "is_company_account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
@@ -98,6 +99,7 @@
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Company",
|
||||
"mandatory_depends_on": "is_company_account",
|
||||
"options": "Company"
|
||||
},
|
||||
{
|
||||
@@ -252,7 +254,7 @@
|
||||
"link_fieldname": "default_bank_account"
|
||||
}
|
||||
],
|
||||
"modified": "2025-08-29 12:32:01.081687",
|
||||
"modified": "2026-01-20 00:46:16.633364",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Account",
|
||||
|
||||
@@ -51,25 +51,29 @@ class BankAccount(Document):
|
||||
delete_contact_and_address("Bank Account", self.name)
|
||||
|
||||
def validate(self):
|
||||
self.validate_company()
|
||||
self.validate_account()
|
||||
self.validate_is_company_account()
|
||||
self.update_default_bank_account()
|
||||
|
||||
def validate_account(self):
|
||||
if self.account:
|
||||
if accounts := frappe.db.get_all(
|
||||
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
|
||||
):
|
||||
frappe.throw(
|
||||
_("'{0}' account is already used by {1}. Use another account.").format(
|
||||
frappe.bold(self.account),
|
||||
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
|
||||
)
|
||||
)
|
||||
def validate_is_company_account(self):
|
||||
if self.is_company_account:
|
||||
if not self.company:
|
||||
frappe.throw(_("Company is mandatory for company account"))
|
||||
|
||||
def validate_company(self):
|
||||
if self.is_company_account and not self.company:
|
||||
frappe.throw(_("Company is mandatory for company account"))
|
||||
if not self.account:
|
||||
frappe.throw(_("Company Account is mandatory"))
|
||||
|
||||
self.validate_account()
|
||||
|
||||
def validate_account(self):
|
||||
if accounts := frappe.db.get_all(
|
||||
"Bank Account", filters={"account": self.account, "name": ["!=", self.name]}, as_list=1
|
||||
):
|
||||
frappe.throw(
|
||||
_("'{0}' account is already used by {1}. Use another account.").format(
|
||||
frappe.bold(self.account),
|
||||
frappe.bold(comma_and([get_link_to_form(self.doctype, x[0]) for x in accounts])),
|
||||
)
|
||||
)
|
||||
|
||||
def update_default_bank_account(self):
|
||||
if self.is_default and not self.disabled:
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import ValidationError
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import cint, flt, fmt_money, get_link_to_form, getdate
|
||||
from frappe.utils import cint, flt, fmt_money, getdate
|
||||
from pypika import Order
|
||||
|
||||
import erpnext
|
||||
@@ -125,7 +125,7 @@ class BankClearance(Document):
|
||||
)
|
||||
|
||||
msg += "</ul>"
|
||||
frappe.throw(_(msg))
|
||||
msgprint(_(msg))
|
||||
return
|
||||
|
||||
if not entries_to_update:
|
||||
@@ -134,16 +134,44 @@ class BankClearance(Document):
|
||||
|
||||
for d in entries_to_update:
|
||||
if d.payment_document == "Sales Invoice":
|
||||
frappe.db.set_value(
|
||||
old_clearance_date = frappe.db.get_value(
|
||||
"Sales Invoice Payment",
|
||||
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
|
||||
{
|
||||
"parent": d.payment_entry,
|
||||
"account": self.account,
|
||||
"amount": [">", 0],
|
||||
},
|
||||
"clearance_date",
|
||||
d.clearance_date,
|
||||
)
|
||||
if d.clearance_date or old_clearance_date:
|
||||
frappe.db.set_value(
|
||||
"Sales Invoice Payment",
|
||||
{"parent": d.payment_entry, "account": self.get("account"), "amount": [">", 0]},
|
||||
"clearance_date",
|
||||
d.clearance_date,
|
||||
)
|
||||
sales_invoice = frappe.get_lazy_doc("Sales Invoice", d.payment_entry)
|
||||
sales_invoice.add_comment(
|
||||
"Comment",
|
||||
_("Clearance date changed from {0} to {1} via Bank Clearance Tool").format(
|
||||
old_clearance_date, d.clearance_date
|
||||
),
|
||||
)
|
||||
|
||||
else:
|
||||
# using db_set to trigger notification
|
||||
payment_entry = frappe.get_lazy_doc(d.payment_document, d.payment_entry)
|
||||
payment_entry.db_set("clearance_date", d.clearance_date)
|
||||
old_clearance_date = payment_entry.clearance_date
|
||||
|
||||
if d.clearance_date or old_clearance_date:
|
||||
# using db_set to trigger notification
|
||||
payment_entry.db_set("clearance_date", d.clearance_date)
|
||||
|
||||
payment_entry.add_comment(
|
||||
"Comment",
|
||||
_("Clearance date changed from {0} to {1} via Bank Clearance Tool").format(
|
||||
old_clearance_date, d.clearance_date
|
||||
),
|
||||
)
|
||||
|
||||
self.get_payment_entries()
|
||||
msgprint(_("Clearance Date updated"))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
"label": "Payment Entry",
|
||||
"oldfieldname": "voucher_id",
|
||||
"oldfieldtype": "Link",
|
||||
"options": "payment_document",
|
||||
"width": "50"
|
||||
"options": "payment_document"
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
@@ -69,7 +68,7 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"columns": 1,
|
||||
"fieldname": "cheque_number",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
@@ -79,8 +78,10 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "cheque_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Cheque Date",
|
||||
"oldfieldname": "cheque_date",
|
||||
"oldfieldtype": "Date",
|
||||
@@ -96,17 +97,19 @@
|
||||
"oldfieldtype": "Date"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:37.609319",
|
||||
"modified": "2025-12-17 14:33:45.913311",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Clearance Detail",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -304,6 +304,7 @@ def create_payment_entry_bts(
|
||||
project=None,
|
||||
cost_center=None,
|
||||
allow_edit=None,
|
||||
company_bank_account=None,
|
||||
):
|
||||
# Create a new payment entry based on the bank transaction
|
||||
bank_transaction = frappe.db.get_values(
|
||||
@@ -345,6 +346,9 @@ def create_payment_entry_bts(
|
||||
pe.project = project
|
||||
pe.cost_center = cost_center
|
||||
|
||||
if company_bank_account:
|
||||
pe.bank_account = company_bank_account
|
||||
|
||||
pe.validate()
|
||||
|
||||
if allow_edit:
|
||||
|
||||
@@ -14,7 +14,6 @@ import openpyxl
|
||||
from frappe import _
|
||||
from frappe.core.doctype.data_import.data_import import DataImport
|
||||
from frappe.core.doctype.data_import.importer import Importer, ImportFile
|
||||
from frappe.query_builder.functions import Count
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.file_manager import get_file, save_file
|
||||
from frappe.utils.xlsxutils import ILLEGAL_CHARACTERS_RE, handle_html
|
||||
|
||||
@@ -50,6 +50,9 @@ class BankTransaction(Document):
|
||||
self.handle_excluded_fee()
|
||||
self.update_allocated_amount()
|
||||
|
||||
def on_discard(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
def validate(self):
|
||||
self.validate_included_fee()
|
||||
self.validate_duplicate_references()
|
||||
|
||||
@@ -2,14 +2,12 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import add_months, flt, fmt_money, get_last_day, getdate, month_diff
|
||||
from frappe.utils.data import get_first_day, nowdate
|
||||
from frappe.utils import add_months, flt, fmt_money, get_last_day, getdate
|
||||
from frappe.utils.data import get_first_day
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.client import submit
|
||||
from frappe.utils import add_days, flt, get_first_day, get_last_day, getdate, now_datetime, nowdate
|
||||
from frappe.utils import flt, now_datetime, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.budget.budget import (
|
||||
BudgetError,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.query_builder.functions import Sum
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -101,10 +101,11 @@
|
||||
"label": "Use HTTP Protocol"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-11-25 13:03:41.896424",
|
||||
"modified": "2026-01-02 18:19:02.873815",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Currency Exchange Settings",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -71,7 +71,9 @@ class PeriodValue:
|
||||
class AccountData:
|
||||
"""Account data across all periods"""
|
||||
|
||||
account_name: str
|
||||
account: str # docname
|
||||
account_name: str = "" # account name
|
||||
account_number: str = ""
|
||||
period_values: dict[str, PeriodValue] = field(default_factory=dict)
|
||||
|
||||
def add_period(self, period_value: PeriodValue) -> None:
|
||||
@@ -103,7 +105,11 @@ class AccountData:
|
||||
# movement is unaccumulated by default
|
||||
|
||||
def copy(self):
|
||||
copied = AccountData(account_name=self.account_name)
|
||||
copied = AccountData(
|
||||
account=self.account,
|
||||
account_name=self.account_name,
|
||||
account_number=self.account_number,
|
||||
)
|
||||
copied.period_values = {k: v.copy() for k, v in self.period_values.items()}
|
||||
return copied
|
||||
|
||||
@@ -329,12 +335,10 @@ class DataCollector:
|
||||
self.account_fields = {field.fieldname for field in frappe.get_meta("Account").fields}
|
||||
|
||||
def add_account_request(self, row):
|
||||
accounts = self._parse_account_filter(self.company, row)
|
||||
|
||||
self.account_requests.append(
|
||||
{
|
||||
"row": row,
|
||||
"accounts": accounts,
|
||||
"accounts": self._parse_account_filter(self.company, row),
|
||||
"balance_type": row.balance_type,
|
||||
"reference_code": row.reference_code,
|
||||
"reverse_sign": row.reverse_sign,
|
||||
@@ -345,12 +349,12 @@ class DataCollector:
|
||||
if not self.account_requests:
|
||||
return {"account_data": {}, "summary": {}, "account_details": {}}
|
||||
|
||||
# Get all unique accounts
|
||||
all_accounts = set()
|
||||
for request in self.account_requests:
|
||||
all_accounts.update(request["accounts"])
|
||||
# Get all accounts
|
||||
all_accounts = []
|
||||
|
||||
for request in self.account_requests:
|
||||
all_accounts.extend(request["accounts"])
|
||||
|
||||
all_accounts = list(all_accounts)
|
||||
if not all_accounts:
|
||||
return {"account_data": {}, "summary": {}, "account_details": {}}
|
||||
|
||||
@@ -373,7 +377,9 @@ class DataCollector:
|
||||
total_values = [0.0] * len(self.periods)
|
||||
request_account_details = {}
|
||||
|
||||
for account_name in accounts:
|
||||
for account in accounts:
|
||||
account_name = account.name
|
||||
|
||||
if account_name not in account_data:
|
||||
continue
|
||||
|
||||
@@ -396,20 +402,21 @@ class DataCollector:
|
||||
return {"account_data": account_data, "summary": summary, "account_details": account_details}
|
||||
|
||||
@staticmethod
|
||||
def _parse_account_filter(company, report_row) -> list[str]:
|
||||
def _parse_account_filter(company, report_row) -> list[dict]:
|
||||
"""
|
||||
Find accounts matching filter criteria.
|
||||
|
||||
Example:
|
||||
Input: '["account_type", "=", "Cash"]'
|
||||
Output: ["Cash - COMP", "Petty Cash - COMP", "Bank - COMP"]
|
||||
|
||||
- Input: '["account_type", "=", "Cash"]'
|
||||
- Output: [{"name": "Cash - COMP", "account_name": "Cash", "account_number": "1001"}]
|
||||
"""
|
||||
filter_parser = FilterExpressionParser()
|
||||
|
||||
account = frappe.qb.DocType("Account")
|
||||
query = (
|
||||
frappe.qb.from_(account)
|
||||
.select(account.name)
|
||||
.select(account.name, account.account_name, account.account_number)
|
||||
.where(account.disabled == 0)
|
||||
.where(account.is_group == 0)
|
||||
)
|
||||
@@ -423,8 +430,8 @@ class DataCollector:
|
||||
|
||||
query = query.where(where_condition)
|
||||
query = query.orderby(account.name)
|
||||
result = query.run(as_dict=True)
|
||||
return [row.name for row in result]
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
@staticmethod
|
||||
def get_filtered_accounts(company: str, account_rows: list) -> list[str]:
|
||||
@@ -456,17 +463,35 @@ class FinancialQueryBuilder:
|
||||
self.filters = filters
|
||||
self.periods = periods
|
||||
self.company = filters.get("company")
|
||||
self.account_meta = {} # {name: {account_name, account_number}}
|
||||
|
||||
def fetch_account_balances(self, accounts: list[str]) -> dict[str, AccountData]:
|
||||
def fetch_account_balances(self, accounts: list[dict]) -> dict[str, AccountData]:
|
||||
"""
|
||||
Fetch account balances for all periods with optimization.
|
||||
Steps: get opening balances → fetch GL entries → calculate running totals
|
||||
|
||||
- accounts: list of accounts with details
|
||||
|
||||
```
|
||||
{
|
||||
"name": "Cash - COMP",
|
||||
"account_name": "Cash",
|
||||
"account_number": "1001",
|
||||
}
|
||||
```
|
||||
|
||||
Returns:
|
||||
dict: {account: AccountData}
|
||||
"""
|
||||
balances_data = self._get_opening_balances(accounts)
|
||||
gl_data = self._get_gl_movements(accounts)
|
||||
account_names = list({acc.name for acc in accounts})
|
||||
# NOTE: do not change accounts list as it is used in caller function
|
||||
self.account_meta = {
|
||||
acc.name: {"account_name": acc.account_name, "account_number": acc.account_number}
|
||||
for acc in accounts
|
||||
}
|
||||
|
||||
balances_data = self._get_opening_balances(account_names)
|
||||
gl_data = self._get_gl_movements(account_names)
|
||||
self._calculate_running_balances(balances_data, gl_data)
|
||||
self._handle_balance_accumulation(balances_data)
|
||||
|
||||
@@ -543,7 +568,8 @@ class FinancialQueryBuilder:
|
||||
gap_movement = gap_movements.get(account, 0.0)
|
||||
opening_balance = closing_balance + gap_movement
|
||||
|
||||
account_data = AccountData(account)
|
||||
account_data = AccountData(account=account, **self._get_account_meta(account))
|
||||
|
||||
account_data.add_period(PeriodValue(first_period_key, opening_balance, 0, 0))
|
||||
balances_data[account] = account_data
|
||||
|
||||
@@ -613,7 +639,7 @@ class FinancialQueryBuilder:
|
||||
for row in gl_data:
|
||||
account = row["account"]
|
||||
if account not in balances_data:
|
||||
balances_data[account] = AccountData(account)
|
||||
balances_data[account] = AccountData(account=account, **self._get_account_meta(account))
|
||||
|
||||
account_data: AccountData = balances_data[account]
|
||||
|
||||
@@ -714,6 +740,9 @@ class FinancialQueryBuilder:
|
||||
|
||||
return query.run(as_dict=True)
|
||||
|
||||
def _get_account_meta(self, account: str) -> dict[str, Any]:
|
||||
return self.account_meta.get(account, {})
|
||||
|
||||
|
||||
class FilterExpressionParser:
|
||||
"""Direct filter expression to SQL condition builder"""
|
||||
@@ -1544,20 +1573,29 @@ class RowFormatterBase(ABC):
|
||||
pass
|
||||
|
||||
def _get_values(self, row_data: RowData) -> dict[str, Any]:
|
||||
# TODO: can be commonify COA? @abdeali
|
||||
def _get_row_data(key: str, default: Any = "") -> Any:
|
||||
return getattr(row_data.row, key, default) or default
|
||||
|
||||
def _get_filter_value(key: str, default: Any = "") -> Any:
|
||||
return getattr(self.context.filters, key, default) or default
|
||||
|
||||
child_accounts = []
|
||||
|
||||
if row_data.account_details:
|
||||
child_accounts = list(row_data.account_details.keys())
|
||||
|
||||
display_name = _get_row_data("display_name", "")
|
||||
|
||||
values = {
|
||||
"account": _get_row_data("account", "") or display_name,
|
||||
"account_name": display_name,
|
||||
"acc_name": _get_row_data("account_name", ""),
|
||||
"acc_number": _get_row_data("account_number", ""),
|
||||
"child_accounts": child_accounts,
|
||||
"account": getattr(row_data.row, "display_name", "") or "",
|
||||
"indent": getattr(row_data.row, "indentation_level", 0),
|
||||
"account_name": getattr(row_data.row, "account", "") or "",
|
||||
"currency": self.context.currency or "",
|
||||
"period_start_date": getattr(self.context.filters, "period_start_date", "") or "",
|
||||
"period_end_date": getattr(self.context.filters, "period_end_date", "") or "",
|
||||
"indent": _get_row_data("indentation_level", 0),
|
||||
"period_start_date": _get_filter_value("period_start_date", ""),
|
||||
"period_end_date": _get_filter_value("period_end_date", ""),
|
||||
"total": 0,
|
||||
}
|
||||
|
||||
@@ -1670,8 +1708,8 @@ class DetailRowBuilder:
|
||||
detail_rows = []
|
||||
parent_row = self.parent_row_data.row
|
||||
|
||||
for account_name, account_data in self.parent_row_data.account_details.items():
|
||||
detail_row = self._create_detail_row_object(account_name, parent_row)
|
||||
for account_data in self.parent_row_data.account_details.values():
|
||||
detail_row = self._create_detail_row_object(account_data, parent_row)
|
||||
|
||||
balance_type = getattr(parent_row, "balance_type", "Closing Balance")
|
||||
values = account_data.get_values_by_type(balance_type)
|
||||
@@ -1687,16 +1725,20 @@ class DetailRowBuilder:
|
||||
|
||||
return detail_rows
|
||||
|
||||
def _create_detail_row_object(self, account_name: str, parent_row):
|
||||
short_name = account_name.rsplit(" - ", 1)[0].strip()
|
||||
def _create_detail_row_object(self, account_data: AccountData, parent_row):
|
||||
acc_name = account_data.account_name or ""
|
||||
acc_number = account_data.account_number or ""
|
||||
|
||||
display_name = f"{_(acc_number)} - {_(acc_name)}" if acc_number else _(acc_name)
|
||||
|
||||
return type(
|
||||
"DetailRow",
|
||||
(),
|
||||
{
|
||||
"display_name": short_name,
|
||||
"account": account_name,
|
||||
"account_name": short_name,
|
||||
"account": account_data.account,
|
||||
"display_name": display_name,
|
||||
"account_name": acc_name,
|
||||
"account_number": acc_number,
|
||||
"data_source": "Account Detail",
|
||||
"indentation_level": getattr(parent_row, "indentation_level", 0) + 1,
|
||||
"fieldtype": getattr(parent_row, "fieldtype", None),
|
||||
|
||||
@@ -5,7 +5,6 @@ import os
|
||||
import shutil
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
from erpnext.accounts.doctype.account_category.account_category import import_account_categories
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
# Copyright (c) 2025, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Any, ClassVar
|
||||
from typing import Any
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.database.operator_map import OPERATOR_MAP
|
||||
from frappe.database.query import SQLFunctionParser
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@@ -5,13 +5,9 @@ import frappe
|
||||
from frappe.utils import flt
|
||||
|
||||
from erpnext.accounts.doctype.financial_report_template.financial_report_engine import (
|
||||
AccountData,
|
||||
DataCollector,
|
||||
DependencyResolver,
|
||||
FilterExpressionParser,
|
||||
FinancialQueryBuilder,
|
||||
FormulaCalculator,
|
||||
PeriodValue,
|
||||
)
|
||||
from erpnext.accounts.doctype.financial_report_template.test_financial_report_template import (
|
||||
FinancialReportTemplateTestCase,
|
||||
|
||||
@@ -4,6 +4,7 @@ from frappe import _
|
||||
def get_data():
|
||||
return {
|
||||
"fieldname": "fiscal_year",
|
||||
"non_standard_fieldnames": {"Budget": "from_fiscal_year"},
|
||||
"transactions": [
|
||||
{"label": _("Budgets"), "items": ["Budget"]},
|
||||
{"label": _("References"), "items": ["Period Closing Voucher"]},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -193,7 +193,6 @@ class GLEntry(Document):
|
||||
account_type == "Profit and Loss"
|
||||
and self.company == dimension.company
|
||||
and dimension.mandatory_for_pl
|
||||
and not dimension.disabled
|
||||
and not self.is_cancelled
|
||||
):
|
||||
if not self.get(dimension.fieldname):
|
||||
@@ -207,7 +206,6 @@ class GLEntry(Document):
|
||||
account_type == "Balance Sheet"
|
||||
and self.company == dimension.company
|
||||
and dimension.mandatory_for_bs
|
||||
and not dimension.disabled
|
||||
and not self.is_cancelled
|
||||
):
|
||||
if not self.get(dimension.fieldname):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.model.naming import parse_naming_series
|
||||
|
||||
@@ -9,8 +9,8 @@ frappe.listview_settings["Invoice Discounting"] = {
|
||||
return [__("Disbursed"), "blue", "status,=,Disbursed"];
|
||||
} else if (doc.status == "Settled") {
|
||||
return [__("Settled"), "orange", "status,=,Settled"];
|
||||
} else if (doc.status == "Canceled") {
|
||||
return [__("Canceled"), "red", "status,=,Canceled"];
|
||||
} else if (doc.status == "Cancelled") {
|
||||
return [__("Cancelled"), "red", "status,=,Cancelled"];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -43,6 +43,20 @@ frappe.ui.form.on("Journal Entry", {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("project", "accounts", function (doc, cdt, cdn) {
|
||||
let row = frappe.get_doc(cdt, cdn);
|
||||
let filters = {
|
||||
company: doc.company,
|
||||
};
|
||||
if (row.party_type == "Customer") {
|
||||
filters.customer = row.party;
|
||||
}
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_project_name",
|
||||
filters,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
get_balance_for_periodic_accounting(frm) {
|
||||
@@ -112,9 +126,11 @@ frappe.ui.form.on("Journal Entry", {
|
||||
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
||||
|
||||
$.each(frm.doc.accounts || [], function (i, row) {
|
||||
erpnext.journal_entry.set_exchange_rate(frm, row.doctype, row.name);
|
||||
});
|
||||
if (frm.doc.voucher_type !== "Exchange Gain Or Loss") {
|
||||
$.each(frm.doc.accounts || [], function (i, row) {
|
||||
erpnext.journal_entry.set_exchange_rate(frm, row.doctype, row.name);
|
||||
});
|
||||
}
|
||||
},
|
||||
before_save: function (frm) {
|
||||
if (frm.doc.docstatus == 0 && !frm.doc.is_system_generated) {
|
||||
@@ -201,6 +217,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
|
||||
erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
|
||||
erpnext.utils.set_letter_head(frm);
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
},
|
||||
|
||||
voucher_type: function (frm) {
|
||||
@@ -251,6 +268,10 @@ frappe.ui.form.on("Journal Entry", {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
apply_tds: function (frm) {
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
},
|
||||
});
|
||||
|
||||
var update_jv_details = function (doc, r) {
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
"total_amount_currency",
|
||||
"total_amount",
|
||||
"total_amount_in_words",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
"override_tax_withholding_entries",
|
||||
"tax_withholding_entries",
|
||||
"reference",
|
||||
"clearance_date",
|
||||
"remark",
|
||||
@@ -517,7 +522,7 @@
|
||||
"depends_on": "eval:['Credit Note', 'Debit Note'].includes(doc.voucher_type)",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax Withholding Amount "
|
||||
"label": "Consider for Tax Withholding "
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus",
|
||||
@@ -586,6 +591,39 @@
|
||||
"hidden": 1,
|
||||
"label": "Party Not Required",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.apply_tds && doc.docstatus == 0",
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "section_tax_withholding_entry",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withholding Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_tax_withholding_threshold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Tax Withholding Threshold"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "override_tax_withholding_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Tax Withholding Entries"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_entries",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withholding Entries",
|
||||
"options": "Tax Withholding Entry",
|
||||
"read_only_depends_on": "eval: !doc.override_tax_withholding_entries"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
@@ -600,7 +638,7 @@
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2025-09-29 13:05:46.982277",
|
||||
"modified": "2025-11-13 17:54:14.542903",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
||||
@@ -6,6 +6,7 @@ import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint, scrub
|
||||
from frappe.core.doctype.submission_queue.submission_queue import queue_submission
|
||||
from frappe.utils import comma_and, cstr, flt, fmt_money, formatdate, get_link_to_form, nowdate
|
||||
|
||||
import erpnext
|
||||
@@ -17,9 +18,7 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import JournalTaxWithholding
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.utils import (
|
||||
cancel_exchange_gain_loss_journal,
|
||||
@@ -50,6 +49,7 @@ class JournalEntry(AccountsController):
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.journal_entry_account.journal_entry_account import JournalEntryAccount
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import TaxWithholdingEntry
|
||||
|
||||
accounts: DF.Table[JournalEntryAccount]
|
||||
amended_from: DF.Link | None
|
||||
@@ -66,6 +66,7 @@ class JournalEntry(AccountsController):
|
||||
finance_book: DF.Link | None
|
||||
for_all_stock_asset_accounts: DF.Check
|
||||
from_template: DF.Link | None
|
||||
ignore_tax_withholding_threshold: DF.Check
|
||||
inter_company_journal_entry_reference: DF.Link | None
|
||||
is_opening: DF.Literal["No", "Yes"]
|
||||
is_system_generated: DF.Check
|
||||
@@ -74,6 +75,7 @@ class JournalEntry(AccountsController):
|
||||
multi_currency: DF.Check
|
||||
naming_series: DF.Literal["ACC-JV-.YYYY.-"]
|
||||
party_not_required: DF.Check
|
||||
override_tax_withholding_entries: DF.Check
|
||||
pay_to_recd_from: DF.Data | None
|
||||
payment_order: DF.Link | None
|
||||
periodic_entry_difference_account: DF.Link | None
|
||||
@@ -85,6 +87,8 @@ class JournalEntry(AccountsController):
|
||||
stock_asset_account: DF.Link | None
|
||||
stock_entry: DF.Link | None
|
||||
tax_withholding_category: DF.Link | None
|
||||
tax_withholding_entries: DF.Table[TaxWithholdingEntry]
|
||||
tax_withholding_group: DF.Link | None
|
||||
title: DF.Data | None
|
||||
total_amount: DF.Currency
|
||||
total_amount_currency: DF.Link | None
|
||||
@@ -151,8 +155,8 @@ class JournalEntry(AccountsController):
|
||||
self.validate_company_in_accounting_dimension()
|
||||
self.validate_advance_accounts()
|
||||
|
||||
if self.docstatus == 0:
|
||||
self.apply_tax_withholding()
|
||||
JournalTaxWithholding(self).on_validate()
|
||||
|
||||
if self.is_new() or not self.title:
|
||||
self.title = self.get_title()
|
||||
|
||||
@@ -176,15 +180,16 @@ class JournalEntry(AccountsController):
|
||||
|
||||
def submit(self):
|
||||
if len(self.accounts) > 100:
|
||||
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
||||
self.queue_action("submit", timeout=4600)
|
||||
queue_submission(self, "_submit")
|
||||
else:
|
||||
return self._submit()
|
||||
|
||||
def before_cancel(self):
|
||||
self.has_asset_adjustment_entry()
|
||||
|
||||
def cancel(self):
|
||||
if len(self.accounts) > 100:
|
||||
msgprint(_("The task has been enqueued as a background job."), alert=True)
|
||||
self.queue_action("cancel", timeout=4600)
|
||||
queue_submission(self, "_cancel")
|
||||
else:
|
||||
return self._cancel()
|
||||
|
||||
@@ -200,6 +205,7 @@ class JournalEntry(AccountsController):
|
||||
self.update_asset_value()
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
JournalTaxWithholding(self).on_submit()
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_balance_for_periodic_accounting(self):
|
||||
@@ -283,6 +289,8 @@ class JournalEntry(AccountsController):
|
||||
self.repost_accounting_entries()
|
||||
|
||||
def on_cancel(self):
|
||||
# Cancel tax withholding entries
|
||||
|
||||
# References for this Journal are removed on the `on_cancel` event in accounts_controller
|
||||
super().on_cancel()
|
||||
self.ignore_linked_doctypes = (
|
||||
@@ -296,8 +304,10 @@ class JournalEntry(AccountsController):
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Advance Payment Ledger Entry",
|
||||
"Tax Withholding Entry",
|
||||
)
|
||||
self.make_gl_entries(1)
|
||||
JournalTaxWithholding(self).on_cancel()
|
||||
self.unlink_advance_entry_reference()
|
||||
self.unlink_asset_reference()
|
||||
self.unlink_inter_company_jv()
|
||||
@@ -353,9 +363,6 @@ class JournalEntry(AccountsController):
|
||||
StockAccountInvalidTransaction,
|
||||
)
|
||||
|
||||
def apply_tax_withholding(self):
|
||||
JournalEntryTaxWithholding(self).apply()
|
||||
|
||||
def update_asset_value(self):
|
||||
self.update_asset_on_depreciation()
|
||||
self.update_asset_on_disposal()
|
||||
@@ -550,12 +557,27 @@ class JournalEntry(AccountsController):
|
||||
)
|
||||
frappe.db.set_value("Journal Entry", self.name, "inter_company_journal_entry_reference", "")
|
||||
|
||||
def unlink_asset_adjustment_entry(self):
|
||||
frappe.db.sql(
|
||||
""" update `tabAsset Value Adjustment`
|
||||
set journal_entry = null where journal_entry = %s""",
|
||||
self.name,
|
||||
def has_asset_adjustment_entry(self):
|
||||
if self.flags.get("via_asset_value_adjustment"):
|
||||
return
|
||||
|
||||
asset_value_adjustment = frappe.db.get_value(
|
||||
"Asset Value Adjustment", {"docstatus": 1, "journal_entry": self.name}, "name"
|
||||
)
|
||||
if asset_value_adjustment:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot cancel this document as it is linked with the submitted Asset Value Adjustment <b>{0}</b>. Please cancel the Asset Value Adjustment to continue."
|
||||
).format(frappe.utils.get_link_to_form("Asset Value Adjustment", asset_value_adjustment))
|
||||
)
|
||||
|
||||
def unlink_asset_adjustment_entry(self):
|
||||
AssetValueAdjustment = frappe.qb.DocType("Asset Value Adjustment")
|
||||
(
|
||||
frappe.qb.update(AssetValueAdjustment)
|
||||
.set(AssetValueAdjustment.journal_entry, None)
|
||||
.where(AssetValueAdjustment.journal_entry == self.name)
|
||||
).run()
|
||||
|
||||
def validate_party(self):
|
||||
for d in self.get("accounts"):
|
||||
@@ -1297,230 +1319,6 @@ class JournalEntry(AccountsController):
|
||||
frappe.throw(_("Accounts table cannot be blank."))
|
||||
|
||||
|
||||
class JournalEntryTaxWithholding:
|
||||
def __init__(self, journal_entry):
|
||||
self.doc: JournalEntry = journal_entry
|
||||
self.party = None
|
||||
self.party_type = None
|
||||
self.party_account = None
|
||||
self.party_row = None
|
||||
self.existing_tds_rows = []
|
||||
self.precision = None
|
||||
self.has_multiple_parties = False
|
||||
|
||||
# Direction fields based on party type
|
||||
self.party_field = None # "credit" for Supplier, "debit" for Customer
|
||||
self.reverse_field = None # opposite of party_field
|
||||
|
||||
def apply(self):
|
||||
if not self._set_party_info():
|
||||
return
|
||||
|
||||
self._setup_direction_fields()
|
||||
self._reset_existing_tds()
|
||||
|
||||
if not self._should_apply_tds():
|
||||
self._cleanup_duplicate_tds_rows(None)
|
||||
return
|
||||
|
||||
if self.has_multiple_parties:
|
||||
frappe.throw(_("Cannot apply TDS against multiple parties in one entry"))
|
||||
|
||||
net_total = self._calculate_net_total()
|
||||
if net_total <= 0:
|
||||
return
|
||||
|
||||
tds_details = self._get_tds_details(net_total)
|
||||
if not tds_details or not tds_details.get("tax_amount"):
|
||||
return
|
||||
|
||||
self._create_or_update_tds_row(tds_details)
|
||||
self._update_party_amount(tds_details.get("tax_amount"), is_reversal=False)
|
||||
|
||||
self._recalculate_totals()
|
||||
|
||||
def _should_apply_tds(self):
|
||||
return self.doc.apply_tds and self.doc.voucher_type in ("Debit Note", "Credit Note")
|
||||
|
||||
def _set_party_info(self):
|
||||
for row in self.doc.get("accounts"):
|
||||
if row.party_type in ("Customer", "Supplier") and row.party:
|
||||
if self.party and row.party != self.party:
|
||||
self.has_multiple_parties = True
|
||||
|
||||
if not self.party:
|
||||
self.party = row.party
|
||||
self.party_type = row.party_type
|
||||
self.party_account = row.account
|
||||
self.party_row = row
|
||||
|
||||
if row.get("is_tax_withholding_account"):
|
||||
self.existing_tds_rows.append(row)
|
||||
|
||||
return bool(self.party)
|
||||
|
||||
def _setup_direction_fields(self):
|
||||
"""
|
||||
For Supplier (TDS): party has credit, TDS reduces credit
|
||||
For Customer (TCS): party has debit, TCS increases debit
|
||||
"""
|
||||
if self.party_type == "Supplier":
|
||||
self.party_field = "credit"
|
||||
self.reverse_field = "debit"
|
||||
else: # Customer
|
||||
self.party_field = "debit"
|
||||
self.reverse_field = "credit"
|
||||
|
||||
self.precision = self.doc.precision(self.party_field, self.party_row)
|
||||
|
||||
def _reset_existing_tds(self):
|
||||
for row in self.existing_tds_rows:
|
||||
# TDS amount is always in credit (liability to government)
|
||||
tds_amount = flt(row.get("credit") - row.get("debit"), self.precision)
|
||||
if not tds_amount:
|
||||
continue
|
||||
|
||||
self._update_party_amount(tds_amount, is_reversal=True)
|
||||
|
||||
# zero_out_tds_row
|
||||
row.update(
|
||||
{
|
||||
"credit": 0,
|
||||
"credit_in_account_currency": 0,
|
||||
"debit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
}
|
||||
)
|
||||
|
||||
def _update_party_amount(self, amount, is_reversal=False):
|
||||
amount = flt(amount, self.precision)
|
||||
amount_in_party_currency = flt(amount / self.party_row.get("exchange_rate", 1), self.precision)
|
||||
|
||||
# Determine which field the party amount is in
|
||||
active_field = self.party_field if self.party_row.get(self.party_field) else self.reverse_field
|
||||
|
||||
# If amount is in reverse field, flip the signs
|
||||
if active_field == self.reverse_field:
|
||||
amount = -amount
|
||||
amount_in_party_currency = -amount_in_party_currency
|
||||
|
||||
# Direction multiplier based on party type:
|
||||
# Customer (TCS): +1 (add to debit)
|
||||
# Supplier (TDS): -1 (subtract from credit)
|
||||
direction = 1 if self.party_type == "Customer" else -1
|
||||
|
||||
# Reversal inverts the direction
|
||||
if is_reversal:
|
||||
direction = -direction
|
||||
|
||||
adjustment = amount * direction
|
||||
adjustment_in_party_currency = amount_in_party_currency * direction
|
||||
|
||||
active_field_account_currency = f"{active_field}_in_account_currency"
|
||||
|
||||
self.party_row.update(
|
||||
{
|
||||
active_field: flt(self.party_row.get(active_field) + adjustment, self.precision),
|
||||
active_field_account_currency: flt(
|
||||
self.party_row.get(active_field_account_currency) + adjustment_in_party_currency,
|
||||
self.precision,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
def _calculate_net_total(self):
|
||||
from erpnext.accounts.report.general_ledger.general_ledger import get_account_type_map
|
||||
|
||||
account_type_map = get_account_type_map(self.doc.company)
|
||||
|
||||
return flt(
|
||||
sum(
|
||||
d.get(self.reverse_field) - d.get(self.party_field)
|
||||
for d in self.doc.get("accounts")
|
||||
if account_type_map.get(d.account) not in ("Tax", "Chargeable")
|
||||
and d.account != self.party_account
|
||||
and not d.get("is_tax_withholding_account")
|
||||
),
|
||||
self.precision,
|
||||
)
|
||||
|
||||
def _get_tds_details(self, net_total):
|
||||
return get_party_tax_withholding_details(
|
||||
frappe._dict(
|
||||
{
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
"doctype": self.doc.doctype,
|
||||
"company": self.doc.company,
|
||||
"posting_date": self.doc.posting_date,
|
||||
"tax_withholding_net_total": net_total,
|
||||
"base_tax_withholding_net_total": net_total,
|
||||
"grand_total": net_total,
|
||||
}
|
||||
),
|
||||
self.doc.tax_withholding_category,
|
||||
)
|
||||
|
||||
def _create_or_update_tds_row(self, tds_details):
|
||||
tax_account = tds_details.get("account_head")
|
||||
account_currency = get_account_currency(tax_account)
|
||||
company_currency = frappe.get_cached_value("Company", self.doc.company, "default_currency")
|
||||
exchange_rate = _get_exchange_rate(account_currency, company_currency, self.doc.posting_date)
|
||||
|
||||
tax_amount = flt(tds_details.get("tax_amount"), self.precision)
|
||||
tax_amount_in_account_currency = flt(tax_amount / exchange_rate, self.precision)
|
||||
|
||||
# Find existing TDS row for this account
|
||||
tax_row = None
|
||||
for row in self.doc.get("accounts"):
|
||||
if row.account == tax_account and row.get("is_tax_withholding_account"):
|
||||
tax_row = row
|
||||
break
|
||||
|
||||
if not tax_row:
|
||||
tax_row = self.doc.append(
|
||||
"accounts",
|
||||
{
|
||||
"account": tax_account,
|
||||
"account_currency": account_currency,
|
||||
"exchange_rate": exchange_rate,
|
||||
"cost_center": tds_details.get("cost_center"),
|
||||
"credit": 0,
|
||||
"credit_in_account_currency": 0,
|
||||
"debit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
"is_tax_withholding_account": 1,
|
||||
},
|
||||
)
|
||||
|
||||
# TDS/TCS is always credited (liability to government)
|
||||
tax_row.update(
|
||||
{
|
||||
"credit": tax_amount,
|
||||
"credit_in_account_currency": tax_amount_in_account_currency,
|
||||
"debit": 0,
|
||||
"debit_in_account_currency": 0,
|
||||
}
|
||||
)
|
||||
|
||||
self._cleanup_duplicate_tds_rows(tax_row)
|
||||
|
||||
def _cleanup_duplicate_tds_rows(self, current_tax_row):
|
||||
rows_to_remove = [
|
||||
row
|
||||
for row in self.doc.get("accounts")
|
||||
if row.get("is_tax_withholding_account") and row != current_tax_row
|
||||
]
|
||||
|
||||
for row in rows_to_remove:
|
||||
self.doc.remove(row)
|
||||
|
||||
def _recalculate_totals(self):
|
||||
self.doc.set_amounts_in_company_currency()
|
||||
self.doc.set_total_debit_credit()
|
||||
self.doc.set_against_account()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_default_bank_cash_account(
|
||||
company, account_type=None, mode_of_payment=None, account=None, *, fetch_balance=True
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2021, Wahni Green Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -7,7 +7,7 @@ frappe.ui.form.on("Mode of Payment", {
|
||||
let d = locals[cdt][cdn];
|
||||
return {
|
||||
filters: [
|
||||
["Account", "account_type", "in", "Bank, Cash, Receivable"],
|
||||
["Account", "account_type", "in", ["Bank", "Cash", "Receivable"]],
|
||||
["Account", "is_group", "=", 0],
|
||||
["Account", "company", "=", d.company],
|
||||
],
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -11,6 +11,5 @@ def get_data():
|
||||
},
|
||||
"transactions": [
|
||||
{"label": _("Target Details"), "items": ["Sales Person", "Territory", "Sales Partner"]},
|
||||
{"items": ["Budget"]},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
# import frappe
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
if (frm.is_new()) {
|
||||
set_default_party_type(frm);
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
}
|
||||
},
|
||||
|
||||
@@ -181,7 +182,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
"Dunning",
|
||||
];
|
||||
|
||||
if (in_list(party_type_doctypes, child.reference_doctype)) {
|
||||
if (party_type_doctypes.includes(child.reference_doctype)) {
|
||||
filters[doc.party_type.toLowerCase()] = doc.party;
|
||||
}
|
||||
|
||||
@@ -426,7 +427,15 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
if (frm.doc.payment_type == "Internal Transfer") {
|
||||
$.each(
|
||||
["party", "party_type", "paid_from", "paid_to", "references", "total_allocated_amount"],
|
||||
[
|
||||
"party",
|
||||
"party_type",
|
||||
"paid_from",
|
||||
"paid_to",
|
||||
"references",
|
||||
"total_allocated_amount",
|
||||
"party_name",
|
||||
],
|
||||
function (i, field) {
|
||||
frm.set_value(field, null);
|
||||
}
|
||||
@@ -532,6 +541,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
},
|
||||
() => frm.set_value("party_name", r.message.party_name),
|
||||
() => frm.clear_table("references"),
|
||||
() => frm.clear_table("tax_withholding_entries"),
|
||||
() => frm.events.hide_unhide_fields(frm),
|
||||
() => frm.events.set_dynamic_labels(frm),
|
||||
() => {
|
||||
@@ -564,14 +574,15 @@ frappe.ui.form.on("Payment Entry", {
|
||||
}
|
||||
},
|
||||
|
||||
apply_tax_withholding_amount: function (frm) {
|
||||
if (!frm.doc.apply_tax_withholding_amount) {
|
||||
apply_tds: function (frm) {
|
||||
if (!frm.doc.apply_tds) {
|
||||
frm.set_value("tax_withholding_category", "");
|
||||
} else {
|
||||
frappe.db.get_value("Supplier", frm.doc.party, "tax_withholding_category", (values) => {
|
||||
} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
|
||||
frappe.db.get_value(frm.doc.party_type, frm.doc.party, "tax_withholding_category", (values) => {
|
||||
frm.set_value("tax_withholding_category", values.tax_withholding_category);
|
||||
});
|
||||
}
|
||||
frm.clear_table("tax_withholding_entries");
|
||||
},
|
||||
|
||||
paid_from: function (frm) {
|
||||
@@ -1030,7 +1041,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
c.allocated_amount = d.allocated_amount;
|
||||
c.account = d.account;
|
||||
|
||||
if (!in_list(frm.events.get_order_doctypes(frm), d.voucher_type)) {
|
||||
if (!frm.events.get_order_doctypes(frm).includes(d.voucher_type)) {
|
||||
if (flt(d.outstanding_amount) > 0)
|
||||
total_positive_outstanding += flt(d.outstanding_amount);
|
||||
else total_negative_outstanding += Math.abs(flt(d.outstanding_amount));
|
||||
@@ -1046,7 +1057,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
} else {
|
||||
c.exchange_rate = 1;
|
||||
}
|
||||
if (in_list(frm.events.get_invoice_doctypes(frm), d.reference_doctype)) {
|
||||
if (frm.events.get_invoice_doctypes(frm).includes(d.reference_doctype)) {
|
||||
c.due_date = d.due_date;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
"party_name",
|
||||
"book_advance_payments_in_separate_party_account",
|
||||
"reconcile_on_advance_payment_date",
|
||||
"apply_tds",
|
||||
"tax_withholding_category",
|
||||
"column_break_11",
|
||||
"bank_account",
|
||||
"party_bank_account",
|
||||
@@ -60,10 +62,6 @@
|
||||
"taxes_and_charges_section",
|
||||
"purchase_taxes_and_charges_template",
|
||||
"sales_taxes_and_charges_template",
|
||||
"column_break_55",
|
||||
"apply_tax_withholding_amount",
|
||||
"tax_withholding_category",
|
||||
"section_break_56",
|
||||
"taxes",
|
||||
"section_break_60",
|
||||
"base_total_taxes_and_charges",
|
||||
@@ -71,6 +69,11 @@
|
||||
"total_taxes_and_charges",
|
||||
"deductions_or_loss_section",
|
||||
"deductions",
|
||||
"section_tax_withholding_entry",
|
||||
"tax_withholding_group",
|
||||
"ignore_tax_withholding_threshold",
|
||||
"override_tax_withholding_entries",
|
||||
"tax_withholding_entries",
|
||||
"transaction_references",
|
||||
"reference_no",
|
||||
"column_break_23",
|
||||
@@ -578,20 +581,13 @@
|
||||
"label": "Custom Remarks"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"depends_on": "eval:doc.apply_tds",
|
||||
"fieldname": "tax_withholding_category",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Category",
|
||||
"mandatory_depends_on": "eval:doc.apply_tax_withholding_amount",
|
||||
"mandatory_depends_on": "eval:doc.apply_tds",
|
||||
"options": "Tax Withholding Category"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||
"fieldname": "apply_tax_withholding_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply Tax Withholding Amount"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxes_and_charges_section",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -648,15 +644,6 @@
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_55",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_56",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.received_amount && doc.payment_type != 'Internal Transfer'",
|
||||
"fieldname": "received_amount_after_tax",
|
||||
@@ -752,6 +739,46 @@
|
||||
"options": "No\nYes",
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.party_type == 'Supplier'",
|
||||
"fieldname": "apply_tds",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider for Tax Withholding"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.apply_tds && doc.docstatus == 0",
|
||||
"depends_on": "eval: doc.apply_tds",
|
||||
"fieldname": "section_tax_withholding_entry",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withholding Entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_group",
|
||||
"fieldtype": "Link",
|
||||
"label": "Tax Withholding Group",
|
||||
"options": "Tax Withholding Group"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_tax_withholding_threshold",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Tax Withholding Threshold"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withholding_entries",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withholding Entries",
|
||||
"options": "Tax Withholding Entry",
|
||||
"read_only_depends_on": "eval: !doc.override_tax_withholding_entries"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "override_tax_withholding_entries",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Tax Withholding Entries"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
|
||||
@@ -12,7 +12,6 @@ from frappe.query_builder import Tuple
|
||||
from frappe.query_builder.functions import Count
|
||||
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
||||
from frappe.utils.data import comma_and, fmt_money, get_link_to_form
|
||||
from pypika import Case
|
||||
from pypika.functions import Coalesce, Sum
|
||||
|
||||
import erpnext
|
||||
@@ -30,9 +29,7 @@ from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import PaymentTaxWithholding
|
||||
from erpnext.accounts.general_ledger import (
|
||||
make_gl_entries,
|
||||
make_reverse_gl_entries,
|
||||
@@ -80,9 +77,10 @@ class PaymentEntry(AccountsController):
|
||||
from erpnext.accounts.doctype.payment_entry_reference.payment_entry_reference import (
|
||||
PaymentEntryReference,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_entry.tax_withholding_entry import TaxWithholdingEntry
|
||||
|
||||
amended_from: DF.Link | None
|
||||
apply_tax_withholding_amount: DF.Check
|
||||
apply_tds: DF.Check
|
||||
auto_repeat: DF.Link | None
|
||||
bank: DF.ReadOnly | None
|
||||
bank_account: DF.Link | None
|
||||
@@ -103,11 +101,13 @@ class PaymentEntry(AccountsController):
|
||||
custom_remarks: DF.Check
|
||||
deductions: DF.Table[PaymentEntryDeduction]
|
||||
difference_amount: DF.Currency
|
||||
ignore_tax_withholding_threshold: DF.Check
|
||||
in_words: DF.SmallText | None
|
||||
is_opening: DF.Literal["No", "Yes"]
|
||||
letter_head: DF.Link | None
|
||||
mode_of_payment: DF.Link | None
|
||||
naming_series: DF.Literal["ACC-PAY-.YYYY.-"]
|
||||
override_tax_withholding_entries: DF.Check
|
||||
paid_amount: DF.Currency
|
||||
paid_amount_after_tax: DF.Currency
|
||||
paid_from: DF.Link
|
||||
@@ -139,6 +139,8 @@ class PaymentEntry(AccountsController):
|
||||
status: DF.Literal["", "Draft", "Submitted", "Cancelled"]
|
||||
target_exchange_rate: DF.Float
|
||||
tax_withholding_category: DF.Link | None
|
||||
tax_withholding_entries: DF.Table[TaxWithholdingEntry]
|
||||
tax_withholding_group: DF.Link | None
|
||||
taxes: DF.Table[AdvanceTaxesandCharges]
|
||||
title: DF.Data | None
|
||||
total_allocated_amount: DF.Currency
|
||||
@@ -189,7 +191,7 @@ class PaymentEntry(AccountsController):
|
||||
self.validate_allocated_amount()
|
||||
self.validate_paid_invoices()
|
||||
self.ensure_supplier_is_not_blocked()
|
||||
self.set_tax_withholding()
|
||||
PaymentTaxWithholding(self).on_validate()
|
||||
self.set_status()
|
||||
self.set_total_in_words()
|
||||
|
||||
@@ -199,6 +201,7 @@ class PaymentEntry(AccountsController):
|
||||
def on_submit(self):
|
||||
if self.difference_amount:
|
||||
frappe.throw(_("Difference Amount must be zero"))
|
||||
PaymentTaxWithholding(self).on_submit()
|
||||
self.update_payment_requests()
|
||||
self.update_payment_schedule()
|
||||
self.make_gl_entries()
|
||||
@@ -300,8 +303,10 @@ class PaymentEntry(AccountsController):
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Advance Payment Ledger Entry",
|
||||
"Tax Withholding Entry",
|
||||
)
|
||||
super().on_cancel()
|
||||
PaymentTaxWithholding(self).on_cancel()
|
||||
self.update_payment_requests(cancel=True)
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.make_gl_entries(cancel=1)
|
||||
@@ -937,93 +942,6 @@ class PaymentEntry(AccountsController):
|
||||
self.base_in_words = money_in_words(base_amount, self.company_currency)
|
||||
self.in_words = money_in_words(amount, currency)
|
||||
|
||||
def set_tax_withholding(self):
|
||||
if self.party_type != "Supplier":
|
||||
return
|
||||
|
||||
if not self.apply_tax_withholding_amount:
|
||||
return
|
||||
|
||||
net_total = self.calculate_tax_withholding_net_total()
|
||||
|
||||
# Adding args as purchase invoice to get TDS amount
|
||||
args = frappe._dict(
|
||||
{
|
||||
"company": self.company,
|
||||
"doctype": "Payment Entry",
|
||||
"supplier": self.party,
|
||||
"posting_date": self.posting_date,
|
||||
"net_total": net_total,
|
||||
}
|
||||
)
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(args, self.tax_withholding_category)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
tax_withholding_details.update(
|
||||
{"cost_center": self.cost_center or erpnext.get_default_cost_center(self.company)}
|
||||
)
|
||||
|
||||
accounts = []
|
||||
for d in self.taxes:
|
||||
if d.account_head == tax_withholding_details.get("account_head"):
|
||||
# Preserve user updated included in paid amount
|
||||
if d.included_in_paid_amount:
|
||||
tax_withholding_details.update({"included_in_paid_amount": d.included_in_paid_amount})
|
||||
|
||||
d.update(tax_withholding_details)
|
||||
accounts.append(d.account_head)
|
||||
|
||||
if not accounts or tax_withholding_details.get("account_head") not in accounts:
|
||||
self.append("taxes", tax_withholding_details)
|
||||
|
||||
to_remove = [
|
||||
d
|
||||
for d in self.taxes
|
||||
if not d.tax_amount and d.account_head == tax_withholding_details.get("account_head")
|
||||
]
|
||||
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
def calculate_tax_withholding_net_total(self):
|
||||
net_total = 0
|
||||
order_details = self.get_order_wise_tax_withholding_net_total()
|
||||
|
||||
for d in self.references:
|
||||
tax_withholding_net_total = order_details.get(d.reference_name)
|
||||
if not tax_withholding_net_total:
|
||||
continue
|
||||
|
||||
net_taxable_outstanding = max(
|
||||
0, d.outstanding_amount - (d.total_amount - tax_withholding_net_total)
|
||||
)
|
||||
|
||||
net_total += min(net_taxable_outstanding, d.allocated_amount)
|
||||
|
||||
net_total += self.unallocated_amount
|
||||
|
||||
return net_total
|
||||
|
||||
def get_order_wise_tax_withholding_net_total(self):
|
||||
if self.party_type == "Supplier":
|
||||
doctype = "Purchase Order"
|
||||
else:
|
||||
doctype = "Sales Order"
|
||||
|
||||
docnames = [d.reference_name for d in self.references if d.reference_doctype == doctype]
|
||||
|
||||
return frappe._dict(
|
||||
frappe.db.get_all(
|
||||
doctype,
|
||||
filters={"name": ["in", docnames]},
|
||||
fields=["name", "base_tax_withholding_net_total"],
|
||||
as_list=True,
|
||||
)
|
||||
)
|
||||
|
||||
def apply_taxes(self):
|
||||
self.initialize_taxes()
|
||||
self.determine_exclusive_rate()
|
||||
@@ -1366,8 +1284,11 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
def make_gl_entries(self, cancel=0, adv_adj=0):
|
||||
gl_entries = self.build_gl_map()
|
||||
gl_entries = process_gl_map(gl_entries)
|
||||
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj)
|
||||
|
||||
merge_entries = frappe.get_single_value("Accounts Settings", "merge_similar_account_heads")
|
||||
|
||||
gl_entries = process_gl_map(gl_entries, merge_entries=merge_entries)
|
||||
make_gl_entries(gl_entries, cancel=cancel, adv_adj=adv_adj, merge_entries=merge_entries)
|
||||
if cancel:
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
else:
|
||||
|
||||
@@ -1045,6 +1045,7 @@ class TestPaymentEntry(IntegrationTestCase):
|
||||
)
|
||||
|
||||
def test_gl_of_multi_currency_payment_with_taxes(self):
|
||||
frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 1)
|
||||
payment_entry = create_payment_entry(
|
||||
party="_Test Supplier USD", paid_to="_Test Payable USD - _TC", save=True
|
||||
)
|
||||
@@ -1606,6 +1607,96 @@ class TestPaymentEntry(IntegrationTestCase):
|
||||
self.voucher_no = pe.name
|
||||
self.check_gl_entries()
|
||||
|
||||
def test_payment_entry_merges_gl_entries_with_same_account_head(self):
|
||||
"""
|
||||
Test that Payment Entry merges GL entries with same account head
|
||||
when 'Merge Similar Account Heads' setting is enabled.
|
||||
"""
|
||||
frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 1)
|
||||
|
||||
pe = create_payment_entry(
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier",
|
||||
paid_from="_Test Bank - _TC",
|
||||
paid_to="Creditors - _TC",
|
||||
)
|
||||
|
||||
pe.append(
|
||||
"deductions",
|
||||
{
|
||||
"account": "Write Off - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"amount": 50,
|
||||
},
|
||||
)
|
||||
|
||||
pe.append(
|
||||
"deductions",
|
||||
{
|
||||
"account": "Write Off - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"amount": 30,
|
||||
},
|
||||
)
|
||||
|
||||
pe.save()
|
||||
pe.submit()
|
||||
|
||||
gl_entries = frappe.db.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": pe.name, "account": "Write Off - _TC", "is_cancelled": 0},
|
||||
fields=["debit", "credit"],
|
||||
)
|
||||
|
||||
self.assertEqual(len(gl_entries), 1)
|
||||
self.assertEqual(gl_entries[0].debit, 80)
|
||||
|
||||
def test_payment_entry_does_not_merge_gl_entries_when_setting_disabled(self):
|
||||
"""
|
||||
Test that Payment Entry does NOT merge GL entries
|
||||
when 'Merge Similar Account Heads' is disabled.
|
||||
"""
|
||||
|
||||
frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 0)
|
||||
|
||||
pe = create_payment_entry(
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier",
|
||||
paid_from="_Test Bank - _TC",
|
||||
paid_to="Creditors - _TC",
|
||||
)
|
||||
|
||||
pe.append(
|
||||
"deductions",
|
||||
{
|
||||
"account": "Write Off - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"amount": 50,
|
||||
},
|
||||
)
|
||||
|
||||
pe.append(
|
||||
"deductions",
|
||||
{
|
||||
"account": "Write Off - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"amount": 30,
|
||||
},
|
||||
)
|
||||
|
||||
pe.save()
|
||||
pe.submit()
|
||||
|
||||
gl_entries = frappe.db.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_no": pe.name, "account": "Write Off - _TC", "is_cancelled": 0},
|
||||
fields=["debit", "credit"],
|
||||
)
|
||||
|
||||
self.assertEqual(len(gl_entries), 2)
|
||||
|
||||
frappe.db.set_single_value("Accounts Settings", "merge_similar_account_heads", 1)
|
||||
|
||||
def check_pl_entries(self):
|
||||
ple = frappe.qb.DocType("Payment Ledger Entry")
|
||||
pl_entries = (
|
||||
|
||||
@@ -59,14 +59,15 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-05 16:07:47.307971",
|
||||
"modified": "2025-08-13 06:52:46.130142",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Deduction",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "total_amount",
|
||||
"fieldtype": "Float",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Grand Total",
|
||||
"print_hide": 1,
|
||||
@@ -79,7 +79,7 @@
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Float",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Outstanding",
|
||||
"read_only": 1
|
||||
@@ -87,7 +87,7 @@
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "allocated_amount",
|
||||
"fieldtype": "Float",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Allocated"
|
||||
},
|
||||
@@ -176,7 +176,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-12-08 13:57:30.098239",
|
||||
"modified": "2026-01-05 14:18:03.286224",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
|
||||
@@ -18,12 +18,12 @@ class PaymentEntryReference(Document):
|
||||
account_type: DF.Data | None
|
||||
advance_voucher_no: DF.DynamicLink | None
|
||||
advance_voucher_type: DF.Link | None
|
||||
allocated_amount: DF.Float
|
||||
allocated_amount: DF.Currency
|
||||
bill_no: DF.Data | None
|
||||
due_date: DF.Date | None
|
||||
exchange_gain_loss: DF.Currency
|
||||
exchange_rate: DF.Float
|
||||
outstanding_amount: DF.Float
|
||||
outstanding_amount: DF.Currency
|
||||
parent: DF.Data
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
@@ -34,7 +34,7 @@ class PaymentEntryReference(Document):
|
||||
reconcile_effect_on: DF.Date | None
|
||||
reference_doctype: DF.Link
|
||||
reference_name: DF.DynamicLink
|
||||
total_amount: DF.Float
|
||||
total_amount: DF.Currency
|
||||
# end: auto-generated types
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -131,7 +131,6 @@ class PaymentLedgerEntry(Document):
|
||||
account_type == "Profit and Loss"
|
||||
and self.company == dimension.company
|
||||
and dimension.mandatory_for_pl
|
||||
and not dimension.disabled
|
||||
):
|
||||
if not self.get(dimension.fieldname):
|
||||
frappe.throw(
|
||||
@@ -144,7 +143,6 @@ class PaymentLedgerEntry(Document):
|
||||
account_type == "Balance Sheet"
|
||||
and self.company == dimension.company
|
||||
and dimension.mandatory_for_bs
|
||||
and not dimension.disabled
|
||||
):
|
||||
if not self.get(dimension.fieldname):
|
||||
frappe.throw(
|
||||
|
||||
@@ -50,12 +50,10 @@ class TestPaymentOrder(IntegrationTestCase):
|
||||
|
||||
def create_payment_order_against_payment_entry(ref_doc, order_type, bank_account):
|
||||
payment_order = frappe.get_doc(
|
||||
dict(
|
||||
doctype="Payment Order",
|
||||
company="_Test Company",
|
||||
payment_order_type=order_type,
|
||||
company_bank_account=bank_account,
|
||||
)
|
||||
doctype="Payment Order",
|
||||
company="_Test Company",
|
||||
payment_order_type=order_type,
|
||||
company_bank_account=bank_account,
|
||||
)
|
||||
doc = make_payment_order(ref_doc.name, payment_order)
|
||||
doc.save()
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _, msgprint, qb
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder import Case, Criterion
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
|
||||
|
||||
@@ -393,6 +393,9 @@ class PaymentReconciliation(Document):
|
||||
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
|
||||
|
||||
def get_difference_amount(self, payment_entry, invoice, allocated_amount):
|
||||
party_account_defaults = frappe.get_cached_value(
|
||||
"Account", self.receivable_payable_account, ["account_type", "account_currency"], as_dict=True
|
||||
)
|
||||
allocated_amount_precision = get_field_precision(
|
||||
frappe.get_meta("Payment Reconciliation Allocation").get_field("allocated_amount")
|
||||
)
|
||||
@@ -400,9 +403,9 @@ class PaymentReconciliation(Document):
|
||||
frappe.get_meta("Payment Reconciliation Allocation").get_field("difference_amount")
|
||||
)
|
||||
difference_amount = 0
|
||||
if frappe.get_cached_value(
|
||||
"Account", self.receivable_payable_account, "account_currency"
|
||||
) != frappe.get_cached_value("Company", self.company, "default_currency"):
|
||||
if party_account_defaults.get("account_currency") != frappe.get_cached_value(
|
||||
"Company", self.company, "default_currency"
|
||||
):
|
||||
if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
|
||||
"exchange_rate", 1
|
||||
):
|
||||
@@ -414,7 +417,14 @@ class PaymentReconciliation(Document):
|
||||
invoice.get("exchange_rate", 1) * flt(allocated_amount, allocated_amount_precision),
|
||||
difference_amount_precision,
|
||||
)
|
||||
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
|
||||
|
||||
# Added If clause to handle return Adhoc payments for account type holders ("Payable")
|
||||
if party_account_defaults.get("account_type") in ("Payable") and invoice.get(
|
||||
"invoice_type"
|
||||
) in ["Payment Entry", "Journal Entry"]:
|
||||
difference_amount = allocated_amount_in_inv_rate - allocated_amount_in_ref_rate
|
||||
else:
|
||||
difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
|
||||
|
||||
return difference_amount
|
||||
|
||||
@@ -677,6 +687,28 @@ class PaymentReconciliation(Document):
|
||||
)
|
||||
invoice_exchange_map.update(journals_map)
|
||||
|
||||
payment_entries = [
|
||||
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Payment Entry"
|
||||
]
|
||||
payment_entries.extend(
|
||||
[d.get("reference_name") for d in payments if d.get("reference_type") == "Payment Entry"]
|
||||
)
|
||||
if payment_entries:
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
query = (
|
||||
frappe.qb.from_(pe)
|
||||
.select(
|
||||
pe.name,
|
||||
Case()
|
||||
.when(pe.payment_type == "Receive", pe.source_exchange_rate)
|
||||
.else_(pe.target_exchange_rate)
|
||||
.as_("exchange_rate"),
|
||||
)
|
||||
.where(pe.name.isin(payment_entries))
|
||||
)
|
||||
payment_entries = query.run(as_list=1)
|
||||
invoice_exchange_map.update(payment_entries)
|
||||
|
||||
return invoice_exchange_map
|
||||
|
||||
def validate_allocation(self):
|
||||
|
||||
@@ -2340,6 +2340,210 @@ class TestPaymentReconciliation(IntegrationTestCase):
|
||||
|
||||
frappe.db.set_value("Company", self.company, default_settings)
|
||||
|
||||
def test_foreign_currency_reverse_payment_entry_against_payment_entry_for_customer(self):
|
||||
transaction_date = nowdate()
|
||||
customer = self.customer3
|
||||
amount = 1000
|
||||
exchange_rate_at_payment = 100
|
||||
exchange_rate_at_reverse_payment = 95
|
||||
|
||||
# Receive amount from customer - 1,00,000
|
||||
pe = self.create_payment_entry(amount=amount, posting_date=transaction_date, customer=customer)
|
||||
pe.payment_type = "Receive"
|
||||
pe.paid_from = self.debtors_eur
|
||||
pe.paid_from_account_currency = "EUR"
|
||||
pe.source_exchange_rate = exchange_rate_at_payment
|
||||
pe.paid_amount = amount
|
||||
pe.received_amount = exchange_rate_at_payment * amount
|
||||
pe.paid_to = self.cash
|
||||
pe.paid_to_account_currency = "INR"
|
||||
pe = pe.save().submit()
|
||||
|
||||
# Pay amount to customer - 95,000
|
||||
reverse_pe = self.create_payment_entry(
|
||||
amount=amount, posting_date=transaction_date, customer=customer
|
||||
)
|
||||
reverse_pe.payment_type = "Pay"
|
||||
reverse_pe.paid_from = self.cash
|
||||
reverse_pe.paid_from_account_currency = "INR"
|
||||
reverse_pe.target_exchange_rate = exchange_rate_at_reverse_payment
|
||||
reverse_pe.paid_amount = exchange_rate_at_reverse_payment * amount
|
||||
reverse_pe.received_amount = amount
|
||||
reverse_pe.paid_to = self.debtors_eur
|
||||
reverse_pe.paid_to_account_currency = "EUR"
|
||||
reverse_pe.save().submit()
|
||||
|
||||
# Reconcile payments
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party = customer
|
||||
pr.receivable_payable_account = self.debtors_eur
|
||||
pr.get_unreconciled_entries()
|
||||
invoices = [invoice.as_dict() for invoice in pr.invoices]
|
||||
payments = [payment.as_dict() for payment in pr.payments]
|
||||
self.assertEqual(len(pr.get("invoices")), 1)
|
||||
self.assertEqual(len(pr.get("payments")), 1)
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
|
||||
# Check the difference_amount is a gain of 5000
|
||||
self.assertEqual(flt(pr.allocation[0].get("difference_amount")), 5000.0)
|
||||
pr.reconcile()
|
||||
|
||||
def test_foreign_currency_reverse_payment_entry_against_payment_entry_for_supplier(self):
|
||||
transaction_date = nowdate()
|
||||
self.supplier = "_Test Supplier USD"
|
||||
amount = 1000
|
||||
exchange_rate_at_payment = 100
|
||||
exchange_rate_at_reverse_payment = 95
|
||||
|
||||
# Pay amount to supplier - 1,00,000
|
||||
pe = self.create_payment_entry(amount=amount, posting_date=transaction_date)
|
||||
pe.payment_type = "Pay"
|
||||
pe.party_type = "Supplier"
|
||||
pe.party = self.supplier
|
||||
pe.paid_from = self.cash
|
||||
pe.paid_from_account_currency = "INR"
|
||||
pe.target_exchange_rate = exchange_rate_at_payment
|
||||
pe.paid_amount = exchange_rate_at_payment * amount
|
||||
pe.received_amount = amount
|
||||
pe.paid_to = self.creditors_usd
|
||||
pe.paid_to_account_currency = "USD"
|
||||
pe.save().submit()
|
||||
|
||||
# Receive amount from supplier - 95,000
|
||||
reverse_pe = self.create_payment_entry(amount=amount, posting_date=transaction_date)
|
||||
reverse_pe.payment_type = "Receive"
|
||||
reverse_pe.party_type = "Supplier"
|
||||
reverse_pe.party = self.supplier
|
||||
reverse_pe.paid_from = self.creditors_usd
|
||||
reverse_pe.paid_from_account_currency = "USD"
|
||||
reverse_pe.source_exchange_rate = exchange_rate_at_reverse_payment
|
||||
reverse_pe.paid_amount = amount
|
||||
reverse_pe.received_amount = exchange_rate_at_reverse_payment * amount
|
||||
reverse_pe.paid_to = self.cash
|
||||
reverse_pe.paid_to_account_currency = "INR"
|
||||
reverse_pe = reverse_pe.save().submit()
|
||||
|
||||
# Reconcile payments
|
||||
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||
pr.party = self.supplier
|
||||
pr.receivable_payable_account = self.creditors_usd
|
||||
pr.get_unreconciled_entries()
|
||||
invoices = [invoice.as_dict() for invoice in pr.invoices]
|
||||
payments = [payment.as_dict() for payment in pr.payments]
|
||||
|
||||
self.assertEqual(len(pr.get("invoices")), 1)
|
||||
self.assertEqual(len(pr.get("payments")), 1)
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
|
||||
# Check the difference_amount is a loss of 5000
|
||||
self.assertEqual(flt(pr.allocation[0].get("difference_amount")), -5000.0)
|
||||
pr.reconcile()
|
||||
|
||||
def test_foreign_currency_reverse_journal_entry_against_journal_entry_for_customer(self):
|
||||
transaction_date = nowdate()
|
||||
customer = self.customer3
|
||||
amount = 1000
|
||||
exchange_rate_at_payment = 95
|
||||
exchange_rate_at_reverse_payment = 100
|
||||
|
||||
# Receive amount from customer - 95,000
|
||||
je1 = self.create_journal_entry(self.cash, self.debtors_eur, amount, transaction_date)
|
||||
je1.multi_currency = 1
|
||||
je1.accounts[0].exchange_rate = 1
|
||||
je1.accounts[0].debit_in_account_currency = exchange_rate_at_payment * amount
|
||||
je1.accounts[0].debit = exchange_rate_at_payment * amount
|
||||
je1.accounts[1].party_type = "Customer"
|
||||
je1.accounts[1].party = customer
|
||||
je1.accounts[1].exchange_rate = exchange_rate_at_payment
|
||||
je1.accounts[1].credit_in_account_currency = amount
|
||||
je1.accounts[1].credit = exchange_rate_at_payment * amount
|
||||
je1.save()
|
||||
je1.submit()
|
||||
|
||||
# Pay amount to customer - 1,00,000
|
||||
je2 = self.create_journal_entry(self.debtors_eur, self.cash, amount, transaction_date)
|
||||
je2.multi_currency = 1
|
||||
je2.accounts[0].party_type = "Customer"
|
||||
je2.accounts[0].party = customer
|
||||
je2.accounts[0].exchange_rate = exchange_rate_at_reverse_payment
|
||||
je2.accounts[0].debit_in_account_currency = amount
|
||||
je2.accounts[0].debit = exchange_rate_at_reverse_payment * amount
|
||||
je2.accounts[1].exchange_rate = 1
|
||||
je2.accounts[1].credit_in_account_currency = exchange_rate_at_reverse_payment * amount
|
||||
je2.accounts[1].credit = exchange_rate_at_reverse_payment * amount
|
||||
je2.save()
|
||||
je2.submit()
|
||||
|
||||
# Reconcile payments
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party = customer
|
||||
pr.receivable_payable_account = self.debtors_eur
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
|
||||
invoices = [invoice.as_dict() for invoice in pr.invoices]
|
||||
payments = [payment.as_dict() for payment in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
|
||||
# Check the difference_amount is a loss of 5000
|
||||
self.assertEqual(flt(pr.allocation[0].difference_amount), -5000.0)
|
||||
pr.reconcile()
|
||||
|
||||
def test_foreign_currency_reverse_journal_entry_against_journal_entry_for_supplier(self):
|
||||
transaction_date = nowdate()
|
||||
self.supplier = "_Test Supplier USD"
|
||||
amount = 1000
|
||||
exchange_rate_at_payment = 95
|
||||
exchange_rate_at_reverse_payment = 100
|
||||
|
||||
# Pay amount to supplier - 95,000
|
||||
je1 = self.create_journal_entry(self.creditors_usd, self.cash, amount, transaction_date)
|
||||
je1.multi_currency = 1
|
||||
je1.accounts[0].party_type = "Supplier"
|
||||
je1.accounts[0].party = self.supplier
|
||||
je1.accounts[0].exchange_rate = exchange_rate_at_payment
|
||||
je1.accounts[0].debit_in_account_currency = amount
|
||||
je1.accounts[0].debit = exchange_rate_at_payment * amount
|
||||
je1.accounts[1].exchange_rate = 1
|
||||
je1.accounts[1].credit = exchange_rate_at_payment * amount
|
||||
je1.accounts[1].credit_in_account_currency = exchange_rate_at_payment * amount
|
||||
je1.save()
|
||||
je1.submit()
|
||||
|
||||
# Receive amount from supplier - 1,00,000
|
||||
je2 = self.create_journal_entry(self.cash, self.creditors_usd, amount, transaction_date)
|
||||
je2.multi_currency = 1
|
||||
je2.accounts[0].exchange_rate = 1
|
||||
je2.accounts[0].debit = exchange_rate_at_reverse_payment * amount
|
||||
je2.accounts[0].debit_in_account_currency = exchange_rate_at_reverse_payment * amount
|
||||
je2.accounts[1].party_type = "Supplier"
|
||||
je2.accounts[1].party = self.supplier
|
||||
je2.accounts[1].exchange_rate = exchange_rate_at_reverse_payment
|
||||
je2.accounts[1].credit_in_account_currency = amount
|
||||
je2.accounts[1].credit = exchange_rate_at_reverse_payment * amount
|
||||
je2.save()
|
||||
je2.submit()
|
||||
|
||||
# Reconcile payments
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.party_type = "Supplier"
|
||||
pr.party = self.supplier
|
||||
pr.receivable_payable_account = self.creditors_usd
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
|
||||
invoices = [invoice.as_dict() for invoice in pr.invoices]
|
||||
payments = [payment.as_dict() for payment in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
|
||||
# Check the difference_amount is a gain of 5000
|
||||
self.assertEqual(flt(pr.allocation[0].difference_amount), 5000.0)
|
||||
pr.reconcile()
|
||||
|
||||
|
||||
def make_customer(customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
|
||||
@@ -100,7 +100,10 @@ class PaymentRequest(Document):
|
||||
subscription_plans: DF.Table[SubscriptionPlanDetail]
|
||||
swift_number: DF.ReadOnly | None
|
||||
transaction_date: DF.Date | None
|
||||
|
||||
# end: auto-generated types
|
||||
def on_discard(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
def validate(self):
|
||||
if self.get("__islocal"):
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# See license.txt
|
||||
|
||||
import re
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -13,9 +13,9 @@ frappe.ui.form.on("Period Closing Voucher", {
|
||||
return {
|
||||
filters: [
|
||||
["Account", "company", "=", frm.doc.company],
|
||||
["Account", "is_group", "=", "0"],
|
||||
["Account", "is_group", "=", 0],
|
||||
["Account", "freeze_account", "=", "No"],
|
||||
["Account", "root_type", "in", "Liability, Equity"],
|
||||
["Account", "root_type", "in", ["Liability", "Equity"]],
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
@@ -6,7 +6,6 @@ import copy
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import add_days, flt, formatdate, getdate
|
||||
|
||||
from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2018, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import copy
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
@@ -539,6 +538,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
rate=1000,
|
||||
serial_no=[serial_nos[0]],
|
||||
do_not_save=1,
|
||||
ignore_sabb_validation=True,
|
||||
)
|
||||
|
||||
pos2.append("payments", {"mode_of_payment": "Bank Draft", "amount": 1000})
|
||||
@@ -1016,6 +1016,7 @@ class TestPOSInvoice(IntegrationTestCase):
|
||||
qty=1,
|
||||
rate=100,
|
||||
do_not_submit=True,
|
||||
ignore_sabb_validation=True,
|
||||
)
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos_inv.submit)
|
||||
@@ -1157,6 +1158,7 @@ def create_pos_invoice(**args):
|
||||
"posting_time": pos_inv.posting_time,
|
||||
"type_of_transaction": type_of_transaction,
|
||||
"do_not_submit": True,
|
||||
"ignore_sabb_validation": args.ignore_sabb_validation,
|
||||
}
|
||||
)
|
||||
).name
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.tests import IntegrationTestCase
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.core.doctype.user_permission.test_user_permission import create_user
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user