mirror of
https://github.com/frappe/erpnext.git
synced 2026-08-08 04:01:46 +00:00
Compare commits
448 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ae050ecd4 | ||
|
|
d3cc0c9aea | ||
|
|
49d914bdb6 | ||
|
|
5fd68f7204 | ||
|
|
760af497ca | ||
|
|
2e6f1378f1 | ||
|
|
2d0e1830a9 | ||
|
|
1cc887a997 | ||
|
|
ee7474ba20 | ||
|
|
a2a8558677 | ||
|
|
cc07213faa | ||
|
|
f4e3f0d656 | ||
|
|
0379ea5086 | ||
|
|
65005d8630 | ||
|
|
3f3fc8214d | ||
|
|
b529883897 | ||
|
|
3d621d47a6 | ||
|
|
8fe346aef8 | ||
|
|
952cee3d6a | ||
|
|
41e384326e | ||
|
|
f42e93bf6c | ||
|
|
a3146c39dd | ||
|
|
a15ad804d0 | ||
|
|
8c496fbf2b | ||
|
|
36ba33c500 | ||
|
|
c2eeeecac8 | ||
|
|
c9d4c41db3 | ||
|
|
71f9b7f675 | ||
|
|
0ebd133eda | ||
|
|
d0e1162c96 | ||
|
|
7b4b630b2c | ||
|
|
5e517cfc77 | ||
|
|
2ea2146b34 | ||
|
|
856926d664 | ||
|
|
0f6477a253 | ||
|
|
580e9f6e10 | ||
|
|
69c460c756 | ||
|
|
06d193ad87 | ||
|
|
f6b10f3d84 | ||
|
|
f236a2c081 | ||
|
|
2866f7c441 | ||
|
|
948045ea32 | ||
|
|
080a742725 | ||
|
|
feaa16d748 | ||
|
|
6ca29a93bf | ||
|
|
20aac93731 | ||
|
|
43cc7e4a59 | ||
|
|
71ecf081c3 | ||
|
|
26ae708d6d | ||
|
|
7d64df05bc | ||
|
|
b6a36270b2 | ||
|
|
fa442ca788 | ||
|
|
558861b634 | ||
|
|
2c90ee23f9 | ||
|
|
78771cd72f | ||
|
|
bfc94cf284 | ||
|
|
cad15cdec2 | ||
|
|
96dfcd7149 | ||
|
|
c7d17d21e8 | ||
|
|
fe9acc898e | ||
|
|
b8dce3eeac | ||
|
|
1cbe1e894d | ||
|
|
81ef7b4c00 | ||
|
|
88e5c9e61b | ||
|
|
c0b598074c | ||
|
|
04fb215ff5 | ||
|
|
a2cba1bf23 | ||
|
|
29d383ad8b | ||
|
|
e4d6df39ff | ||
|
|
7ad42ec957 | ||
|
|
c74e6aaebb | ||
|
|
92d61eb19e | ||
|
|
6e479f918d | ||
|
|
8e8d0ad9aa | ||
|
|
400c78c600 | ||
|
|
f6f2712db0 | ||
|
|
1eee203f7f | ||
|
|
ec337a1c9f | ||
|
|
7577706a9d | ||
|
|
81b5e6c5f1 | ||
|
|
28f052d586 | ||
|
|
2eac618bba | ||
|
|
6f6de13813 | ||
|
|
be1a269f9f | ||
|
|
0d70be56d2 | ||
|
|
50655821c1 | ||
|
|
b254a72d41 | ||
|
|
2770ca1b65 | ||
|
|
0d7cb1a9be | ||
|
|
3abb91b2c4 | ||
|
|
a63b8df867 | ||
|
|
3b9feffc00 | ||
|
|
0c13cb829c | ||
|
|
8aba707b6a | ||
|
|
98bfcc4c75 | ||
|
|
3a668bbe96 | ||
|
|
f2f410093a | ||
|
|
8169c7de0d | ||
|
|
eefac19cc8 | ||
|
|
afc049154d | ||
|
|
4d1ccd9e27 | ||
|
|
c48c8b8ddb | ||
|
|
c2f692a4e4 | ||
|
|
d94b5a318d | ||
|
|
30cb218638 | ||
|
|
d9e0d55b88 | ||
|
|
1e9b4e2403 | ||
|
|
8657ba374a | ||
|
|
3a8b7e9b96 | ||
|
|
22b442d593 | ||
|
|
f7bfbd82dd | ||
|
|
d375164100 | ||
|
|
7fdac62393 | ||
|
|
2184e8ef58 | ||
|
|
87e8dd0d63 | ||
|
|
857843bc29 | ||
|
|
37a18299d0 | ||
|
|
e0c8ff10da | ||
|
|
61b02ec75c | ||
|
|
939b7f7cbf | ||
|
|
77da0da945 | ||
|
|
226d0e0196 | ||
|
|
4114760efd | ||
|
|
23042dfc3c | ||
|
|
9216a852d4 | ||
|
|
6478c80359 | ||
|
|
e18dc5cea3 | ||
|
|
fb5090fd3f | ||
|
|
39aa36e44b | ||
|
|
20a764a4a3 | ||
|
|
24ae45ce84 | ||
|
|
a14e7f0e84 | ||
|
|
7eee573fcb | ||
|
|
0894022695 | ||
|
|
3c717db3fd | ||
|
|
942f34a734 | ||
|
|
4821919695 | ||
|
|
34549b62bd | ||
|
|
d593f81a16 | ||
|
|
e05b23c388 | ||
|
|
d4c131b153 | ||
|
|
9836205725 | ||
|
|
98198d35ea | ||
|
|
12cd7a7083 | ||
|
|
e539152c6b | ||
|
|
6ed7c6610a | ||
|
|
d6d717c315 | ||
|
|
935e23449f | ||
|
|
ed9b38bc8d | ||
|
|
2c52f2ce13 | ||
|
|
1469ca7d0b | ||
|
|
73d525e84d | ||
|
|
f6c611d026 | ||
|
|
30b6321fd5 | ||
|
|
20a26c3dd4 | ||
|
|
d975eccaab | ||
|
|
cdef0bd503 | ||
|
|
a714114cd9 | ||
|
|
bf585de558 | ||
|
|
b7fd24074d | ||
|
|
d53a9f6530 | ||
|
|
dda95ea892 | ||
|
|
4908d03a88 | ||
|
|
229075048b | ||
|
|
0d49dd3648 | ||
|
|
214861d68f | ||
|
|
e7b96209cd | ||
|
|
753a1b511d | ||
|
|
6715dd6167 | ||
|
|
a061798a6f | ||
|
|
fa79a7cdab | ||
|
|
6884aac2a0 | ||
|
|
e61a12b4cb | ||
|
|
4542a12787 | ||
|
|
f0408c89ce | ||
|
|
7c080bd60c | ||
|
|
c87a3c7793 | ||
|
|
038a30feb0 | ||
|
|
c63f873a6f | ||
|
|
df93447d30 | ||
|
|
7477ff3b5c | ||
|
|
812b301aab | ||
|
|
9dfe342838 | ||
|
|
7813efad35 | ||
|
|
29cd474f44 | ||
|
|
008400d287 | ||
|
|
4b9d9c0df4 | ||
|
|
b28deaae2c | ||
|
|
4e6b8ead8f | ||
|
|
c2a137f0fb | ||
|
|
f6fcb7ee26 | ||
|
|
973d06d96f | ||
|
|
37bf57cf75 | ||
|
|
8d4c38f6b9 | ||
|
|
01b2a1ffeb | ||
|
|
b858d1ceeb | ||
|
|
14b908f8ab | ||
|
|
25ec1fce61 | ||
|
|
70965efd94 | ||
|
|
9f43e9e668 | ||
|
|
47ef075759 | ||
|
|
4544727654 | ||
|
|
6c89f351f4 | ||
|
|
35c06fabc5 | ||
|
|
cbe15e159a | ||
|
|
3411814ee3 | ||
|
|
12d3cda2d9 | ||
|
|
efc6a8a044 | ||
|
|
0d913a6401 | ||
|
|
2e3a860a46 | ||
|
|
c07723d49d | ||
|
|
7551ba3851 | ||
|
|
67f149ee06 | ||
|
|
d27e588672 | ||
|
|
2e333e6802 | ||
|
|
59773acdd2 | ||
|
|
454d789232 | ||
|
|
c349c89ae3 | ||
|
|
36dc7bd55e | ||
|
|
5a8bf7bf7d | ||
|
|
1042d4cfeb | ||
|
|
9f23b7e46f | ||
|
|
b7fe163de5 | ||
|
|
8c9f4c8700 | ||
|
|
419943a9d9 | ||
|
|
eee9062814 | ||
|
|
6db63c971e | ||
|
|
b88c7d63de | ||
|
|
9345334196 | ||
|
|
ae285f3417 | ||
|
|
b813964ee3 | ||
|
|
3f311fcc1e | ||
|
|
8d6d74c237 | ||
|
|
0df52d2c4f | ||
|
|
497049b3fd | ||
|
|
bec7fb5493 | ||
|
|
7b6f25cb9e | ||
|
|
865288976c | ||
|
|
00347cdc8e | ||
|
|
161b0da29a | ||
|
|
b7556f9b30 | ||
|
|
2b570ed68c | ||
|
|
d82a123cf8 | ||
|
|
e8ea6022f0 | ||
|
|
2908d966b6 | ||
|
|
2dc4b02fc8 | ||
|
|
f9f65a53fb | ||
|
|
ecde1d58b5 | ||
|
|
7fa97fa7b5 | ||
|
|
72db68dd39 | ||
|
|
d0224cecac | ||
|
|
49296cd5cb | ||
|
|
55e4951445 | ||
|
|
8e370876cc | ||
|
|
c71b31e3b8 | ||
|
|
44b11750d3 | ||
|
|
d337533907 | ||
|
|
fd6a23cf41 | ||
|
|
cec25740ea | ||
|
|
9170ffc371 | ||
|
|
6c805fff15 | ||
|
|
53d083a121 | ||
|
|
d8c3935e64 | ||
|
|
47d37aa628 | ||
|
|
6260241c55 | ||
|
|
6871331ff3 | ||
|
|
bb2aa904b2 | ||
|
|
e490bd7785 | ||
|
|
ca6a78882c | ||
|
|
321ac4c5f0 | ||
|
|
3929cc30ca | ||
|
|
25f67923ea | ||
|
|
5dc7faba0c | ||
|
|
2aa9171027 | ||
|
|
730a3bb7ce | ||
|
|
e9a2b002d8 | ||
|
|
faf025454f | ||
|
|
50f3b01eb7 | ||
|
|
f777d24679 | ||
|
|
d72fbe9125 | ||
|
|
3ad6d0890a | ||
|
|
776c884403 | ||
|
|
a4b5fc8100 | ||
|
|
b0547b5619 | ||
|
|
9cf8955d24 | ||
|
|
f7c820ffad | ||
|
|
1773e31499 | ||
|
|
466d0849e8 | ||
|
|
28b6afcb10 | ||
|
|
bdde30a456 | ||
|
|
aea169d4d4 | ||
|
|
b0aa4efd3d | ||
|
|
7dce68bdcc | ||
|
|
436ba6f244 | ||
|
|
79ded4bf19 | ||
|
|
5af23e760c | ||
|
|
7601861f21 | ||
|
|
1d1af26f7a | ||
|
|
47093bebd3 | ||
|
|
205d971253 | ||
|
|
49a787b1a8 | ||
|
|
a88d322e4f | ||
|
|
dfdaad0111 | ||
|
|
d80e8c6b15 | ||
|
|
d7d5317b0e | ||
|
|
445e9c9041 | ||
|
|
e96ef7338e | ||
|
|
ae960e735a | ||
|
|
fd19ac55e3 | ||
|
|
8a3a0688ba | ||
|
|
8d5efc901d | ||
|
|
5dcd806176 | ||
|
|
0448d56a4f | ||
|
|
f74132b0be | ||
|
|
6353deacf0 | ||
|
|
756f08b9ba | ||
|
|
2293696ab4 | ||
|
|
06c6da5c0d | ||
|
|
5261aba81f | ||
|
|
f170cb9214 | ||
|
|
8055773425 | ||
|
|
82fa58d4e3 | ||
|
|
57c6d616e6 | ||
|
|
639512a7c2 | ||
|
|
ac13e7362c | ||
|
|
1e7dfd3071 | ||
|
|
e7183e3ea9 | ||
|
|
c4e57040b7 | ||
|
|
c1a7d614a2 | ||
|
|
7392f9c662 | ||
|
|
aa6ef76043 | ||
|
|
f6f5ff0be9 | ||
|
|
00def82843 | ||
|
|
e08f1145c9 | ||
|
|
ddf82cf433 | ||
|
|
59a5e132fe | ||
|
|
7fc7105c48 | ||
|
|
3daf6f822a | ||
|
|
d7278477d6 | ||
|
|
d0571530eb | ||
|
|
392ee2e0fd | ||
|
|
562a55122b | ||
|
|
b226adeb2f | ||
|
|
12f027b3f4 | ||
|
|
9f8aa7c59d | ||
|
|
40f904391e | ||
|
|
bfb85527dc | ||
|
|
b868e2bd5b | ||
|
|
b7e74b479f | ||
|
|
7c92ecdc9d | ||
|
|
80bc235d6e | ||
|
|
883eaee014 | ||
|
|
ae319760a0 | ||
|
|
4e2d0aa892 | ||
|
|
5afb8b59c9 | ||
|
|
8136657dfc | ||
|
|
9b5185adc9 | ||
|
|
296433a1dd | ||
|
|
5033f189f9 | ||
|
|
d85185db2e | ||
|
|
76b6e0407d | ||
|
|
2fdc575441 | ||
|
|
7b9afa43ff | ||
|
|
49735bc120 | ||
|
|
a70696ea77 | ||
|
|
2849e0daed | ||
|
|
da27c0fb2d | ||
|
|
430980a836 | ||
|
|
51e33e1556 | ||
|
|
8d4a19cecf | ||
|
|
9a34518e54 | ||
|
|
39841f9edd | ||
|
|
1ccd5e4ff5 | ||
|
|
05d514ca8e | ||
|
|
c7c751ecd0 | ||
|
|
fa1f623801 | ||
|
|
8dbe753a25 | ||
|
|
a060000b70 | ||
|
|
b615cfc540 | ||
|
|
32b6eed691 | ||
|
|
ad03008a1a | ||
|
|
2ec1a2f862 | ||
|
|
f5f48f7480 | ||
|
|
8dbf2ced79 | ||
|
|
b951dbfced | ||
|
|
863a200989 | ||
|
|
8dafe103bd | ||
|
|
eda9800c93 | ||
|
|
6d983b5f45 | ||
|
|
65614497ed | ||
|
|
785bec98d3 | ||
|
|
6b08aa2198 | ||
|
|
dd76695d3a | ||
|
|
236a064cd3 | ||
|
|
dd685e50df | ||
|
|
85853295c4 | ||
|
|
4eb37cce26 | ||
|
|
d0698b32bb | ||
|
|
bc01007c16 | ||
|
|
fb2ca3e172 | ||
|
|
7d66cb2195 | ||
|
|
58cb4303ee | ||
|
|
56c70d300b | ||
|
|
1f16c47a2c | ||
|
|
7b14e9d5f6 | ||
|
|
966adfc081 | ||
|
|
d1a8202624 | ||
|
|
7a2df24129 | ||
|
|
ecb116aa99 | ||
|
|
8446e87cc2 | ||
|
|
fb19e7f15e | ||
|
|
c840f30a20 | ||
|
|
0813f02ca5 | ||
|
|
a7e2867526 | ||
|
|
077f3928cf | ||
|
|
c5528844fd | ||
|
|
55885e84ab | ||
|
|
2faf2ce468 | ||
|
|
7ab18fc434 | ||
|
|
a104a4bca6 | ||
|
|
6979c3e490 | ||
|
|
57ce73d540 | ||
|
|
424ebd154a | ||
|
|
7c7dc5328e | ||
|
|
cb655226de | ||
|
|
010ccf80f5 | ||
|
|
13ea910fa6 | ||
|
|
9c0d80cef4 | ||
|
|
620f4f2c10 | ||
|
|
89695022c7 | ||
|
|
c3d7348c73 | ||
|
|
ad178db28f | ||
|
|
984703c3c9 | ||
|
|
6b34568128 | ||
|
|
cf0d936195 | ||
|
|
0eab723243 | ||
|
|
935286ff94 | ||
|
|
db29180eec | ||
|
|
b5bed65d9e | ||
|
|
3f7f5bd8f0 | ||
|
|
043dc1b14b | ||
|
|
c5a6b17c15 | ||
|
|
16c3f3bcbd | ||
|
|
b0a184e937 | ||
|
|
21b430a575 | ||
|
|
2c1fdecc91 | ||
|
|
4d2169e48e | ||
|
|
b75aae99ca |
@@ -5,7 +5,7 @@ fail_fast: false
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.0.1
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
files: "erpnext.*"
|
||||
@@ -15,6 +15,10 @@ repos:
|
||||
args: ['--branch', 'develop']
|
||||
- id: check-merge-conflict
|
||||
- id: check-ast
|
||||
- id: check-json
|
||||
- id: check-toml
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 5.0.4
|
||||
|
||||
@@ -3,7 +3,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.47.3"
|
||||
__version__ = "14.60.1"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -53,8 +53,13 @@
|
||||
},
|
||||
"II. Forderungen und sonstige Vermögensgegenstände": {
|
||||
"is_group": 1,
|
||||
"Ford. a. Lieferungen und Leistungen": {
|
||||
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": {
|
||||
"account_number": "1400",
|
||||
"account_type": "Receivable",
|
||||
"is_group": 1
|
||||
},
|
||||
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
|
||||
"account_number": "1410",
|
||||
"account_type": "Receivable"
|
||||
},
|
||||
"Durchlaufende Posten": {
|
||||
@@ -180,8 +185,13 @@
|
||||
},
|
||||
"IV. Verbindlichkeiten aus Lieferungen und Leistungen": {
|
||||
"is_group": 1,
|
||||
"Verbindlichkeiten aus Lieferungen u. Leistungen": {
|
||||
"Verbindlichkeiten aus Lieferungen und Leistungen mit Kontokorrent": {
|
||||
"account_number": "1600",
|
||||
"account_type": "Payable",
|
||||
"is_group": 1
|
||||
},
|
||||
"Verbindlichkeiten aus Lieferungen und Leistungen ohne Kontokorrent": {
|
||||
"account_number": "1610",
|
||||
"account_type": "Payable"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -407,13 +407,10 @@
|
||||
"Bewertungskorrektur zu Forderungen aus Lieferungen und Leistungen": {
|
||||
"account_number": "9960"
|
||||
},
|
||||
"Debitoren": {
|
||||
"is_group": 1,
|
||||
"account_number": "10000"
|
||||
},
|
||||
"Forderungen aus Lieferungen und Leistungen": {
|
||||
"Forderungen aus Lieferungen und Leistungen mit Kontokorrent": {
|
||||
"account_number": "1200",
|
||||
"account_type": "Receivable"
|
||||
"account_type": "Receivable",
|
||||
"is_group": 1
|
||||
},
|
||||
"Forderungen aus Lieferungen und Leistungen ohne Kontokorrent": {
|
||||
"account_number": "1210"
|
||||
@@ -1138,18 +1135,15 @@
|
||||
"Bewertungskorrektur zu Verb. aus Lieferungen und Leistungen": {
|
||||
"account_number": "9964"
|
||||
},
|
||||
"Kreditoren": {
|
||||
"account_number": "70000",
|
||||
"Verb. aus Lieferungen und Leistungen mit Kontokorrent": {
|
||||
"account_number": "3300",
|
||||
"account_type": "Payable",
|
||||
"is_group": 1,
|
||||
"Wareneingangs-Verrechnungskonto" : {
|
||||
"Wareneingangs-Verrechnungskonto" : {
|
||||
"account_number": "70001",
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
}
|
||||
},
|
||||
"Verb. aus Lieferungen und Leistungen": {
|
||||
"account_number": "3300",
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Verb. aus Lieferungen und Leistungen ohne Kontokorrent": {
|
||||
"account_number": "3310"
|
||||
},
|
||||
|
||||
@@ -68,7 +68,12 @@
|
||||
"enable_party_matching",
|
||||
"enable_fuzzy_matching",
|
||||
"tab_break_dpet",
|
||||
"show_balance_in_coa"
|
||||
"show_balance_in_coa",
|
||||
"reports_tab",
|
||||
"remarks_section",
|
||||
"general_ledger_remarks_length",
|
||||
"column_break_lvjk",
|
||||
"receivable_payable_remarks_length"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -429,6 +434,34 @@
|
||||
"fieldname": "show_balance_in_coa",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Balances in Chart Of Accounts"
|
||||
},
|
||||
{
|
||||
"fieldname": "reports_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Reports"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Truncates 'Remarks' column to set character length",
|
||||
"fieldname": "general_ledger_remarks_length",
|
||||
"fieldtype": "Int",
|
||||
"label": "General Ledger"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Truncates 'Remarks' column to set character length",
|
||||
"fieldname": "receivable_payable_remarks_length",
|
||||
"fieldtype": "Int",
|
||||
"label": "Accounts Receivable/Payable"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lvjk",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "remarks_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Remarks Column Length"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -436,7 +469,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-27 15:05:34.000264",
|
||||
"modified": "2023-11-20 09:37:47.650347",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"ignore_user_permissions": 1,
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
@@ -19,7 +20,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-05-01 12:32:34.044911",
|
||||
"modified": "2024-01-03 11:13:02.669632",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Allowed To Transact With",
|
||||
@@ -28,5 +29,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -137,7 +137,7 @@ frappe.ui.form.on("Bank Reconciliation Tool", {
|
||||
"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_account_balance",
|
||||
args: {
|
||||
bank_account: frm.doc.bank_account,
|
||||
till_date: frm.doc.bank_statement_from_date,
|
||||
till_date: frappe.datetime.add_days(frm.doc.bank_statement_from_date, -1)
|
||||
},
|
||||
callback: (response) => {
|
||||
frm.set_value("account_opening_balance", response.message);
|
||||
|
||||
@@ -112,7 +112,8 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
for party in parties:
|
||||
filters = {"status": "Active"} if party == "Employee" else {"disabled": 0}
|
||||
names = frappe.get_all(party, filters=filters, pluck=party.lower() + "_name")
|
||||
field = party.lower() + "_name"
|
||||
names = frappe.get_all(party, filters=filters, fields=[f"{field} as party_name", "name"])
|
||||
|
||||
for field in ["bank_party_name", "description"]:
|
||||
if not self.get(field):
|
||||
@@ -131,7 +132,11 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
def fuzzy_search_and_return_result(self, party, names, field) -> Union[Tuple, None]:
|
||||
skip = False
|
||||
result = process.extract(query=self.get(field), choices=names, scorer=fuzz.token_set_ratio)
|
||||
result = process.extract(
|
||||
query=self.get(field),
|
||||
choices={row.get("name"): row.get("party_name") for row in names},
|
||||
scorer=fuzz.token_set_ratio,
|
||||
)
|
||||
party_name, skip = self.process_fuzzy_result(result)
|
||||
|
||||
if not party_name:
|
||||
@@ -149,14 +154,14 @@ class AutoMatchbyPartyNameDescription:
|
||||
|
||||
Returns: Result, Skip (whether or not to discontinue matching)
|
||||
"""
|
||||
PARTY, SCORE, CUTOFF = 0, 1, 80
|
||||
SCORE, PARTY_ID, CUTOFF = 1, 2, 80
|
||||
|
||||
if not result or not len(result):
|
||||
return None, False
|
||||
|
||||
first_result = result[0]
|
||||
if len(result) == 1:
|
||||
return (first_result[PARTY] if first_result[SCORE] > CUTOFF else None), True
|
||||
return (first_result[PARTY_ID] if first_result[SCORE] > CUTOFF else None), True
|
||||
|
||||
second_result = result[1]
|
||||
if first_result[SCORE] > CUTOFF:
|
||||
@@ -165,7 +170,7 @@ class AutoMatchbyPartyNameDescription:
|
||||
if first_result[SCORE] == second_result[SCORE]:
|
||||
return None, True
|
||||
|
||||
return first_result[PARTY], True
|
||||
return first_result[PARTY_ID], True
|
||||
else:
|
||||
return None, False
|
||||
|
||||
|
||||
@@ -1,457 +1,152 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"actions": [],
|
||||
"autoname": "naming_series:",
|
||||
"beta": 0,
|
||||
"creation": "2018-06-18 16:51:49.994750",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"naming_series",
|
||||
"user",
|
||||
"date",
|
||||
"from_time",
|
||||
"time",
|
||||
"expense",
|
||||
"custody",
|
||||
"returns",
|
||||
"outstanding_amount",
|
||||
"payments",
|
||||
"net_amount",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "POS-CLO-",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 1,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Series",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "POS-CLO-",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "User",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "User",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "Today",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "From Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "time",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 1,
|
||||
"label": "To Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.00",
|
||||
"fieldname": "expense",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Expense",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Expense"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.00",
|
||||
"fieldname": "custody",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Custody",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"label": "Custody"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.00",
|
||||
"fieldname": "returns",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Returns",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "2",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"precision": "2"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.00",
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Outstanding Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "0.0",
|
||||
"fieldname": "payments",
|
||||
"fieldtype": "Table",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Payments",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Cashier Closing Payments",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"options": "Cashier Closing Payments"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "net_amount",
|
||||
"fieldtype": "Float",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 1,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Net Amount",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Amended From",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Cashier Closing",
|
||||
"permlevel": 0,
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 1,
|
||||
"issingle": 0,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"modified": "2019-02-19 08:35:24.157327",
|
||||
"links": [],
|
||||
"modified": "2023-12-28 13:15:46.858427",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cashier Closing",
|
||||
"name_case": "",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"set_user_permissions": 0,
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0,
|
||||
"track_views": 0
|
||||
}
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -53,10 +53,18 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
of Accounts. Please enter the account names and add more rows as per your requirement.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label : "Company",
|
||||
fieldname: "company",
|
||||
fieldtype: "Link",
|
||||
reqd: 1,
|
||||
hidden: 1,
|
||||
default: frm.doc.company,
|
||||
},
|
||||
],
|
||||
primary_action: function() {
|
||||
var data = d.get_values();
|
||||
let data = d.get_values();
|
||||
|
||||
if (!data.template_type) {
|
||||
frappe.throw(__('Please select <b>Template Type</b> to download template'));
|
||||
@@ -66,7 +74,8 @@ frappe.ui.form.on('Chart of Accounts Importer', {
|
||||
'/api/method/erpnext.accounts.doctype.chart_of_accounts_importer.chart_of_accounts_importer.download_template',
|
||||
{
|
||||
file_type: data.file_type,
|
||||
template_type: data.template_type
|
||||
template_type: data.template_type,
|
||||
company: data.company
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ from functools import reduce
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.desk.form.linked_with import get_linked_fields
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, cstr
|
||||
from frappe.utils.csvutils import UnicodeWriter
|
||||
@@ -112,7 +113,7 @@ def generate_data_from_csv(file_doc, as_dict=False):
|
||||
if as_dict:
|
||||
data.append({frappe.scrub(header): row[index] for index, header in enumerate(headers)})
|
||||
else:
|
||||
if not row[1]:
|
||||
if not row[1] and len(row) > 1:
|
||||
row[1] = row[0]
|
||||
row[3] = row[2]
|
||||
data.append(row)
|
||||
@@ -294,10 +295,8 @@ def build_response_as_excel(writer):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def download_template(file_type, template_type):
|
||||
data = frappe._dict(frappe.local.form_dict)
|
||||
|
||||
writer = get_template(template_type)
|
||||
def download_template(file_type, template_type, company):
|
||||
writer = get_template(template_type, company)
|
||||
|
||||
if file_type == "CSV":
|
||||
# download csv file
|
||||
@@ -308,8 +307,7 @@ def download_template(file_type, template_type):
|
||||
build_response_as_excel(writer)
|
||||
|
||||
|
||||
def get_template(template_type):
|
||||
|
||||
def get_template(template_type, company):
|
||||
fields = [
|
||||
"Account Name",
|
||||
"Parent Account",
|
||||
@@ -335,34 +333,17 @@ def get_template(template_type):
|
||||
["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
|
||||
)
|
||||
else:
|
||||
writer = get_sample_template(writer)
|
||||
writer = get_sample_template(writer, company)
|
||||
|
||||
return writer
|
||||
|
||||
|
||||
def get_sample_template(writer):
|
||||
template = [
|
||||
["Application Of Funds(Assets)", "", "", "", 1, "", "Asset"],
|
||||
["Sources Of Funds(Liabilities)", "", "", "", 1, "", "Liability"],
|
||||
["Equity", "", "", "", 1, "", "Equity"],
|
||||
["Expenses", "", "", "", 1, "", "Expense"],
|
||||
["Income", "", "", "", 1, "", "Income"],
|
||||
["Bank Accounts", "Application Of Funds(Assets)", "", "", 1, "Bank", "Asset"],
|
||||
["Cash In Hand", "Application Of Funds(Assets)", "", "", 1, "Cash", "Asset"],
|
||||
["Stock Assets", "Application Of Funds(Assets)", "", "", 1, "Stock", "Asset"],
|
||||
["Cost Of Goods Sold", "Expenses", "", "", 0, "Cost of Goods Sold", "Expense"],
|
||||
["Asset Depreciation", "Expenses", "", "", 0, "Depreciation", "Expense"],
|
||||
["Fixed Assets", "Application Of Funds(Assets)", "", "", 0, "Fixed Asset", "Asset"],
|
||||
["Accounts Payable", "Sources Of Funds(Liabilities)", "", "", 0, "Payable", "Liability"],
|
||||
["Accounts Receivable", "Application Of Funds(Assets)", "", "", 1, "Receivable", "Asset"],
|
||||
["Stock Expenses", "Expenses", "", "", 0, "Stock Adjustment", "Expense"],
|
||||
["Sample Bank", "Bank Accounts", "", "", 0, "Bank", "Asset"],
|
||||
["Cash", "Cash In Hand", "", "", 0, "Cash", "Asset"],
|
||||
["Stores", "Stock Assets", "", "", 0, "Stock", "Asset"],
|
||||
]
|
||||
|
||||
for row in template:
|
||||
writer.writerow(row)
|
||||
def get_sample_template(writer, company):
|
||||
currency = frappe.db.get_value("Company", company, "default_currency")
|
||||
with open(os.path.join(os.path.dirname(__file__), "coa_sample_template.csv"), "r") as f:
|
||||
for row in f:
|
||||
row = row.strip().split(",") + [currency]
|
||||
writer.writerow(row)
|
||||
|
||||
return writer
|
||||
|
||||
@@ -453,14 +434,11 @@ def get_mandatory_account_types():
|
||||
|
||||
|
||||
def unset_existing_data(company):
|
||||
linked = frappe.db.sql(
|
||||
'''select fieldname from tabDocField
|
||||
where fieldtype="Link" and options="Account" and parent="Company"''',
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
# remove accounts data from company
|
||||
update_values = {d.fieldname: "" for d in linked}
|
||||
|
||||
fieldnames = get_linked_fields("Account").get("Company", {}).get("fieldname", [])
|
||||
linked = [{"fieldname": name} for name in fieldnames]
|
||||
update_values = {d.get("fieldname"): "" for d in linked}
|
||||
frappe.db.set_value("Company", company, update_values, update_values)
|
||||
|
||||
# remove accounts data from various doctypes
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
Application Of Funds(Assets),,,,1,,Asset
|
||||
Sources Of Funds(Liabilities),,,,1,,Liability
|
||||
Equity,,,,1,,Equity
|
||||
Expenses,,,,1,Expense Account,Expense
|
||||
Income,,,,1,Income Account,Income
|
||||
Bank Accounts,Application Of Funds(Assets),,,1,Bank,Asset
|
||||
Cash In Hand,Application Of Funds(Assets),,,1,Cash,Asset
|
||||
Stock Assets,Application Of Funds(Assets),,,1,Stock,Asset
|
||||
Cost Of Goods Sold,Expenses,,,0,Cost of Goods Sold,Expense
|
||||
Asset Depreciation,Expenses,,,0,Depreciation,Expense
|
||||
Fixed Assets,Application Of Funds(Assets),,,0,Fixed Asset,Asset
|
||||
Accounts Payable,Sources Of Funds(Liabilities),,,0,Payable,Liability
|
||||
Accounts Receivable,Application Of Funds(Assets),,,1,Receivable,Asset
|
||||
Stock Expenses,Expenses,,,0,Stock Adjustment,Expense
|
||||
Sample Bank,Bank Accounts,,,0,Bank,Asset
|
||||
Cash,Cash In Hand,,,0,Cash,Asset
|
||||
Stores,Stock Assets,,,0,Stock,Asset
|
||||
|
@@ -192,7 +192,7 @@ class ExchangeRateRevaluation(Document):
|
||||
# round off balance based on currency precision
|
||||
# and consider debit-credit difference allowance
|
||||
currency_precision = get_currency_precision()
|
||||
rounding_loss_allowance = float(rounding_loss_allowance) or 0.05
|
||||
rounding_loss_allowance = float(rounding_loss_allowance)
|
||||
for acc in account_details:
|
||||
acc.balance_in_account_currency = flt(acc.balance_in_account_currency, currency_precision)
|
||||
if abs(acc.balance_in_account_currency) <= rounding_loss_allowance:
|
||||
|
||||
@@ -138,8 +138,7 @@
|
||||
"label": "Against Voucher Type",
|
||||
"oldfieldname": "against_voucher_type",
|
||||
"oldfieldtype": "Data",
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "against_voucher",
|
||||
@@ -158,8 +157,7 @@
|
||||
"label": "Voucher Type",
|
||||
"oldfieldname": "voucher_type",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "DocType",
|
||||
"search_index": 1
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_no",
|
||||
@@ -291,4 +289,4 @@
|
||||
"search_fields": "voucher_no,account,posting_date,against_voucher",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
|
||||
frappe.ui.form.on("Journal Entry", {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch("bank_account", "account", "account");
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger', 'Asset', 'Asset Movement', 'Repost Accounting Ledger'];
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', "Repost Payment Ledger", 'Asset', 'Asset Movement', "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
@@ -51,7 +51,7 @@ frappe.ui.form.on("Journal Entry", {
|
||||
}, __('Make'));
|
||||
}
|
||||
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
||||
},
|
||||
before_save: function(frm) {
|
||||
if ((frm.doc.docstatus == 0) && (!frm.doc.is_system_generated)) {
|
||||
|
||||
@@ -548,8 +548,16 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 176,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-10 14:32:22.366895",
|
||||
"links": [
|
||||
{
|
||||
"is_child_table": 1,
|
||||
"link_doctype": "Bank Transaction Payments",
|
||||
"link_fieldname": "payment_entry",
|
||||
"parent_doctype": "Bank Transaction",
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2023-11-23 12:11:04.128015",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry",
|
||||
|
||||
@@ -98,6 +98,8 @@ class JournalEntry(AccountsController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
)
|
||||
self.make_gl_entries(1)
|
||||
self.update_advance_paid()
|
||||
@@ -496,7 +498,7 @@ class JournalEntry(AccountsController):
|
||||
).format(d.reference_name, d.account)
|
||||
)
|
||||
else:
|
||||
dr_or_cr = "debit" if d.credit > 0 else "credit"
|
||||
dr_or_cr = "debit" if flt(d.credit) > 0 else "credit"
|
||||
valid = False
|
||||
for jvd in against_entries:
|
||||
if flt(jvd[dr_or_cr]) > 0:
|
||||
|
||||
@@ -81,7 +81,6 @@
|
||||
},
|
||||
{
|
||||
"account": "Sales - _TC",
|
||||
"cost_center": "_Test Cost Center - _TC",
|
||||
"credit_in_account_currency": 400.0,
|
||||
"debit_in_account_currency": 0.0,
|
||||
"doctype": "Journal Entry Account",
|
||||
|
||||
@@ -203,7 +203,8 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Reference Type",
|
||||
"no_copy": 1,
|
||||
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry"
|
||||
"options": "\nSales Invoice\nPurchase Invoice\nJournal Entry\nSales Order\nPurchase Order\nExpense Claim\nAsset\nLoan\nPayroll Entry\nEmployee Advance\nExchange Rate Revaluation\nInvoice Discounting\nFees\nFull and Final Statement\nPayment Entry",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "reference_name",
|
||||
@@ -211,7 +212,8 @@
|
||||
"in_list_view": 1,
|
||||
"label": "Reference Name",
|
||||
"no_copy": 1,
|
||||
"options": "reference_type"
|
||||
"options": "reference_type",
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.reference_type&&!in_list(doc.reference_type, ['Expense Claim', 'Asset', 'Employee Loan', 'Employee Advance'])",
|
||||
@@ -278,13 +280,14 @@
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Reference Detail No",
|
||||
"no_copy": 1
|
||||
"no_copy": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-16 14:11:13.507807",
|
||||
"modified": "2023-11-23 11:44:25.841187",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
|
||||
@@ -7,7 +7,7 @@ cur_frm.cscript.tax_table = "Advance Taxes and Charges";
|
||||
|
||||
frappe.ui.form.on('Payment Entry', {
|
||||
onload: function(frm) {
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payments', 'Unreconcile Payment Entries'];
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Journal Entry', 'Repost Payment Ledger','Repost Accounting Ledger', 'Unreconcile Payment', 'Unreconcile Payment Entries'];
|
||||
|
||||
if(frm.doc.__islocal) {
|
||||
if (!frm.doc.paid_from) frm.set_value("paid_from_account_currency", null);
|
||||
@@ -158,7 +158,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}, __('Actions'));
|
||||
|
||||
}
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(frm);
|
||||
},
|
||||
|
||||
validate_company: (frm) => {
|
||||
@@ -829,7 +829,6 @@ frappe.ui.form.on('Payment Entry', {
|
||||
else
|
||||
total_negative_outstanding += Math.abs(flt(row.outstanding_amount));
|
||||
})
|
||||
|
||||
var allocated_negative_outstanding = 0;
|
||||
if (
|
||||
(frm.doc.payment_type=="Receive" && frm.doc.party_type=="Customer") ||
|
||||
@@ -844,6 +843,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
|
||||
} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
|
||||
total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"))
|
||||
if(paid_amount > total_negative_outstanding) {
|
||||
if(total_negative_outstanding == 0) {
|
||||
frappe.msgprint(
|
||||
|
||||
@@ -739,8 +739,16 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-19 11:38:04.387219",
|
||||
"links": [
|
||||
{
|
||||
"is_child_table": 1,
|
||||
"link_doctype": "Bank Transaction Payments",
|
||||
"link_fieldname": "payment_entry",
|
||||
"parent_doctype": "Bank Transaction",
|
||||
"table_fieldname": "payment_entries"
|
||||
}
|
||||
],
|
||||
"modified": "2023-11-23 12:07:20.887885",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
@@ -786,4 +794,4 @@
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import frappe
|
||||
from frappe import ValidationError, _, qb, scrub, throw
|
||||
from frappe.utils import cint, comma_or, flt, getdate, nowdate
|
||||
from frappe.utils.data import comma_and, fmt_money
|
||||
from pypika import Case
|
||||
from pypika.functions import Coalesce, Sum
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.bank_account.bank_account import (
|
||||
@@ -107,7 +109,7 @@ class PaymentEntry(AccountsController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payments",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
)
|
||||
super(PaymentEntry, self).on_cancel()
|
||||
@@ -913,8 +915,11 @@ class PaymentEntry(AccountsController):
|
||||
):
|
||||
return
|
||||
|
||||
total_negative_outstanding = sum(
|
||||
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
|
||||
total_negative_outstanding = flt(
|
||||
sum(
|
||||
abs(flt(d.outstanding_amount)) for d in self.get("references") if flt(d.outstanding_amount) < 0
|
||||
),
|
||||
self.references[0].precision("outstanding_amount") if self.references else None,
|
||||
)
|
||||
|
||||
paid_amount = self.paid_amount if self.payment_type == "Receive" else self.received_amount
|
||||
@@ -1549,13 +1554,43 @@ def get_outstanding_reference_documents(args):
|
||||
return data
|
||||
|
||||
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||
invoice_ref_based_on_payment_terms = {}
|
||||
def split_invoices_based_on_payment_terms(outstanding_invoices, company) -> list:
|
||||
"""Split a list of invoices based on their payment terms."""
|
||||
exc_rates = get_currency_data(outstanding_invoices, company)
|
||||
|
||||
outstanding_invoices_after_split = []
|
||||
for entry in outstanding_invoices:
|
||||
if entry.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if payment_term_template := frappe.db.get_value(
|
||||
entry.voucher_type, entry.voucher_no, "payment_terms_template"
|
||||
):
|
||||
split_rows = get_split_invoice_rows(entry, payment_term_template, exc_rates)
|
||||
if not split_rows:
|
||||
continue
|
||||
|
||||
if len(split_rows) > 1:
|
||||
frappe.msgprint(
|
||||
_("Splitting {0} {1} into {2} rows as per Payment Terms").format(
|
||||
_(entry.voucher_type), frappe.bold(entry.voucher_no), len(split_rows)
|
||||
),
|
||||
alert=True,
|
||||
)
|
||||
outstanding_invoices_after_split += split_rows
|
||||
continue
|
||||
|
||||
# If not an invoice or no payment terms template, add as it is
|
||||
outstanding_invoices_after_split.append(entry)
|
||||
|
||||
return outstanding_invoices_after_split
|
||||
|
||||
|
||||
def get_currency_data(outstanding_invoices: list, company: str = None) -> dict:
|
||||
"""Get currency and conversion data for a list of invoices."""
|
||||
exc_rates = frappe._dict()
|
||||
company_currency = (
|
||||
frappe.db.get_value("Company", company, "default_currency") if company else None
|
||||
)
|
||||
exc_rates = frappe._dict()
|
||||
|
||||
for doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
invoices = [x.voucher_no for x in outstanding_invoices if x.voucher_type == doctype]
|
||||
for x in frappe.db.get_all(
|
||||
@@ -1570,71 +1605,54 @@ def split_invoices_based_on_payment_terms(outstanding_invoices, company):
|
||||
company_currency=company_currency,
|
||||
)
|
||||
|
||||
for idx, d in enumerate(outstanding_invoices):
|
||||
if d.voucher_type in ["Sales Invoice", "Purchase Invoice"]:
|
||||
payment_term_template = frappe.db.get_value(
|
||||
d.voucher_type, d.voucher_no, "payment_terms_template"
|
||||
return exc_rates
|
||||
|
||||
|
||||
def get_split_invoice_rows(invoice: dict, payment_term_template: str, exc_rates: dict) -> list:
|
||||
"""Split invoice based on its payment schedule table."""
|
||||
split_rows = []
|
||||
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
||||
)
|
||||
|
||||
if not allocate_payment_based_on_payment_terms:
|
||||
return [invoice]
|
||||
|
||||
payment_schedule = frappe.get_all(
|
||||
"Payment Schedule", filters={"parent": invoice.voucher_no}, fields=["*"], order_by="due_date"
|
||||
)
|
||||
for payment_term in payment_schedule:
|
||||
if not payment_term.outstanding > 0.1:
|
||||
continue
|
||||
|
||||
doc_details = exc_rates.get(payment_term.parent, None)
|
||||
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||
doc_details.party_account_currency != doc_details.company_currency
|
||||
)
|
||||
payment_term_outstanding = flt(payment_term.outstanding)
|
||||
if not is_multi_currency_acc:
|
||||
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||
|
||||
split_rows.append(
|
||||
frappe._dict(
|
||||
{
|
||||
"due_date": invoice.due_date,
|
||||
"currency": invoice.currency,
|
||||
"voucher_no": invoice.voucher_no,
|
||||
"voucher_type": invoice.voucher_type,
|
||||
"posting_date": invoice.posting_date,
|
||||
"invoice_amount": flt(invoice.invoice_amount),
|
||||
"outstanding_amount": payment_term_outstanding
|
||||
if payment_term_outstanding
|
||||
else invoice.outstanding_amount,
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"payment_amount": payment_term.payment_amount,
|
||||
"payment_term": payment_term.payment_term,
|
||||
}
|
||||
)
|
||||
if payment_term_template:
|
||||
allocate_payment_based_on_payment_terms = frappe.db.get_value(
|
||||
"Payment Terms Template", payment_term_template, "allocate_payment_based_on_payment_terms"
|
||||
)
|
||||
if allocate_payment_based_on_payment_terms:
|
||||
payment_schedule = frappe.get_all(
|
||||
"Payment Schedule", filters={"parent": d.voucher_no}, fields=["*"]
|
||||
)
|
||||
)
|
||||
|
||||
for payment_term in payment_schedule:
|
||||
if payment_term.outstanding > 0.1:
|
||||
doc_details = exc_rates.get(payment_term.parent, None)
|
||||
is_multi_currency_acc = (doc_details.currency != doc_details.company_currency) and (
|
||||
doc_details.party_account_currency != doc_details.company_currency
|
||||
)
|
||||
payment_term_outstanding = flt(payment_term.outstanding)
|
||||
if not is_multi_currency_acc:
|
||||
payment_term_outstanding = doc_details.conversion_rate * flt(payment_term.outstanding)
|
||||
|
||||
invoice_ref_based_on_payment_terms.setdefault(idx, [])
|
||||
invoice_ref_based_on_payment_terms[idx].append(
|
||||
frappe._dict(
|
||||
{
|
||||
"due_date": d.due_date,
|
||||
"currency": d.currency,
|
||||
"voucher_no": d.voucher_no,
|
||||
"voucher_type": d.voucher_type,
|
||||
"posting_date": d.posting_date,
|
||||
"invoice_amount": flt(d.invoice_amount),
|
||||
"outstanding_amount": payment_term_outstanding
|
||||
if payment_term_outstanding
|
||||
else d.outstanding_amount,
|
||||
"payment_term_outstanding": payment_term_outstanding,
|
||||
"payment_amount": payment_term.payment_amount,
|
||||
"payment_term": payment_term.payment_term,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
outstanding_invoices_after_split = []
|
||||
if invoice_ref_based_on_payment_terms:
|
||||
for idx, ref in invoice_ref_based_on_payment_terms.items():
|
||||
voucher_no = ref[0]["voucher_no"]
|
||||
voucher_type = ref[0]["voucher_type"]
|
||||
|
||||
frappe.msgprint(
|
||||
_("Spliting {} {} into {} row(s) as per Payment Terms").format(
|
||||
voucher_type, voucher_no, len(ref)
|
||||
),
|
||||
alert=True,
|
||||
)
|
||||
|
||||
outstanding_invoices_after_split += invoice_ref_based_on_payment_terms[idx]
|
||||
|
||||
existing_row = list(filter(lambda x: x.get("voucher_no") == voucher_no, outstanding_invoices))
|
||||
index = outstanding_invoices.index(existing_row[0])
|
||||
outstanding_invoices.pop(index)
|
||||
|
||||
outstanding_invoices_after_split += outstanding_invoices
|
||||
return outstanding_invoices_after_split
|
||||
return split_rows
|
||||
|
||||
|
||||
def get_orders_to_be_billed(
|
||||
@@ -1838,18 +1856,24 @@ def get_company_defaults(company):
|
||||
|
||||
|
||||
def get_outstanding_on_journal_entry(name):
|
||||
res = frappe.db.sql(
|
||||
"SELECT "
|
||||
'CASE WHEN party_type IN ("Customer") '
|
||||
"THEN ifnull(sum(debit_in_account_currency - credit_in_account_currency), 0) "
|
||||
"ELSE ifnull(sum(credit_in_account_currency - debit_in_account_currency), 0) "
|
||||
"END as outstanding_amount "
|
||||
"FROM `tabGL Entry` WHERE (voucher_no=%s OR against_voucher=%s) "
|
||||
"AND party_type IS NOT NULL "
|
||||
'AND party_type != ""',
|
||||
(name, name),
|
||||
as_dict=1,
|
||||
)
|
||||
gl = frappe.qb.DocType("GL Entry")
|
||||
res = (
|
||||
frappe.qb.from_(gl)
|
||||
.select(
|
||||
Case()
|
||||
.when(
|
||||
gl.party_type == "Customer",
|
||||
Coalesce(Sum(gl.debit_in_account_currency - gl.credit_in_account_currency), 0),
|
||||
)
|
||||
.else_(Coalesce(Sum(gl.credit_in_account_currency - gl.debit_in_account_currency), 0))
|
||||
.as_("outstanding_amount")
|
||||
)
|
||||
.where(
|
||||
(Coalesce(gl.party_type, "") != "")
|
||||
& (gl.is_cancelled == 0)
|
||||
& ((gl.voucher_no == name) | (gl.against_voucher == name))
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
outstanding_amount = res[0].get("outstanding_amount", 0) if res else 0
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import json
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import flt, nowdate
|
||||
from frappe.utils import add_days, flt, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
InvalidPaymentEntry,
|
||||
get_outstanding_reference_documents,
|
||||
get_payment_entry,
|
||||
get_reference_details,
|
||||
)
|
||||
@@ -21,6 +23,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import (
|
||||
create_sales_invoice,
|
||||
create_sales_invoice_against_cost_center,
|
||||
)
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.setup.doctype.employee.test_employee import make_employee
|
||||
|
||||
@@ -1219,6 +1222,115 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, so.rounded_total)
|
||||
|
||||
def test_outstanding_invoices_api(self):
|
||||
"""
|
||||
Test if `get_outstanding_reference_documents` fetches invoices in the right order.
|
||||
"""
|
||||
customer = create_customer("Max Mustermann", "INR")
|
||||
create_payment_terms_template()
|
||||
|
||||
# SI has an earlier due date and SI2 has a later due date
|
||||
si = create_sales_invoice(
|
||||
qty=1, rate=100, customer=customer, posting_date=add_days(nowdate(), -4)
|
||||
)
|
||||
si2 = create_sales_invoice(do_not_save=1, qty=1, rate=100, customer=customer)
|
||||
si2.payment_terms_template = "Test Receivable Template"
|
||||
si2.submit()
|
||||
|
||||
args = {
|
||||
"posting_date": nowdate(),
|
||||
"company": "_Test Company",
|
||||
"party_type": "Customer",
|
||||
"payment_type": "Pay",
|
||||
"party": customer,
|
||||
"party_account": "Debtors - _TC",
|
||||
}
|
||||
args.update(
|
||||
{
|
||||
"get_outstanding_invoices": True,
|
||||
"from_posting_date": add_days(nowdate(), -4),
|
||||
"to_posting_date": add_days(nowdate(), 2),
|
||||
}
|
||||
)
|
||||
references = get_outstanding_reference_documents(args)
|
||||
|
||||
self.assertEqual(len(references), 3)
|
||||
self.assertEqual(references[0].voucher_no, si.name)
|
||||
self.assertEqual(references[1].voucher_no, si2.name)
|
||||
self.assertEqual(references[2].voucher_no, si2.name)
|
||||
self.assertEqual(references[1].payment_term, "Basic Amount Receivable")
|
||||
self.assertEqual(references[2].payment_term, "Tax Receivable")
|
||||
|
||||
def test_partial_cancel_for_payment_entry(self):
|
||||
si = create_sales_invoice()
|
||||
|
||||
pe = get_payment_entry(si.doctype, si.name)
|
||||
pe.save()
|
||||
pe.submit()
|
||||
|
||||
# Additional GL Entry
|
||||
tax_amount = 10
|
||||
reference_row = pe.references[0]
|
||||
gl_args = {
|
||||
"party_type": pe.party_type,
|
||||
"party": pe.party,
|
||||
"against_voucher_type": reference_row.reference_doctype,
|
||||
"against_voucher": reference_row.reference_name,
|
||||
"voucher_detail_no": reference_row.name,
|
||||
}
|
||||
|
||||
gl_dicts = []
|
||||
|
||||
gl_dicts.extend(
|
||||
[
|
||||
pe.get_gl_dict(
|
||||
{
|
||||
"account": pe.paid_to,
|
||||
"credit": tax_amount,
|
||||
"credit_in_account_currency": tax_amount,
|
||||
**gl_args,
|
||||
}
|
||||
),
|
||||
pe.get_gl_dict(
|
||||
{
|
||||
"account": pe.paid_from,
|
||||
"debit": tax_amount,
|
||||
"debit_in_account_currency": tax_amount,
|
||||
**gl_args,
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
make_gl_entries(gl_dicts)
|
||||
|
||||
# Assert PLEs Before
|
||||
self.assertPLEntries(
|
||||
pe,
|
||||
[
|
||||
{"amount": -100.0, "against_voucher_no": si.name},
|
||||
{"amount": 10.0, "against_voucher_no": si.name},
|
||||
],
|
||||
)
|
||||
|
||||
# Partially cancel Payment Entry
|
||||
make_reverse_gl_entries(gl_dicts, partial_cancel=True)
|
||||
self.assertPLEntries(pe, [{"amount": -100.0, "against_voucher_no": si.name}])
|
||||
|
||||
def assertPLEntries(self, payment_doc, expected_pl_entries):
|
||||
pl_entries = frappe.get_all(
|
||||
"Payment Ledger Entry",
|
||||
filters={
|
||||
"voucher_type": payment_doc.doctype,
|
||||
"voucher_no": payment_doc.name,
|
||||
"delinked": 0,
|
||||
},
|
||||
fields=["amount", "against_voucher_no"],
|
||||
)
|
||||
out_str = json.dumps(sorted(pl_entries, key=json.dumps))
|
||||
expected_out_str = json.dumps(sorted(expected_pl_entries, key=json.dumps))
|
||||
self.assertEqual(out_str, expected_out_str)
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
@@ -1279,6 +1391,9 @@ def create_payment_terms_template():
|
||||
def create_payment_terms_template_with_discount(
|
||||
name=None, discount_type=None, discount=None, template_name=None
|
||||
):
|
||||
"""
|
||||
Create a Payment Terms Template with % or amount discount.
|
||||
"""
|
||||
create_payment_term(name or "30 Credit Days with 10% Discount")
|
||||
template_name = template_name or "Test Discount Template"
|
||||
|
||||
|
||||
@@ -203,9 +203,10 @@
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"icon": "icon-resize-horizontal",
|
||||
"is_virtual": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-15 05:35:50.109290",
|
||||
"modified": "2023-11-17 17:33:55.701726",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation",
|
||||
@@ -230,6 +231,5 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
"states": []
|
||||
}
|
||||
|
||||
@@ -29,6 +29,58 @@ class PaymentReconciliation(Document):
|
||||
self.accounting_dimension_filter_conditions = []
|
||||
self.ple_posting_date_filter = []
|
||||
|
||||
def load_from_db(self):
|
||||
# 'modified' attribute is required for `run_doc_method` to work properly.
|
||||
doc_dict = frappe._dict(
|
||||
{
|
||||
"modified": None,
|
||||
"company": None,
|
||||
"party": None,
|
||||
"party_type": None,
|
||||
"receivable_payable_account": None,
|
||||
"default_advance_account": None,
|
||||
"from_invoice_date": None,
|
||||
"to_invoice_date": None,
|
||||
"invoice_limit": 50,
|
||||
"from_payment_date": None,
|
||||
"to_payment_date": None,
|
||||
"payment_limit": 50,
|
||||
"minimum_invoice_amount": None,
|
||||
"minimum_payment_amount": None,
|
||||
"maximum_invoice_amount": None,
|
||||
"maximum_payment_amount": None,
|
||||
"bank_cash_account": None,
|
||||
"cost_center": None,
|
||||
"payment_name": None,
|
||||
"invoice_name": None,
|
||||
}
|
||||
)
|
||||
super(Document, self).__init__(doc_dict)
|
||||
|
||||
def save(self):
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def get_list(args):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_count(args):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_stats(args):
|
||||
pass
|
||||
|
||||
def db_insert(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def db_update(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def delete(self):
|
||||
pass
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_unreconciled_entries(self):
|
||||
self.get_nonreconciled_payment_entries()
|
||||
@@ -59,8 +111,6 @@ class PaymentReconciliation(Document):
|
||||
def get_payment_entries(self):
|
||||
order_doctype = "Sales Order" if self.party_type == "Customer" else "Purchase Order"
|
||||
condition = self.get_conditions(get_payments=True)
|
||||
if self.payment_name:
|
||||
condition += "name like '%%{0}%%'".format(self.payment_name)
|
||||
|
||||
payment_entries = get_advance_payment_entries_for_regional(
|
||||
self.party_type,
|
||||
@@ -70,6 +120,7 @@ class PaymentReconciliation(Document):
|
||||
against_all_orders=True,
|
||||
limit=self.payment_limit,
|
||||
condition=condition,
|
||||
payment_name=self.payment_name,
|
||||
)
|
||||
|
||||
return payment_entries
|
||||
@@ -487,6 +538,27 @@ class PaymentReconciliation(Document):
|
||||
|
||||
invoice_exchange_map.update(purchase_invoice_map)
|
||||
|
||||
journals = [
|
||||
d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"
|
||||
]
|
||||
journals.extend(
|
||||
[d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"]
|
||||
)
|
||||
if journals:
|
||||
journals = list(set(journals))
|
||||
journals_map = frappe._dict(
|
||||
frappe.db.get_all(
|
||||
"Journal Entry Account",
|
||||
filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])},
|
||||
fields=[
|
||||
"parent as `name`",
|
||||
"exchange_rate",
|
||||
],
|
||||
as_list=1,
|
||||
)
|
||||
)
|
||||
invoice_exchange_map.update(journals_map)
|
||||
|
||||
return invoice_exchange_map
|
||||
|
||||
def validate_allocation(self):
|
||||
|
||||
@@ -1137,6 +1137,40 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(pay.unallocated_amount, 1000)
|
||||
self.assertEqual(pay.difference_amount, 0)
|
||||
|
||||
def test_rounding_of_unallocated_amount(self):
|
||||
self.supplier = "_Test Supplier USD"
|
||||
pi = self.create_purchase_invoice(qty=1, rate=10, do_not_submit=True)
|
||||
pi.supplier = self.supplier
|
||||
pi.currency = "USD"
|
||||
pi.conversion_rate = 80
|
||||
pi.credit_to = self.creditors_usd
|
||||
pi.save().submit()
|
||||
|
||||
pe = get_payment_entry(pi.doctype, pi.name)
|
||||
pe.target_exchange_rate = 78.726500000
|
||||
pe.received_amount = 26.75
|
||||
pe.paid_amount = 2105.93
|
||||
pe.references = []
|
||||
pe.save().submit()
|
||||
|
||||
# unallocated_amount will have some rounding loss - 26.749950
|
||||
self.assertNotEqual(pe.unallocated_amount, 26.75)
|
||||
|
||||
pr = frappe.get_doc("Payment Reconciliation")
|
||||
pr.company = self.company
|
||||
pr.party_type = "Supplier"
|
||||
pr.party = self.supplier
|
||||
pr.receivable_payable_account = self.creditors_usd
|
||||
pr.from_invoice_date = pr.to_invoice_date = pr.from_payment_date = pr.to_payment_date = nowdate()
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
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}))
|
||||
|
||||
# Should not raise frappe.exceptions.ValidationError: Payment Entry has been modified after you pulled it. Please pull it again.
|
||||
pr.reconcile()
|
||||
|
||||
|
||||
def make_customer(customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
|
||||
@@ -159,9 +159,10 @@
|
||||
"label": "Difference Posting Date"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-23 10:44:56.066303",
|
||||
"modified": "2023-11-17 17:33:38.612615",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
|
||||
@@ -6,4 +6,6 @@ from frappe.model.document import Document
|
||||
|
||||
|
||||
class PaymentReconciliationAllocation(Document):
|
||||
pass
|
||||
@staticmethod
|
||||
def get_list(args):
|
||||
pass
|
||||
|
||||
@@ -71,9 +71,10 @@
|
||||
"label": "Exchange Rate"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-08 18:18:02.502149",
|
||||
"modified": "2023-11-17 17:33:45.455166",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Invoice",
|
||||
|
||||
@@ -6,4 +6,6 @@ from frappe.model.document import Document
|
||||
|
||||
|
||||
class PaymentReconciliationInvoice(Document):
|
||||
pass
|
||||
@staticmethod
|
||||
def get_list(args):
|
||||
pass
|
||||
|
||||
@@ -107,9 +107,10 @@
|
||||
"options": "Cost Center"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-09-03 07:43:29.965353",
|
||||
"modified": "2023-11-17 17:33:34.818530",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Payment",
|
||||
|
||||
@@ -6,4 +6,6 @@ from frappe.model.document import Document
|
||||
|
||||
|
||||
class PaymentReconciliationPayment(Document):
|
||||
pass
|
||||
@staticmethod
|
||||
def get_list(args):
|
||||
pass
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"is_pos",
|
||||
"is_return",
|
||||
"update_billed_amount_in_sales_order",
|
||||
"update_billed_amount_in_delivery_note",
|
||||
"column_break1",
|
||||
"company",
|
||||
"posting_date",
|
||||
@@ -1549,12 +1550,19 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Amount Eligible for Commission",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"depends_on": "eval: doc.is_return && doc.return_against",
|
||||
"fieldname": "update_billed_amount_in_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
"label": "Update Billed Amount in Delivery Note"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-30 03:49:50.455199",
|
||||
"modified": "2023-11-20 12:27:12.848149",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -659,7 +659,7 @@ def get_stock_availability(item_code, warehouse):
|
||||
return bin_qty - pos_sales_qty, is_stock_item
|
||||
else:
|
||||
is_stock_item = True
|
||||
if frappe.db.exists("Product Bundle", item_code):
|
||||
if frappe.db.exists("Product Bundle", {"name": item_code, "disabled": 0}):
|
||||
return get_bundle_availability(item_code, warehouse), is_stock_item
|
||||
else:
|
||||
is_stock_item = False
|
||||
@@ -705,7 +705,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
||||
reserved_qty = (
|
||||
frappe.qb.from_(p_inv)
|
||||
.from_(p_item)
|
||||
.select(Sum(p_item.qty).as_("qty"))
|
||||
.select(Sum(p_item.stock_qty).as_("stock_qty"))
|
||||
.where(
|
||||
(p_inv.name == p_item.parent)
|
||||
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
||||
@@ -716,7 +716,7 @@ def get_pos_reserved_qty(item_code, warehouse):
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
||||
return flt(reserved_qty[0].stock_qty) if reserved_qty else 0
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -185,6 +185,7 @@
|
||||
"label": "Image"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -821,7 +822,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-11-02 12:52:39.125295",
|
||||
"modified": "2023-11-14 18:33:22.585715",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Item",
|
||||
@@ -831,4 +832,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,7 +521,7 @@ def get_qty_amount_data_for_cumulative(pr_doc, doc, items=None):
|
||||
values.extend(warehouses)
|
||||
|
||||
if items:
|
||||
condition = " and `tab{child_doc}`.{apply_on} in ({items})".format(
|
||||
condition += " and `tab{child_doc}`.{apply_on} in ({items})".format(
|
||||
child_doc=child_doctype, apply_on=apply_on, items=",".join(["%s"] * len(items))
|
||||
)
|
||||
|
||||
|
||||
@@ -415,7 +415,7 @@ def reconcile(doc: None | str = None) -> None:
|
||||
# Update the parent doc about the exception
|
||||
frappe.db.rollback()
|
||||
|
||||
traceback = frappe.get_traceback()
|
||||
traceback = frappe.get_traceback(with_context=True)
|
||||
if traceback:
|
||||
message = "Traceback: <br>" + traceback
|
||||
frappe.db.set_value("Process Payment Reconciliation Log", log, "error_log", message)
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
"group_by",
|
||||
"cost_center",
|
||||
"territory",
|
||||
"ignore_exchange_rate_revaluation_journals",
|
||||
"column_break_14",
|
||||
"to_date",
|
||||
"finance_book",
|
||||
@@ -374,10 +375,16 @@
|
||||
"fieldname": "pdf_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "PDF Name"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_exchange_rate_revaluation_journals",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Exchange Rate Revaluation Journals"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2023-08-28 12:59:53.071334",
|
||||
"modified": "2023-12-18 12:20:08.965120",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -64,6 +64,18 @@ def get_statement_dict(doc, get_statement_dict=False):
|
||||
statement_dict = {}
|
||||
ageing = ""
|
||||
|
||||
err_journals = None
|
||||
if doc.report == "General Ledger" and doc.ignore_exchange_rate_revaluation_journals:
|
||||
err_journals = frappe.db.get_all(
|
||||
"Journal Entry",
|
||||
filters={
|
||||
"company": doc.company,
|
||||
"docstatus": 1,
|
||||
"voucher_type": ("in", ["Exchange Rate Revaluation", "Exchange Gain Or Loss"]),
|
||||
},
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
for entry in doc.customers:
|
||||
if doc.include_ageing:
|
||||
ageing = set_ageing(doc, entry)
|
||||
@@ -76,6 +88,8 @@ def get_statement_dict(doc, get_statement_dict=False):
|
||||
)
|
||||
|
||||
filters = get_common_filters(doc)
|
||||
if err_journals:
|
||||
filters.update({"voucher_no_not_in": [x[0] for x in err_journals]})
|
||||
|
||||
if doc.report == "General Ledger":
|
||||
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
||||
|
||||
@@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
super.onload();
|
||||
|
||||
// Ignore linked advances
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger"];
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||
|
||||
if(!this.frm.doc.__islocal) {
|
||||
// show credit_to in print format
|
||||
@@ -164,6 +164,18 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
}
|
||||
})
|
||||
}, __("Get Items From"));
|
||||
|
||||
if (!this.frm.doc.is_return) {
|
||||
frappe.db.get_single_value("Buying Settings", "maintain_same_rate").then((value) => {
|
||||
if (value) {
|
||||
this.frm.doc.items.forEach((item) => {
|
||||
this.frm.fields_dict.items.grid.update_docfield_property(
|
||||
"rate", "read_only", (item.purchase_receipt && item.pr_detail)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
this.frm.toggle_reqd("supplier_warehouse", this.frm.doc.is_subcontracted);
|
||||
|
||||
@@ -181,7 +193,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
}
|
||||
|
||||
this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
unblock_invoice() {
|
||||
|
||||
@@ -13,6 +13,7 @@ from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
from erpnext.accounts.doctype.gl_entry.gl_entry import update_outstanding_amt
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
check_if_return_invoice_linked_with_payment_entry,
|
||||
@@ -494,6 +495,7 @@ class PurchaseInvoice(BuyingController):
|
||||
def validate_for_repost(self):
|
||||
self.validate_write_off_account()
|
||||
self.validate_expense_account()
|
||||
validate_docs_for_voucher_types(["Purchase Invoice"])
|
||||
validate_docs_for_deferred_accounting([], [self.name])
|
||||
|
||||
def on_submit(self):
|
||||
@@ -532,7 +534,11 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
self.update_project()
|
||||
if (
|
||||
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
|
||||
):
|
||||
self.update_project()
|
||||
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
self.update_advance_tax_references()
|
||||
|
||||
@@ -919,17 +925,6 @@ class PurchaseInvoice(BuyingController):
|
||||
item=item,
|
||||
)
|
||||
)
|
||||
|
||||
# update gross amount of asset bought through this document
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value(
|
||||
"Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate)
|
||||
)
|
||||
|
||||
if (
|
||||
self.auto_accounting_for_stock
|
||||
and self.is_opening == "No"
|
||||
@@ -969,12 +964,25 @@ class PurchaseInvoice(BuyingController):
|
||||
item.item_tax_amount, item.precision("item_tax_amount")
|
||||
)
|
||||
|
||||
if item.is_fixed_asset and item.landed_cost_voucher_amount:
|
||||
self.update_gross_purchase_amount_for_linked_assets(item)
|
||||
|
||||
def update_gross_purchase_amount_for_linked_assets(self, item):
|
||||
assets = frappe.db.get_all(
|
||||
"Asset", filters={"purchase_invoice": self.name, "item_code": item.item_code}
|
||||
"Asset",
|
||||
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
||||
fields=["name", "asset_quantity"],
|
||||
)
|
||||
for asset in assets:
|
||||
frappe.db.set_value("Asset", asset.name, "gross_purchase_amount", flt(item.valuation_rate))
|
||||
frappe.db.set_value("Asset", asset.name, "purchase_receipt_amount", flt(item.valuation_rate))
|
||||
purchase_amount = flt(item.valuation_rate) * asset.asset_quantity
|
||||
frappe.db.set_value(
|
||||
"Asset",
|
||||
asset.name,
|
||||
{
|
||||
"gross_purchase_amount": purchase_amount,
|
||||
"purchase_receipt_amount": purchase_amount,
|
||||
},
|
||||
)
|
||||
|
||||
def make_stock_adjustment_entry(
|
||||
self, gl_entries, item, voucher_wise_stock_value, account_currency
|
||||
@@ -1269,7 +1277,10 @@ class PurchaseInvoice(BuyingController):
|
||||
if self.update_stock == 1:
|
||||
self.repost_future_sle_and_gle()
|
||||
|
||||
self.update_project()
|
||||
if (
|
||||
frappe.db.get_single_value("Buying Settings", "project_update_frequency") == "Each Transaction"
|
||||
):
|
||||
self.update_project()
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
@@ -1281,19 +1292,29 @@ class PurchaseInvoice(BuyingController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Payment Ledger Entry",
|
||||
"Tax Withheld Vouchers",
|
||||
)
|
||||
self.update_advance_tax_references(cancel=1)
|
||||
|
||||
def update_project(self):
|
||||
project_list = []
|
||||
projects = frappe._dict()
|
||||
for d in self.items:
|
||||
if d.project and d.project not in project_list:
|
||||
project = frappe.get_doc("Project", d.project)
|
||||
project.update_purchase_costing()
|
||||
project.db_update()
|
||||
project_list.append(d.project)
|
||||
if d.project:
|
||||
if self.docstatus == 1:
|
||||
projects[d.project] = projects.get(d.project, 0) + d.base_net_amount
|
||||
elif self.docstatus == 2:
|
||||
projects[d.project] = projects.get(d.project, 0) - d.base_net_amount
|
||||
|
||||
pj = frappe.qb.DocType("Project")
|
||||
for proj, value in projects.items():
|
||||
res = (
|
||||
frappe.qb.from_(pj).select(pj.total_purchase_cost).where(pj.name == proj).for_update().run()
|
||||
)
|
||||
current_purchase_cost = res and res[0][0] or 0
|
||||
frappe.db.set_value("Project", proj, "total_purchase_cost", current_purchase_cost + value)
|
||||
|
||||
def validate_supplier_invoice(self):
|
||||
if self.bill_date:
|
||||
@@ -1641,10 +1662,6 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||
return make_inter_company_transaction("Purchase Invoice", source_name, target_doc)
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Purchase Invoice", ["supplier", "is_return", "return_against"])
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
@@ -1686,6 +1703,4 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
target_doc,
|
||||
)
|
||||
|
||||
doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -156,6 +156,7 @@
|
||||
"width": "300px"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -491,6 +492,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"label": "Project",
|
||||
@@ -498,6 +500,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": ":Company",
|
||||
"depends_on": "eval:!doc.is_fixed_asset",
|
||||
"fieldname": "cost_center",
|
||||
@@ -890,7 +893,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-10-03 21:01:01.824892",
|
||||
"modified": "2023-12-25 22:00:28.043555",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -10,12 +10,7 @@ from frappe.utils.data import comma_and
|
||||
class RepostAccountingLedger(Document):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RepostAccountingLedger, self).__init__(*args, **kwargs)
|
||||
self._allowed_types = [
|
||||
x.document_type
|
||||
for x in frappe.db.get_all(
|
||||
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
||||
)
|
||||
]
|
||||
self._allowed_types = get_allowed_types_from_settings()
|
||||
|
||||
def validate(self):
|
||||
self.validate_vouchers()
|
||||
@@ -56,15 +51,7 @@ class RepostAccountingLedger(Document):
|
||||
|
||||
def validate_vouchers(self):
|
||||
if self.vouchers:
|
||||
# Validate voucher types
|
||||
voucher_types = set([x.voucher_type for x in self.vouchers])
|
||||
if disallowed_types := voucher_types.difference(self._allowed_types):
|
||||
frappe.throw(
|
||||
_("{0} types are not allowed. Only {1} are.").format(
|
||||
frappe.bold(comma_and(list(disallowed_types))),
|
||||
frappe.bold(comma_and(list(self._allowed_types))),
|
||||
)
|
||||
)
|
||||
validate_docs_for_voucher_types([x.voucher_type for x in self.vouchers])
|
||||
|
||||
def get_existing_ledger_entries(self):
|
||||
vouchers = [x.voucher_no for x in self.vouchers]
|
||||
@@ -121,7 +108,7 @@ class RepostAccountingLedger(Document):
|
||||
return rendered_page
|
||||
|
||||
def on_submit(self):
|
||||
if len(self.vouchers) > 1:
|
||||
if len(self.vouchers) > 5:
|
||||
job_name = "repost_accounting_ledger_" + self.name
|
||||
frappe.enqueue(
|
||||
method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
|
||||
@@ -165,7 +152,14 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
doc.make_gl_entries(1)
|
||||
doc.make_gl_entries()
|
||||
|
||||
frappe.db.commit()
|
||||
|
||||
def get_allowed_types_from_settings():
|
||||
return [
|
||||
x.document_type
|
||||
for x in frappe.db.get_all(
|
||||
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||
@@ -191,6 +185,25 @@ def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||
)
|
||||
|
||||
|
||||
def validate_docs_for_voucher_types(doc_voucher_types):
|
||||
allowed_types = get_allowed_types_from_settings()
|
||||
# Validate voucher types
|
||||
voucher_types = set(doc_voucher_types)
|
||||
if disallowed_types := voucher_types.difference(allowed_types):
|
||||
message = "are" if len(disallowed_types) > 1 else "is"
|
||||
frappe.throw(
|
||||
_("{0} {1} not allowed to be reposted. Modify {2} to enable reposting.").format(
|
||||
frappe.bold(comma_and(list(disallowed_types))),
|
||||
message,
|
||||
frappe.bold(
|
||||
frappe.utils.get_link_to_form(
|
||||
"Repost Accounting Ledger Settings", "Repost Accounting Ledger Settings"
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_repost_allowed_types(doctype, txt, searchfield, start, page_len, filters):
|
||||
|
||||
@@ -20,18 +20,11 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
self.create_item()
|
||||
self.update_repost_settings()
|
||||
update_repost_settings()
|
||||
|
||||
def teadDown(self):
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def update_repost_settings(self):
|
||||
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
||||
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||
for x in allowed_types:
|
||||
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
||||
repost_settings.save()
|
||||
|
||||
def test_01_basic_functions(self):
|
||||
si = create_sales_invoice(
|
||||
item=self.item,
|
||||
@@ -90,9 +83,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
||||
# Submit repost document
|
||||
ral.save().submit()
|
||||
|
||||
# background jobs don't run on test cases. Manually triggering repost function.
|
||||
start_repost(ral.name)
|
||||
|
||||
res = (
|
||||
qb.from_(gl)
|
||||
.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
|
||||
@@ -177,26 +167,6 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
||||
pe = get_payment_entry(si.doctype, si.name)
|
||||
pe.save().submit()
|
||||
|
||||
# without deletion flag set
|
||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||
ral.company = self.company
|
||||
ral.delete_cancelled_entries = False
|
||||
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
|
||||
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
||||
ral.save()
|
||||
|
||||
# assert preview data is generated
|
||||
preview = ral.generate_preview()
|
||||
self.assertIsNotNone(preview)
|
||||
|
||||
ral.save().submit()
|
||||
|
||||
# background jobs don't run on test cases. Manually triggering repost function.
|
||||
start_repost(ral.name)
|
||||
|
||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
||||
|
||||
# with deletion flag set
|
||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||
ral.company = self.company
|
||||
@@ -205,6 +175,38 @@ class TestRepostAccountingLedger(AccountsTestMixin, FrappeTestCase):
|
||||
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
||||
ral.save().submit()
|
||||
|
||||
start_repost(ral.name)
|
||||
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
||||
self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
||||
|
||||
def test_05_without_deletion_flag(self):
|
||||
si = create_sales_invoice(
|
||||
item=self.item,
|
||||
company=self.company,
|
||||
customer=self.customer,
|
||||
debit_to=self.debit_to,
|
||||
parent_cost_center=self.cost_center,
|
||||
cost_center=self.cost_center,
|
||||
rate=100,
|
||||
)
|
||||
|
||||
pe = get_payment_entry(si.doctype, si.name)
|
||||
pe.save().submit()
|
||||
|
||||
# without deletion flag set
|
||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||
ral.company = self.company
|
||||
ral.delete_cancelled_entries = False
|
||||
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
|
||||
ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
|
||||
ral.save().submit()
|
||||
|
||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
|
||||
self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
|
||||
|
||||
|
||||
def update_repost_settings():
|
||||
allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
||||
repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
|
||||
for x in allowed_types:
|
||||
repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
|
||||
repost_settings.save()
|
||||
|
||||
@@ -43,7 +43,7 @@ def start_payment_ledger_repost(docname=None):
|
||||
except Exception as e:
|
||||
frappe.db.rollback()
|
||||
|
||||
traceback = frappe.get_traceback()
|
||||
traceback = frappe.get_traceback(with_context=True)
|
||||
if traceback:
|
||||
message = "Traceback: <br>" + traceback
|
||||
frappe.db.set_value(repost_doc.doctype, repost_doc.name, "repost_error_log", message)
|
||||
|
||||
@@ -34,7 +34,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
super.onload();
|
||||
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['POS Invoice', 'Timesheet', 'POS Invoice Merge Log',
|
||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payments", "Unreconcile Payment Entries"];
|
||||
'POS Closing Entry', 'Journal Entry', 'Payment Entry', "Repost Payment Ledger", "Repost Accounting Ledger", "Unreconcile Payment", "Unreconcile Payment Entries"];
|
||||
|
||||
if(!this.frm.doc.__islocal && !this.frm.doc.customer && this.frm.doc.debit_to) {
|
||||
// show debit_to in print format
|
||||
@@ -178,10 +178,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
}
|
||||
}
|
||||
|
||||
erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
|
||||
erpnext.accounts.unreconcile_payment.add_unreconcile_btn(me.frm);
|
||||
}
|
||||
|
||||
|
||||
make_maintenance_schedule() {
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_maintenance_schedule",
|
||||
@@ -557,15 +556,6 @@ cur_frm.fields_dict.write_off_cost_center.get_query = function(doc) {
|
||||
}
|
||||
}
|
||||
|
||||
// Income Account in Details Table
|
||||
// --------------------------------
|
||||
cur_frm.set_query("income_account", "items", function(doc) {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: {'company': doc.company}
|
||||
}
|
||||
});
|
||||
|
||||
// Cost Center in Details Table
|
||||
// -----------------------------
|
||||
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
|
||||
@@ -660,6 +650,16 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("income_account", "items", function() {
|
||||
return{
|
||||
query: "erpnext.controllers.queries.get_income_account",
|
||||
filters: {
|
||||
'company': frm.doc.company,
|
||||
"disabled": 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Delivery Note': 'Delivery',
|
||||
'Sales Invoice': 'Return / Credit Note',
|
||||
@@ -890,8 +890,8 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
frm.events.append_time_log(frm, timesheet, 1.0);
|
||||
}
|
||||
});
|
||||
frm.refresh_field("timesheets");
|
||||
frm.trigger("calculate_timesheet_totals");
|
||||
frm.refresh();
|
||||
},
|
||||
|
||||
async get_exchange_rate(frm, from_currency, to_currency) {
|
||||
|
||||
@@ -1613,7 +1613,8 @@
|
||||
"hide_seconds": 1,
|
||||
"label": "Inter Company Invoice Reference",
|
||||
"options": "Purchase Invoice",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_group",
|
||||
@@ -2147,7 +2148,7 @@
|
||||
"label": "Use Company default Cost Center for Round off"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"default": "1",
|
||||
"depends_on": "eval: doc.is_return",
|
||||
"fieldname": "update_billed_amount_in_delivery_note",
|
||||
"fieldtype": "Check",
|
||||
@@ -2164,7 +2165,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2023-11-03 14:39:38.012346",
|
||||
"modified": "2023-11-23 16:56:29.679499",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -17,6 +17,7 @@ from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||
)
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import (
|
||||
validate_docs_for_deferred_accounting,
|
||||
validate_docs_for_voucher_types,
|
||||
)
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
@@ -180,6 +181,7 @@ class SalesInvoice(SellingController):
|
||||
self.validate_write_off_account()
|
||||
self.validate_account_for_change_amount()
|
||||
self.validate_income_account()
|
||||
validate_docs_for_voucher_types(["Sales Invoice"])
|
||||
validate_docs_for_deferred_accounting([self.name], [])
|
||||
|
||||
def validate_fixed_asset(self):
|
||||
@@ -408,7 +410,7 @@ class SalesInvoice(SellingController):
|
||||
"Repost Payment Ledger Items",
|
||||
"Repost Accounting Ledger",
|
||||
"Repost Accounting Ledger Items",
|
||||
"Unreconcile Payments",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
"Payment Ledger Entry",
|
||||
)
|
||||
@@ -1932,7 +1934,6 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
set_missing_values,
|
||||
)
|
||||
|
||||
doclist.set_onload("ignore_price_list", True)
|
||||
return doclist
|
||||
|
||||
|
||||
@@ -2194,9 +2195,18 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
|
||||
|
||||
def get_received_items(reference_name, doctype, reference_fieldname):
|
||||
reference_field = "inter_company_invoice_reference"
|
||||
if doctype == "Purchase Order":
|
||||
reference_field = "inter_company_order_reference"
|
||||
|
||||
filters = {
|
||||
reference_field: reference_name,
|
||||
"docstatus": 1,
|
||||
}
|
||||
|
||||
target_doctypes = frappe.get_all(
|
||||
doctype,
|
||||
filters={"inter_company_invoice_reference": reference_name, "docstatus": 1},
|
||||
filters=filters,
|
||||
as_list=True,
|
||||
)
|
||||
|
||||
@@ -2378,10 +2388,6 @@ def get_loyalty_programs(customer):
|
||||
return lp_details
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("Sales Invoice", ["customer", "is_return", "return_against"])
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_invoice_discounting(source_name, target_doc=None):
|
||||
invoice = frappe.get_doc("Sales Invoice", source_name)
|
||||
|
||||
@@ -782,6 +782,28 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
w = self.make()
|
||||
self.assertEqual(w.outstanding_amount, w.base_rounded_total)
|
||||
|
||||
def test_rounded_total_with_cash_discount(self):
|
||||
si = frappe.copy_doc(test_records[2])
|
||||
|
||||
item = copy.deepcopy(si.get("items")[0])
|
||||
item.update(
|
||||
{
|
||||
"qty": 1,
|
||||
"rate": 14960.66,
|
||||
}
|
||||
)
|
||||
|
||||
si.set("items", [item])
|
||||
si.set("taxes", [])
|
||||
si.apply_discount_on = "Grand Total"
|
||||
si.is_cash_or_non_trade_discount = 1
|
||||
si.discount_amount = 1
|
||||
si.insert()
|
||||
|
||||
self.assertEqual(si.grand_total, 14959.66)
|
||||
self.assertEqual(si.rounded_total, 14960)
|
||||
self.assertEqual(si.rounding_adjustment, 0.34)
|
||||
|
||||
def test_payment(self):
|
||||
w = self.make()
|
||||
|
||||
@@ -2769,6 +2791,12 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
@change_settings("Selling Settings", {"enable_discount_accounting": 1})
|
||||
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
|
||||
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
|
||||
update_repost_settings,
|
||||
)
|
||||
|
||||
update_repost_settings()
|
||||
|
||||
additional_discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC",
|
||||
|
||||
@@ -167,6 +167,7 @@
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.image",
|
||||
"fieldname": "image",
|
||||
"fieldtype": "Attach",
|
||||
"hidden": 1,
|
||||
@@ -891,7 +892,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-25 11:58:10.723833",
|
||||
"modified": "2023-11-14 18:34:10.479329",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
@@ -901,4 +902,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
|
||||
|
||||
class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer()
|
||||
@@ -73,7 +73,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
@@ -138,7 +138,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe2.doctype,
|
||||
"voucher_no": pe2.name,
|
||||
@@ -196,7 +196,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
@@ -281,7 +281,7 @@ class TestUnreconcilePayments(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payments",
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe2.doctype,
|
||||
"voucher_no": pe2.name,
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Unreconcile Payments", {
|
||||
frappe.ui.form.on("Unreconcile Payment", {
|
||||
refresh(frm) {
|
||||
frm.set_query("voucher_type", function() {
|
||||
return {
|
||||
@@ -21,7 +21,7 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Unreconcile Payments",
|
||||
"options": "Unreconcile Payment",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -61,7 +61,7 @@
|
||||
"modified": "2023-08-28 17:42:50.261377",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Unreconcile Payments",
|
||||
"name": "Unreconcile Payment",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
@@ -90,4 +90,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ from erpnext.accounts.utils import (
|
||||
)
|
||||
|
||||
|
||||
class UnreconcilePayments(Document):
|
||||
class UnreconcilePayment(Document):
|
||||
def validate(self):
|
||||
self.supported_types = ["Payment Entry", "Journal Entry"]
|
||||
if not self.voucher_type in self.supported_types:
|
||||
@@ -142,7 +142,7 @@ def create_unreconcile_doc_for_selection(selections=None):
|
||||
selections = frappe.json.loads(selections)
|
||||
# assuming each row is a unique voucher
|
||||
for row in selections:
|
||||
unrecon = frappe.new_doc("Unreconcile Payments")
|
||||
unrecon = frappe.new_doc("Unreconcile Payment")
|
||||
unrecon.company = row.get("company")
|
||||
unrecon.voucher_type = row.get("voucher_type")
|
||||
unrecon.voucher_no = row.get("voucher_no")
|
||||
@@ -556,7 +556,12 @@ def get_round_off_account_and_cost_center(
|
||||
|
||||
|
||||
def make_reverse_gl_entries(
|
||||
gl_entries=None, voucher_type=None, voucher_no=None, adv_adj=False, update_outstanding="Yes"
|
||||
gl_entries=None,
|
||||
voucher_type=None,
|
||||
voucher_no=None,
|
||||
adv_adj=False,
|
||||
update_outstanding="Yes",
|
||||
partial_cancel=False,
|
||||
):
|
||||
"""
|
||||
Get original gl entries of the voucher
|
||||
@@ -576,14 +581,19 @@ def make_reverse_gl_entries(
|
||||
|
||||
if gl_entries:
|
||||
create_payment_ledger_entry(
|
||||
gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
|
||||
gl_entries,
|
||||
cancel=1,
|
||||
adv_adj=adv_adj,
|
||||
update_outstanding=update_outstanding,
|
||||
partial_cancel=partial_cancel,
|
||||
)
|
||||
validate_accounting_period(gl_entries)
|
||||
check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
|
||||
|
||||
is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
|
||||
validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
|
||||
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
||||
if not partial_cancel:
|
||||
set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
|
||||
|
||||
for entry in gl_entries:
|
||||
new_gle = copy.deepcopy(entry)
|
||||
|
||||
@@ -31,7 +31,12 @@ from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
|
||||
from erpnext.utilities.regional import temporary_flag
|
||||
|
||||
PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
|
||||
PURCHASE_TRANSACTION_TYPES = {
|
||||
"Supplier Quotation",
|
||||
"Purchase Order",
|
||||
"Purchase Receipt",
|
||||
"Purchase Invoice",
|
||||
}
|
||||
SALES_TRANSACTION_TYPES = {
|
||||
"Quotation",
|
||||
"Sales Order",
|
||||
@@ -109,14 +114,12 @@ def _get_party_details(
|
||||
set_account_and_due_date(party, account, party_type, company, posting_date, bill_date, doctype)
|
||||
)
|
||||
party = party_details[party_type.lower()]
|
||||
|
||||
if not ignore_permissions and not (
|
||||
frappe.has_permission(party_type, "read", party)
|
||||
or frappe.has_permission(party_type, "select", party)
|
||||
):
|
||||
frappe.throw(_("Not permitted for {0}").format(party), frappe.PermissionError)
|
||||
|
||||
party = frappe.get_doc(party_type, party)
|
||||
|
||||
if not ignore_permissions:
|
||||
ptype = "select" if frappe.only_has_select_perm(party_type) else "read"
|
||||
frappe.has_permission(party_type, ptype, party, throw=True)
|
||||
|
||||
currency = party.get("default_currency") or currency or get_company_currency(company)
|
||||
|
||||
party_address, shipping_address = set_address_details(
|
||||
@@ -190,7 +193,7 @@ def set_address_details(
|
||||
company_address=None,
|
||||
shipping_address=None,
|
||||
*,
|
||||
ignore_permissions=False
|
||||
ignore_permissions=False,
|
||||
):
|
||||
billing_address_field = (
|
||||
"customer_address" if party_type == "Lead" else party_type.lower() + "_address"
|
||||
@@ -231,8 +234,10 @@ def set_address_details(
|
||||
if shipping_address:
|
||||
party_details.update(
|
||||
shipping_address=shipping_address,
|
||||
shipping_address_display=render_address(shipping_address),
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||
shipping_address_display=render_address(
|
||||
shipping_address, check_permissions=not ignore_permissions
|
||||
),
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address),
|
||||
)
|
||||
|
||||
if party_details.company_address:
|
||||
@@ -243,7 +248,7 @@ def set_address_details(
|
||||
party_details.company_address_display
|
||||
or render_address(party_details.company_address, check_permissions=False)
|
||||
),
|
||||
**get_fetch_values(doctype, "billing_address", party_details.company_address)
|
||||
**get_fetch_values(doctype, "billing_address", party_details.company_address),
|
||||
)
|
||||
|
||||
# shipping address - if not already set
|
||||
@@ -251,7 +256,7 @@ def set_address_details(
|
||||
party_details.update(
|
||||
shipping_address=party_details.billing_address,
|
||||
shipping_address_display=party_details.billing_address_display,
|
||||
**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
|
||||
**get_fetch_values(doctype, "shipping_address", party_details.billing_address),
|
||||
)
|
||||
|
||||
party_address, shipping_address = (
|
||||
@@ -949,6 +954,9 @@ def get_partywise_advanced_payment_amount(
|
||||
if party:
|
||||
query = query.where(ple.party == party)
|
||||
|
||||
if invoice_doctypes := frappe.get_hooks("invoice_doctypes"):
|
||||
query = query.where(ple.voucher_type.notin(invoice_doctypes))
|
||||
|
||||
data = query.run()
|
||||
if data:
|
||||
return frappe._dict(data)
|
||||
|
||||
@@ -144,11 +144,21 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
"label": __("Show Future Payments"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "in_party_currency",
|
||||
"label": __("In Party Currency"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
},
|
||||
|
||||
],
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"in_party_currency": 1,
|
||||
}
|
||||
|
||||
data = execute(filters)
|
||||
|
||||
@@ -110,6 +110,11 @@ frappe.query_reports["Accounts Payable Summary"] = {
|
||||
"fieldname":"based_on_payment_terms",
|
||||
"label": __("Based On Payment Terms"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -114,10 +114,13 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_group",
|
||||
"fieldname":"customer_group",
|
||||
"label": __("Customer Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group"
|
||||
"fieldtype": "MultiSelectList",
|
||||
"options": "Customer Group",
|
||||
get_data: function(txt) {
|
||||
return frappe.db.get_link_options('Customer Group', txt);
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_terms_template",
|
||||
@@ -173,6 +176,16 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "in_party_currency",
|
||||
"label": __("In Party Currency"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "ignore_accounts",
|
||||
"label": __("Group by Voucher"),
|
||||
|
||||
82
erpnext/accounts/report/accounts_receivable/accounts_receivable.py
Executable file → Normal file
82
erpnext/accounts/report/accounts_receivable/accounts_receivable.py
Executable file → Normal file
@@ -7,7 +7,7 @@ from collections import OrderedDict
|
||||
import frappe
|
||||
from frappe import _, qb, scrub
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.query_builder.functions import Date, Sum
|
||||
from frappe.query_builder.functions import Date, Substring, Sum
|
||||
from frappe.utils import cint, cstr, flt, getdate, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
@@ -28,8 +28,8 @@ from erpnext.accounts.utils import get_currency_precision
|
||||
# 6. Configurable Ageing Groups (0-30, 30-60 etc) can be set via filters
|
||||
# 7. For overpayment against an invoice with payment terms, there will be an additional row
|
||||
# 8. Invoice details like Sales Persons, Delivery Notes are also fetched comma separated
|
||||
# 9. Report amounts are in "Party Currency" if party is selected, or company currency for multi-party
|
||||
# 10. This reports is based on all GL Entries that are made against account_type "Receivable" or "Payable"
|
||||
# 9. Report amounts are in party currency if in_party_currency is selected, otherwise company currency
|
||||
# 10. This report is based on Payment Ledger Entries
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -84,6 +84,9 @@ class ReceivablePayableReport(object):
|
||||
self.total_row_map = {}
|
||||
self.skip_total_row = 1
|
||||
|
||||
if self.filters.get("in_party_currency"):
|
||||
self.skip_total_row = 1
|
||||
|
||||
def get_data(self):
|
||||
self.get_ple_entries()
|
||||
self.get_sales_invoices_or_customers_based_on_sales_person()
|
||||
@@ -145,7 +148,7 @@ class ReceivablePayableReport(object):
|
||||
if self.filters.get("group_by_party"):
|
||||
self.init_subtotal_row(ple.party)
|
||||
|
||||
if self.filters.get("group_by_party"):
|
||||
if self.filters.get("group_by_party") and not self.filters.get("in_party_currency"):
|
||||
self.init_subtotal_row("Total")
|
||||
|
||||
def get_invoices(self, ple):
|
||||
@@ -224,8 +227,7 @@ class ReceivablePayableReport(object):
|
||||
if not row:
|
||||
return
|
||||
|
||||
# amount in "Party Currency", if its supplied. If not, amount in company currency
|
||||
if self.filters.get("party_type") and self.filters.get("party"):
|
||||
if self.filters.get("in_party_currency") or self.filters.get("party_account"):
|
||||
amount = ple.amount_in_account_currency
|
||||
else:
|
||||
amount = ple.amount
|
||||
@@ -244,8 +246,12 @@ class ReceivablePayableReport(object):
|
||||
row.invoiced_in_account_currency += amount_in_account_currency
|
||||
else:
|
||||
if self.is_invoice(ple):
|
||||
row.credit_note -= amount
|
||||
row.credit_note_in_account_currency -= amount_in_account_currency
|
||||
if row.voucher_no == ple.voucher_no == ple.against_voucher_no:
|
||||
row.paid -= amount
|
||||
row.paid_in_account_currency -= amount_in_account_currency
|
||||
else:
|
||||
row.credit_note -= amount
|
||||
row.credit_note_in_account_currency -= amount_in_account_currency
|
||||
else:
|
||||
row.paid -= amount
|
||||
row.paid_in_account_currency -= amount_in_account_currency
|
||||
@@ -256,8 +262,10 @@ class ReceivablePayableReport(object):
|
||||
def update_sub_total_row(self, row, party):
|
||||
total_row = self.total_row_map.get(party)
|
||||
|
||||
for field in self.get_currency_fields():
|
||||
total_row[field] += row.get(field, 0.0)
|
||||
if total_row:
|
||||
for field in self.get_currency_fields():
|
||||
total_row[field] += row.get(field, 0.0)
|
||||
total_row["currency"] = row.get("currency", "")
|
||||
|
||||
def append_subtotal_row(self, party):
|
||||
sub_total_row = self.total_row_map.get(party)
|
||||
@@ -281,11 +289,20 @@ class ReceivablePayableReport(object):
|
||||
|
||||
row.invoice_grand_total = row.invoiced
|
||||
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
or (row.voucher_no in self.err_journals)
|
||||
):
|
||||
must_consider = False
|
||||
if self.filters.get("for_revaluation_journals"):
|
||||
if (abs(row.outstanding) > 0.0 / 10**self.currency_precision) or (
|
||||
(abs(row.outstanding_in_account_currency) > 0.0 / 10**self.currency_precision)
|
||||
):
|
||||
must_consider = True
|
||||
else:
|
||||
if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
|
||||
(abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision)
|
||||
or (row.voucher_no in self.err_journals)
|
||||
):
|
||||
must_consider = True
|
||||
|
||||
if must_consider:
|
||||
# non-zero oustanding, we must consider this row
|
||||
|
||||
if self.is_invoice(row) and self.filters.based_on_payment_terms:
|
||||
@@ -309,7 +326,7 @@ class ReceivablePayableReport(object):
|
||||
if self.filters.get("group_by_party"):
|
||||
self.append_subtotal_row(self.previous_party)
|
||||
if self.data:
|
||||
self.data.append(self.total_row_map.get("Total"))
|
||||
self.data.append(self.total_row_map.get("Total", {}))
|
||||
|
||||
def append_row(self, row):
|
||||
self.allocate_future_payments(row)
|
||||
@@ -440,7 +457,7 @@ class ReceivablePayableReport(object):
|
||||
party_details = self.get_party_details(row.party) or {}
|
||||
row.update(party_details)
|
||||
|
||||
if self.filters.get("party_type") and self.filters.get("party"):
|
||||
if self.filters.get("in_party_currency") or self.filters.get("party_account"):
|
||||
row.currency = row.account_currency
|
||||
else:
|
||||
row.currency = self.company_currency
|
||||
@@ -753,7 +770,12 @@ class ReceivablePayableReport(object):
|
||||
)
|
||||
|
||||
if self.filters.get("show_remarks"):
|
||||
query = query.select(ple.remarks)
|
||||
if remarks_length := frappe.db.get_single_value(
|
||||
"Accounts Settings", "receivable_payable_remarks_length"
|
||||
):
|
||||
query = query.select(Substring(ple.remarks, 1, remarks_length).as_("remarks"))
|
||||
else:
|
||||
query = query.select(ple.remarks)
|
||||
|
||||
if self.filters.get("group_by_party"):
|
||||
query = query.orderby(self.ple.party, self.ple.posting_date)
|
||||
@@ -840,7 +862,13 @@ class ReceivablePayableReport(object):
|
||||
self.customer = qb.DocType("Customer")
|
||||
|
||||
if self.filters.get("customer_group"):
|
||||
self.get_hierarchical_filters("Customer Group", "customer_group")
|
||||
groups = get_customer_group_with_children(self.filters.customer_group)
|
||||
customers = (
|
||||
qb.from_(self.customer)
|
||||
.select(self.customer.name)
|
||||
.where(self.customer["customer_group"].isin(groups))
|
||||
)
|
||||
self.qb_selection_filter.append(self.ple.party.isin(customers))
|
||||
|
||||
if self.filters.get("territory"):
|
||||
self.get_hierarchical_filters("Territory", "territory")
|
||||
@@ -1065,7 +1093,7 @@ class ReceivablePayableReport(object):
|
||||
)
|
||||
|
||||
if self.filters.show_remarks:
|
||||
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
|
||||
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200)
|
||||
|
||||
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
|
||||
if not fieldname:
|
||||
@@ -1132,3 +1160,19 @@ class ReceivablePayableReport(object):
|
||||
.run()
|
||||
)
|
||||
self.err_journals = [x[0] for x in results] if results else []
|
||||
|
||||
|
||||
def get_customer_group_with_children(customer_groups):
|
||||
if not isinstance(customer_groups, list):
|
||||
customer_groups = [d.strip() for d in customer_groups.strip().split(",") if d]
|
||||
|
||||
all_customer_groups = []
|
||||
for d in customer_groups:
|
||||
if frappe.db.exists("Customer Group", d):
|
||||
lft, rgt = frappe.db.get_value("Customer Group", d, ["lft", "rgt"])
|
||||
children = frappe.get_all("Customer Group", filters={"lft": [">=", lft], "rgt": ["<=", rgt]})
|
||||
all_customer_groups += [c.name for c in children]
|
||||
else:
|
||||
frappe.throw(_("Customer Group: {0} does not exist").format(d))
|
||||
|
||||
return list(set(all_customer_groups))
|
||||
|
||||
@@ -76,6 +76,41 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
|
||||
return credit_note
|
||||
|
||||
def test_pos_receivable(self):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"party_type": "Customer",
|
||||
"party": [self.customer],
|
||||
"report_date": add_days(today(), 2),
|
||||
"based_on_payment_terms": 0,
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"show_remarks": False,
|
||||
}
|
||||
|
||||
pos_inv = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
pos_inv.posting_date = add_days(today(), 2)
|
||||
pos_inv.is_pos = 1
|
||||
pos_inv.append(
|
||||
"payments",
|
||||
frappe._dict(
|
||||
mode_of_payment="Cash",
|
||||
amount=flt(pos_inv.grand_total / 2),
|
||||
),
|
||||
)
|
||||
pos_inv.disable_rounded_total = 1
|
||||
pos_inv.save()
|
||||
pos_inv.submit()
|
||||
|
||||
report = execute(filters)
|
||||
expected_data = [[pos_inv.grand_total, pos_inv.paid_amount, 0]]
|
||||
|
||||
row = report[1][-1]
|
||||
self.assertEqual(expected_data[0], [row.invoiced, row.paid, row.credit_note])
|
||||
pos_inv.cancel()
|
||||
|
||||
def test_accounts_receivable(self):
|
||||
filters = {
|
||||
"company": self.company,
|
||||
@@ -475,6 +510,30 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
report = execute(filters)[1]
|
||||
self.assertEqual(len(report), 0)
|
||||
|
||||
def test_multi_customer_group_filter(self):
|
||||
si = self.create_sales_invoice()
|
||||
cus_group = frappe.db.get_value("Customer", self.customer, "customer_group")
|
||||
# Create a list of customer groups, e.g., ["Group1", "Group2"]
|
||||
cus_groups_list = [cus_group, "_Test Customer Group 1"]
|
||||
|
||||
filters = {
|
||||
"company": self.company,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"customer_group": cus_groups_list, # Use the list of customer groups
|
||||
}
|
||||
report = execute(filters)[1]
|
||||
|
||||
# Assert that the report contains data for the specified customer groups
|
||||
self.assertTrue(len(report) > 0)
|
||||
|
||||
for row in report:
|
||||
# Assert that the customer group of each row is in the list of customer groups
|
||||
self.assertIn(row.customer_group, cus_groups_list)
|
||||
|
||||
def test_party_account_filter(self):
|
||||
si1 = self.create_sales_invoice()
|
||||
self.customer2 = (
|
||||
@@ -520,7 +579,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
filters.update({"party_account": self.debtors_usd})
|
||||
report = execute(filters)[1]
|
||||
self.assertEqual(len(report), 1)
|
||||
expected_data = [8000.0, 8000.0, self.debtors_usd, si2.currency]
|
||||
expected_data = [100.0, 100.0, self.debtors_usd, si2.currency]
|
||||
row = report[0]
|
||||
self.assertEqual(
|
||||
expected_data, [row.invoiced, row.outstanding, row.party_account, row.account_currency]
|
||||
@@ -557,6 +616,7 @@ class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
"in_party_currency": 1,
|
||||
}
|
||||
|
||||
si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
|
||||
|
||||
@@ -139,6 +139,11 @@ frappe.query_reports["Accounts Receivable Summary"] = {
|
||||
"label": __("Show GL Balance"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "for_revaluation_journals",
|
||||
"label": __("Revaluation Journals"),
|
||||
"fieldtype": "Check",
|
||||
}
|
||||
],
|
||||
|
||||
onload: function(report) {
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe.utils import cint, flt
|
||||
|
||||
from erpnext.accounts.party import get_partywise_advanced_payment_amount
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import ReceivablePayableReport
|
||||
from erpnext.accounts.utils import get_currency_precision
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -35,6 +36,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
def get_data(self, args):
|
||||
self.data = []
|
||||
self.receivables = ReceivablePayableReport(self.filters).run(args)[1]
|
||||
self.currency_precision = get_currency_precision() or 2
|
||||
|
||||
self.get_party_total(args)
|
||||
|
||||
@@ -58,7 +60,7 @@ class AccountsReceivableSummary(ReceivablePayableReport):
|
||||
gl_balance_map = get_gl_balance(self.filters.report_date, self.filters.company)
|
||||
|
||||
for party, party_dict in self.party_total.items():
|
||||
if party_dict.outstanding == 0:
|
||||
if flt(party_dict.outstanding, self.currency_precision) == 0:
|
||||
continue
|
||||
|
||||
row = frappe._dict()
|
||||
|
||||
@@ -2,7 +2,44 @@
|
||||
// License: GNU General Public License v3. See license.txt
|
||||
|
||||
frappe.query_reports["Budget Variance Report"] = {
|
||||
"filters": [
|
||||
"filters": get_filters(),
|
||||
"formatter": function (value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (column.fieldname.includes(__("variance"))) {
|
||||
|
||||
if (data[column.fieldname] < 0) {
|
||||
value = "<span style='color:red'>" + value + "</span>";
|
||||
}
|
||||
else if (data[column.fieldname] > 0) {
|
||||
value = "<span style='color:green'>" + value + "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
function get_filters() {
|
||||
function get_dimensions() {
|
||||
let result = [];
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.accounting_dimension.accounting_dimension.get_dimensions",
|
||||
args: {
|
||||
'with_cost_center_and_project': true
|
||||
},
|
||||
async: false,
|
||||
callback: function(r) {
|
||||
if(!r.exc) {
|
||||
result = r.message[0].map(elem => elem.document_type);
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
let budget_against_options = get_dimensions();
|
||||
|
||||
let filters = [
|
||||
{
|
||||
fieldname: "from_fiscal_year",
|
||||
label: __("From Fiscal Year"),
|
||||
@@ -44,9 +81,13 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
fieldname: "budget_against",
|
||||
label: __("Budget Against"),
|
||||
fieldtype: "Select",
|
||||
options: ["Cost Center", "Project"],
|
||||
options: budget_against_options,
|
||||
default: "Cost Center",
|
||||
reqd: 1,
|
||||
get_data: function() {
|
||||
console.log(this.options);
|
||||
return ["Emacs", "Rocks"];
|
||||
},
|
||||
on_change: function() {
|
||||
frappe.query_report.set_filter_value("budget_against_filter", []);
|
||||
frappe.query_report.refresh();
|
||||
@@ -71,24 +112,8 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
fieldtype: "Check",
|
||||
default: 0,
|
||||
},
|
||||
],
|
||||
"formatter": function (value, row, column, data, default_formatter) {
|
||||
value = default_formatter(value, row, column, data);
|
||||
]
|
||||
|
||||
if (column.fieldname.includes(__("variance"))) {
|
||||
|
||||
if (data[column.fieldname] < 0) {
|
||||
value = "<span style='color:red'>" + value + "</span>";
|
||||
}
|
||||
else if (data[column.fieldname] > 0) {
|
||||
value = "<span style='color:green'>" + value + "</span>";
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
return filters;
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frappe.query_reports["Budget Variance Report"].filters[4].options.push(dimension["document_type"]);
|
||||
});
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _, scrub
|
||||
from frappe import _, qb, scrub
|
||||
from frappe.utils import getdate, nowdate
|
||||
|
||||
|
||||
@@ -38,7 +38,6 @@ class PartyLedgerSummaryReport(object):
|
||||
"""
|
||||
Additional Columns for 'User Permission' based access control
|
||||
"""
|
||||
from frappe import qb
|
||||
|
||||
if self.filters.party_type == "Customer":
|
||||
self.territories = frappe._dict({})
|
||||
@@ -365,13 +364,29 @@ class PartyLedgerSummaryReport(object):
|
||||
|
||||
def get_party_adjustment_amounts(self):
|
||||
conditions = self.prepare_conditions()
|
||||
income_or_expense = (
|
||||
"Expense Account" if self.filters.party_type == "Customer" else "Income Account"
|
||||
account_type = "Expense Account" if self.filters.party_type == "Customer" else "Income Account"
|
||||
income_or_expense_accounts = frappe.db.get_all(
|
||||
"Account", filters={"account_type": account_type, "company": self.filters.company}, pluck="name"
|
||||
)
|
||||
invoice_dr_or_cr = "debit" if self.filters.party_type == "Customer" else "credit"
|
||||
reverse_dr_or_cr = "credit" if self.filters.party_type == "Customer" else "debit"
|
||||
round_off_account = frappe.get_cached_value("Company", self.filters.company, "round_off_account")
|
||||
|
||||
gl = qb.DocType("GL Entry")
|
||||
if not income_or_expense_accounts:
|
||||
# prevent empty 'in' condition
|
||||
income_or_expense_accounts.append("")
|
||||
|
||||
accounts_query = (
|
||||
qb.from_(gl)
|
||||
.select(gl.voucher_type, gl.voucher_no)
|
||||
.where(
|
||||
(gl.account.isin(income_or_expense_accounts))
|
||||
& (gl.posting_date.gte(self.filters.from_date))
|
||||
& (gl.posting_date.lte(self.filters.to_date))
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
@@ -381,16 +396,15 @@ class PartyLedgerSummaryReport(object):
|
||||
where
|
||||
docstatus < 2 and is_cancelled = 0
|
||||
and (voucher_type, voucher_no) in (
|
||||
select voucher_type, voucher_no from `tabGL Entry` gle, `tabAccount` acc
|
||||
where acc.name = gle.account and acc.account_type = '{income_or_expense}'
|
||||
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2
|
||||
{accounts_query}
|
||||
) and (voucher_type, voucher_no) in (
|
||||
select voucher_type, voucher_no from `tabGL Entry` gle
|
||||
where gle.party_type=%(party_type)s and ifnull(party, '') != ''
|
||||
and gle.posting_date between %(from_date)s and %(to_date)s and gle.docstatus < 2 {conditions}
|
||||
)
|
||||
""".format(
|
||||
conditions=conditions, income_or_expense=income_or_expense
|
||||
""".format(
|
||||
accounts_query=accounts_query,
|
||||
conditions=conditions,
|
||||
),
|
||||
self.filters,
|
||||
as_dict=True,
|
||||
@@ -414,7 +428,7 @@ class PartyLedgerSummaryReport(object):
|
||||
elif gle.party:
|
||||
parties.setdefault(gle.party, 0)
|
||||
parties[gle.party] += gle.get(reverse_dr_or_cr) - gle.get(invoice_dr_or_cr)
|
||||
elif frappe.get_cached_value("Account", gle.account, "account_type") == income_or_expense:
|
||||
elif frappe.get_cached_value("Account", gle.account, "account_type") == account_type:
|
||||
accounts.setdefault(gle.account, 0)
|
||||
accounts[gle.account] += gle.get(invoice_dr_or_cr) - gle.get(reverse_dr_or_cr)
|
||||
else:
|
||||
|
||||
@@ -8,7 +8,17 @@ import re
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_days, add_months, cint, cstr, flt, formatdate, get_first_day, getdate
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
cint,
|
||||
cstr,
|
||||
flt,
|
||||
formatdate,
|
||||
get_first_day,
|
||||
getdate,
|
||||
today,
|
||||
)
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
@@ -43,6 +53,8 @@ def get_period_list(
|
||||
year_start_date = getdate(period_start_date)
|
||||
year_end_date = getdate(period_end_date)
|
||||
|
||||
year_end_date = getdate(today()) if year_end_date > getdate(today()) else year_end_date
|
||||
|
||||
months_to_add = {"Yearly": 12, "Half-Yearly": 6, "Quarterly": 3, "Monthly": 1}[periodicity]
|
||||
|
||||
period_list = []
|
||||
|
||||
@@ -164,7 +164,12 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
credit_in_account_currency """
|
||||
|
||||
if filters.get("show_remarks"):
|
||||
select_fields += """,remarks"""
|
||||
if remarks_length := frappe.db.get_single_value(
|
||||
"Accounts Settings", "general_ledger_remarks_length"
|
||||
):
|
||||
select_fields += f",substr(remarks, 1, {remarks_length}) as 'remarks'"
|
||||
else:
|
||||
select_fields += """,remarks"""
|
||||
|
||||
order_by_statement = "order by posting_date, account, creation"
|
||||
|
||||
@@ -226,6 +231,9 @@ def get_conditions(filters):
|
||||
if filters.get("voucher_no"):
|
||||
conditions.append("voucher_no=%(voucher_no)s")
|
||||
|
||||
if filters.get("voucher_no_not_in"):
|
||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||
|
||||
if filters.get("group_by") == "Group by Party" and not filters.get("party_type"):
|
||||
conditions.append("party_type in ('Customer', 'Supplier')")
|
||||
|
||||
@@ -277,7 +285,8 @@ def get_conditions(filters):
|
||||
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
if not dimension.disabled:
|
||||
# Ignore 'Finance Book' set up as dimension in below logic, as it is already handled in above section
|
||||
if not dimension.disabled and dimension.document_type != "Finance Book":
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
|
||||
@@ -309,7 +309,8 @@ def get_conditions(filters):
|
||||
|
||||
def get_items(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
|
||||
@@ -381,7 +381,8 @@ def get_group_by_conditions(filters, doctype):
|
||||
|
||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
conditions = get_conditions(filters, additional_conditions)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
|
||||
@@ -119,8 +119,4 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"initial_depth": 3
|
||||
}
|
||||
|
||||
erpnext.dimension_filters.forEach((dimension) => {
|
||||
frappe.query_reports["Profitability Analysis"].filters[1].options.push(dimension["document_type"]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -52,6 +52,12 @@ frappe.query_reports["Purchase Register"] = {
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_payments",
|
||||
"label": __("Show Ledger View"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,13 +4,22 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.utils import flt
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, getdate
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.report.utils import (
|
||||
apply_common_conditions,
|
||||
get_advance_taxes_and_charges,
|
||||
get_journal_entries,
|
||||
get_opening_row,
|
||||
get_party_details,
|
||||
get_payment_entries,
|
||||
get_query_columns,
|
||||
get_taxes_query,
|
||||
get_values_for_columns,
|
||||
)
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -21,9 +30,15 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
include_payments = filters.get("include_payments")
|
||||
if filters.get("include_payments") and not filters.get("supplier"):
|
||||
frappe.throw(_("Please select a supplier for fetching payments."))
|
||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||
if filters.get("include_payments"):
|
||||
invoice_list += get_payments(filters)
|
||||
|
||||
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||
invoice_list, additional_table_columns
|
||||
invoice_list, additional_table_columns, include_payments
|
||||
)
|
||||
|
||||
if not invoice_list:
|
||||
@@ -33,14 +48,28 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(
|
||||
invoice_list, invoice_expense_map, expense_accounts
|
||||
invoice_list, invoice_expense_map, expense_accounts, include_payments
|
||||
)
|
||||
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||
suppliers = list(set(d.supplier for d in invoice_list))
|
||||
supplier_details = get_supplier_details(suppliers)
|
||||
supplier_details = get_party_details("Supplier", suppliers)
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||
|
||||
res = []
|
||||
if include_payments:
|
||||
opening_row = get_opening_row(
|
||||
"Supplier", filters.supplier, getdate(filters.from_date), filters.company
|
||||
)[0]
|
||||
res.append(
|
||||
{
|
||||
"payable_account": opening_row.account,
|
||||
"debit": flt(opening_row.debit),
|
||||
"credit": flt(opening_row.credit),
|
||||
"balance": flt(opening_row.balance),
|
||||
}
|
||||
)
|
||||
|
||||
data = []
|
||||
for inv in invoice_list:
|
||||
# invoice details
|
||||
@@ -48,24 +77,23 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
purchase_receipt = list(set(invoice_po_pr_map.get(inv.name, {}).get("purchase_receipt", [])))
|
||||
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
|
||||
|
||||
row = [
|
||||
inv.name,
|
||||
inv.posting_date,
|
||||
inv.supplier,
|
||||
inv.supplier_name,
|
||||
*get_values_for_columns(additional_table_columns, inv).values(),
|
||||
supplier_details.get(inv.supplier), # supplier_group
|
||||
inv.tax_id,
|
||||
inv.credit_to,
|
||||
inv.mode_of_payment,
|
||||
", ".join(project),
|
||||
inv.bill_no,
|
||||
inv.bill_date,
|
||||
inv.remarks,
|
||||
", ".join(purchase_order),
|
||||
", ".join(purchase_receipt),
|
||||
company_currency,
|
||||
]
|
||||
row = {
|
||||
"voucher_type": inv.doctype,
|
||||
"voucher_no": inv.name,
|
||||
"posting_date": inv.posting_date,
|
||||
"supplier_id": inv.supplier,
|
||||
"supplier_name": inv.supplier_name,
|
||||
**get_values_for_columns(additional_table_columns, inv),
|
||||
"supplier_group": supplier_details.get(inv.supplier).get("supplier_group"), # supplier_group
|
||||
"tax_id": supplier_details.get(inv.supplier).get("tax_id"),
|
||||
"payable_account": inv.credit_to,
|
||||
"mode_of_payment": inv.mode_of_payment,
|
||||
"project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project,
|
||||
"remarks": inv.remarks,
|
||||
"purchase_order": ", ".join(purchase_order),
|
||||
"purchase_receipt": ", ".join(purchase_receipt),
|
||||
"currency": company_currency,
|
||||
}
|
||||
|
||||
# map expense values
|
||||
base_net_total = 0
|
||||
@@ -75,14 +103,16 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
else:
|
||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||
base_net_total += expense_amount
|
||||
row.append(expense_amount)
|
||||
row.update({frappe.scrub(expense_acc): expense_amount})
|
||||
|
||||
# Add amount in unrealized account
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
row.append(flt(internal_invoice_map.get((inv.name, account))))
|
||||
row.update(
|
||||
{frappe.scrub(account + "_unrealized"): flt(internal_invoice_map.get((inv.name, account)))}
|
||||
)
|
||||
|
||||
# net total
|
||||
row.append(base_net_total or inv.base_net_total)
|
||||
row.update({"net_total": base_net_total or inv.base_net_total})
|
||||
|
||||
# tax account
|
||||
total_tax = 0
|
||||
@@ -90,45 +120,190 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
if tax_acc not in expense_accounts:
|
||||
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc))
|
||||
total_tax += tax_amount
|
||||
row.append(tax_amount)
|
||||
row.update({frappe.scrub(tax_acc): tax_amount})
|
||||
|
||||
# total tax, grand total, rounded total & outstanding amount
|
||||
row += [total_tax, inv.base_grand_total, flt(inv.base_grand_total, 0), inv.outstanding_amount]
|
||||
row.update(
|
||||
{
|
||||
"total_tax": total_tax,
|
||||
"grand_total": inv.base_grand_total,
|
||||
"rounded_total": inv.base_rounded_total,
|
||||
"outstanding_amount": inv.outstanding_amount,
|
||||
}
|
||||
)
|
||||
|
||||
if inv.doctype == "Purchase Invoice":
|
||||
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||
else:
|
||||
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
res += sorted(data, key=lambda x: x["posting_date"])
|
||||
|
||||
if include_payments:
|
||||
running_balance = flt(opening_row.balance)
|
||||
for row in range(1, len(res)):
|
||||
running_balance += res[row]["debit"] - res[row]["credit"]
|
||||
res[row].update({"balance": running_balance})
|
||||
|
||||
return columns, res, None, None, None, include_payments
|
||||
|
||||
|
||||
def get_columns(invoice_list, additional_table_columns):
|
||||
def get_columns(invoice_list, additional_table_columns, include_payments=False):
|
||||
"""return columns based on filters"""
|
||||
columns = [
|
||||
_("Invoice") + ":Link/Purchase Invoice:120",
|
||||
_("Posting Date") + ":Date:80",
|
||||
_("Supplier Id") + "::120",
|
||||
_("Supplier Name") + "::120",
|
||||
{
|
||||
"label": _("Voucher Type"),
|
||||
"fieldname": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Voucher"),
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
||||
{
|
||||
"label": _("Supplier"),
|
||||
"fieldname": "supplier_id",
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 120},
|
||||
]
|
||||
|
||||
if additional_table_columns:
|
||||
if additional_table_columns and not include_payments:
|
||||
columns += additional_table_columns
|
||||
|
||||
columns += [
|
||||
_("Supplier Group") + ":Link/Supplier Group:120",
|
||||
_("Tax Id") + "::80",
|
||||
_("Payable Account") + ":Link/Account:120",
|
||||
_("Mode of Payment") + ":Link/Mode of Payment:80",
|
||||
_("Project") + ":Link/Project:80",
|
||||
_("Bill No") + "::120",
|
||||
_("Bill Date") + ":Date:80",
|
||||
_("Remarks") + "::150",
|
||||
_("Purchase Order") + ":Link/Purchase Order:100",
|
||||
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Supplier Group"),
|
||||
"fieldname": "supplier_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Group",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||
{
|
||||
"label": _("Payable Account"),
|
||||
"fieldname": "payable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Bill No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 120},
|
||||
{"label": _("Bill Date"), "fieldname": "bill_date", "fieldtype": "Date", "width": 80},
|
||||
{
|
||||
"label": _("Purchase Order"),
|
||||
"fieldname": "purchase_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Purchase Receipt"),
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Receipt",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
else:
|
||||
columns += [
|
||||
{
|
||||
"fieldname": "payable_account",
|
||||
"label": _("Payable Account"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 120,
|
||||
},
|
||||
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "balance", "label": _("Balance"), "fieldtype": "Currency", "width": 120},
|
||||
]
|
||||
|
||||
account_columns, accounts = get_account_columns(invoice_list, include_payments)
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ account_columns[0]
|
||||
+ account_columns[1]
|
||||
+ [
|
||||
{
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
+ account_columns[2]
|
||||
+ [
|
||||
{
|
||||
"label": _("Total Tax"),
|
||||
"fieldname": "total_tax",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
columns += [{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 120}]
|
||||
return columns, accounts[0], accounts[2], accounts[1]
|
||||
|
||||
|
||||
def get_account_columns(invoice_list, include_payments):
|
||||
expense_accounts = []
|
||||
tax_accounts = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
|
||||
expense_columns = []
|
||||
tax_columns = []
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
expense_accounts = frappe.db.sql_list(
|
||||
"""select distinct expense_account
|
||||
@@ -139,15 +314,18 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple([inv.name for inv in invoice_list]),
|
||||
)
|
||||
|
||||
tax_accounts = frappe.db.sql_list(
|
||||
"""select distinct account_head
|
||||
from `tabPurchase Taxes and Charges` where parenttype = 'Purchase Invoice'
|
||||
and docstatus = 1 and (account_head is not null and account_head != '')
|
||||
and category in ('Total', 'Valuation and Total')
|
||||
and parent in (%s) order by account_head"""
|
||||
% ", ".join(["%s"] * len(invoice_list)),
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
purchase_taxes_query = get_taxes_query(
|
||||
invoice_list, "Purchase Taxes and Charges", "Purchase Invoice"
|
||||
)
|
||||
purchase_tax_accounts = purchase_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = purchase_tax_accounts
|
||||
|
||||
if include_payments:
|
||||
advance_taxes_query = get_taxes_query(
|
||||
invoice_list, "Advance Taxes and Charges", "Payment Entry"
|
||||
)
|
||||
advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = set(tax_accounts + advance_tax_accounts)
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||
"""SELECT distinct unrealized_profit_loss_account
|
||||
@@ -158,108 +336,109 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
|
||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
||||
unrealized_profit_loss_account_columns = [
|
||||
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
|
||||
]
|
||||
tax_columns = [
|
||||
(account + ":Currency/currency:120")
|
||||
for account in tax_accounts
|
||||
if account not in expense_accounts
|
||||
]
|
||||
for account in expense_accounts:
|
||||
expense_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ expense_columns
|
||||
+ unrealized_profit_loss_account_columns
|
||||
+ [_("Net Total") + ":Currency/currency:120"]
|
||||
+ tax_columns
|
||||
+ [
|
||||
_("Total Tax") + ":Currency/currency:120",
|
||||
_("Grand Total") + ":Currency/currency:120",
|
||||
_("Rounded Total") + ":Currency/currency:120",
|
||||
_("Outstanding Amount") + ":Currency/currency:120",
|
||||
]
|
||||
)
|
||||
for account in tax_accounts:
|
||||
if account not in expense_accounts:
|
||||
tax_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
unrealized_profit_loss_account_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
columns = [expense_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||
accounts = [expense_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " and company=%(company)s"
|
||||
if filters.get("supplier"):
|
||||
conditions += " and supplier = %(supplier)s"
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " and posting_date>=%(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and posting_date<=%(to_date)s"
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
conditions += " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"
|
||||
|
||||
if filters.get("cost_center"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.cost_center, '') = %(cost_center)s)"""
|
||||
|
||||
if filters.get("warehouse"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.warehouse, '') = %(warehouse)s)"""
|
||||
|
||||
if filters.get("item_group"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s)"""
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
if accounting_dimensions:
|
||||
common_condition = """
|
||||
and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
"""
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
return columns, accounts
|
||||
|
||||
|
||||
def get_invoices(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name, posting_date, credit_to, supplier, supplier_name, tax_id, bill_no, bill_date,
|
||||
remarks, base_net_total, base_grand_total, outstanding_amount,
|
||||
mode_of_payment {0}
|
||||
from `tabPurchase Invoice`
|
||||
where docstatus = 1 {1}
|
||||
order by posting_date desc, name desc""".format(
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
query = (
|
||||
frappe.qb.from_(pi)
|
||||
.select(
|
||||
ConstantColumn("Purchase Invoice").as_("doctype"),
|
||||
pi.name,
|
||||
pi.posting_date,
|
||||
pi.credit_to,
|
||||
pi.supplier,
|
||||
pi.supplier_name,
|
||||
pi.tax_id,
|
||||
pi.bill_no,
|
||||
pi.bill_date,
|
||||
pi.remarks,
|
||||
pi.base_net_total,
|
||||
pi.base_grand_total,
|
||||
pi.base_rounded_total,
|
||||
pi.outstanding_amount,
|
||||
pi.mode_of_payment,
|
||||
)
|
||||
.where((pi.docstatus == 1))
|
||||
.orderby(pi.posting_date, pi.name, order=Order.desc)
|
||||
)
|
||||
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
query = query.select(col)
|
||||
|
||||
if filters.get("supplier"):
|
||||
query = query.where(pi.supplier == filters.supplier)
|
||||
|
||||
query = get_conditions(filters, query, "Purchase Invoice")
|
||||
|
||||
query = apply_common_conditions(
|
||||
filters, query, doctype="Purchase Invoice", child_doctype="Purchase Invoice Item"
|
||||
)
|
||||
|
||||
invoices = query.run(as_dict=True)
|
||||
return invoices
|
||||
|
||||
|
||||
def get_conditions(filters, query, doctype):
|
||||
parent_doc = frappe.qb.DocType(doctype)
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
query = query.where(parent_doc.mode_of_payment == filters.mode_of_payment)
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def get_payments(filters):
|
||||
args = frappe._dict(
|
||||
account="credit_to",
|
||||
account_fieldname="paid_to",
|
||||
party="supplier",
|
||||
party_name="supplier_name",
|
||||
party_account=get_party_account("Supplier", filters.supplier, filters.company),
|
||||
)
|
||||
payment_entries = get_payment_entries(filters, args)
|
||||
journal_entries = get_journal_entries(filters, args)
|
||||
return payment_entries + journal_entries
|
||||
|
||||
|
||||
def get_invoice_expense_map(invoice_list):
|
||||
expense_details = frappe.db.sql(
|
||||
@@ -300,7 +479,9 @@ def get_internal_invoice_map(invoice_list):
|
||||
return internal_invoice_map
|
||||
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||
def get_invoice_tax_map(
|
||||
invoice_list, invoice_expense_map, expense_accounts, include_payments=False
|
||||
):
|
||||
tax_details = frappe.db.sql(
|
||||
"""
|
||||
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
||||
@@ -315,6 +496,9 @@ def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if include_payments:
|
||||
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||
|
||||
invoice_tax_map = {}
|
||||
for d in tax_details:
|
||||
if d.account_head in expense_accounts:
|
||||
@@ -382,17 +566,3 @@ def get_account_details(invoice_list):
|
||||
account_map[acc.name] = acc.parent_account
|
||||
|
||||
return account_map
|
||||
|
||||
|
||||
def get_supplier_details(suppliers):
|
||||
supplier_details = {}
|
||||
for supp in frappe.db.sql(
|
||||
"""select name, supplier_group from `tabSupplier`
|
||||
where name in (%s)"""
|
||||
% ", ".join(["%s"] * len(suppliers)),
|
||||
tuple(suppliers),
|
||||
as_dict=1,
|
||||
):
|
||||
supplier_details.setdefault(supp.name, supp.supplier_group)
|
||||
|
||||
return supplier_details
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_months, getdate, today
|
||||
|
||||
from erpnext.accounts.report.purchase_register.purchase_register import execute
|
||||
|
||||
|
||||
class TestPurchaseRegister(FrappeTestCase):
|
||||
def test_purchase_register(self):
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
|
||||
|
||||
filters = frappe._dict(
|
||||
company="_Test Company 6", from_date=add_months(today(), -1), to_date=today()
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice()
|
||||
|
||||
report_results = execute(filters)
|
||||
first_row = frappe._dict(report_results[1][0])
|
||||
self.assertEqual(first_row.voucher_type, "Purchase Invoice")
|
||||
self.assertEqual(first_row.voucher_no, pi.name)
|
||||
self.assertEqual(first_row.payable_account, "Creditors - _TC6")
|
||||
self.assertEqual(first_row.net_total, 1000)
|
||||
self.assertEqual(first_row.total_tax, 100)
|
||||
self.assertEqual(first_row.grand_total, 1100)
|
||||
|
||||
def test_purchase_register_ledger_view(self):
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
|
||||
|
||||
filters = frappe._dict(
|
||||
company="_Test Company 6",
|
||||
from_date=add_months(today(), -1),
|
||||
to_date=today(),
|
||||
include_payments=True,
|
||||
supplier="_Test Supplier",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice()
|
||||
pe = make_payment_entry()
|
||||
|
||||
report_results = execute(filters)
|
||||
first_row = frappe._dict(report_results[1][2])
|
||||
self.assertEqual(first_row.voucher_type, "Payment Entry")
|
||||
self.assertEqual(first_row.voucher_no, pe.name)
|
||||
self.assertEqual(first_row.payable_account, "Creditors - _TC6")
|
||||
self.assertEqual(first_row.debit, 0)
|
||||
self.assertEqual(first_row.credit, 600)
|
||||
self.assertEqual(first_row.balance, 500)
|
||||
|
||||
|
||||
def make_purchase_invoice():
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
gst_acc = create_account(
|
||||
account_name="GST",
|
||||
account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TC6",
|
||||
company="_Test Company 6",
|
||||
account_currency="INR",
|
||||
)
|
||||
create_warehouse(warehouse_name="_Test Warehouse - _TC6", company="_Test Company 6")
|
||||
create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company 6")
|
||||
pi = create_purchase_invoice_with_taxes()
|
||||
pi.submit()
|
||||
return pi
|
||||
|
||||
|
||||
def create_purchase_invoice_with_taxes():
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "Purchase Invoice",
|
||||
"posting_date": today(),
|
||||
"supplier": "_Test Supplier",
|
||||
"company": "_Test Company 6",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"taxes_and_charges": "",
|
||||
"currency": "INR",
|
||||
"credit_to": "Creditors - _TC6",
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Invoice Item",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"item_code": "_Test Item",
|
||||
"qty": 1,
|
||||
"rate": 1000,
|
||||
"expense_account": "Stock Received But Not Billed - _TC6",
|
||||
}
|
||||
],
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "GST - _TC6",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"add_deduct_tax": "Add",
|
||||
"category": "Valuation and Total",
|
||||
"charge_type": "Actual",
|
||||
"description": "Shipping Charges",
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"parentfield": "taxes",
|
||||
"rate": 100,
|
||||
"tax_amount": 100.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def make_payment_entry():
|
||||
frappe.set_user("Administrator")
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||
|
||||
return create_payment_entry(
|
||||
company="_Test Company 6",
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier",
|
||||
payment_type="Pay",
|
||||
paid_from="Cash - _TC6",
|
||||
paid_to="Creditors - _TC6",
|
||||
paid_amount=600,
|
||||
save=1,
|
||||
submit=1,
|
||||
)
|
||||
@@ -64,6 +64,12 @@ frappe.query_reports["Sales Register"] = {
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_payments",
|
||||
"label": __("Show Ledger View"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,13 +5,22 @@
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.utils import flt
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, getdate
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.report.utils import (
|
||||
apply_common_conditions,
|
||||
get_advance_taxes_and_charges,
|
||||
get_journal_entries,
|
||||
get_opening_row,
|
||||
get_party_details,
|
||||
get_payment_entries,
|
||||
get_query_columns,
|
||||
get_taxes_query,
|
||||
get_values_for_columns,
|
||||
)
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -22,9 +31,15 @@ def _execute(filters, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = frappe._dict({})
|
||||
|
||||
include_payments = filters.get("include_payments")
|
||||
if filters.get("include_payments") and not filters.get("customer"):
|
||||
frappe.throw(_("Please select a customer for fetching payments."))
|
||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||
invoice_list, additional_table_columns
|
||||
if filters.get("include_payments"):
|
||||
invoice_list += get_payments(filters)
|
||||
|
||||
columns, income_accounts, unrealized_profit_loss_accounts, tax_accounts = get_columns(
|
||||
invoice_list, additional_table_columns, include_payments
|
||||
)
|
||||
|
||||
if not invoice_list:
|
||||
@@ -34,13 +49,29 @@ def _execute(filters, additional_table_columns=None):
|
||||
invoice_income_map = get_invoice_income_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_income_map, invoice_tax_map = get_invoice_tax_map(
|
||||
invoice_list, invoice_income_map, income_accounts
|
||||
invoice_list, invoice_income_map, income_accounts, include_payments
|
||||
)
|
||||
# Cost Center & Warehouse Map
|
||||
invoice_cc_wh_map = get_invoice_cc_wh_map(invoice_list)
|
||||
invoice_so_dn_map = get_invoice_so_dn_map(invoice_list)
|
||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||
mode_of_payments = get_mode_of_payments([inv.name for inv in invoice_list])
|
||||
customers = list(set(d.customer for d in invoice_list))
|
||||
customer_details = get_party_details("Customer", customers)
|
||||
|
||||
res = []
|
||||
if include_payments:
|
||||
opening_row = get_opening_row(
|
||||
"Customer", filters.customer, getdate(filters.from_date), filters.company
|
||||
)[0]
|
||||
res.append(
|
||||
{
|
||||
"receivable_account": opening_row.account,
|
||||
"debit": flt(opening_row.debit),
|
||||
"credit": flt(opening_row.credit),
|
||||
"balance": flt(opening_row.balance),
|
||||
}
|
||||
)
|
||||
|
||||
data = []
|
||||
for inv in invoice_list:
|
||||
@@ -51,14 +82,15 @@ def _execute(filters, additional_table_columns=None):
|
||||
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
|
||||
|
||||
row = {
|
||||
"invoice": inv.name,
|
||||
"voucher_type": inv.doctype,
|
||||
"voucher_no": inv.name,
|
||||
"posting_date": inv.posting_date,
|
||||
"customer": inv.customer,
|
||||
"customer_name": inv.customer_name,
|
||||
**get_values_for_columns(additional_table_columns, inv),
|
||||
"customer_group": inv.get("customer_group"),
|
||||
"territory": inv.get("territory"),
|
||||
"tax_id": inv.get("tax_id"),
|
||||
"customer_group": customer_details.get(inv.customer).get("customer_group"),
|
||||
"territory": customer_details.get(inv.customer).get("territory"),
|
||||
"tax_id": customer_details.get(inv.customer).get("tax_id"),
|
||||
"receivable_account": inv.debit_to,
|
||||
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
||||
"project": inv.project,
|
||||
@@ -66,7 +98,7 @@ def _execute(filters, additional_table_columns=None):
|
||||
"remarks": inv.remarks,
|
||||
"sales_order": ", ".join(sales_order),
|
||||
"delivery_note": ", ".join(delivery_note),
|
||||
"cost_center": ", ".join(cost_center),
|
||||
"cost_center": ", ".join(cost_center) if inv.doctype == "Sales Invoice" else inv.cost_center,
|
||||
"warehouse": ", ".join(warehouse),
|
||||
"currency": company_currency,
|
||||
}
|
||||
@@ -116,19 +148,36 @@ def _execute(filters, additional_table_columns=None):
|
||||
}
|
||||
)
|
||||
|
||||
if inv.doctype == "Sales Invoice":
|
||||
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||
else:
|
||||
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
res += sorted(data, key=lambda x: x["posting_date"])
|
||||
|
||||
if include_payments:
|
||||
running_balance = flt(opening_row.balance)
|
||||
for row in range(1, len(res)):
|
||||
running_balance += res[row]["debit"] - res[row]["credit"]
|
||||
res[row].update({"balance": running_balance})
|
||||
|
||||
return columns, res, None, None, None, include_payments
|
||||
|
||||
|
||||
def get_columns(invoice_list, additional_table_columns):
|
||||
def get_columns(invoice_list, additional_table_columns, include_payments=False):
|
||||
"""return columns based on filters"""
|
||||
columns = [
|
||||
{
|
||||
"label": _("Invoice"),
|
||||
"fieldname": "invoice",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Invoice",
|
||||
"label": _("Voucher Type"),
|
||||
"fieldname": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Voucher"),
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
||||
@@ -142,83 +191,156 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
{"label": _("Customer Name"), "fieldname": "customer_name", "fieldtype": "Data", "width": 120},
|
||||
]
|
||||
|
||||
if additional_table_columns:
|
||||
if additional_table_columns and not include_payments:
|
||||
columns += additional_table_columns
|
||||
|
||||
columns += [
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Customer Group"),
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||
{
|
||||
"label": _("Receivable Account"),
|
||||
"fieldname": "receivable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 100},
|
||||
{
|
||||
"label": _("Sales Order"),
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Delivery Note"),
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"options": "Delivery Note",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
else:
|
||||
columns += [
|
||||
{
|
||||
"fieldname": "receivable_account",
|
||||
"label": _("Receivable Account"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 120,
|
||||
},
|
||||
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "balance", "label": _("Balance"), "fieldtype": "Currency", "width": 120},
|
||||
]
|
||||
|
||||
account_columns, accounts = get_account_columns(invoice_list, include_payments)
|
||||
|
||||
net_total_column = [
|
||||
{
|
||||
"label": _("Customer Group"),
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group",
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 120},
|
||||
{
|
||||
"label": _("Receivable Account"),
|
||||
"fieldname": "receivable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 80,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 150},
|
||||
{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150},
|
||||
{
|
||||
"label": _("Sales Order"),
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Delivery Note"),
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"options": "Delivery Note",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
}
|
||||
]
|
||||
|
||||
total_columns = [
|
||||
{
|
||||
"label": _("Tax Total"),
|
||||
"fieldname": "tax_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
if not include_payments:
|
||||
total_columns += [
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ account_columns[0]
|
||||
+ account_columns[2]
|
||||
+ net_total_column
|
||||
+ account_columns[1]
|
||||
+ total_columns
|
||||
)
|
||||
columns += [{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150}]
|
||||
return columns, accounts[0], accounts[1], accounts[2]
|
||||
|
||||
|
||||
def get_account_columns(invoice_list, include_payments):
|
||||
income_accounts = []
|
||||
tax_accounts = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
|
||||
income_columns = []
|
||||
tax_columns = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
@@ -230,14 +352,16 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
|
||||
tax_accounts = frappe.db.sql_list(
|
||||
"""select distinct account_head
|
||||
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
|
||||
and docstatus = 1 and base_tax_amount_after_discount_amount != 0
|
||||
and parent in (%s) order by account_head"""
|
||||
% ", ".join(["%s"] * len(invoice_list)),
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
sales_taxes_query = get_taxes_query(invoice_list, "Sales Taxes and Charges", "Sales Invoice")
|
||||
sales_tax_accounts = sales_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = sales_tax_accounts
|
||||
|
||||
if include_payments:
|
||||
advance_taxes_query = get_taxes_query(
|
||||
invoice_list, "Advance Taxes and Charges", "Payment Entry"
|
||||
)
|
||||
advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = set(tax_accounts + advance_tax_accounts)
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||
"""SELECT distinct unrealized_profit_loss_account
|
||||
@@ -283,134 +407,82 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
}
|
||||
)
|
||||
|
||||
net_total_column = [
|
||||
{
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
columns = [income_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||
accounts = [income_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||
|
||||
total_columns = [
|
||||
{
|
||||
"label": _("Tax Total"),
|
||||
"fieldname": "tax_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ income_columns
|
||||
+ unrealized_profit_loss_account_columns
|
||||
+ net_total_column
|
||||
+ tax_columns
|
||||
+ total_columns
|
||||
)
|
||||
|
||||
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False) or []
|
||||
accounting_dimensions_list = [d.fieldname for d in accounting_dimensions]
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " and company=%(company)s"
|
||||
|
||||
if filters.get("customer") and "customer" not in accounting_dimensions_list:
|
||||
conditions += " and customer = %(customer)s"
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " and posting_date >= %(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and posting_date <= %(to_date)s"
|
||||
|
||||
if filters.get("owner"):
|
||||
conditions += " and owner = %(owner)s"
|
||||
|
||||
def get_sales_invoice_item_field_condition(field, table="Sales Invoice Item") -> str:
|
||||
if not filters.get(field) or field in accounting_dimensions_list:
|
||||
return ""
|
||||
return f""" and exists(select name from `tab{table}`
|
||||
where parent=`tabSales Invoice`.name
|
||||
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
|
||||
|
||||
conditions += get_sales_invoice_item_field_condition("mode_of_payment", "Sales Invoice Payment")
|
||||
conditions += get_sales_invoice_item_field_condition("cost_center")
|
||||
conditions += get_sales_invoice_item_field_condition("warehouse")
|
||||
conditions += get_sales_invoice_item_field_condition("brand")
|
||||
conditions += get_sales_invoice_item_field_condition("item_group")
|
||||
|
||||
if accounting_dimensions:
|
||||
common_condition = """
|
||||
and exists(select name from `tabSales Invoice Item`
|
||||
where parent=`tabSales Invoice`.name
|
||||
"""
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
return columns, accounts
|
||||
|
||||
|
||||
def get_invoices(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select name, posting_date, debit_to, project, customer,
|
||||
customer_name, owner, remarks, territory, tax_id, customer_group,
|
||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
||||
is_internal_customer, represents_company, company {0}
|
||||
from `tabSales Invoice`
|
||||
where docstatus = 1 {1}
|
||||
order by posting_date desc, name desc""".format(
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
query = (
|
||||
frappe.qb.from_(si)
|
||||
.select(
|
||||
ConstantColumn("Sales Invoice").as_("doctype"),
|
||||
si.name,
|
||||
si.posting_date,
|
||||
si.debit_to,
|
||||
si.project,
|
||||
si.customer,
|
||||
si.customer_name,
|
||||
si.owner,
|
||||
si.remarks,
|
||||
si.territory,
|
||||
si.tax_id,
|
||||
si.customer_group,
|
||||
si.base_net_total,
|
||||
si.base_grand_total,
|
||||
si.base_rounded_total,
|
||||
si.outstanding_amount,
|
||||
si.is_internal_customer,
|
||||
si.represents_company,
|
||||
si.company,
|
||||
)
|
||||
.where((si.docstatus == 1))
|
||||
.orderby(si.posting_date, si.name, order=Order.desc)
|
||||
)
|
||||
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
query = query.select(col)
|
||||
|
||||
if filters.get("customer"):
|
||||
query = query.where(si.customer == filters.customer)
|
||||
|
||||
query = get_conditions(filters, query, "Sales Invoice")
|
||||
query = apply_common_conditions(
|
||||
filters, query, doctype="Sales Invoice", child_doctype="Sales Invoice Item"
|
||||
)
|
||||
|
||||
invoices = query.run(as_dict=True)
|
||||
return invoices
|
||||
|
||||
|
||||
def get_conditions(filters, query, doctype):
|
||||
parent_doc = frappe.qb.DocType(doctype)
|
||||
if filters.get("owner"):
|
||||
query = query.where(parent_doc.owner == filters.owner)
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
payment_doc = frappe.qb.DocType("Sales Invoice Payment")
|
||||
query = query.inner_join(payment_doc).on(parent_doc.name == payment_doc.parent)
|
||||
query = query.where(payment_doc.mode_of_payment == filters.mode_of_payment).distinct()
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def get_payments(filters):
|
||||
args = frappe._dict(
|
||||
account="debit_to",
|
||||
account_fieldname="paid_from",
|
||||
party="customer",
|
||||
party_name="customer_name",
|
||||
party_account=get_party_account("Customer", filters.customer, filters.company),
|
||||
)
|
||||
payment_entries = get_payment_entries(filters, args)
|
||||
journal_entries = get_journal_entries(filters, args)
|
||||
return payment_entries + journal_entries
|
||||
|
||||
|
||||
def get_invoice_income_map(invoice_list):
|
||||
income_details = frappe.db.sql(
|
||||
@@ -447,7 +519,7 @@ def get_internal_invoice_map(invoice_list):
|
||||
return internal_invoice_map
|
||||
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts, include_payments=False):
|
||||
tax_details = frappe.db.sql(
|
||||
"""select parent, account_head,
|
||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
||||
@@ -457,6 +529,9 @@ def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if include_payments:
|
||||
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||
|
||||
invoice_tax_map = {}
|
||||
for d in tax_details:
|
||||
if d.account_head in income_accounts:
|
||||
|
||||
@@ -184,6 +184,16 @@ def get_columns(filters):
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
else:
|
||||
columns.append(
|
||||
{
|
||||
"label": _(filters.get("party_type")),
|
||||
"fieldname": "party",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "party_type",
|
||||
"width": 180,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
@@ -335,21 +345,16 @@ def get_tds_docs_query(filters, bank_accounts, tds_accounts):
|
||||
|
||||
if filters.get("party"):
|
||||
party = [filters.get("party")]
|
||||
query = query.where(
|
||||
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
|
||||
| ((gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party")))
|
||||
| gle.party.isin(party)
|
||||
jv_condition = gle.against.isin(party) | (
|
||||
(gle.voucher_type == "Journal Entry") & (gle.party == filters.get("party"))
|
||||
)
|
||||
else:
|
||||
party = frappe.get_all(filters.get("party_type"), pluck="name")
|
||||
query = query.where(
|
||||
((gle.account.isin(tds_accounts) & gle.against.isin(party)))
|
||||
| (
|
||||
(gle.voucher_type == "Journal Entry")
|
||||
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
|
||||
)
|
||||
| gle.party.isin(party)
|
||||
jv_condition = gle.against.isin(party) | (
|
||||
(gle.voucher_type == "Journal Entry")
|
||||
& ((gle.party_type == filters.get("party_type")) | (gle.party_type == ""))
|
||||
)
|
||||
query = query.where((gle.account.isin(tds_accounts) & jv_condition) | gle.party.isin(party))
|
||||
return query
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import frappe
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import flt, formatdate, get_datetime_str, get_table_name
|
||||
from pypika import Order
|
||||
|
||||
from erpnext import get_company_currency, get_default_company
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
)
|
||||
from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_date
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
__exchange_rates = {}
|
||||
@@ -165,7 +173,7 @@ def get_query_columns(report_columns):
|
||||
else:
|
||||
columns.append(fieldname)
|
||||
|
||||
return ", " + ", ".join(columns)
|
||||
return columns
|
||||
|
||||
|
||||
def get_values_for_columns(report_columns, report_row):
|
||||
@@ -179,3 +187,203 @@ def get_values_for_columns(report_columns, report_row):
|
||||
values[fieldname] = report_row.get(fieldname)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def get_party_details(party_type, party_list):
|
||||
party_details = {}
|
||||
party = frappe.qb.DocType(party_type)
|
||||
query = frappe.qb.from_(party).select(party.name, party.tax_id).where(party.name.isin(party_list))
|
||||
if party_type == "Supplier":
|
||||
query = query.select(party.supplier_group)
|
||||
else:
|
||||
query = query.select(party.customer_group, party.territory)
|
||||
|
||||
party_detail_list = query.run(as_dict=True)
|
||||
for party_dict in party_detail_list:
|
||||
party_details[party_dict.name] = party_dict
|
||||
return party_details
|
||||
|
||||
|
||||
def get_taxes_query(invoice_list, doctype, parenttype):
|
||||
taxes = frappe.qb.DocType(doctype)
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(taxes)
|
||||
.select(taxes.account_head)
|
||||
.distinct()
|
||||
.where(
|
||||
(taxes.parenttype == parenttype)
|
||||
& (taxes.docstatus == 1)
|
||||
& (taxes.account_head.isnotnull())
|
||||
& (taxes.parent.isin([inv.name for inv in invoice_list]))
|
||||
)
|
||||
.orderby(taxes.account_head)
|
||||
)
|
||||
|
||||
if doctype == "Purchase Taxes and Charges":
|
||||
return query.where(taxes.category.isin(["Total", "Valuation and Total"]))
|
||||
elif doctype == "Sales Taxes and Charges":
|
||||
return query
|
||||
return query.where(taxes.charge_type.isin(["On Paid Amount", "Actual"]))
|
||||
|
||||
|
||||
def get_journal_entries(filters, args):
|
||||
je = frappe.qb.DocType("Journal Entry")
|
||||
journal_account = frappe.qb.DocType("Journal Entry Account")
|
||||
query = (
|
||||
frappe.qb.from_(je)
|
||||
.inner_join(journal_account)
|
||||
.on(je.name == journal_account.parent)
|
||||
.select(
|
||||
je.voucher_type.as_("doctype"),
|
||||
je.name,
|
||||
je.posting_date,
|
||||
journal_account.account.as_(args.account),
|
||||
journal_account.party.as_(args.party),
|
||||
journal_account.party.as_(args.party_name),
|
||||
je.bill_no,
|
||||
je.bill_date,
|
||||
je.remark.as_("remarks"),
|
||||
je.total_amount.as_("base_net_total"),
|
||||
je.total_amount.as_("base_grand_total"),
|
||||
je.mode_of_payment,
|
||||
journal_account.project,
|
||||
)
|
||||
.where(
|
||||
(je.voucher_type == "Journal Entry")
|
||||
& (journal_account.party == filters.get(args.party))
|
||||
& (journal_account.account == args.party_account)
|
||||
)
|
||||
.orderby(je.posting_date, je.name, order=Order.desc)
|
||||
)
|
||||
query = apply_common_conditions(filters, query, doctype="Journal Entry", payments=True)
|
||||
|
||||
journal_entries = query.run(as_dict=True)
|
||||
return journal_entries
|
||||
|
||||
|
||||
def get_payment_entries(filters, args):
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
query = (
|
||||
frappe.qb.from_(pe)
|
||||
.select(
|
||||
ConstantColumn("Payment Entry").as_("doctype"),
|
||||
pe.name,
|
||||
pe.posting_date,
|
||||
pe[args.account_fieldname].as_(args.account),
|
||||
pe.party.as_(args.party),
|
||||
pe.party_name.as_(args.party_name),
|
||||
pe.remarks,
|
||||
pe.paid_amount.as_("base_net_total"),
|
||||
pe.paid_amount_after_tax.as_("base_grand_total"),
|
||||
pe.mode_of_payment,
|
||||
pe.project,
|
||||
pe.cost_center,
|
||||
)
|
||||
.where(
|
||||
(pe.party == filters.get(args.party)) & (pe[args.account_fieldname] == args.party_account)
|
||||
)
|
||||
.orderby(pe.posting_date, pe.name, order=Order.desc)
|
||||
)
|
||||
query = apply_common_conditions(filters, query, doctype="Payment Entry", payments=True)
|
||||
payment_entries = query.run(as_dict=True)
|
||||
return payment_entries
|
||||
|
||||
|
||||
def apply_common_conditions(filters, query, doctype, child_doctype=None, payments=False):
|
||||
parent_doc = frappe.qb.DocType(doctype)
|
||||
if child_doctype:
|
||||
child_doc = frappe.qb.DocType(child_doctype)
|
||||
|
||||
join_required = False
|
||||
|
||||
if filters.get("company"):
|
||||
query = query.where(parent_doc.company == filters.company)
|
||||
if filters.get("from_date"):
|
||||
query = query.where(parent_doc.posting_date >= filters.from_date)
|
||||
if filters.get("to_date"):
|
||||
query = query.where(parent_doc.posting_date <= filters.to_date)
|
||||
|
||||
if payments:
|
||||
if filters.get("cost_center"):
|
||||
query = query.where(parent_doc.cost_center == filters.cost_center)
|
||||
else:
|
||||
if filters.get("cost_center"):
|
||||
query = query.where(child_doc.cost_center == filters.cost_center)
|
||||
join_required = True
|
||||
if filters.get("warehouse"):
|
||||
query = query.where(child_doc.warehouse == filters.warehouse)
|
||||
join_required = True
|
||||
if filters.get("item_group"):
|
||||
query = query.where(child_doc.item_group == filters.item_group)
|
||||
join_required = True
|
||||
|
||||
if not payments:
|
||||
if filters.get("brand"):
|
||||
query = query.where(child_doc.brand == filters.brand)
|
||||
join_required = True
|
||||
|
||||
if join_required:
|
||||
query = query.inner_join(child_doc).on(parent_doc.name == child_doc.parent)
|
||||
query = query.distinct()
|
||||
|
||||
if parent_doc.get_table_name() != "tabJournal Entry":
|
||||
query = filter_invoices_based_on_dimensions(filters, query, parent_doc)
|
||||
|
||||
return query
|
||||
|
||||
|
||||
def get_advance_taxes_and_charges(invoice_list):
|
||||
adv_taxes = frappe.qb.DocType("Advance Taxes and Charges")
|
||||
return (
|
||||
frappe.qb.from_(adv_taxes)
|
||||
.select(
|
||||
adv_taxes.parent,
|
||||
adv_taxes.account_head,
|
||||
(
|
||||
frappe.qb.terms.Case()
|
||||
.when(adv_taxes.add_deduct_tax == "Add", Sum(adv_taxes.base_tax_amount))
|
||||
.else_(Sum(adv_taxes.base_tax_amount) * -1)
|
||||
).as_("tax_amount"),
|
||||
)
|
||||
.where(
|
||||
(adv_taxes.parent.isin([inv.name for inv in invoice_list]))
|
||||
& (adv_taxes.charge_type.isin(["On Paid Amount", "Actual"]))
|
||||
& (adv_taxes.base_tax_amount != 0)
|
||||
)
|
||||
.groupby(adv_taxes.parent, adv_taxes.account_head, adv_taxes.add_deduct_tax)
|
||||
).run(as_dict=True)
|
||||
|
||||
|
||||
def filter_invoices_based_on_dimensions(filters, query, parent_doc):
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
fieldname = dimension.fieldname
|
||||
query = query.where(parent_doc[fieldname] == filters.fieldname)
|
||||
return query
|
||||
|
||||
|
||||
def get_opening_row(party_type, party, from_date, company):
|
||||
party_account = get_party_account(party_type, party, company)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
return (
|
||||
frappe.qb.from_(gle)
|
||||
.select(
|
||||
ConstantColumn("Opening").as_("account"),
|
||||
Sum(gle.debit).as_("debit"),
|
||||
Sum(gle.credit).as_("credit"),
|
||||
(Sum(gle.debit) - Sum(gle.credit)).as_("balance"),
|
||||
)
|
||||
.where(
|
||||
(gle.account == party_account)
|
||||
& (gle.party == party)
|
||||
& (gle.posting_date < from_date)
|
||||
& (gle.is_cancelled == 0)
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
@@ -179,6 +179,7 @@ def get_balance_on(
|
||||
in_account_currency=True,
|
||||
cost_center=None,
|
||||
ignore_account_permission=False,
|
||||
start_date=None,
|
||||
):
|
||||
if not account and frappe.form_dict.get("account"):
|
||||
account = frappe.form_dict.get("account")
|
||||
@@ -192,6 +193,8 @@ def get_balance_on(
|
||||
cost_center = frappe.form_dict.get("cost_center")
|
||||
|
||||
cond = ["is_cancelled=0"]
|
||||
if start_date:
|
||||
cond.append("posting_date >= %s" % frappe.db.escape(cstr(start_date)))
|
||||
if date:
|
||||
cond.append("posting_date <= %s" % frappe.db.escape(cstr(date)))
|
||||
else:
|
||||
@@ -533,7 +536,7 @@ def check_if_advance_entry_modified(args):
|
||||
where
|
||||
name = %(voucher_no)s and docstatus = 1
|
||||
and party_type = %(party_type)s and party = %(party)s and {0} = %(account)s
|
||||
and round(unallocated_amount, {1}) = %(unreconciled_amount)s
|
||||
and round(unallocated_amount, {1}) = round(%(unreconciled_amount)s, {1})
|
||||
""".format(
|
||||
party_account_field, precision
|
||||
),
|
||||
@@ -623,8 +626,10 @@ def update_reference_in_payment_entry(
|
||||
"total_amount": d.grand_total,
|
||||
"outstanding_amount": d.outstanding_amount,
|
||||
"allocated_amount": d.allocated_amount,
|
||||
"exchange_rate": d.exchange_rate if d.exchange_gain_loss else payment_entry.get_exchange_rate(),
|
||||
"exchange_gain_loss": d.exchange_gain_loss,
|
||||
"exchange_rate": d.exchange_rate
|
||||
if d.difference_amount is not None
|
||||
else payment_entry.get_exchange_rate(),
|
||||
"exchange_gain_loss": d.difference_amount,
|
||||
}
|
||||
|
||||
if d.voucher_detail_no:
|
||||
@@ -1617,6 +1622,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
due_date=gle.due_date,
|
||||
voucher_type=gle.voucher_type,
|
||||
voucher_no=gle.voucher_no,
|
||||
voucher_detail_no=gle.voucher_detail_no,
|
||||
against_voucher_type=gle.against_voucher_type
|
||||
if gle.against_voucher_type
|
||||
else gle.voucher_type,
|
||||
@@ -1638,7 +1644,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
|
||||
|
||||
def create_payment_ledger_entry(
|
||||
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0
|
||||
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
|
||||
):
|
||||
if gl_entries:
|
||||
ple_map = get_payment_ledger_entries(gl_entries, cancel=cancel)
|
||||
@@ -1648,7 +1654,7 @@ def create_payment_ledger_entry(
|
||||
ple = frappe.get_doc(entry)
|
||||
|
||||
if cancel:
|
||||
delink_original_entry(ple)
|
||||
delink_original_entry(ple, partial_cancel=partial_cancel)
|
||||
|
||||
ple.flags.ignore_permissions = 1
|
||||
ple.flags.adv_adj = adv_adj
|
||||
@@ -1695,7 +1701,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
|
||||
ref_doc.set_status(update=True)
|
||||
|
||||
|
||||
def delink_original_entry(pl_entry):
|
||||
def delink_original_entry(pl_entry, partial_cancel=False):
|
||||
if pl_entry:
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
query = (
|
||||
@@ -1715,6 +1721,10 @@ def delink_original_entry(pl_entry):
|
||||
& (ple.against_voucher_no == pl_entry.against_voucher_no)
|
||||
)
|
||||
)
|
||||
|
||||
if partial_cancel:
|
||||
query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no)
|
||||
|
||||
query.run()
|
||||
|
||||
|
||||
@@ -1791,6 +1801,30 @@ class QueryPaymentLedger(object):
|
||||
Table("outstanding").amount_in_account_currency >= self.max_outstanding
|
||||
)
|
||||
|
||||
if self.limit and self.get_invoices:
|
||||
outstanding_vouchers = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.against_voucher_no.as_("voucher_no"),
|
||||
Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
|
||||
)
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(filter_on_against_voucher_no))
|
||||
.where(Criterion.all(self.common_filter))
|
||||
.where(Criterion.all(self.dimensions_filter))
|
||||
.where(Criterion.all(self.voucher_posting_date))
|
||||
.groupby(ple.against_voucher_type, ple.against_voucher_no, ple.party_type, ple.party)
|
||||
.orderby(ple.posting_date, ple.voucher_no)
|
||||
.having(qb.Field("amount_in_account_currency") > 0)
|
||||
.limit(self.limit)
|
||||
.run()
|
||||
)
|
||||
if outstanding_vouchers:
|
||||
filter_on_voucher_no.append(ple.voucher_no.isin([x[0] for x in outstanding_vouchers]))
|
||||
filter_on_against_voucher_no.append(
|
||||
ple.against_voucher_no.isin([x[0] for x in outstanding_vouchers])
|
||||
)
|
||||
|
||||
# build query for voucher amount
|
||||
query_voucher_amount = (
|
||||
qb.from_(ple)
|
||||
|
||||
@@ -322,13 +322,16 @@ frappe.ui.form.on('Asset', {
|
||||
},
|
||||
|
||||
make_schedules_editable: function(frm) {
|
||||
if (frm.doc.finance_books) {
|
||||
var is_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
|
||||
if (frm.doc.finance_books.length) {
|
||||
var is_manual_hence_editable = frm.doc.finance_books.filter(d => d.depreciation_method == "Manual").length > 0
|
||||
? true : false;
|
||||
var is_shift_hence_editable = frm.doc.finance_books.filter(d => d.shift_based).length > 0
|
||||
? true : false;
|
||||
|
||||
frm.toggle_enable("schedules", is_editable);
|
||||
frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_editable);
|
||||
frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_editable);
|
||||
frm.toggle_enable("schedules", is_manual_hence_editable || is_shift_hence_editable);
|
||||
frm.fields_dict["schedules"].grid.toggle_enable("schedule_date", is_manual_hence_editable);
|
||||
frm.fields_dict["schedules"].grid.toggle_enable("depreciation_amount", is_manual_hence_editable);
|
||||
frm.fields_dict["schedules"].grid.toggle_enable("shift", is_shift_hence_editable);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -516,10 +519,16 @@ frappe.ui.form.on('Asset', {
|
||||
indicator: 'red'
|
||||
});
|
||||
}
|
||||
frm.set_value('gross_purchase_amount', item.base_net_rate + item.item_tax_amount);
|
||||
frm.set_value('purchase_receipt_amount', item.base_net_rate + item.item_tax_amount);
|
||||
item.asset_location && frm.set_value('location', item.asset_location);
|
||||
var is_grouped_asset = frappe.db.get_value('Item', item.item_code, 'is_grouped_asset');
|
||||
var asset_quantity = is_grouped_asset ? item.qty : 1;
|
||||
var purchase_amount = flt(item.valuation_rate * asset_quantity, precision('gross_purchase_amount'));
|
||||
|
||||
frm.set_value('gross_purchase_amount', purchase_amount);
|
||||
frm.set_value('purchase_receipt_amount', purchase_amount);
|
||||
frm.set_value('asset_quantity', asset_quantity);
|
||||
frm.set_value('cost_center', item.cost_center || purchase_doc.cost_center);
|
||||
if(item.asset_location) { frm.set_value('location', item.asset_location); }
|
||||
|
||||
},
|
||||
|
||||
set_depreciation_rate: function(frm, row) {
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
"purchase_receipt",
|
||||
"purchase_invoice",
|
||||
"available_for_use_date",
|
||||
"total_asset_cost",
|
||||
"additional_asset_cost",
|
||||
"column_break_23",
|
||||
"gross_purchase_amount",
|
||||
"asset_quantity",
|
||||
@@ -200,9 +202,9 @@
|
||||
"fieldname": "purchase_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Purchase Date",
|
||||
"mandatory_depends_on": "eval:!doc.is_existing_asset",
|
||||
"read_only": 1,
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset",
|
||||
"reqd": 1
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "disposal_date",
|
||||
@@ -225,15 +227,15 @@
|
||||
"fieldname": "gross_purchase_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Gross Purchase Amount",
|
||||
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset",
|
||||
"reqd": 1
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "available_for_use_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Available-for-use Date",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval:(!doc.is_composite_asset || doc.docstatus==1)"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
@@ -495,11 +497,11 @@
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval.doc.asset_quantity",
|
||||
"default": "1",
|
||||
"fieldname": "asset_quantity",
|
||||
"fieldtype": "Int",
|
||||
"label": "Asset Quantity",
|
||||
"read_only": 1
|
||||
"read_only_depends_on": "eval:!doc.is_existing_asset && !doc.is_composite_asset"
|
||||
},
|
||||
{
|
||||
"fieldname": "depr_entry_posting_status",
|
||||
@@ -532,6 +534,22 @@
|
||||
"label": "Capitalized In",
|
||||
"options": "Asset Capitalization",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus > 0",
|
||||
"fieldname": "total_asset_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Asset Cost",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus > 0",
|
||||
"fieldname": "additional_asset_cost",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Additional Asset Cost",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 72,
|
||||
@@ -565,7 +583,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2023-10-27 17:03:46.629617",
|
||||
"modified": "2024-01-05 17:36:53.131512",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
@@ -609,4 +627,4 @@
|
||||
"states": [],
|
||||
"title_field": "asset_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,12 @@ class Asset(AccountsController):
|
||||
self.validate_finance_books()
|
||||
if not self.split_from:
|
||||
self.prepare_depreciation_data()
|
||||
self.update_shift_depr_schedule()
|
||||
self.validate_gross_and_purchase_amount()
|
||||
if self.get("schedules"):
|
||||
self.validate_expected_value_after_useful_life()
|
||||
|
||||
self.total_asset_cost = self.gross_purchase_amount
|
||||
self.status = self.get_status()
|
||||
|
||||
def on_submit(self):
|
||||
@@ -104,6 +106,13 @@ class Asset(AccountsController):
|
||||
self.opening_accumulated_depreciation
|
||||
)
|
||||
|
||||
def update_shift_depr_schedule(self):
|
||||
if not any(fb.get("shift_based") for fb in self.finance_books) or self.docstatus != 0:
|
||||
return
|
||||
|
||||
self.make_depreciation_schedule()
|
||||
self.set_accumulated_depreciation()
|
||||
|
||||
def should_prepare_depreciation_schedule(self):
|
||||
if not self.get("schedules"):
|
||||
return True
|
||||
@@ -238,7 +247,12 @@ class Asset(AccountsController):
|
||||
frappe.throw(_("Gross Purchase Amount is mandatory"), frappe.MandatoryError)
|
||||
|
||||
if is_cwip_accounting_enabled(self.asset_category):
|
||||
if not self.is_existing_asset and not (self.purchase_receipt or self.purchase_invoice):
|
||||
if (
|
||||
not self.is_existing_asset
|
||||
and not self.is_composite_asset
|
||||
and not self.purchase_receipt
|
||||
and not self.purchase_invoice
|
||||
):
|
||||
frappe.throw(
|
||||
_("Please create purchase receipt or purchase invoice for the item {0}").format(
|
||||
self.item_code
|
||||
@@ -251,7 +265,7 @@ class Asset(AccountsController):
|
||||
and not frappe.db.get_value("Purchase Invoice", self.purchase_invoice, "update_stock")
|
||||
):
|
||||
frappe.throw(
|
||||
_("Update stock must be enable for the purchase invoice {0}").format(self.purchase_invoice)
|
||||
_("Update stock must be enabled for the purchase invoice {0}").format(self.purchase_invoice)
|
||||
)
|
||||
|
||||
if not self.calculate_depreciation:
|
||||
@@ -318,7 +332,7 @@ class Asset(AccountsController):
|
||||
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
|
||||
)
|
||||
|
||||
def make_depreciation_schedule(self, date_of_disposal, value_after_depreciation=None):
|
||||
def make_depreciation_schedule(self, date_of_disposal=None, value_after_depreciation=None):
|
||||
if not self.get("schedules"):
|
||||
self.schedules = []
|
||||
|
||||
@@ -410,13 +424,7 @@ class Asset(AccountsController):
|
||||
)
|
||||
|
||||
if depreciation_amount > 0:
|
||||
self._add_depreciation_row(
|
||||
date_of_disposal,
|
||||
depreciation_amount,
|
||||
finance_book.depreciation_method,
|
||||
finance_book.finance_book,
|
||||
finance_book.idx,
|
||||
)
|
||||
self._add_depreciation_row(date_of_disposal, depreciation_amount, finance_book, n)
|
||||
|
||||
break
|
||||
|
||||
@@ -425,6 +433,7 @@ class Asset(AccountsController):
|
||||
n == 0
|
||||
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||
and not self.opening_accumulated_depreciation
|
||||
and not self.flags.wdv_it_act_applied
|
||||
):
|
||||
from_date = add_days(
|
||||
self.available_for_use_date, -1
|
||||
@@ -498,25 +507,27 @@ class Asset(AccountsController):
|
||||
skip_row = True
|
||||
|
||||
if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0:
|
||||
self._add_depreciation_row(
|
||||
schedule_date,
|
||||
depreciation_amount,
|
||||
finance_book.depreciation_method,
|
||||
finance_book.finance_book,
|
||||
finance_book.idx,
|
||||
)
|
||||
self._add_depreciation_row(schedule_date, depreciation_amount, finance_book, n)
|
||||
|
||||
def _add_depreciation_row(self, schedule_date, depreciation_amount, finance_book, schedule_idx):
|
||||
if finance_book.shift_based:
|
||||
shift = (
|
||||
self.schedules_before_clearing[schedule_idx].shift
|
||||
if self.schedules_before_clearing and len(self.schedules_before_clearing) > schedule_idx
|
||||
else frappe.get_cached_value("Asset Shift Factor", {"default": 1}, "shift_name")
|
||||
)
|
||||
else:
|
||||
shift = None
|
||||
|
||||
def _add_depreciation_row(
|
||||
self, schedule_date, depreciation_amount, depreciation_method, finance_book, finance_book_id
|
||||
):
|
||||
self.append(
|
||||
"schedules",
|
||||
{
|
||||
"schedule_date": schedule_date,
|
||||
"depreciation_amount": depreciation_amount,
|
||||
"depreciation_method": depreciation_method,
|
||||
"finance_book": finance_book,
|
||||
"finance_book_id": finance_book_id,
|
||||
"depreciation_method": finance_book.depreciation_method,
|
||||
"finance_book": finance_book.finance_book,
|
||||
"finance_book_id": finance_book.idx,
|
||||
"shift": shift,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -545,6 +556,8 @@ class Asset(AccountsController):
|
||||
num_of_depreciations_completed = 0
|
||||
depr_schedule = []
|
||||
|
||||
self.schedules_before_clearing = self.get("schedules")
|
||||
|
||||
for schedule in self.get("schedules"):
|
||||
# to update start when there are JEs linked with all the schedule rows corresponding to an FB
|
||||
if len(start) == (int(schedule.finance_book_id) - 2):
|
||||
@@ -752,10 +765,12 @@ class Asset(AccountsController):
|
||||
and not date_of_return
|
||||
):
|
||||
book = self.get("finance_books")[cint(d.finance_book_id) - 1]
|
||||
depreciation_amount += flt(
|
||||
value_after_depreciation - flt(book.expected_value_after_useful_life),
|
||||
d.precision("depreciation_amount"),
|
||||
)
|
||||
|
||||
if not book.shift_based:
|
||||
depreciation_amount += flt(
|
||||
value_after_depreciation - flt(book.expected_value_after_useful_life),
|
||||
d.precision("depreciation_amount"),
|
||||
)
|
||||
|
||||
d.depreciation_amount = depreciation_amount
|
||||
accumulated_depreciation += d.depreciation_amount
|
||||
@@ -1217,10 +1232,12 @@ def get_item_details(item_code, asset_category, gross_purchase_amount):
|
||||
"total_number_of_depreciations": d.total_number_of_depreciations,
|
||||
"frequency_of_depreciation": d.frequency_of_depreciation,
|
||||
"daily_prorata_based": d.daily_prorata_based,
|
||||
"shift_based": d.shift_based,
|
||||
"salvage_value_percentage": d.salvage_value_percentage,
|
||||
"expected_value_after_useful_life": flt(gross_purchase_amount)
|
||||
* flt(d.salvage_value_percentage / 100),
|
||||
"depreciation_start_date": d.depreciation_start_date or nowdate(),
|
||||
"rate_of_depreciation": d.rate_of_depreciation,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1363,27 +1380,22 @@ def get_depreciation_amount(
|
||||
asset, fb_row, schedule_idx, number_of_pending_depreciations
|
||||
)
|
||||
else:
|
||||
rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
|
||||
asset, depreciable_value, fb_row
|
||||
)
|
||||
return get_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
depreciable_value,
|
||||
rate_of_depreciation,
|
||||
fb_row.frequency_of_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row):
|
||||
return fb_row.rate_of_depreciation
|
||||
|
||||
|
||||
def get_straight_line_or_manual_depr_amount(
|
||||
asset, row, schedule_idx, number_of_pending_depreciations
|
||||
):
|
||||
if row.shift_based:
|
||||
return get_shift_depr_amount(asset, row, schedule_idx)
|
||||
|
||||
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
|
||||
if asset.flags.increase_in_asset_life:
|
||||
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
|
||||
@@ -1478,30 +1490,87 @@ def get_straight_line_or_manual_depr_amount(
|
||||
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
||||
|
||||
|
||||
def get_shift_depr_amount(asset, row, schedule_idx):
|
||||
if asset.get("__islocal") and not asset.flags.shift_allocation:
|
||||
return (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(row.expected_value_after_useful_life)
|
||||
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
||||
|
||||
asset_shift_factors_map = get_asset_shift_factors_map()
|
||||
shift = (
|
||||
asset.schedules_before_clearing[schedule_idx].shift
|
||||
if len(asset.schedules_before_clearing) > schedule_idx
|
||||
else None
|
||||
)
|
||||
shift_factor = asset_shift_factors_map.get(shift) if shift else 0
|
||||
|
||||
shift_factors_sum = sum(
|
||||
flt(asset_shift_factors_map.get(schedule.shift)) for schedule in asset.schedules_before_clearing
|
||||
)
|
||||
|
||||
return (
|
||||
(
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(row.expected_value_after_useful_life)
|
||||
)
|
||||
/ flt(shift_factors_sum)
|
||||
) * shift_factor
|
||||
|
||||
|
||||
def get_asset_shift_factors_map():
|
||||
return dict(frappe.db.get_all("Asset Shift Factor", ["shift_name", "shift_factor"], as_list=True))
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def get_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
depreciable_value,
|
||||
rate_of_depreciation,
|
||||
frequency_of_depreciation,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
):
|
||||
if cint(frequency_of_depreciation) == 12:
|
||||
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
|
||||
return get_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
depreciable_value,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
|
||||
def get_default_wdv_or_dd_depr_amount(
|
||||
asset,
|
||||
fb_row,
|
||||
depreciable_value,
|
||||
schedule_idx,
|
||||
prev_depreciation_amount,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
):
|
||||
if cint(fb_row.frequency_of_depreciation) == 12:
|
||||
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
|
||||
else:
|
||||
if has_wdv_or_dd_non_yearly_pro_rata:
|
||||
if schedule_idx == 0:
|
||||
return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
|
||||
elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1:
|
||||
return flt(depreciable_value) * (flt(fb_row.rate_of_depreciation) / 100)
|
||||
elif schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 1:
|
||||
return (
|
||||
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
|
||||
flt(depreciable_value)
|
||||
* flt(fb_row.frequency_of_depreciation)
|
||||
* (flt(fb_row.rate_of_depreciation) / 1200)
|
||||
)
|
||||
else:
|
||||
return prev_depreciation_amount
|
||||
else:
|
||||
if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0:
|
||||
if schedule_idx % (12 / cint(fb_row.frequency_of_depreciation)) == 0:
|
||||
return (
|
||||
flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
|
||||
flt(depreciable_value)
|
||||
* flt(fb_row.frequency_of_depreciation)
|
||||
* (flt(fb_row.rate_of_depreciation) / 1200)
|
||||
)
|
||||
else:
|
||||
return prev_depreciation_amount
|
||||
|
||||
@@ -19,6 +19,7 @@ from frappe.utils import (
|
||||
from frappe.utils.data import get_link_to_form
|
||||
from frappe.utils.user import get_users_with_role
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_checks_for_pl_and_bs_accounts,
|
||||
)
|
||||
@@ -464,14 +465,27 @@ def restore_asset(asset_name):
|
||||
|
||||
|
||||
def depreciate_asset(asset, date):
|
||||
if not asset.calculate_depreciation:
|
||||
return
|
||||
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.prepare_depreciation_data(date_of_disposal=date)
|
||||
asset.save()
|
||||
|
||||
make_depreciation_entry(asset.name, date)
|
||||
|
||||
cancel_depreciation_entries(asset, date)
|
||||
|
||||
|
||||
@erpnext.allow_regional
|
||||
def cancel_depreciation_entries(asset, date):
|
||||
pass
|
||||
|
||||
|
||||
def reset_depreciation_schedule(asset, date):
|
||||
if not asset.calculate_depreciation:
|
||||
return
|
||||
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
|
||||
# recreate original depreciation schedule of the asset
|
||||
|
||||
@@ -142,12 +142,7 @@ class TestAsset(AssetSetup):
|
||||
("Creditors - _TC", 0.0, 100000.0),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
pi.name,
|
||||
)
|
||||
gle = get_gl_entries("Purchase Invoice", pi.name)
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
pi.cancel()
|
||||
@@ -249,12 +244,7 @@ class TestAsset(AssetSetup):
|
||||
),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Journal Entry' and voucher_no = %s
|
||||
order by account""",
|
||||
asset.journal_entry_for_scrap,
|
||||
)
|
||||
gle = get_gl_entries("Journal Entry", asset.journal_entry_for_scrap)
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
restore_asset(asset.name)
|
||||
@@ -316,13 +306,7 @@ class TestAsset(AssetSetup):
|
||||
("Debtors - _TC", 25000.0, 0.0),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Sales Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
si.name,
|
||||
)
|
||||
|
||||
gle = get_gl_entries("Sales Invoice", si.name)
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
si.cancel()
|
||||
@@ -392,13 +376,7 @@ class TestAsset(AssetSetup):
|
||||
("Debtors - _TC", 40000.0, 0.0),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Sales Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
si.name,
|
||||
)
|
||||
|
||||
gle = get_gl_entries("Sales Invoice", si.name)
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
def test_asset_with_maintenance_required_status_after_sale(self):
|
||||
@@ -526,13 +504,7 @@ class TestAsset(AssetSetup):
|
||||
("CWIP Account - _TC", 5250.0, 0.0),
|
||||
)
|
||||
|
||||
pr_gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Receipt' and voucher_no = %s
|
||||
order by account""",
|
||||
pr.name,
|
||||
)
|
||||
|
||||
pr_gle = get_gl_entries("Purchase Receipt", pr.name)
|
||||
self.assertSequenceEqual(pr_gle, expected_gle)
|
||||
|
||||
pi = make_invoice(pr.name)
|
||||
@@ -545,13 +517,7 @@ class TestAsset(AssetSetup):
|
||||
("Creditors - _TC", 0.0, 5500.0),
|
||||
)
|
||||
|
||||
pi_gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Purchase Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
pi.name,
|
||||
)
|
||||
|
||||
pi_gle = get_gl_entries("Purchase Invoice", pi.name)
|
||||
self.assertSequenceEqual(pi_gle, expected_gle)
|
||||
|
||||
asset = frappe.db.get_value("Asset", {"purchase_receipt": pr.name, "docstatus": 0}, "name")
|
||||
@@ -578,13 +544,7 @@ class TestAsset(AssetSetup):
|
||||
|
||||
expected_gle = (("_Test Fixed Asset - _TC", 5250.0, 0.0), ("CWIP Account - _TC", 0.0, 5250.0))
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Asset' and voucher_no = %s
|
||||
order by account""",
|
||||
asset_doc.name,
|
||||
)
|
||||
|
||||
gle = get_gl_entries("Asset", asset_doc.name)
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
def test_asset_cwip_toggling_cases(self):
|
||||
@@ -607,10 +567,7 @@ class TestAsset(AssetSetup):
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql(
|
||||
"""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
|
||||
asset_doc.name,
|
||||
)
|
||||
gle = get_gl_entries("Asset", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 1 -- PR with cwip disabled, Asset with cwip enabled
|
||||
@@ -624,10 +581,7 @@ class TestAsset(AssetSetup):
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql(
|
||||
"""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
|
||||
asset_doc.name,
|
||||
)
|
||||
gle = get_gl_entries("Asset", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 2 -- PR with cwip enabled, Asset with cwip disabled
|
||||
@@ -640,10 +594,7 @@ class TestAsset(AssetSetup):
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql(
|
||||
"""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
|
||||
asset_doc.name,
|
||||
)
|
||||
gle = get_gl_entries("Asset", asset_doc.name)
|
||||
self.assertTrue(gle)
|
||||
|
||||
# case 3 -- PI with cwip disabled, Asset with cwip enabled
|
||||
@@ -656,10 +607,7 @@ class TestAsset(AssetSetup):
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql(
|
||||
"""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
|
||||
asset_doc.name,
|
||||
)
|
||||
gle = get_gl_entries("Asset", asset_doc.name)
|
||||
self.assertFalse(gle)
|
||||
|
||||
# case 4 -- PI with cwip enabled, Asset with cwip disabled
|
||||
@@ -672,10 +620,7 @@ class TestAsset(AssetSetup):
|
||||
asset_doc.available_for_use_date = nowdate()
|
||||
asset_doc.calculate_depreciation = 0
|
||||
asset_doc.submit()
|
||||
gle = frappe.db.sql(
|
||||
"""select name from `tabGL Entry` where voucher_type='Asset' and voucher_no = %s""",
|
||||
asset_doc.name,
|
||||
)
|
||||
gle = get_gl_entries("Asset", asset_doc.name)
|
||||
self.assertTrue(gle)
|
||||
|
||||
frappe.db.set_value("Asset Category", "Computers", "enable_cwip_accounting", cwip)
|
||||
@@ -1644,6 +1589,30 @@ class TestDepreciationBasics(AssetSetup):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, jv.insert)
|
||||
|
||||
def test_multi_currency_asset_pr_creation(self):
|
||||
pr = make_purchase_receipt(
|
||||
item_code="Macbook Pro",
|
||||
qty=1,
|
||||
rate=100.0,
|
||||
location="Test Location",
|
||||
supplier="_Test Supplier USD",
|
||||
currency="USD",
|
||||
)
|
||||
|
||||
pr.submit()
|
||||
self.assertTrue(get_gl_entries("Purchase Receipt", pr.name))
|
||||
|
||||
|
||||
def get_gl_entries(doctype, docname):
|
||||
gl_entry = frappe.qb.DocType("GL Entry")
|
||||
return (
|
||||
frappe.qb.from_(gl_entry)
|
||||
.select(gl_entry.account, gl_entry.debit, gl_entry.credit)
|
||||
.where((gl_entry.voucher_type == doctype) & (gl_entry.voucher_no == docname))
|
||||
.orderby(gl_entry.account)
|
||||
.run()
|
||||
)
|
||||
|
||||
|
||||
def create_asset_data():
|
||||
if not frappe.db.exists("Asset Category", "Computers"):
|
||||
@@ -1706,6 +1675,7 @@ def create_asset(**args):
|
||||
"expected_value_after_useful_life": args.expected_value_after_useful_life or 0,
|
||||
"depreciation_start_date": args.depreciation_start_date,
|
||||
"daily_prorata_based": args.daily_prorata_based or 0,
|
||||
"shift_based": args.shift_based or 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -67,12 +67,12 @@ class AssetCategory(Document):
|
||||
if selected_key_type not in expected_key_types:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: {} of {} should be {}. Please modify the account or select a different account."
|
||||
"Row #{0}: {1} of {2} should be {3}. Please update the {1} or select a different account."
|
||||
).format(
|
||||
d.idx,
|
||||
frappe.unscrub(key_to_match),
|
||||
frappe.bold(selected_account),
|
||||
frappe.bold(expected_key_types),
|
||||
frappe.bold(" or ".join(expected_key_types)),
|
||||
),
|
||||
title=_("Invalid Account"),
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"depreciation_method",
|
||||
"total_number_of_depreciations",
|
||||
"daily_prorata_based",
|
||||
"shift_based",
|
||||
"column_break_5",
|
||||
"frequency_of_depreciation",
|
||||
"depreciation_start_date",
|
||||
@@ -89,16 +90,22 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.depreciation_method == \"Straight Line\" || doc.depreciation_method == \"Manual\"",
|
||||
"fieldname": "daily_prorata_based",
|
||||
"fieldtype": "Check",
|
||||
"label": "Depreciate based on daily pro-rata"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.depreciation_method == \"Straight Line\"",
|
||||
"fieldname": "shift_based",
|
||||
"fieldtype": "Check",
|
||||
"label": "Depreciate based on shifts"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-03 22:21:52.090191",
|
||||
"modified": "2023-12-29 08:49:39.876439",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Finance Book",
|
||||
|
||||
@@ -46,6 +46,10 @@ class AssetRepair(AccountsController):
|
||||
|
||||
self.increase_asset_value()
|
||||
|
||||
if self.capitalize_repair_cost:
|
||||
self.asset_doc.total_asset_cost += self.repair_cost
|
||||
self.asset_doc.additional_asset_cost += self.repair_cost
|
||||
|
||||
if self.get("stock_consumption"):
|
||||
self.check_for_stock_items_and_warehouse()
|
||||
self.decrease_stock_quantity()
|
||||
@@ -68,6 +72,10 @@ class AssetRepair(AccountsController):
|
||||
|
||||
self.decrease_asset_value()
|
||||
|
||||
if self.capitalize_repair_cost:
|
||||
self.asset_doc.total_asset_cost -= self.repair_cost
|
||||
self.asset_doc.additional_asset_cost -= self.repair_cost
|
||||
|
||||
if self.get("stock_consumption"):
|
||||
self.increase_stock_quantity()
|
||||
if self.get("capitalize_repair_cost"):
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Asset Shift Allocation', {
|
||||
onload: function(frm) {
|
||||
frm.events.make_schedules_editable(frm);
|
||||
},
|
||||
|
||||
make_schedules_editable: function(frm) {
|
||||
frm.toggle_enable("depreciation_schedule", true);
|
||||
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("schedule_date", false);
|
||||
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("depreciation_amount", false);
|
||||
frm.fields_dict["depreciation_schedule"].grid.toggle_enable("shift", true);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2023-11-29 04:01:33.796458",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"section_break_esaa",
|
||||
"asset",
|
||||
"naming_series",
|
||||
"column_break_tdae",
|
||||
"finance_book",
|
||||
"amended_from",
|
||||
"depreciation_schedule_section",
|
||||
"depreciation_schedule"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "section_break_esaa",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "asset",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Asset",
|
||||
"options": "Asset",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"label": "Naming Series",
|
||||
"options": "ACC-ASA-.YYYY.-",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_tdae",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "depreciation_schedule_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Depreciation Schedule"
|
||||
},
|
||||
{
|
||||
"fieldname": "depreciation_schedule",
|
||||
"fieldtype": "Table",
|
||||
"label": "Depreciation Schedule",
|
||||
"options": "Depreciation Schedule"
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Asset Shift Allocation",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-29 04:06:20.586168",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Shift Allocation",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import add_months, cint, flt, get_last_day
|
||||
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_shift_factors_map
|
||||
from erpnext.assets.doctype.asset.depreciation import is_last_day_of_the_month
|
||||
|
||||
|
||||
class AssetShiftAllocation(Document):
|
||||
def after_insert(self):
|
||||
self.fetch_and_set_depr_schedule()
|
||||
|
||||
def validate(self):
|
||||
self.asset_doc = frappe.get_doc("Asset", self.asset)
|
||||
|
||||
self.validate_invalid_shift_change()
|
||||
self.update_depr_schedule()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_asset_schedule()
|
||||
|
||||
def fetch_and_set_depr_schedule(self):
|
||||
if len(self.asset_doc.finance_books) != 1:
|
||||
frappe.throw(_("Only assets with one finance book allowed in v14."))
|
||||
|
||||
if not any(fb.get("shift_based") for fb in self.asset_doc.finance_books):
|
||||
frappe.throw(_("Asset {0} is not using shift based depreciation").format(self.asset))
|
||||
|
||||
for schedule in self.asset_doc.get("schedules"):
|
||||
self.append(
|
||||
"depreciation_schedule",
|
||||
{
|
||||
"schedule_date": schedule.schedule_date,
|
||||
"depreciation_amount": schedule.depreciation_amount,
|
||||
"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
|
||||
"journal_entry": schedule.journal_entry,
|
||||
"shift": schedule.shift,
|
||||
"depreciation_method": self.asset_doc.finance_books[0].depreciation_method,
|
||||
"finance_book": self.asset_doc.finance_books[0].finance_book,
|
||||
"finance_book_id": self.asset_doc.finance_books[0].idx,
|
||||
},
|
||||
)
|
||||
|
||||
self.flags.ignore_validate = True
|
||||
self.save()
|
||||
|
||||
def validate_invalid_shift_change(self):
|
||||
if not self.get("depreciation_schedule") or self.docstatus == 1:
|
||||
return
|
||||
|
||||
for i, sch in enumerate(self.depreciation_schedule):
|
||||
if sch.journal_entry and self.asset_doc.schedules[i].shift != sch.shift:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row {0}: Shift cannot be changed since the depreciation has already been processed"
|
||||
).format(i)
|
||||
)
|
||||
|
||||
def update_depr_schedule(self):
|
||||
if not self.get("depreciation_schedule") or self.docstatus == 1:
|
||||
return
|
||||
|
||||
self.allocate_shift_diff_in_depr_schedule()
|
||||
|
||||
temp_asset_doc = frappe.copy_doc(self.asset_doc)
|
||||
|
||||
temp_asset_doc.flags.shift_allocation = True
|
||||
|
||||
temp_asset_doc.schedules = []
|
||||
|
||||
for schedule in self.depreciation_schedule:
|
||||
temp_asset_doc.append(
|
||||
"schedules",
|
||||
{
|
||||
"schedule_date": schedule.schedule_date,
|
||||
"depreciation_amount": schedule.depreciation_amount,
|
||||
"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
|
||||
"journal_entry": schedule.journal_entry,
|
||||
"shift": schedule.shift,
|
||||
"depreciation_method": self.asset_doc.finance_books[0].depreciation_method,
|
||||
"finance_book": self.asset_doc.finance_books[0].finance_book,
|
||||
"finance_book_id": self.asset_doc.finance_books[0].idx,
|
||||
},
|
||||
)
|
||||
|
||||
temp_asset_doc.prepare_depreciation_data()
|
||||
|
||||
self.depreciation_schedule = []
|
||||
|
||||
for schedule in temp_asset_doc.get("schedules"):
|
||||
self.append(
|
||||
"depreciation_schedule",
|
||||
{
|
||||
"schedule_date": schedule.schedule_date,
|
||||
"depreciation_amount": schedule.depreciation_amount,
|
||||
"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
|
||||
"journal_entry": schedule.journal_entry,
|
||||
"shift": schedule.shift,
|
||||
"depreciation_method": self.asset_doc.finance_books[0].depreciation_method,
|
||||
"finance_book": self.asset_doc.finance_books[0].finance_book,
|
||||
"finance_book_id": self.asset_doc.finance_books[0].idx,
|
||||
},
|
||||
)
|
||||
|
||||
def allocate_shift_diff_in_depr_schedule(self):
|
||||
asset_shift_factors_map = get_asset_shift_factors_map()
|
||||
reverse_asset_shift_factors_map = {
|
||||
asset_shift_factors_map[k]: k for k in asset_shift_factors_map
|
||||
}
|
||||
|
||||
original_shift_factors_sum = sum(
|
||||
flt(asset_shift_factors_map.get(schedule.shift)) for schedule in self.asset_doc.schedules
|
||||
)
|
||||
|
||||
new_shift_factors_sum = sum(
|
||||
flt(asset_shift_factors_map.get(schedule.shift)) for schedule in self.depreciation_schedule
|
||||
)
|
||||
|
||||
diff = new_shift_factors_sum - original_shift_factors_sum
|
||||
|
||||
if diff > 0:
|
||||
for i, schedule in reversed(list(enumerate(self.depreciation_schedule))):
|
||||
if diff <= 0:
|
||||
break
|
||||
|
||||
shift_factor = flt(asset_shift_factors_map.get(schedule.shift))
|
||||
|
||||
if shift_factor <= diff:
|
||||
self.depreciation_schedule.pop()
|
||||
diff -= shift_factor
|
||||
else:
|
||||
try:
|
||||
self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get(
|
||||
shift_factor - diff
|
||||
)
|
||||
diff = 0
|
||||
except Exception:
|
||||
frappe.throw(_("Could not auto update shifts. Shift with shift factor {0} needed.")).format(
|
||||
shift_factor - diff
|
||||
)
|
||||
elif diff < 0:
|
||||
shift_factors = list(asset_shift_factors_map.values())
|
||||
desc_shift_factors = sorted(shift_factors, reverse=True)
|
||||
depr_schedule_len_diff = self.asset_doc.total_number_of_depreciations - len(
|
||||
self.depreciation_schedule
|
||||
)
|
||||
subsets_result = []
|
||||
|
||||
if depr_schedule_len_diff > 0:
|
||||
num_rows_to_add = depr_schedule_len_diff
|
||||
|
||||
while not subsets_result and num_rows_to_add > 0:
|
||||
find_subsets_with_sum(shift_factors, num_rows_to_add, abs(diff), [], subsets_result)
|
||||
if subsets_result:
|
||||
break
|
||||
num_rows_to_add -= 1
|
||||
|
||||
if subsets_result:
|
||||
for i in range(num_rows_to_add):
|
||||
schedule_date = add_months(
|
||||
self.depreciation_schedule[-1].schedule_date,
|
||||
cint(self.asset_doc.frequency_of_depreciation),
|
||||
)
|
||||
|
||||
if is_last_day_of_the_month(self.depreciation_schedule[-1].schedule_date):
|
||||
schedule_date = get_last_day(schedule_date)
|
||||
|
||||
self.append(
|
||||
"depreciation_schedule",
|
||||
{
|
||||
"schedule_date": schedule_date,
|
||||
"shift": reverse_asset_shift_factors_map.get(subsets_result[0][i]),
|
||||
"depreciation_method": self.asset_doc.finance_books[0].depreciation_method,
|
||||
"finance_book": self.asset_doc.finance_books[0].finance_book,
|
||||
"finance_book_id": self.asset_doc.finance_books[0].idx,
|
||||
},
|
||||
)
|
||||
|
||||
if depr_schedule_len_diff <= 0 or not subsets_result:
|
||||
for i, schedule in reversed(list(enumerate(self.depreciation_schedule))):
|
||||
diff = abs(diff)
|
||||
|
||||
if diff <= 0:
|
||||
break
|
||||
|
||||
shift_factor = flt(asset_shift_factors_map.get(schedule.shift))
|
||||
|
||||
if shift_factor <= diff:
|
||||
for sf in desc_shift_factors:
|
||||
if sf - shift_factor <= diff:
|
||||
self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get(sf)
|
||||
diff -= sf - shift_factor
|
||||
break
|
||||
else:
|
||||
try:
|
||||
self.depreciation_schedule[i].shift = reverse_asset_shift_factors_map.get(
|
||||
shift_factor + diff
|
||||
)
|
||||
diff = 0
|
||||
except Exception:
|
||||
frappe.throw(_("Could not auto update shifts. Shift with shift factor {0} needed.")).format(
|
||||
shift_factor + diff
|
||||
)
|
||||
|
||||
def update_asset_schedule(self):
|
||||
self.asset_doc.flags.shift_allocation = True
|
||||
|
||||
self.asset_doc.schedules = []
|
||||
|
||||
for schedule in self.depreciation_schedule:
|
||||
self.asset_doc.append(
|
||||
"schedules",
|
||||
{
|
||||
"schedule_date": schedule.schedule_date,
|
||||
"depreciation_amount": schedule.depreciation_amount,
|
||||
"accumulated_depreciation_amount": schedule.accumulated_depreciation_amount,
|
||||
"journal_entry": schedule.journal_entry,
|
||||
"shift": schedule.shift,
|
||||
"depreciation_method": self.asset_doc.finance_books[0].depreciation_method,
|
||||
"finance_book": self.asset_doc.finance_books[0].finance_book,
|
||||
"finance_book_id": self.asset_doc.finance_books[0].idx,
|
||||
},
|
||||
)
|
||||
|
||||
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
self.asset_doc.prepare_depreciation_data()
|
||||
self.asset_doc.save()
|
||||
|
||||
|
||||
def find_subsets_with_sum(numbers, k, target_sum, current_subset, result):
|
||||
if k == 0 and target_sum == 0:
|
||||
result.append(current_subset.copy())
|
||||
return
|
||||
if k <= 0 or target_sum <= 0 or not numbers:
|
||||
return
|
||||
|
||||
# Include the current number in the subset
|
||||
find_subsets_with_sum(
|
||||
numbers, k - 1, target_sum - numbers[0], current_subset + [numbers[0]], result
|
||||
)
|
||||
|
||||
# Exclude the current number from the subset
|
||||
find_subsets_with_sum(numbers[1:], k, target_sum, current_subset, result)
|
||||
@@ -0,0 +1,112 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import cstr
|
||||
|
||||
from erpnext.assets.doctype.asset.test_asset import create_asset
|
||||
|
||||
|
||||
class TestAssetShiftAllocation(FrappeTestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
create_asset_shift_factors()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_asset_shift_allocation(self):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2023-01-01",
|
||||
purchase_date="2023-01-01",
|
||||
gross_purchase_amount=120000,
|
||||
depreciation_start_date="2023-01-31",
|
||||
total_number_of_depreciations=12,
|
||||
frequency_of_depreciation=1,
|
||||
shift_based=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
expected_schedules = [
|
||||
["2023-01-31", 10000.0, 10000.0, "Single"],
|
||||
["2023-02-28", 10000.0, 20000.0, "Single"],
|
||||
["2023-03-31", 10000.0, 30000.0, "Single"],
|
||||
["2023-04-30", 10000.0, 40000.0, "Single"],
|
||||
["2023-05-31", 10000.0, 50000.0, "Single"],
|
||||
["2023-06-30", 10000.0, 60000.0, "Single"],
|
||||
["2023-07-31", 10000.0, 70000.0, "Single"],
|
||||
["2023-08-31", 10000.0, 80000.0, "Single"],
|
||||
["2023-09-30", 10000.0, 90000.0, "Single"],
|
||||
["2023-10-31", 10000.0, 100000.0, "Single"],
|
||||
["2023-11-30", 10000.0, 110000.0, "Single"],
|
||||
["2023-12-31", 10000.0, 120000.0, "Single"],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift]
|
||||
for d in asset.get("schedules")
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
asset_shift_allocation = frappe.get_doc(
|
||||
{"doctype": "Asset Shift Allocation", "asset": asset.name}
|
||||
).insert()
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift]
|
||||
for d in asset_shift_allocation.get("depreciation_schedule")
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
asset_shift_allocation = frappe.get_doc("Asset Shift Allocation", asset_shift_allocation.name)
|
||||
asset_shift_allocation.depreciation_schedule[4].shift = "Triple"
|
||||
asset_shift_allocation.save()
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift]
|
||||
for d in asset_shift_allocation.get("depreciation_schedule")
|
||||
]
|
||||
|
||||
expected_schedules = [
|
||||
["2023-01-31", 10000.0, 10000.0, "Single"],
|
||||
["2023-02-28", 10000.0, 20000.0, "Single"],
|
||||
["2023-03-31", 10000.0, 30000.0, "Single"],
|
||||
["2023-04-30", 10000.0, 40000.0, "Single"],
|
||||
["2023-05-31", 20000.0, 60000.0, "Triple"],
|
||||
["2023-06-30", 10000.0, 70000.0, "Single"],
|
||||
["2023-07-31", 10000.0, 80000.0, "Single"],
|
||||
["2023-08-31", 10000.0, 90000.0, "Single"],
|
||||
["2023-09-30", 10000.0, 100000.0, "Single"],
|
||||
["2023-10-31", 10000.0, 110000.0, "Single"],
|
||||
["2023-11-30", 10000.0, 120000.0, "Single"],
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
asset_shift_allocation.submit()
|
||||
|
||||
asset.reload()
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount, d.shift]
|
||||
for d in asset.get("schedules")
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
|
||||
def create_asset_shift_factors():
|
||||
shifts = [
|
||||
{"doctype": "Asset Shift Factor", "shift_name": "Half", "shift_factor": 0.5, "default": 0},
|
||||
{"doctype": "Asset Shift Factor", "shift_name": "Single", "shift_factor": 1, "default": 1},
|
||||
{"doctype": "Asset Shift Factor", "shift_name": "Double", "shift_factor": 1.5, "default": 0},
|
||||
{"doctype": "Asset Shift Factor", "shift_name": "Triple", "shift_factor": 2, "default": 0},
|
||||
]
|
||||
|
||||
for s in shifts:
|
||||
frappe.get_doc(s).insert()
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on('Asset Shift Factor', {
|
||||
// refresh: function(frm) {
|
||||
|
||||
// }
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"autoname": "field:shift_name",
|
||||
"creation": "2023-11-29 03:45:13.247372",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"shift_name",
|
||||
"shift_factor",
|
||||
"default"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "shift_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Shift Name",
|
||||
"reqd": 1,
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shift_factor",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Shift Factor",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "default",
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Default"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-29 04:06:31.723038",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Shift Factor",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AssetShiftFactor(Document):
|
||||
def validate(self):
|
||||
self.validate_default()
|
||||
|
||||
def validate_default(self):
|
||||
if self.default:
|
||||
existing_default_shift_factor = frappe.db.get_value(
|
||||
"Asset Shift Factor", {"default": 1}, "name"
|
||||
)
|
||||
|
||||
if existing_default_shift_factor:
|
||||
frappe.throw(
|
||||
_("Asset Shift Factor {0} is set as default currently. Please change it first.").format(
|
||||
frappe.bold(existing_default_shift_factor)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestAssetShiftFactor(FrappeTestCase):
|
||||
pass
|
||||
@@ -15,6 +15,9 @@ from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_pu
|
||||
class TestAssetValueAdjustment(unittest.TestCase):
|
||||
def setUp(self):
|
||||
create_asset_data()
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company", "capital_work_in_progress_account", "CWIP Account - _TC"
|
||||
)
|
||||
|
||||
def test_current_asset_value(self):
|
||||
pr = make_purchase_receipt(
|
||||
|
||||
@@ -1,318 +1,115 @@
|
||||
{
|
||||
"allow_copy": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"autoname": "",
|
||||
"beta": 0,
|
||||
"creation": "2016-03-02 15:11:01.278862",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_rename": 1,
|
||||
"creation": "2016-03-02 15:11:01.278862",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"finance_book",
|
||||
"schedule_date",
|
||||
"depreciation_amount",
|
||||
"column_break_3",
|
||||
"accumulated_depreciation_amount",
|
||||
"journal_entry",
|
||||
"shift",
|
||||
"make_depreciation_entry",
|
||||
"finance_book_id",
|
||||
"depreciation_method"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Finance Book",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Finance Book",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Schedule Date",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "schedule_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"label": "Schedule Date",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "depreciation_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Depreciation Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "depreciation_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Depreciation Amount",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "accumulated_depreciation_amount",
|
||||
"fieldtype": "Currency",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Accumulated Depreciation Amount",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "accumulated_depreciation_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Accumulated Depreciation Amount",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:doc.docstatus==1",
|
||||
"fieldname": "journal_entry",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Journal Entry",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "Journal Entry",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"depends_on": "eval:doc.docstatus==1",
|
||||
"fieldname": "journal_entry",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Journal Entry",
|
||||
"no_copy": 1,
|
||||
"options": "Journal Entry",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 1,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())",
|
||||
"fieldname": "make_depreciation_entry",
|
||||
"fieldtype": "Button",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Make Depreciation Entry",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())",
|
||||
"fieldname": "make_depreciation_entry",
|
||||
"fieldtype": "Button",
|
||||
"label": "Make Depreciation Entry"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "finance_book_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Finance Book Id",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "finance_book_id",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Finance Book Id",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "depreciation_method",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Depreciation Method",
|
||||
"length": 0,
|
||||
"no_copy": 1,
|
||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 1,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"unique": 0
|
||||
"fieldname": "depreciation_method",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 1,
|
||||
"label": "Depreciation Method",
|
||||
"no_copy": 1,
|
||||
"options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "shift",
|
||||
"fieldtype": "Link",
|
||||
"label": "Shift",
|
||||
"options": "Asset Shift Factor"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 0,
|
||||
"hide_toolbar": 0,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"max_attachments": 0,
|
||||
"modified": "2018-05-10 15:12:41.679436",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Depreciation Schedule",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 0,
|
||||
"track_seen": 0
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-11-29 04:43:04.218580",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Depreciation Schedule",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user