mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-22 10:39:41 +00:00
Compare commits
338 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2851f6e786 | ||
|
|
3b527e4155 | ||
|
|
1d9b77460d | ||
|
|
b57dabd8ef | ||
|
|
e2f6ed693c | ||
|
|
dd0aa83882 | ||
|
|
1d28135898 | ||
|
|
fc47fbeaa1 | ||
|
|
8b2f384af3 | ||
|
|
a3605d8370 | ||
|
|
a8531eec5b | ||
|
|
f2c4baeb0e | ||
|
|
28d69ca26f | ||
|
|
e36ff57c49 | ||
|
|
7eb523fd5b | ||
|
|
835b9c3378 | ||
|
|
9f0d743e1b | ||
|
|
ab089fae7e | ||
|
|
ecb7858062 | ||
|
|
293735db32 | ||
|
|
9d34eb686c | ||
|
|
1e99ba5932 | ||
|
|
e6a305e972 | ||
|
|
ab0f37740c | ||
|
|
8db3307d6b | ||
|
|
6f20a2c6e5 | ||
|
|
6329f4524d | ||
|
|
6761a023d0 | ||
|
|
6317db19e1 | ||
|
|
45cdb39edd | ||
|
|
dcb3e01df1 | ||
|
|
f43ed2e50e | ||
|
|
e8025365cd | ||
|
|
c06f72f9f7 | ||
|
|
17f7351f85 | ||
|
|
b5d3863ecd | ||
|
|
3ddaba9553 | ||
|
|
9dc91e2bc6 | ||
|
|
064ce4bf6d | ||
|
|
caa1681c5f | ||
|
|
700f96af61 | ||
|
|
0b3fac3a0e | ||
|
|
c875ff972f | ||
|
|
7926cfd8c4 | ||
|
|
be6f79330a | ||
|
|
7bb180e5da | ||
|
|
c86e95e524 | ||
|
|
c00e20e17c | ||
|
|
45aa450f88 | ||
|
|
756216b2cb | ||
|
|
2cf1dd37ba | ||
|
|
45c60911db | ||
|
|
2cb2bdeb00 | ||
|
|
aa35770423 | ||
|
|
94cd4549c6 | ||
|
|
bd40106c7d | ||
|
|
f231b1e32f | ||
|
|
3a50554149 | ||
|
|
64c5af202c | ||
|
|
9cd15939c9 | ||
|
|
c52f2c75f6 | ||
|
|
083661b779 | ||
|
|
6983573243 | ||
|
|
1380ee21b6 | ||
|
|
b403b6c7b4 | ||
|
|
54030eb721 | ||
|
|
5a7161eeb3 | ||
|
|
0767c2e0f6 | ||
|
|
fbbd5a3af5 | ||
|
|
04c2fca4e2 | ||
|
|
bec385a667 | ||
|
|
ba840d22e5 | ||
|
|
655af24183 | ||
|
|
c375ac3939 | ||
|
|
e4f3836b31 | ||
|
|
be01c4fafe | ||
|
|
196812017e | ||
|
|
c2d07eb2b2 | ||
|
|
db4fc8545d | ||
|
|
b7307b4a56 | ||
|
|
edaff5d699 | ||
|
|
dec7680d73 | ||
|
|
1c7a18866f | ||
|
|
6a31b7b118 | ||
|
|
36bc8fe3f0 | ||
|
|
cf86843d48 | ||
|
|
696fbbb0e2 | ||
|
|
dffa3baea6 | ||
|
|
cea4b50bbc | ||
|
|
da3d8fbbc5 | ||
|
|
dac8dc6b8a | ||
|
|
419f717542 | ||
|
|
80d9181b29 | ||
|
|
b6c992ffeb | ||
|
|
cfb7558465 | ||
|
|
2cfdb2d2cd | ||
|
|
529a84154b | ||
|
|
4af814dc3b | ||
|
|
dc953f70d1 | ||
|
|
0a2a7fa6aa | ||
|
|
4ac386a84e | ||
|
|
92391a69cf | ||
|
|
69389bb355 | ||
|
|
4a0d7fd205 | ||
|
|
c9f79b3ba9 | ||
|
|
9d0fe060c8 | ||
|
|
a3c5b0a510 | ||
|
|
0cd45a0022 | ||
|
|
e50d6c6b62 | ||
|
|
a853010537 | ||
|
|
d494d8c299 | ||
|
|
ac4a5bfe6d | ||
|
|
089007f88a | ||
|
|
89ad9f1bb4 | ||
|
|
d14391fb6f | ||
|
|
9a12c73e22 | ||
|
|
605f513ce3 | ||
|
|
fb34991980 | ||
|
|
1af8ab2a3b | ||
|
|
07ff97f647 | ||
|
|
b49f85b877 | ||
|
|
e8490196ba | ||
|
|
bc1c57a18a | ||
|
|
016948804d | ||
|
|
41e7463412 | ||
|
|
e4398d3761 | ||
|
|
6030eb2992 | ||
|
|
db486356db | ||
|
|
464f504b61 | ||
|
|
84fda4afb5 | ||
|
|
93ad17ac7b | ||
|
|
f831d45cc3 | ||
|
|
cc82836109 | ||
|
|
383744b8e4 | ||
|
|
1f3d8e8d64 | ||
|
|
c9902eed72 | ||
|
|
0d793c11a1 | ||
|
|
5ce0dc2a7a | ||
|
|
d6d8903d13 | ||
|
|
ad052d72d7 | ||
|
|
8d9095def0 | ||
|
|
aaeb3ab06c | ||
|
|
1975ec3f76 | ||
|
|
b4533ee362 | ||
|
|
de641d7f68 | ||
|
|
02735e69dd | ||
|
|
ae22a90160 | ||
|
|
7a04bf85bc | ||
|
|
ef0f91f862 | ||
|
|
3ecb09ae52 | ||
|
|
9c5dacd977 | ||
|
|
4187e60b07 | ||
|
|
69eb31028a | ||
|
|
15a104b0f8 | ||
|
|
6b436e1e71 | ||
|
|
5f06d87f01 | ||
|
|
ed50c3d896 | ||
|
|
85bb086e90 | ||
|
|
76b0f4fb25 | ||
|
|
167ff00c99 | ||
|
|
6e7fb2ea01 | ||
|
|
daf3aab6ae | ||
|
|
1c9a74f81c | ||
|
|
2eaffa119f | ||
|
|
6ad3461953 | ||
|
|
52db89f73f | ||
|
|
4f72635698 | ||
|
|
0e7f778b3f | ||
|
|
d6e9216a45 | ||
|
|
9cd60531d2 | ||
|
|
3763ad451b | ||
|
|
680fa3b8f3 | ||
|
|
0644c78c2e | ||
|
|
c2ad5d0fc7 | ||
|
|
6de74697c9 | ||
|
|
36197afa19 | ||
|
|
e762007e0e | ||
|
|
c462219dd7 | ||
|
|
0cd9330e44 | ||
|
|
faba523086 | ||
|
|
90913c66ae | ||
|
|
d47c25287d | ||
|
|
614d38d0e6 | ||
|
|
534b27afa5 | ||
|
|
faae734797 | ||
|
|
68c65866bf | ||
|
|
62db42cf2f | ||
|
|
106b83e9f9 | ||
|
|
9df6424a20 | ||
|
|
474ddbae0c | ||
|
|
9da2be2325 | ||
|
|
15e8fa3189 | ||
|
|
02d2ad6442 | ||
|
|
eafe33a176 | ||
|
|
bb2c21be44 | ||
|
|
d4519e5d3d | ||
|
|
1fceebd0a8 | ||
|
|
418d14ecc9 | ||
|
|
d3dde833f7 | ||
|
|
0e56c47a4c | ||
|
|
5f2725f61f | ||
|
|
b45d74c56e | ||
|
|
90e583db19 | ||
|
|
0d47eb1fa0 | ||
|
|
b060cdb4f5 | ||
|
|
09c96d6a83 | ||
|
|
8a3fdb4ec2 | ||
|
|
e0313bb27f | ||
|
|
04c5369792 | ||
|
|
a11d368465 | ||
|
|
fb126e0838 | ||
|
|
96e467670a | ||
|
|
105838091e | ||
|
|
90a0873044 | ||
|
|
965de96251 | ||
|
|
1a57f603fb | ||
|
|
36cb5b6589 | ||
|
|
dab6bb8e05 | ||
|
|
eebeb36b6c | ||
|
|
38c886db8b | ||
|
|
f4f1fdedee | ||
|
|
24ca7bb64f | ||
|
|
849f646bd2 | ||
|
|
e19c4c6452 | ||
|
|
e693ab76fa | ||
|
|
6b38654542 | ||
|
|
935b8cfcf7 | ||
|
|
639c75312d | ||
|
|
e1121d1c68 | ||
|
|
5224b6677d | ||
|
|
ad3d6a7c91 | ||
|
|
2d420ed661 | ||
|
|
4cc9061990 | ||
|
|
5266690cd8 | ||
|
|
373a17e3de | ||
|
|
2af2002431 | ||
|
|
5b4093069c | ||
|
|
53fef46f9a | ||
|
|
1070322695 | ||
|
|
3ea6278eb9 | ||
|
|
e822a74479 | ||
|
|
e36e5027d7 | ||
|
|
f97e29058c | ||
|
|
964f9275dc | ||
|
|
f3895e9dc9 | ||
|
|
3e8deeed07 | ||
|
|
84e91e0c7c | ||
|
|
83e9842dd3 | ||
|
|
3760ff8a7a | ||
|
|
ddecce400a | ||
|
|
6367bd9561 | ||
|
|
104569318e | ||
|
|
4a8465df90 | ||
|
|
78857cd798 | ||
|
|
2c9ee7ae14 | ||
|
|
4a5e0b181f | ||
|
|
35f826c499 | ||
|
|
b565d4f0a8 | ||
|
|
b964b122ed | ||
|
|
cee9f200ad | ||
|
|
214f15e700 | ||
|
|
0341941d3f | ||
|
|
8d1b599d5f | ||
|
|
4022fcb0d9 | ||
|
|
4f14651a42 | ||
|
|
497247d89a | ||
|
|
137d2d4044 | ||
|
|
37eaa07192 | ||
|
|
9d8cb2f57c | ||
|
|
eec327c02b | ||
|
|
093c94aa7c | ||
|
|
19a8ddef86 | ||
|
|
0fdd944418 | ||
|
|
f997393b0e | ||
|
|
0d02d7086c | ||
|
|
913bde0dfe | ||
|
|
21d33cf6b9 | ||
|
|
dc7ac3550e | ||
|
|
e8ba2b1576 | ||
|
|
d9c1ef0926 | ||
|
|
0061bde950 | ||
|
|
5a984de697 | ||
|
|
3603cdf457 | ||
|
|
a74b3e3602 | ||
|
|
8259a748f6 | ||
|
|
59c6e8f233 | ||
|
|
dfa35363b9 | ||
|
|
bab2e86c01 | ||
|
|
fc462d4526 | ||
|
|
5b0486ca26 | ||
|
|
dc4b236951 | ||
|
|
070190d07b | ||
|
|
03550066a7 | ||
|
|
b93cfc5d83 | ||
|
|
ad56177234 | ||
|
|
e22f93f1fb | ||
|
|
56dca02cab | ||
|
|
8795ce975f | ||
|
|
629cdd62f2 | ||
|
|
58f6534d8b | ||
|
|
c7db277aa8 | ||
|
|
4ce4d345e7 | ||
|
|
f714888d48 | ||
|
|
0207b82f82 | ||
|
|
7f1dbeee8b | ||
|
|
a96fa55704 | ||
|
|
4488b27fe3 | ||
|
|
d862a742b0 | ||
|
|
31343b6287 | ||
|
|
e0850ed209 | ||
|
|
52b9f92553 | ||
|
|
cb0addc122 | ||
|
|
5de5a8bfd5 | ||
|
|
396886c6e8 | ||
|
|
3a0db8d550 | ||
|
|
9e661b206c | ||
|
|
14d5c7f3cd | ||
|
|
5291c66ea1 | ||
|
|
ed927a2147 | ||
|
|
2f4a9f283d | ||
|
|
79bfcbf424 | ||
|
|
c9d69d9629 | ||
|
|
6f12029477 | ||
|
|
7bb127de63 | ||
|
|
87de70f4fd | ||
|
|
782fecca46 | ||
|
|
8cd90de70b | ||
|
|
0b9a8ee67c | ||
|
|
652589f636 | ||
|
|
106f7ea112 | ||
|
|
db41b14317 | ||
|
|
609191c3a5 | ||
|
|
7489c9159d | ||
|
|
b48619078d | ||
|
|
523007588d | ||
|
|
184cabdcb0 | ||
|
|
f8ea431551 | ||
|
|
284e45011e |
4
.github/workflows/patch_faux.yml
vendored
4
.github/workflows/patch_faux.yml
vendored
@@ -1,7 +1,6 @@
|
||||
# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
|
||||
|
||||
name: Skipped Patch Test
|
||||
permissions: none
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -12,6 +11,9 @@ on:
|
||||
- "**.html"
|
||||
- "**.csv"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Tests are skipped for these files but github doesn't allow "passing" hence this is required.
|
||||
|
||||
name: Skipped Tests
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
@@ -11,6 +10,9 @@ on:
|
||||
- "**.md"
|
||||
- "**.html"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
25
CODEOWNERS
25
CODEOWNERS
@@ -3,22 +3,21 @@
|
||||
# These owners will be the default owners for everything in
|
||||
# the repo. Unless a later match takes precedence,
|
||||
|
||||
erpnext/accounts/ @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/assets/ @khushi8112 @deepeshgarg007
|
||||
erpnext/regional @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/selling @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/support/ @deepeshgarg007
|
||||
pos*
|
||||
erpnext/accounts/ @ruthra-kumar
|
||||
erpnext/assets/ @khushi8112
|
||||
erpnext/regional @ruthra-kumar
|
||||
erpnext/selling @ruthra-kumar
|
||||
erpnext/support/ @ruthra-kumar
|
||||
|
||||
erpnext/buying/ @rohitwaghchaure
|
||||
erpnext/buying/ @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/maintenance/ @rohitwaghchaure
|
||||
erpnext/manufacturing/ @rohitwaghchaure
|
||||
erpnext/manufacturing/ @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/quality_management/ @rohitwaghchaure
|
||||
erpnext/stock/ @rohitwaghchaure
|
||||
erpnext/subcontracting @rohitwaghchaure
|
||||
erpnext/stock/ @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/subcontracting @mihir-kandoi
|
||||
|
||||
erpnext/controllers/ @deepeshgarg007 @rohitwaghchaure
|
||||
erpnext/patches/ @deepeshgarg007
|
||||
erpnext/controllers/ @ruthra-kumar @rohitwaghchaure @mihir-kandoi
|
||||
erpnext/patches/ @ruthra-kumar
|
||||
|
||||
.github/ @deepeshgarg007
|
||||
.github/ @ruthra-kumar
|
||||
pyproject.toml @akhilnarang
|
||||
|
||||
@@ -4,7 +4,7 @@ import inspect
|
||||
import frappe
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "15.72.0"
|
||||
__version__ = "15.78.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -169,7 +169,7 @@ class Account(NestedSet):
|
||||
if par.root_type:
|
||||
self.root_type = par.root_type
|
||||
|
||||
if self.is_group:
|
||||
if cint(self.is_group):
|
||||
db_value = self.get_doc_before_save()
|
||||
if db_value:
|
||||
if self.report_type != db_value.report_type:
|
||||
@@ -212,7 +212,7 @@ class Account(NestedSet):
|
||||
if doc_before_save and not doc_before_save.parent_account:
|
||||
throw(_("Root cannot be edited."), RootNotEditable)
|
||||
|
||||
if not self.parent_account and not self.is_group:
|
||||
if not self.parent_account and not cint(self.is_group):
|
||||
throw(_("The root account {0} must be a group").format(frappe.bold(self.name)))
|
||||
|
||||
def validate_root_company_and_sync_account_to_children(self):
|
||||
@@ -261,7 +261,7 @@ class Account(NestedSet):
|
||||
|
||||
if self.check_gle_exists():
|
||||
throw(_("Account with existing transaction cannot be converted to ledger"))
|
||||
elif self.is_group:
|
||||
elif cint(self.is_group):
|
||||
if self.account_type and not self.flags.exclude_account_type_check:
|
||||
throw(_("Cannot covert to Group because Account Type is selected."))
|
||||
elif self.check_if_child_exists():
|
||||
@@ -304,7 +304,9 @@ class Account(NestedSet):
|
||||
self.account_currency = frappe.get_cached_value("Company", self.company, "default_currency")
|
||||
self.currency_explicitly_specified = False
|
||||
|
||||
gl_currency = frappe.db.get_value("GL Entry", {"account": self.name}, "account_currency")
|
||||
gl_currency = frappe.db.get_value(
|
||||
"GL Entry", {"account": self.name, "is_cancelled": 0}, "account_currency"
|
||||
)
|
||||
|
||||
if gl_currency and self.account_currency != gl_currency:
|
||||
if frappe.db.get_value("GL Entry", {"account": self.name}):
|
||||
|
||||
@@ -252,10 +252,6 @@ frappe.treeview_settings["Account"] = {
|
||||
root_company,
|
||||
]);
|
||||
} else {
|
||||
const node = treeview.tree.get_selected_node();
|
||||
if (node.is_root) {
|
||||
frappe.throw(__("Cannot create root account."));
|
||||
}
|
||||
treeview.new_node();
|
||||
}
|
||||
},
|
||||
@@ -274,8 +270,7 @@ frappe.treeview_settings["Account"] = {
|
||||
].treeview.page.fields_dict.root_company.get_value() ||
|
||||
frappe.flags.ignore_root_company_validation) &&
|
||||
node.expandable &&
|
||||
!node.hide_add &&
|
||||
!node.is_root
|
||||
!node.hide_add
|
||||
);
|
||||
},
|
||||
click: function () {
|
||||
|
||||
@@ -0,0 +1,817 @@
|
||||
{
|
||||
"country_code": "au",
|
||||
"name": "Australia - Chart of Accounts with Account Numbers",
|
||||
"tree": {
|
||||
"Assets": {
|
||||
"Current Assets": {
|
||||
"Cash On Hand": {
|
||||
"Cash On Hand": {
|
||||
"account_number": "11010",
|
||||
"account_type": "Cash"
|
||||
},
|
||||
"account_number": "110",
|
||||
"is_group": 1
|
||||
},
|
||||
"Cash at Bank": {
|
||||
"Every Day Bank Account": {
|
||||
"account_number": "11510",
|
||||
"account_type": "Bank"
|
||||
},
|
||||
"Business Savings Account": {
|
||||
"account_number": "11520"
|
||||
},
|
||||
"Business Term Deposit": {
|
||||
"account_number": "11530"
|
||||
},
|
||||
"account_number": "115",
|
||||
"is_group": 1
|
||||
},
|
||||
"Trade Receivables": {
|
||||
"Trade Debtors": {
|
||||
"account_number": "12010",
|
||||
"account_type": "Receivable"
|
||||
},
|
||||
"Provision for Doubtful Debts": {
|
||||
"account_number": "12020"
|
||||
},
|
||||
"Sundry Debtors": {
|
||||
"account_number": "12030"
|
||||
},
|
||||
"Debtor Refund": {
|
||||
"account_number": "12040"
|
||||
},
|
||||
"account_number": "120",
|
||||
"is_group": 1
|
||||
},
|
||||
"Inventory": {
|
||||
"Stock On Hand": {
|
||||
"account_number": "13010",
|
||||
"account_type": "Stock"
|
||||
},
|
||||
"WIP - Work In Progress - Manufacturing": {
|
||||
"account_number": "13020"
|
||||
},
|
||||
"account_number": "130",
|
||||
"is_group": 1
|
||||
},
|
||||
"Prepayments": {
|
||||
"Prepayments": {
|
||||
"account_number": "14010"
|
||||
},
|
||||
"Provisional Tax Paid": {
|
||||
"account_number": "14020"
|
||||
},
|
||||
"account_number": "140",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "11",
|
||||
"is_group": 1
|
||||
},
|
||||
"Non Current Assets": {
|
||||
"Plant & Equipment": {
|
||||
"Plant & Equipment": {
|
||||
"account_number": "16010",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Accumulated Depreciation Plant & Equipment": {
|
||||
"account_number": "16020",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"account_number": "160",
|
||||
"is_group": 1
|
||||
},
|
||||
"Motor Vehicle": {
|
||||
"Motor Vehicle": {
|
||||
"account_number": "16110",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Accumulated Depreciation Motor Vehicle": {
|
||||
"account_number": "16120",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"account_number": "161",
|
||||
"is_group": 1
|
||||
},
|
||||
"Office Equipment": {
|
||||
"Office Furniture & Equipment": {
|
||||
"account_number": "16210",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Accumulated Depreciation Office Furniture & Equipment": {
|
||||
"account_number": "16220",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"account_number": "162",
|
||||
"is_group": 1
|
||||
},
|
||||
"Computer Equipment": {
|
||||
"Computer Equipment": {
|
||||
"account_number": "16310",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Accumulated Depreciation Computer Equipment": {
|
||||
"account_number": "16320",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"account_number": "163",
|
||||
"is_group": 1
|
||||
},
|
||||
"Building": {
|
||||
"Buildings": {
|
||||
"account_number": "16410",
|
||||
"account_type": "Fixed Asset"
|
||||
},
|
||||
"Accumulated Depreciation Buildings": {
|
||||
"account_number": "16420",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"CWIP - Construction Work In Progress": {
|
||||
"account_number": "16430",
|
||||
"account_type": "Capital Work in Progress"
|
||||
},
|
||||
"Accumulated Depreciation - Others": {
|
||||
"account_number": "16440",
|
||||
"account_type": "Accumulated Depreciation"
|
||||
},
|
||||
"account_number": "164",
|
||||
"is_group": 1
|
||||
},
|
||||
"Related Party": {
|
||||
"Loan to Party 1": {
|
||||
"account_number": "17010"
|
||||
},
|
||||
"account_number": "170",
|
||||
"is_group": 1
|
||||
},
|
||||
"Investments & Unlisted Entities": {
|
||||
"Investment - Entity 1": {
|
||||
"account_number": "17510"
|
||||
},
|
||||
"account_number": "175",
|
||||
"is_group": 1
|
||||
},
|
||||
"Intagible Assets": {
|
||||
"Goodwill": {
|
||||
"account_number": "18010"
|
||||
},
|
||||
"Opening Balance Temporary ": {
|
||||
"account_number": "18090",
|
||||
"account_type": "Temporary"
|
||||
},
|
||||
"account_number": "180",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "16",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "1",
|
||||
"root_type": "Asset"
|
||||
},
|
||||
"Liabilities": {
|
||||
"Current Liabilities": {
|
||||
"Trade Payables - Current": {
|
||||
"Trade Creditors": {
|
||||
"account_number": "21010",
|
||||
"account_type": "Payable"
|
||||
},
|
||||
"Goods Received Not Invoiced": {
|
||||
"account_number": "21050",
|
||||
"account_type": "Stock Received But Not Billed"
|
||||
},
|
||||
"Service Received Not Invoiced": {
|
||||
"account_number": "21060"
|
||||
},
|
||||
"Asset Received Not Invoiced": {
|
||||
"account_number": "21070",
|
||||
"account_type": "Asset Received But Not Billed"
|
||||
},
|
||||
"account_number": "210",
|
||||
"is_group": 1
|
||||
},
|
||||
"Other Payables - Current": {
|
||||
"Accrued Expenses": {
|
||||
"account_number": "21510"
|
||||
},
|
||||
"Payroll - Wages Clearing": {
|
||||
"account_number": "21550"
|
||||
},
|
||||
"Payroll - Superannuation Deductions": {
|
||||
"account_number": "21555"
|
||||
},
|
||||
"Payroll - Misc Deductions": {
|
||||
"account_number": "21560"
|
||||
},
|
||||
"Payroll - Withholding Tax Payable": {
|
||||
"account_number": "21565"
|
||||
},
|
||||
"account_number": "215",
|
||||
"is_group": 1
|
||||
},
|
||||
"GST": {
|
||||
"GST Payments to ATO": {
|
||||
"account_number": "22030"
|
||||
},
|
||||
"Provision for PAYG Tax": {
|
||||
"account_number": "22040"
|
||||
},
|
||||
"account_number": "220",
|
||||
"account_type": "Tax",
|
||||
"is_group": 1
|
||||
},
|
||||
"Interest & Non Bearing Liabilities - Current": {
|
||||
"Credit Card - VISA": {
|
||||
"account_number": "22510"
|
||||
},
|
||||
"account_number": "225",
|
||||
"is_group": 1
|
||||
},
|
||||
"Bank Overdraft": {
|
||||
"Bank Overdraft Cash at Bank": {
|
||||
"account_number": "23010"
|
||||
},
|
||||
"account_number": "230",
|
||||
"is_group": 1
|
||||
},
|
||||
"Trade Finance": {
|
||||
"Trade Finance": {
|
||||
"account_number": "23510"
|
||||
},
|
||||
"account_number": "235",
|
||||
"is_group": 1
|
||||
},
|
||||
"Lease Liabilities": {
|
||||
"Finance Lease - Current": {
|
||||
"account_number": "24010"
|
||||
},
|
||||
"account_number": "240",
|
||||
"is_group": 1
|
||||
},
|
||||
"Provisions": {
|
||||
"Provision for Long Service Leave": {
|
||||
"account_number": "24510"
|
||||
},
|
||||
"Provision for Holiday Pay": {
|
||||
"account_number": "24520"
|
||||
},
|
||||
"account_number": "245",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "21",
|
||||
"is_group": 1
|
||||
},
|
||||
"Non Current Liabilities": {
|
||||
"Trade & Other Payables - Non Current": {
|
||||
"Loan Account - Party 1": {
|
||||
"account_number": "25010"
|
||||
},
|
||||
"account_number": "250",
|
||||
"is_group": 1
|
||||
},
|
||||
"Interest & Non Bearing Liabilities - Non Current": {
|
||||
"Non Current Liability - Director Loan": {
|
||||
"account_number": "25510"
|
||||
},
|
||||
"account_number": "255",
|
||||
"is_group": 1
|
||||
},
|
||||
"Bank Loans - Non Current": {
|
||||
"Bank Loan 1 - Non Current": {
|
||||
"account_number": "26010"
|
||||
},
|
||||
"account_number": "260",
|
||||
"is_group": 1
|
||||
},
|
||||
"Lease Liabilities - Non Current": {
|
||||
"Finance Lease - Non Current": {
|
||||
"account_number": "27010"
|
||||
},
|
||||
"account_number": "270",
|
||||
"is_group": 1
|
||||
},
|
||||
"Provisions - Non Current": {
|
||||
"Provision for Long Service Leave": {
|
||||
"account_number": "27510"
|
||||
},
|
||||
"Provision for Holiday Pay": {
|
||||
"account_number": "27520"
|
||||
},
|
||||
"account_number": "275",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "25",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "2",
|
||||
"root_type": "Liability"
|
||||
},
|
||||
"Equity": {
|
||||
"Equity": {
|
||||
"Owner's/Shareholder's Equity": {
|
||||
"Owner's/Shareholders Capital": {
|
||||
"account_number": "31010",
|
||||
"account_type": "Equity"
|
||||
},
|
||||
"Owner's/Shareholders Drawings": {
|
||||
"account_number": "31020",
|
||||
"account_type": "Equity"
|
||||
},
|
||||
"account_number": "310",
|
||||
"is_group": 1
|
||||
},
|
||||
"Earnings": {
|
||||
"Current Year Earnings": {
|
||||
"account_number": "35010",
|
||||
"account_type": "Equity"
|
||||
},
|
||||
"Retained Earnings": {
|
||||
"account_number": "35020",
|
||||
"account_type": "Equity"
|
||||
},
|
||||
"account_number": "350",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "31",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "3",
|
||||
"root_type": "Equity"
|
||||
},
|
||||
"Revenue": {
|
||||
"Revenue": {
|
||||
"Sales Revenue": {
|
||||
"Sales Income": {
|
||||
"account_number": "41010",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Freight Income": {
|
||||
"account_number": "41020",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Other Income": {
|
||||
"account_number": "41030",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"Service Income": {
|
||||
"account_number": "41040",
|
||||
"account_type": "Income Account"
|
||||
},
|
||||
"account_number": "410",
|
||||
"is_group": 1
|
||||
},
|
||||
"Other Revenue": {
|
||||
"Commission Received": {
|
||||
"account_number": "42010"
|
||||
},
|
||||
"Discounts Received": {
|
||||
"account_number": "42020"
|
||||
},
|
||||
"Interest received": {
|
||||
"account_number": "42030"
|
||||
},
|
||||
"Profit/Loss on Sales of Assets": {
|
||||
"account_number": "42040"
|
||||
},
|
||||
"Rent Received": {
|
||||
"account_number": "42050"
|
||||
},
|
||||
"Sundry Income": {
|
||||
"account_number": "42060"
|
||||
},
|
||||
"account_number": "420",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "41",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "4",
|
||||
"root_type": "Income"
|
||||
},
|
||||
"Cost of Goods": {
|
||||
"Cost of Goods": {
|
||||
"Cost of Goods Sold": {
|
||||
"Cost of Goods Sold": {
|
||||
"account_number": "51010",
|
||||
"account_type": "Cost of Goods Sold"
|
||||
},
|
||||
"Freight Expenses (sales related)": {
|
||||
"account_number": "51020"
|
||||
},
|
||||
"Discounts Given": {
|
||||
"account_number": "51030"
|
||||
},
|
||||
"Subcontracting Charges": {
|
||||
"account_number": "51040"
|
||||
},
|
||||
"account_number": "510",
|
||||
"is_group": 1
|
||||
},
|
||||
"Other COGS": {
|
||||
"Purchases - Miscellaneous": {
|
||||
"account_number": "52010"
|
||||
},
|
||||
"Duty & Customs Fees": {
|
||||
"account_number": "52020",
|
||||
"account_type": "Tax"
|
||||
},
|
||||
"Freight Inwards": {
|
||||
"account_number": "52030",
|
||||
"account_type": "Chargeable"
|
||||
},
|
||||
"Stock Adjustment": {
|
||||
"account_number": "52040",
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Stock Wirte Off": {
|
||||
"account_number": "52050",
|
||||
"account_type": "Stock Adjustment"
|
||||
},
|
||||
"Stock Valuation Expenses": {
|
||||
"account_number": "52060",
|
||||
"account_type": "Expenses Included In Valuation"
|
||||
},
|
||||
"Asset Valuation Expenses": {
|
||||
"account_number": "52070",
|
||||
"account_type": "Expenses Included In Asset Valuation"
|
||||
},
|
||||
"account_number": "520",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "51",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "5",
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Expenses": {
|
||||
"Fixed Expenses": {
|
||||
"Payroll & Related Expenses": {
|
||||
"Salaries & Wages": {
|
||||
"account_number": "61010"
|
||||
},
|
||||
"Superannuation": {
|
||||
"account_number": "61015"
|
||||
},
|
||||
"Staff Amenities - GST Paid": {
|
||||
"account_number": "61020"
|
||||
},
|
||||
"Staff Amenities - GST Free": {
|
||||
"account_number": "61025"
|
||||
},
|
||||
"Staff Recruitment": {
|
||||
"account_number": "61030"
|
||||
},
|
||||
"Staff Training": {
|
||||
"account_number": "61035"
|
||||
},
|
||||
"Fringe Benefits Tax": {
|
||||
"account_number": "61040"
|
||||
},
|
||||
"Payroll Tax": {
|
||||
"account_number": "61045"
|
||||
},
|
||||
"Workers Compensation": {
|
||||
"account_number": "61050"
|
||||
},
|
||||
"Long Service Leave": {
|
||||
"account_number": "61060"
|
||||
},
|
||||
"Mileage Reimbursement": {
|
||||
"account_number": "61070"
|
||||
},
|
||||
"Overtime": {
|
||||
"account_number": "61080"
|
||||
},
|
||||
"Worksafe Insurance": {
|
||||
"account_number": "61090"
|
||||
},
|
||||
"account_number": "610",
|
||||
"is_group": 1
|
||||
},
|
||||
"Depreciation Expenses": {
|
||||
"Depreciation - Plant & Equipment": {
|
||||
"account_number": "62010",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Depreciation - Motor Vehicle": {
|
||||
"account_number": "62020",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Depreciation - Office Equipment": {
|
||||
"account_number": "62030",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Depreciation - Computer Equipment": {
|
||||
"account_number": "62040",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Depreciation - Building": {
|
||||
"account_number": "62050",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"Depreciation - Others": {
|
||||
"account_number": "62510",
|
||||
"account_type": "Depreciation"
|
||||
},
|
||||
"account_number": "620",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "61",
|
||||
"is_group": 1
|
||||
},
|
||||
"Accrued Expenses": {
|
||||
"Accrued Expenses": {
|
||||
"Accrued Expenses - Salaries & Wages": {
|
||||
"account_number": "63010"
|
||||
},
|
||||
"Accrued Expenses - Interest": {
|
||||
"account_number": "63020"
|
||||
},
|
||||
"account_number": "630",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "63",
|
||||
"is_group": 1
|
||||
},
|
||||
"Operating Expenses": {
|
||||
"General and Administrative Expenses": {
|
||||
"Low Value Assets less than $300": {
|
||||
"account_number": "64010"
|
||||
},
|
||||
"Office Supplies": {
|
||||
"account_number": "64020"
|
||||
},
|
||||
"Postage & Courier": {
|
||||
"account_number": "64025"
|
||||
},
|
||||
"Printing & Stationery": {
|
||||
"account_number": "64030"
|
||||
},
|
||||
"Registration Fees / Filing Fees": {
|
||||
"account_number": "64040"
|
||||
},
|
||||
"Travel & Accommodation - Local": {
|
||||
"account_number": "64050"
|
||||
},
|
||||
"Travel & Accommodation - Overseas": {
|
||||
"account_number": "64060"
|
||||
},
|
||||
"Relocation Costs": {
|
||||
"account_number": "64070"
|
||||
},
|
||||
"Hire Charges": {
|
||||
"account_number": "64080"
|
||||
},
|
||||
"Repairs & Maintenance": {
|
||||
"account_number": "64210"
|
||||
},
|
||||
"Cleaning Expenses": {
|
||||
"account_number": "64215"
|
||||
},
|
||||
"Uniforms": {
|
||||
"account_number": "64220"
|
||||
},
|
||||
"Security": {
|
||||
"account_number": "64225"
|
||||
},
|
||||
"Subscriptions & Licences": {
|
||||
"account_number": "64510"
|
||||
},
|
||||
"Software Expenses": {
|
||||
"account_number": "64515"
|
||||
},
|
||||
"Marketing Expenses": {
|
||||
"account_number": "64520"
|
||||
},
|
||||
"Advertising Expenses": {
|
||||
"account_number": "64525"
|
||||
},
|
||||
"Website Hosting & Domain Expenses": {
|
||||
"account_number": "64530"
|
||||
},
|
||||
"Computer Repairs / Supplies": {
|
||||
"account_number": "64540"
|
||||
},
|
||||
"Conferences": {
|
||||
"account_number": "64550"
|
||||
},
|
||||
"Consultancy /Contract Services": {
|
||||
"account_number": "64560"
|
||||
},
|
||||
"Training Services": {
|
||||
"account_number": "64570"
|
||||
},
|
||||
"Workshop Supplies": {
|
||||
"account_number": "64580"
|
||||
},
|
||||
"Consumables": {
|
||||
"account_number": "64585"
|
||||
},
|
||||
"Entertainment Expenses - Deductible": {
|
||||
"account_number": "64810"
|
||||
},
|
||||
"Entertainment Expenses - Non Deductible": {
|
||||
"account_number": "64820"
|
||||
},
|
||||
"Amortisation Of Goodwill": {
|
||||
"account_number": "64910"
|
||||
},
|
||||
"General / Miscellaneous Expenses": {
|
||||
"account_number": "64915",
|
||||
"account_type": "Chargeable"
|
||||
},
|
||||
"Donations": {
|
||||
"account_number": "64920"
|
||||
},
|
||||
"Client Gifts": {
|
||||
"account_number": "64930"
|
||||
},
|
||||
"Employee Gifts": {
|
||||
"account_number": "64935"
|
||||
},
|
||||
"account_number": "640",
|
||||
"is_group": 1
|
||||
},
|
||||
"Occupancy Expenses": {
|
||||
"Rental Expenses": {
|
||||
"account_number": "65010"
|
||||
},
|
||||
"Property Insurance": {
|
||||
"account_number": "65020"
|
||||
},
|
||||
"Electricity Expenses": {
|
||||
"account_number": "65030"
|
||||
},
|
||||
"Water Rates": {
|
||||
"account_number": "65040"
|
||||
},
|
||||
"Gas Expenses": {
|
||||
"account_number": "65050"
|
||||
},
|
||||
"Property Taxes": {
|
||||
"account_number": "65060"
|
||||
},
|
||||
"Rates": {
|
||||
"account_number": "65070"
|
||||
},
|
||||
"account_number": "650",
|
||||
"is_group": 1
|
||||
},
|
||||
"Communication & Vehicle Expenses": {
|
||||
"Internet Expenses": {
|
||||
"account_number": "66010"
|
||||
},
|
||||
"Mobile Telephone": {
|
||||
"account_number": "66020"
|
||||
},
|
||||
"Telephone Expenses": {
|
||||
"account_number": "66030"
|
||||
},
|
||||
"Motor Vehicle - Fuel Expenses": {
|
||||
"account_number": "66040"
|
||||
},
|
||||
"Motor Vehicle - Parking & Tolls": {
|
||||
"account_number": "66050"
|
||||
},
|
||||
"Motor Vehicle - Registration & Insurance": {
|
||||
"account_number": "66060"
|
||||
},
|
||||
"Motor Vehicle - Service & Repairs": {
|
||||
"account_number": "66070"
|
||||
},
|
||||
"Taxi": {
|
||||
"account_number": "66080"
|
||||
},
|
||||
"account_number": "660",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "64",
|
||||
"is_group": 1
|
||||
},
|
||||
"Non-Operating Expenses": {
|
||||
"Finance Costs": {
|
||||
"Interest - Bank Loans": {
|
||||
"account_number": "67010"
|
||||
},
|
||||
"Interest - Finance Leases": {
|
||||
"account_number": "67020"
|
||||
},
|
||||
"Interest - Other Loans": {
|
||||
"account_number": "67025"
|
||||
},
|
||||
"Insurance": {
|
||||
"account_number": "67030"
|
||||
},
|
||||
"Bank Charges": {
|
||||
"account_number": "67050"
|
||||
},
|
||||
"Rounding off": {
|
||||
"account_number": "67055",
|
||||
"account_type": "Round Off"
|
||||
},
|
||||
"Audit Fees": {
|
||||
"account_number": "67060"
|
||||
},
|
||||
"Accounting Fees": {
|
||||
"account_number": "67070"
|
||||
},
|
||||
"Legal Fees": {
|
||||
"account_number": "67080"
|
||||
},
|
||||
"Management Fees": {
|
||||
"account_number": "67090"
|
||||
},
|
||||
"account_number": "670",
|
||||
"is_group": 1
|
||||
},
|
||||
"Other Costs": {
|
||||
"Doubtful Debts": {
|
||||
"account_number": "67510"
|
||||
},
|
||||
"Fines": {
|
||||
"account_number": "67520"
|
||||
},
|
||||
"Debt Collection": {
|
||||
"account_number": "67530"
|
||||
},
|
||||
"Bad Debts": {
|
||||
"account_number": "67540"
|
||||
},
|
||||
"account_number": "675",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "67",
|
||||
"is_group": 1
|
||||
},
|
||||
"Variable Expenses": {
|
||||
"Variable Expenses": {
|
||||
"Bonus & Commissions Paid": {
|
||||
"account_number": "68010"
|
||||
},
|
||||
"Bonus & Commissions To be Paid": {
|
||||
"account_number": "68020"
|
||||
},
|
||||
"Warranty Claims": {
|
||||
"account_number": "68030"
|
||||
},
|
||||
"account_number": "680",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "68",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "6",
|
||||
"root_type": "Expense"
|
||||
},
|
||||
"Other Income": {
|
||||
"Other Income": {
|
||||
"Interest Income": {
|
||||
"Interest Income": {
|
||||
"account_number": "71010"
|
||||
},
|
||||
"account_number": "710",
|
||||
"is_group": 1
|
||||
},
|
||||
"Asset Disposal Income": {
|
||||
"Gain on Asset Disposal": {
|
||||
"account_number": "73010"
|
||||
},
|
||||
"account_number": "730",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "71",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "7",
|
||||
"root_type": "Income"
|
||||
},
|
||||
"Other Expenses": {
|
||||
"Other Expenses": {
|
||||
"Income Tax Expenses": {
|
||||
"Income Tax Expenses": {
|
||||
"account_number": "81010"
|
||||
},
|
||||
"account_number": "810",
|
||||
"is_group": 1
|
||||
},
|
||||
"Foreign Exchange Gain/Loss": {
|
||||
"Exchange Loss/Gain - Realized": {
|
||||
"account_number": "82010"
|
||||
},
|
||||
"account_number": "820",
|
||||
"is_group": 1
|
||||
},
|
||||
"Asset Disposal Expenses": {
|
||||
"Loss on Asset Disposal": {
|
||||
"account_number": "83010"
|
||||
},
|
||||
"account_number": "830",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "81",
|
||||
"is_group": 1
|
||||
},
|
||||
"account_number": "8",
|
||||
"root_type": "Expense"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ def get():
|
||||
_("Bank Accounts"): {"account_type": "Bank", "is_group": 1},
|
||||
_("Cash In Hand"): {_("Cash"): {"account_type": "Cash"}, "account_type": "Cash"},
|
||||
_("Loans and Advances (Assets)"): {
|
||||
_("Employee Advances"): {},
|
||||
_("Employee Advances"): {"account_type": "Payable"},
|
||||
},
|
||||
_("Securities and Deposits"): {_("Earnest Money"): {}},
|
||||
_("Stock Assets"): {
|
||||
|
||||
@@ -20,7 +20,7 @@ def get():
|
||||
"account_number": "1100",
|
||||
},
|
||||
_("Loans and Advances (Assets)"): {
|
||||
_("Employee Advances"): {"account_number": "1610"},
|
||||
_("Employee Advances"): {"account_number": "1610", "account_type": "Payable"},
|
||||
"account_number": "1600",
|
||||
},
|
||||
_("Securities and Deposits"): {
|
||||
|
||||
@@ -111,17 +111,15 @@ class AccountingDimension(Document):
|
||||
def make_dimension_in_accounting_doctypes(doc, doclist=None):
|
||||
if not doclist:
|
||||
doclist = get_doctypes_with_dimensions()
|
||||
|
||||
doc_count = len(get_accounting_dimensions())
|
||||
count = 0
|
||||
repostable_doctypes = get_allowed_types_from_settings()
|
||||
repostable_doctypes = get_allowed_types_from_settings(child_doc=True)
|
||||
|
||||
for doctype in doclist:
|
||||
if (doc_count + 1) % 2 == 0:
|
||||
insert_after_field = "dimension_col_break"
|
||||
else:
|
||||
insert_after_field = "accounting_dimensions_section"
|
||||
|
||||
df = {
|
||||
"fieldname": doc.fieldname,
|
||||
"label": doc.label,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"accounting_dimension",
|
||||
"fieldname",
|
||||
"disabled",
|
||||
"column_break_2",
|
||||
"company",
|
||||
@@ -90,11 +91,17 @@
|
||||
"fieldname": "apply_restriction_on_values",
|
||||
"fieldtype": "Check",
|
||||
"label": "Apply restriction on dimension values"
|
||||
},
|
||||
{
|
||||
"fieldname": "fieldname",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Fieldname"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-07 14:59:41.869117",
|
||||
"modified": "2025-08-08 14:13:22.203011",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounting Dimension Filter",
|
||||
@@ -139,8 +146,8 @@
|
||||
}
|
||||
],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,16 @@ class AccountingDimensionFilter(Document):
|
||||
from frappe.types import DF
|
||||
|
||||
from erpnext.accounts.doctype.allowed_dimension.allowed_dimension import AllowedDimension
|
||||
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import (
|
||||
ApplicableOnAccount,
|
||||
)
|
||||
from erpnext.accounts.doctype.applicable_on_account.applicable_on_account import ApplicableOnAccount
|
||||
|
||||
accounting_dimension: DF.Literal
|
||||
accounting_dimension: DF.Literal[None]
|
||||
accounts: DF.Table[ApplicableOnAccount]
|
||||
allow_or_restrict: DF.Literal["Allow", "Restrict"]
|
||||
apply_restriction_on_values: DF.Check
|
||||
company: DF.Link
|
||||
dimensions: DF.Table[AllowedDimension]
|
||||
disabled: DF.Check
|
||||
fieldname: DF.Data | None
|
||||
# end: auto-generated types
|
||||
|
||||
def before_save(self):
|
||||
@@ -37,6 +36,10 @@ class AccountingDimensionFilter(Document):
|
||||
self.set("dimensions", [])
|
||||
|
||||
def validate(self):
|
||||
self.fieldname = frappe.db.get_value(
|
||||
"Accounting Dimension", {"document_type": self.accounting_dimension}, "fieldname"
|
||||
) or frappe.scrub(self.accounting_dimension) # scrub to handle default accounting dimension
|
||||
|
||||
self.validate_applicable_accounts()
|
||||
|
||||
def validate_applicable_accounts(self):
|
||||
@@ -72,7 +75,7 @@ def get_dimension_filter_map():
|
||||
"""
|
||||
SELECT
|
||||
a.applicable_on_account, d.dimension_value, p.accounting_dimension,
|
||||
p.allow_or_restrict, a.is_mandatory
|
||||
p.allow_or_restrict, p.fieldname, a.is_mandatory
|
||||
FROM
|
||||
`tabApplicable On Account` a,
|
||||
`tabAccounting Dimension Filter` p
|
||||
@@ -87,8 +90,6 @@ def get_dimension_filter_map():
|
||||
dimension_filter_map = {}
|
||||
|
||||
for f in filters:
|
||||
f.fieldname = scrub(f.accounting_dimension)
|
||||
|
||||
build_map(
|
||||
dimension_filter_map,
|
||||
f.fieldname,
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"against_voucher_no",
|
||||
"amount",
|
||||
"currency",
|
||||
"event"
|
||||
"event",
|
||||
"delinked"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -68,12 +69,20 @@
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "delinked",
|
||||
"fieldtype": "Check",
|
||||
"label": "DeLinked",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-11-05 10:31:28.736671",
|
||||
"modified": "2025-07-29 11:37:42.678556",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Advance Payment Ledger Entry",
|
||||
@@ -107,7 +116,8 @@
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"row_format": "Dynamic",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
from erpnext.accounts.utils import get_advance_payment_doctypes, update_voucher_outstanding
|
||||
|
||||
|
||||
class AdvancePaymentLedgerEntry(Document):
|
||||
# begin: auto-generated types
|
||||
@@ -19,9 +21,16 @@ class AdvancePaymentLedgerEntry(Document):
|
||||
amount: DF.Currency
|
||||
company: DF.Link | None
|
||||
currency: DF.Link | None
|
||||
delinked: DF.Check
|
||||
event: DF.Data | None
|
||||
voucher_no: DF.DynamicLink | None
|
||||
voucher_type: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
def on_update(self):
|
||||
if (
|
||||
self.against_voucher_type in get_advance_payment_doctypes()
|
||||
and self.flags.update_outstanding == "Yes"
|
||||
and not frappe.flags.is_reverse_depr_entry
|
||||
):
|
||||
update_voucher_outstanding(self.against_voucher_type, self.against_voucher_no, None, None, None)
|
||||
|
||||
@@ -109,6 +109,7 @@ class BankAccount(Document):
|
||||
"party_type": self.party_type,
|
||||
"party": self.party,
|
||||
"is_company_account": self.is_company_account,
|
||||
"company": self.company,
|
||||
"is_default": 1,
|
||||
"disabled": 0,
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import cint, flt
|
||||
from frappe.utils import cint, create_batch, flt
|
||||
|
||||
from erpnext import get_default_cost_center
|
||||
from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
|
||||
@@ -377,16 +377,17 @@ def auto_reconcile_vouchers(
|
||||
bank_transactions = get_bank_transactions(bank_account)
|
||||
|
||||
if len(bank_transactions) > 10:
|
||||
frappe.enqueue(
|
||||
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
|
||||
queue="long",
|
||||
bank_transactions=bank_transactions,
|
||||
from_date=from_date,
|
||||
to_date=to_date,
|
||||
filter_by_reference_date=filter_by_reference_date,
|
||||
from_reference_date=from_reference_date,
|
||||
to_reference_date=to_reference_date,
|
||||
)
|
||||
for bank_transaction_batch in create_batch(bank_transactions, 1000):
|
||||
frappe.enqueue(
|
||||
method="erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.start_auto_reconcile",
|
||||
queue="long",
|
||||
bank_transactions=bank_transaction_batch,
|
||||
from_date=from_date,
|
||||
to_date=to_date,
|
||||
filter_by_reference_date=filter_by_reference_date,
|
||||
from_reference_date=from_reference_date,
|
||||
to_reference_date=to_reference_date,
|
||||
)
|
||||
frappe.msgprint(_("Auto Reconciliation has started in the background"))
|
||||
else:
|
||||
start_auto_reconcile(
|
||||
|
||||
@@ -142,8 +142,10 @@ def validate_expense_against_budget(args, expense_amount=0):
|
||||
if not frappe.get_all("Budget", limit=1):
|
||||
return
|
||||
|
||||
if args.get("company") and not args.fiscal_year:
|
||||
if not args.fiscal_year:
|
||||
args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
|
||||
|
||||
if args.get("company"):
|
||||
frappe.flags.exception_approver_role = frappe.get_cached_value(
|
||||
"Company", args.get("company"), "exception_budget_approver_role"
|
||||
)
|
||||
|
||||
@@ -462,9 +462,8 @@ def unset_existing_data(company):
|
||||
"Sales Taxes and Charges Template",
|
||||
"Purchase Taxes and Charges Template",
|
||||
]:
|
||||
frappe.db.sql(
|
||||
f'''delete from `tab{doctype}` where `company`="%s"''' % (company) # nosec
|
||||
)
|
||||
dt = frappe.qb.DocType(doctype)
|
||||
frappe.qb.from_(dt).where(dt.company == company).delete().run()
|
||||
|
||||
|
||||
def set_default_accounts(company):
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import add_days, today
|
||||
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
@@ -190,6 +192,31 @@ class TestCostCenterAllocation(unittest.TestCase):
|
||||
coa2.cancel()
|
||||
jv.cancel()
|
||||
|
||||
@change_settings("System Settings", {"rounding_method": "Commercial Rounding"})
|
||||
def test_debit_credit_on_cost_center_allocation_for_commercial_rounding(self):
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
cca = create_cost_center_allocation(
|
||||
"_Test Company",
|
||||
"Main Cost Center 1 - _TC",
|
||||
{"Sub Cost Center 2 - _TC": 50, "Sub Cost Center 3 - _TC": 50},
|
||||
)
|
||||
|
||||
si = create_sales_invoice(rate=145.65, cost_center="Main Cost Center 1 - _TC")
|
||||
|
||||
gl_entry = frappe.qb.DocType("GL Entry")
|
||||
gl_entries = (
|
||||
frappe.qb.from_(gl_entry)
|
||||
.select(Sum(gl_entry.credit).as_("cr"), Sum(gl_entry.debit).as_("dr"))
|
||||
.where(gl_entry.voucher_type == "Sales Invoice")
|
||||
.where(gl_entry.voucher_no == si.name)
|
||||
).run(as_dict=1)
|
||||
|
||||
self.assertEqual(gl_entries[0].cr, gl_entries[0].dr)
|
||||
|
||||
si.cancel()
|
||||
cca.cancel()
|
||||
|
||||
|
||||
def create_cost_center_allocation(
|
||||
company,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
-> Resolves dunning automatically
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
@@ -156,40 +157,66 @@ class Dunning(AccountsController):
|
||||
]
|
||||
|
||||
|
||||
def resolve_dunning(doc, state):
|
||||
"""
|
||||
Check if all payments have been made and resolve dunning, if yes. Called
|
||||
when a Payment Entry is submitted.
|
||||
"""
|
||||
for reference in doc.references:
|
||||
# Consider partial and full payments:
|
||||
# Submitting full payment: outstanding_amount will be 0
|
||||
# Submitting 1st partial payment: outstanding_amount will be the pending installment
|
||||
# Cancelling full payment: outstanding_amount will revert to total amount
|
||||
# Cancelling last partial payment: outstanding_amount will revert to pending amount
|
||||
submit_condition = reference.outstanding_amount < reference.total_amount
|
||||
cancel_condition = reference.outstanding_amount <= reference.total_amount
|
||||
def update_linked_dunnings(doc, previous_outstanding_amount):
|
||||
if (
|
||||
doc.doctype != "Sales Invoice"
|
||||
or doc.is_return
|
||||
or previous_outstanding_amount == doc.outstanding_amount
|
||||
):
|
||||
return
|
||||
|
||||
if reference.reference_doctype == "Sales Invoice" and (
|
||||
submit_condition if doc.docstatus == 1 else cancel_condition
|
||||
):
|
||||
state = "Resolved" if doc.docstatus == 2 else "Unresolved"
|
||||
dunnings = get_linked_dunnings_as_per_state(reference.reference_name, state)
|
||||
to_resolve = doc.outstanding_amount < previous_outstanding_amount
|
||||
state = "Unresolved" if to_resolve else "Resolved"
|
||||
dunnings = get_linked_dunnings_as_per_state(doc.name, state)
|
||||
if not dunnings:
|
||||
return
|
||||
|
||||
for dunning in dunnings:
|
||||
resolve = True
|
||||
dunning = frappe.get_doc("Dunning", dunning.get("name"))
|
||||
for overdue_payment in dunning.overdue_payments:
|
||||
outstanding_inv = frappe.get_value(
|
||||
"Sales Invoice", overdue_payment.sales_invoice, "outstanding_amount"
|
||||
)
|
||||
outstanding_ps = frappe.get_value(
|
||||
"Payment Schedule", overdue_payment.payment_schedule, "outstanding"
|
||||
)
|
||||
resolve = False if (outstanding_ps > 0 and outstanding_inv > 0) else True
|
||||
dunnings = [frappe.get_doc("Dunning", dunning.name) for dunning in dunnings]
|
||||
invoices = set()
|
||||
payment_schedule_ids = set()
|
||||
|
||||
dunning.status = "Resolved" if resolve else "Unresolved"
|
||||
dunning.save()
|
||||
for dunning in dunnings:
|
||||
for overdue_payment in dunning.overdue_payments:
|
||||
invoices.add(overdue_payment.sales_invoice)
|
||||
if overdue_payment.payment_schedule:
|
||||
payment_schedule_ids.add(overdue_payment.payment_schedule)
|
||||
|
||||
invoice_outstanding_amounts = dict(
|
||||
frappe.get_all(
|
||||
"Sales Invoice",
|
||||
filters={"name": ["in", list(invoices)]},
|
||||
fields=["name", "outstanding_amount"],
|
||||
as_list=True,
|
||||
)
|
||||
)
|
||||
|
||||
ps_outstanding_amounts = (
|
||||
dict(
|
||||
frappe.get_all(
|
||||
"Payment Schedule",
|
||||
filters={"name": ["in", list(payment_schedule_ids)]},
|
||||
fields=["name", "outstanding"],
|
||||
as_list=True,
|
||||
)
|
||||
)
|
||||
if payment_schedule_ids
|
||||
else {}
|
||||
)
|
||||
|
||||
for dunning in dunnings:
|
||||
has_outstanding = False
|
||||
for overdue_payment in dunning.overdue_payments:
|
||||
invoice_outstanding = invoice_outstanding_amounts[overdue_payment.sales_invoice]
|
||||
ps_outstanding = ps_outstanding_amounts.get(overdue_payment.payment_schedule, 0)
|
||||
has_outstanding = invoice_outstanding > 0 and ps_outstanding > 0
|
||||
if has_outstanding:
|
||||
break
|
||||
|
||||
new_status = "Resolved" if not has_outstanding else "Unresolved"
|
||||
|
||||
if dunning.status != new_status:
|
||||
dunning.status = new_status
|
||||
dunning.save()
|
||||
|
||||
|
||||
def get_linked_dunnings_as_per_state(sales_invoice, state):
|
||||
|
||||
@@ -139,6 +139,64 @@ class TestDunning(FrappeTestCase):
|
||||
self.assertEqual(sales_invoice.status, "Overdue")
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
def test_dunning_resolution_from_credit_note(self):
|
||||
"""
|
||||
Test that dunning is resolved when a credit note is issued against the original invoice.
|
||||
"""
|
||||
sales_invoice = create_sales_invoice_against_cost_center(
|
||||
posting_date=add_days(today(), -10), qty=1, rate=100
|
||||
)
|
||||
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||
dunning.submit()
|
||||
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
credit_note = frappe.copy_doc(sales_invoice)
|
||||
credit_note.is_return = 1
|
||||
credit_note.return_against = sales_invoice.name
|
||||
credit_note.update_outstanding_for_self = 0
|
||||
|
||||
for item in credit_note.items:
|
||||
item.qty = -item.qty
|
||||
|
||||
credit_note.save()
|
||||
credit_note.submit()
|
||||
|
||||
dunning.reload()
|
||||
self.assertEqual(dunning.status, "Resolved")
|
||||
|
||||
credit_note.cancel()
|
||||
dunning.reload()
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
def test_dunning_not_affected_by_standalone_credit_note(self):
|
||||
"""
|
||||
Test that dunning is NOT resolved when a credit note has update_outstanding_for_self checked.
|
||||
"""
|
||||
sales_invoice = create_sales_invoice_against_cost_center(
|
||||
posting_date=add_days(today(), -10), qty=1, rate=100
|
||||
)
|
||||
dunning = create_dunning_from_sales_invoice(sales_invoice.name)
|
||||
dunning.submit()
|
||||
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
credit_note = frappe.copy_doc(sales_invoice)
|
||||
credit_note.is_return = 1
|
||||
credit_note.return_against = sales_invoice.name
|
||||
credit_note.update_outstanding_for_self = 1
|
||||
|
||||
for item in credit_note.items:
|
||||
item.qty = -item.qty
|
||||
|
||||
credit_note.save()
|
||||
|
||||
credit_note = frappe.get_doc("Sales Invoice", credit_note.name)
|
||||
credit_note.submit()
|
||||
|
||||
dunning.reload()
|
||||
self.assertEqual(dunning.status, "Unresolved")
|
||||
|
||||
|
||||
def create_dunning(overdue_days, dunning_type_name=None):
|
||||
posting_date = add_days(today(), -1 * overdue_days)
|
||||
|
||||
@@ -134,7 +134,8 @@ class ExchangeRateRevaluation(Document):
|
||||
accounts = self.get_accounts_data()
|
||||
if accounts:
|
||||
for acc in accounts:
|
||||
self.append("accounts", acc)
|
||||
if acc.get("gain_loss"):
|
||||
self.append("accounts", acc)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_accounts_data(self):
|
||||
|
||||
@@ -311,7 +311,7 @@ def validate_balance_type(account, adv_adj=False):
|
||||
if balance_must_be:
|
||||
balance = frappe.db.sql(
|
||||
"""select sum(debit) - sum(credit)
|
||||
from `tabGL Entry` where account = %s""",
|
||||
from `tabGL Entry` where is_cancelled = 0 and account = %s""",
|
||||
account,
|
||||
)[0][0]
|
||||
|
||||
|
||||
@@ -191,8 +191,6 @@ class JournalEntry(AccountsController):
|
||||
self.validate_cheque_info()
|
||||
self.check_credit_limit()
|
||||
self.make_gl_entries()
|
||||
self.make_advance_payment_ledger_entries()
|
||||
self.update_advance_paid()
|
||||
self.update_asset_value()
|
||||
self.update_inter_company_jv()
|
||||
self.update_invoice_discounting()
|
||||
@@ -225,8 +223,6 @@ class JournalEntry(AccountsController):
|
||||
"Advance Payment Ledger Entry",
|
||||
)
|
||||
self.make_gl_entries(1)
|
||||
self.make_advance_payment_ledger_entries()
|
||||
self.update_advance_paid()
|
||||
self.unlink_advance_entry_reference()
|
||||
self.unlink_asset_reference()
|
||||
self.unlink_inter_company_jv()
|
||||
@@ -237,18 +233,6 @@ class JournalEntry(AccountsController):
|
||||
def get_title(self):
|
||||
return self.pay_to_recd_from or self.accounts[0].account
|
||||
|
||||
def update_advance_paid(self):
|
||||
advance_paid = frappe._dict()
|
||||
advance_payment_doctypes = get_advance_payment_doctypes()
|
||||
for d in self.get("accounts"):
|
||||
if d.is_advance:
|
||||
if d.reference_type in advance_payment_doctypes:
|
||||
advance_paid.setdefault(d.reference_type, []).append(d.reference_name)
|
||||
|
||||
for voucher_type, order_list in advance_paid.items():
|
||||
for voucher_no in list(set(order_list)):
|
||||
frappe.get_doc(voucher_type, voucher_no).set_total_advance_paid()
|
||||
|
||||
def validate_inter_company_accounts(self):
|
||||
if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
|
||||
doc = frappe.db.get_value(
|
||||
@@ -568,6 +552,8 @@ class JournalEntry(AccountsController):
|
||||
elif (
|
||||
d.party_type
|
||||
and frappe.db.get_value("Party Type", d.party_type, "account_type") != account_type
|
||||
and d.party_type
|
||||
!= "Employee" # making an excpetion for employee since they can be both payable and receivable
|
||||
):
|
||||
frappe.throw(
|
||||
_("Row {0}: Account {1} and Party Type {2} have different account types").format(
|
||||
@@ -1094,49 +1080,65 @@ class JournalEntry(AccountsController):
|
||||
self.transaction_exchange_rate = row.exchange_rate
|
||||
break
|
||||
|
||||
advance_doctypes = get_advance_payment_doctypes()
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if d.debit or d.credit or (self.voucher_type == "Exchange Gain Or Loss"):
|
||||
r = [d.user_remark, self.remark]
|
||||
r = [x for x in r if x]
|
||||
remarks = "\n".join(r)
|
||||
|
||||
row = {
|
||||
"account": d.account,
|
||||
"party_type": d.party_type,
|
||||
"due_date": self.due_date,
|
||||
"party": d.party,
|
||||
"against": d.against_account,
|
||||
"debit": flt(d.debit, d.precision("debit")),
|
||||
"credit": flt(d.credit, d.precision("credit")),
|
||||
"account_currency": d.account_currency,
|
||||
"debit_in_account_currency": flt(
|
||||
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||
),
|
||||
"credit_in_account_currency": flt(
|
||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||
),
|
||||
"transaction_currency": self.transaction_currency,
|
||||
"transaction_exchange_rate": self.transaction_exchange_rate,
|
||||
"debit_in_transaction_currency": flt(
|
||||
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||
)
|
||||
if self.transaction_currency == d.account_currency
|
||||
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
|
||||
"credit_in_transaction_currency": flt(
|
||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||
)
|
||||
if self.transaction_currency == d.account_currency
|
||||
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
|
||||
"against_voucher_type": d.reference_type,
|
||||
"against_voucher": d.reference_name,
|
||||
"remarks": remarks,
|
||||
"voucher_detail_no": d.reference_detail_no,
|
||||
"cost_center": d.cost_center,
|
||||
"project": d.project,
|
||||
"finance_book": self.finance_book,
|
||||
"advance_voucher_type": d.advance_voucher_type,
|
||||
"advance_voucher_no": d.advance_voucher_no,
|
||||
}
|
||||
|
||||
if d.reference_type in advance_doctypes:
|
||||
row.update(
|
||||
{
|
||||
"against_voucher_type": self.doctype,
|
||||
"against_voucher": self.name,
|
||||
"advance_voucher_type": d.reference_type,
|
||||
"advance_voucher_no": d.reference_name,
|
||||
}
|
||||
)
|
||||
|
||||
gl_map.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": d.account,
|
||||
"party_type": d.party_type,
|
||||
"due_date": self.due_date,
|
||||
"party": d.party,
|
||||
"against": d.against_account,
|
||||
"debit": flt(d.debit, d.precision("debit")),
|
||||
"credit": flt(d.credit, d.precision("credit")),
|
||||
"account_currency": d.account_currency,
|
||||
"debit_in_account_currency": flt(
|
||||
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||
),
|
||||
"credit_in_account_currency": flt(
|
||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||
),
|
||||
"transaction_currency": self.transaction_currency,
|
||||
"transaction_exchange_rate": self.transaction_exchange_rate,
|
||||
"debit_in_transaction_currency": flt(
|
||||
d.debit_in_account_currency, d.precision("debit_in_account_currency")
|
||||
)
|
||||
if self.transaction_currency == d.account_currency
|
||||
else flt(d.debit, d.precision("debit")) / self.transaction_exchange_rate,
|
||||
"credit_in_transaction_currency": flt(
|
||||
d.credit_in_account_currency, d.precision("credit_in_account_currency")
|
||||
)
|
||||
if self.transaction_currency == d.account_currency
|
||||
else flt(d.credit, d.precision("credit")) / self.transaction_exchange_rate,
|
||||
"against_voucher_type": d.reference_type,
|
||||
"against_voucher": d.reference_name,
|
||||
"remarks": remarks,
|
||||
"voucher_detail_no": d.reference_detail_no,
|
||||
"cost_center": d.cost_center,
|
||||
"project": d.project,
|
||||
"finance_book": self.finance_book,
|
||||
},
|
||||
row,
|
||||
item=d,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
"reference_name",
|
||||
"reference_due_date",
|
||||
"reference_detail_no",
|
||||
"advance_voucher_type",
|
||||
"advance_voucher_no",
|
||||
"col_break3",
|
||||
"is_advance",
|
||||
"user_remark",
|
||||
@@ -263,12 +265,28 @@
|
||||
"label": "Reference Detail No",
|
||||
"no_copy": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "advance_voucher_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Advance Voucher Type",
|
||||
"no_copy": 1,
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "advance_voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Advance Voucher No",
|
||||
"no_copy": 1,
|
||||
"options": "advance_voucher_type",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-02-05 01:10:50.224840",
|
||||
"modified": "2025-07-25 04:45:28.117715",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Journal Entry Account",
|
||||
@@ -279,4 +297,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@ class JournalEntryAccount(Document):
|
||||
account: DF.Link
|
||||
account_currency: DF.Link | None
|
||||
account_type: DF.Data | None
|
||||
advance_voucher_no: DF.DynamicLink | None
|
||||
advance_voucher_type: DF.Link | None
|
||||
against_account: DF.Text | None
|
||||
balance: DF.Currency
|
||||
bank_account: DF.Link | None
|
||||
cost_center: DF.Link | None
|
||||
credit: DF.Currency
|
||||
@@ -31,7 +32,6 @@ class JournalEntryAccount(Document):
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
party: DF.DynamicLink | None
|
||||
party_balance: DF.Currency
|
||||
party_type: DF.Link | None
|
||||
project: DF.Link | None
|
||||
reference_detail_no: DF.Data | None
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import flt, today
|
||||
|
||||
|
||||
@@ -45,22 +46,30 @@ def get_loyalty_details(
|
||||
if not expiry_date:
|
||||
expiry_date = today()
|
||||
|
||||
condition = ""
|
||||
if company:
|
||||
condition = " and company=%s " % frappe.db.escape(company)
|
||||
if not include_expired_entry:
|
||||
condition += " and expiry_date>='%s' " % expiry_date
|
||||
LoyaltyPointEntry = frappe.qb.DocType("Loyalty Point Entry")
|
||||
|
||||
loyalty_point_details = frappe.db.sql(
|
||||
f"""select sum(loyalty_points) as loyalty_points,
|
||||
sum(purchase_amount) as total_spent from `tabLoyalty Point Entry`
|
||||
where customer=%s and loyalty_program=%s and posting_date <= %s
|
||||
{condition}
|
||||
group by customer""",
|
||||
(customer, loyalty_program, expiry_date),
|
||||
as_dict=1,
|
||||
query = (
|
||||
frappe.qb.from_(LoyaltyPointEntry)
|
||||
.select(
|
||||
Sum(LoyaltyPointEntry.loyalty_points).as_("loyalty_points"),
|
||||
Sum(LoyaltyPointEntry.purchase_amount).as_("total_spent"),
|
||||
)
|
||||
.where(
|
||||
(LoyaltyPointEntry.customer == customer)
|
||||
& (LoyaltyPointEntry.loyalty_program == loyalty_program)
|
||||
& (LoyaltyPointEntry.posting_date <= expiry_date)
|
||||
)
|
||||
.groupby(LoyaltyPointEntry.customer)
|
||||
)
|
||||
|
||||
if company:
|
||||
query = query.where(LoyaltyPointEntry.company == company)
|
||||
|
||||
if not include_expired_entry:
|
||||
query = query.where(LoyaltyPointEntry.expiry_date >= expiry_date)
|
||||
|
||||
loyalty_point_details = query.run(as_dict=True)
|
||||
|
||||
if loyalty_point_details:
|
||||
return loyalty_point_details[0]
|
||||
else:
|
||||
|
||||
@@ -117,12 +117,10 @@ class PaymentEntry(AccountsController):
|
||||
def on_submit(self):
|
||||
if self.difference_amount:
|
||||
frappe.throw(_("Difference Amount must be zero"))
|
||||
self.update_payment_requests()
|
||||
self.update_payment_schedule()
|
||||
self.make_gl_entries()
|
||||
self.update_outstanding_amounts()
|
||||
self.update_payment_schedule()
|
||||
self.update_payment_requests()
|
||||
self.make_advance_payment_ledger_entries()
|
||||
self.update_advance_paid() # advance_paid_status depends on the payment request amount
|
||||
self.set_status()
|
||||
|
||||
def validate_for_repost(self):
|
||||
@@ -222,13 +220,11 @@ class PaymentEntry(AccountsController):
|
||||
"Advance Payment Ledger Entry",
|
||||
)
|
||||
super().on_cancel()
|
||||
self.update_payment_requests(cancel=True)
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.make_gl_entries(cancel=1)
|
||||
self.update_outstanding_amounts()
|
||||
self.delink_advance_entry_references()
|
||||
self.update_payment_schedule(cancel=1)
|
||||
self.update_payment_requests(cancel=True)
|
||||
self.make_advance_payment_ledger_entries()
|
||||
self.update_advance_paid() # advance_paid_status depends on the payment request amount
|
||||
self.set_status()
|
||||
|
||||
def update_payment_requests(self, cancel=False):
|
||||
@@ -1366,23 +1362,27 @@ class PaymentEntry(AccountsController):
|
||||
dr_or_cr + "_in_transaction_currency": d.allocated_amount
|
||||
if self.transaction_currency == self.party_account_currency
|
||||
else allocated_amount_in_company_currency / self.transaction_exchange_rate,
|
||||
"advance_voucher_type": d.advance_voucher_type,
|
||||
"advance_voucher_no": d.advance_voucher_no,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
if self.book_advance_payments_in_separate_party_account:
|
||||
if d.reference_doctype in advance_payment_doctypes:
|
||||
# Upon reconciliation, whole ledger will be reposted. So, reference to SO/PO is fine
|
||||
gle.update(
|
||||
{
|
||||
"against_voucher_type": d.reference_doctype,
|
||||
"against_voucher": d.reference_name,
|
||||
}
|
||||
)
|
||||
else:
|
||||
# Do not reference Invoices while Advance is in separate party account
|
||||
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
|
||||
if d.reference_doctype in advance_payment_doctypes:
|
||||
# advance reference
|
||||
gle.update(
|
||||
{
|
||||
"against_voucher_type": self.doctype,
|
||||
"against_voucher": self.name,
|
||||
"advance_voucher_type": d.reference_doctype,
|
||||
"advance_voucher_no": d.reference_name,
|
||||
}
|
||||
)
|
||||
|
||||
elif self.book_advance_payments_in_separate_party_account:
|
||||
# Do not reference Invoices while Advance is in separate party account
|
||||
gle.update({"against_voucher_type": self.doctype, "against_voucher": self.name})
|
||||
else:
|
||||
gle.update(
|
||||
{
|
||||
@@ -1512,6 +1512,8 @@ class PaymentEntry(AccountsController):
|
||||
{
|
||||
"against_voucher_type": invoice.reference_doctype,
|
||||
"against_voucher": invoice.reference_name,
|
||||
"advance_voucher_type": invoice.advance_voucher_type,
|
||||
"advance_voucher_no": invoice.advance_voucher_no,
|
||||
"posting_date": posting_date,
|
||||
}
|
||||
)
|
||||
@@ -1536,6 +1538,8 @@ class PaymentEntry(AccountsController):
|
||||
{
|
||||
"against_voucher_type": "Payment Entry",
|
||||
"against_voucher": self.name,
|
||||
"advance_voucher_type": invoice.advance_voucher_type,
|
||||
"advance_voucher_no": invoice.advance_voucher_no,
|
||||
}
|
||||
)
|
||||
gle = self.get_gl_dict(
|
||||
@@ -1684,17 +1688,6 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
return flt(gl_dict.get(field, 0) / (conversion_rate or 1))
|
||||
|
||||
def update_advance_paid(self):
|
||||
if self.payment_type not in ("Receive", "Pay") or not self.party:
|
||||
return
|
||||
|
||||
advance_payment_doctypes = get_advance_payment_doctypes()
|
||||
for d in self.get("references"):
|
||||
if d.allocated_amount and d.reference_doctype in advance_payment_doctypes:
|
||||
frappe.get_doc(
|
||||
d.reference_doctype, d.reference_name, for_update=True
|
||||
).set_total_advance_paid()
|
||||
|
||||
def on_recurring(self, reference_doc, auto_repeat_doc):
|
||||
self.reference_no = reference_doc.name
|
||||
self.reference_date = nowdate()
|
||||
|
||||
@@ -52,7 +52,7 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
self.assertEqual(pe.paid_to_account_type, "Cash")
|
||||
|
||||
expected_gle = dict(
|
||||
(d[0], d) for d in [["Debtors - _TC", 0, 1000, so.name], ["_Test Cash - _TC", 1000.0, 0, None]]
|
||||
(d[0], d) for d in [["Debtors - _TC", 0, 1000, pe.name], ["_Test Cash - _TC", 1000.0, 0, None]]
|
||||
)
|
||||
|
||||
self.validate_gl_entries(pe.name, expected_gle)
|
||||
@@ -84,7 +84,7 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
|
||||
expected_gle = dict(
|
||||
(d[0], d)
|
||||
for d in [["_Test Receivable USD - _TC", 0, 5500, so.name], [pe.paid_to, 5500.0, 0, None]]
|
||||
for d in [["_Test Receivable USD - _TC", 0, 5500, pe.name], [pe.paid_to, 5500.0, 0, None]]
|
||||
)
|
||||
|
||||
self.validate_gl_entries(pe.name, expected_gle)
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
"exchange_gain_loss",
|
||||
"account",
|
||||
"payment_request",
|
||||
"payment_request_outstanding"
|
||||
"payment_request_outstanding",
|
||||
"advance_voucher_type",
|
||||
"advance_voucher_no"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -151,12 +153,28 @@
|
||||
"fieldtype": "Date",
|
||||
"label": "Reconcile Effect On",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "advance_voucher_type",
|
||||
"fieldtype": "Link",
|
||||
"label": "Advance Voucher Type",
|
||||
"options": "DocType",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"columns": 2,
|
||||
"fieldname": "advance_voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"label": "Advance Voucher No",
|
||||
"options": "advance_voucher_type",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-01-13 15:56:18.895082",
|
||||
"modified": "2025-07-25 04:32:11.040025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry Reference",
|
||||
@@ -167,4 +185,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ class PaymentEntryReference(Document):
|
||||
|
||||
account: DF.Link | None
|
||||
account_type: DF.Data | None
|
||||
advance_voucher_no: DF.DynamicLink | None
|
||||
advance_voucher_type: DF.Link | None
|
||||
allocated_amount: DF.Float
|
||||
bill_no: DF.Data | None
|
||||
due_date: DF.Date | None
|
||||
@@ -26,7 +28,6 @@ class PaymentEntryReference(Document):
|
||||
parentfield: DF.Data
|
||||
parenttype: DF.Data
|
||||
payment_request: DF.Link | None
|
||||
payment_request_outstanding: DF.Float
|
||||
payment_term: DF.Link | None
|
||||
payment_term_outstanding: DF.Float
|
||||
payment_type: DF.Data | None
|
||||
|
||||
@@ -8,4 +8,14 @@ frappe.ui.form.on("Payment Gateway Account", {
|
||||
frm.set_df_property("payment_gateway", "read_only", 1);
|
||||
}
|
||||
},
|
||||
|
||||
setup(frm) {
|
||||
frm.set_query("payment_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"field_order": [
|
||||
"payment_gateway",
|
||||
"payment_channel",
|
||||
"company",
|
||||
"is_default",
|
||||
"column_break_4",
|
||||
"payment_account",
|
||||
@@ -70,11 +71,21 @@
|
||||
"fieldtype": "Select",
|
||||
"label": "Payment Channel",
|
||||
"options": "\nEmail\nPhone"
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"print_hide": 1,
|
||||
"remember_last_selected_value": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-20 13:30:27.722852",
|
||||
"modified": "2025-07-14 16:49:55.210352",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Gateway Account",
|
||||
@@ -95,4 +106,4 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class PaymentGatewayAccount(Document):
|
||||
if TYPE_CHECKING:
|
||||
from frappe.types import DF
|
||||
|
||||
company: DF.Link
|
||||
currency: DF.ReadOnly | None
|
||||
is_default: DF.Check
|
||||
message: DF.SmallText | None
|
||||
@@ -24,7 +25,8 @@ class PaymentGatewayAccount(Document):
|
||||
# end: auto-generated types
|
||||
|
||||
def autoname(self):
|
||||
self.name = self.payment_gateway + " - " + self.currency
|
||||
abbr = frappe.db.get_value("Company", self.company, "abbr")
|
||||
self.name = self.payment_gateway + " - " + self.currency + " - " + abbr
|
||||
|
||||
def validate(self):
|
||||
self.currency = frappe.get_cached_value("Account", self.payment_account, "account_currency")
|
||||
@@ -34,13 +36,15 @@ class PaymentGatewayAccount(Document):
|
||||
|
||||
def update_default_payment_gateway(self):
|
||||
if self.is_default:
|
||||
frappe.db.sql(
|
||||
"""update `tabPayment Gateway Account` set is_default = 0
|
||||
where is_default = 1 """
|
||||
frappe.db.set_value(
|
||||
"Payment Gateway Account",
|
||||
{"is_default": 1, "name": ["!=", self.name], "company": self.company},
|
||||
"is_default",
|
||||
0,
|
||||
)
|
||||
|
||||
def set_as_default_if_not_set(self):
|
||||
if not frappe.db.get_value(
|
||||
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name)}, "name"
|
||||
if not frappe.db.exists(
|
||||
"Payment Gateway Account", {"is_default": 1, "name": ("!=", self.name), "company": self.company}
|
||||
):
|
||||
self.is_default = 1
|
||||
|
||||
@@ -197,4 +197,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -576,6 +576,7 @@ class PaymentReconciliation(Document):
|
||||
"difference_amount": flt(row.get("difference_amount")),
|
||||
"difference_account": row.get("difference_account"),
|
||||
"difference_posting_date": row.get("gain_loss_posting_date"),
|
||||
"debit_or_credit_note_posting_date": row.get("debit_or_credit_note_posting_date"),
|
||||
"cost_center": row.get("cost_center"),
|
||||
}
|
||||
)
|
||||
@@ -765,7 +766,7 @@ def reconcile_dr_cr_note(dr_cr_notes, company, active_dimensions=None):
|
||||
{
|
||||
"doctype": "Journal Entry",
|
||||
"voucher_type": voucher_type,
|
||||
"posting_date": today(),
|
||||
"posting_date": inv.get("debit_or_credit_note_posting_date") or today(),
|
||||
"company": company,
|
||||
"multi_currency": 1 if inv.currency != company_currency else 0,
|
||||
"accounts": [
|
||||
|
||||
@@ -2206,6 +2206,136 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(len(pr.get("payments")), 0)
|
||||
self.assertEqual(pr.get("invoices")[0].get("outstanding_amount"), 200)
|
||||
|
||||
def test_partial_advance_payment_with_closed_fiscal_year(self):
|
||||
"""
|
||||
Test Advance Payment partial reconciliation before period closing and partial after period closing
|
||||
"""
|
||||
default_settings = frappe.db.get_value(
|
||||
"Company",
|
||||
self.company,
|
||||
[
|
||||
"book_advance_payments_in_separate_party_account",
|
||||
"default_advance_paid_account",
|
||||
"reconciliation_takes_effect_on",
|
||||
],
|
||||
as_dict=True,
|
||||
)
|
||||
first_fy_start_date = frappe.db.get_value("Fiscal Year", {"disabled": 0}, "min(year_start_date)")
|
||||
prev_fy_start_date = add_years(first_fy_start_date, -1)
|
||||
prev_fy_end_date = add_days(first_fy_start_date, -1)
|
||||
|
||||
create_fiscal_year(
|
||||
company=self.company, year_start_date=prev_fy_start_date, year_end_date=prev_fy_end_date
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Company",
|
||||
self.company,
|
||||
{
|
||||
"book_advance_payments_in_separate_party_account": 1,
|
||||
"default_advance_paid_account": self.advance_payable_account,
|
||||
"reconciliation_takes_effect_on": "Oldest Of Invoice Or Advance",
|
||||
},
|
||||
)
|
||||
|
||||
self.supplier = "_Test Supplier"
|
||||
|
||||
# Create advance payment of 1000 (previous FY)
|
||||
pe = self.create_payment_entry(amount=1000, posting_date=prev_fy_start_date)
|
||||
pe.party_type = "Supplier"
|
||||
pe.party = self.supplier
|
||||
pe.payment_type = "Pay"
|
||||
pe.paid_from = self.cash
|
||||
pe.paid_to = self.advance_payable_account
|
||||
pe.save().submit()
|
||||
|
||||
# Create purchase invoice of 600 (previous FY)
|
||||
pi1 = self.create_purchase_invoice(qty=1, rate=600, do_not_submit=True)
|
||||
pi1.posting_date = prev_fy_start_date
|
||||
pi1.set_posting_time = 1
|
||||
pi1.supplier = self.supplier
|
||||
pi1.credit_to = self.creditors
|
||||
pi1.save().submit()
|
||||
|
||||
# Reconcile advance payment
|
||||
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||
pr.party = self.supplier
|
||||
pr.receivable_payable_account = self.creditors
|
||||
pr.default_advance_account = self.advance_payable_account
|
||||
pr.from_invoice_date = pr.to_invoice_date = pi1.posting_date
|
||||
pr.from_payment_date = pr.to_payment_date = pe.posting_date
|
||||
pr.get_unreconciled_entries()
|
||||
invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi1.name]
|
||||
payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
|
||||
# Verify partial reconciliation
|
||||
pe.reload()
|
||||
pi1.reload()
|
||||
|
||||
self.assertEqual(len(pe.references), 1)
|
||||
self.assertEqual(pe.references[0].allocated_amount, 600)
|
||||
self.assertEqual(flt(pe.unallocated_amount), 400)
|
||||
|
||||
self.assertEqual(pi1.outstanding_amount, 0)
|
||||
self.assertEqual(pi1.status, "Paid")
|
||||
|
||||
# Close accounting period for March (previous FY)
|
||||
pcv = make_period_closing_voucher(
|
||||
company=self.company, cost_center=self.cost_center, posting_date=prev_fy_end_date
|
||||
)
|
||||
pcv.reload()
|
||||
self.assertEqual(pcv.gle_processing_status, "Completed")
|
||||
|
||||
# Change reconciliation setting to "Reconciliation Date"
|
||||
frappe.db.set_value(
|
||||
"Company",
|
||||
self.company,
|
||||
"reconciliation_takes_effect_on",
|
||||
"Reconciliation Date",
|
||||
)
|
||||
|
||||
# Create new purchase invoice for 400 in new fiscal year
|
||||
pi2 = self.create_purchase_invoice(qty=1, rate=400, do_not_submit=True)
|
||||
pi2.posting_date = today()
|
||||
pi2.set_posting_time = 1
|
||||
pi2.supplier = self.supplier
|
||||
pi2.currency = "INR"
|
||||
pi2.credit_to = self.creditors
|
||||
pi2.save()
|
||||
pi2.submit()
|
||||
|
||||
# Allocate 600 from advance payment to purchase invoice
|
||||
pr = self.create_payment_reconciliation(party_is_customer=False)
|
||||
pr.party = self.supplier
|
||||
pr.receivable_payable_account = self.creditors
|
||||
pr.default_advance_account = self.advance_payable_account
|
||||
pr.from_invoice_date = pr.to_invoice_date = pi2.posting_date
|
||||
pr.from_payment_date = pr.to_payment_date = pe.posting_date
|
||||
pr.get_unreconciled_entries()
|
||||
invoices = [x.as_dict() for x in pr.invoices if x.invoice_number == pi2.name]
|
||||
payments = [x.as_dict() for x in pr.payments if x.reference_name == pe.name]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
|
||||
pe.reload()
|
||||
pi2.reload()
|
||||
|
||||
# Assert advance payment is fully allocated
|
||||
self.assertEqual(len(pe.references), 2)
|
||||
self.assertEqual(flt(pe.unallocated_amount), 0)
|
||||
|
||||
# Assert new invoice is fully paid
|
||||
self.assertEqual(pi2.outstanding_amount, 0)
|
||||
self.assertEqual(pi2.status, "Paid")
|
||||
|
||||
# Verify reconciliation dates are correct based on company setting
|
||||
self.assertEqual(getdate(pe.references[0].reconcile_effect_on), getdate(pi1.posting_date))
|
||||
self.assertEqual(getdate(pe.references[1].reconcile_effect_on), getdate(pi2.posting_date))
|
||||
|
||||
frappe.db.set_value("Company", self.company, default_settings)
|
||||
|
||||
|
||||
def make_customer(customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"section_break_5",
|
||||
"difference_amount",
|
||||
"gain_loss_posting_date",
|
||||
"debit_or_credit_note_posting_date",
|
||||
"column_break_7",
|
||||
"difference_account",
|
||||
"exchange_rate",
|
||||
@@ -168,12 +169,17 @@
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "debit_or_credit_note_posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Debit / Credit Note Posting Date"
|
||||
}
|
||||
],
|
||||
"is_virtual": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-12-14 13:38:26.104150",
|
||||
"modified": "2025-08-20 19:12:50.406769",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Reconciliation Allocation",
|
||||
@@ -183,4 +189,4 @@
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class PaymentReconciliationAllocation(Document):
|
||||
amount: DF.Currency
|
||||
cost_center: DF.Link | None
|
||||
currency: DF.Link | None
|
||||
debit_or_credit_note_posting_date: DF.Date | None
|
||||
difference_account: DF.Link | None
|
||||
difference_amount: DF.Currency
|
||||
exchange_rate: DF.Float
|
||||
|
||||
@@ -9,6 +9,14 @@ frappe.ui.form.on("Payment Request", {
|
||||
query: "erpnext.setup.doctype.party_type.party_type.get_party_type",
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("payment_gateway_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -539,7 +539,9 @@ def make_payment_request(**args):
|
||||
if args.dt not in ALLOWED_DOCTYPES_FOR_PAYMENT_REQUEST:
|
||||
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
|
||||
|
||||
ref_doc = frappe.get_doc(args.dt, args.dn)
|
||||
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
|
||||
if not args.get("company"):
|
||||
args.company = ref_doc.company
|
||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||
|
||||
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||
@@ -782,7 +784,7 @@ def get_gateway_details(args): # nosemgrep
|
||||
"""
|
||||
Return gateway and payment account of default payment gateway
|
||||
"""
|
||||
gateway_account = args.get("payment_gateway_account", {"is_default": 1})
|
||||
gateway_account = args.get("payment_gateway_account", {"is_default": 1, "company": args.company})
|
||||
if gateway_account:
|
||||
return get_payment_gateway_account(gateway_account)
|
||||
|
||||
|
||||
@@ -28,12 +28,14 @@ payment_method = [
|
||||
"payment_gateway": "_Test Gateway",
|
||||
"payment_account": "_Test Bank - _TC",
|
||||
"currency": "INR",
|
||||
"company": "_Test Company",
|
||||
},
|
||||
{
|
||||
"doctype": "Payment Gateway Account",
|
||||
"payment_gateway": "_Test Gateway",
|
||||
"payment_account": "_Test Bank USD - _TC",
|
||||
"currency": "USD",
|
||||
"company": "_Test Company",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -46,7 +48,11 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
for method in payment_method:
|
||||
if not frappe.db.get_value(
|
||||
"Payment Gateway Account",
|
||||
{"payment_gateway": method["payment_gateway"], "currency": method["currency"]},
|
||||
{
|
||||
"payment_gateway": method["payment_gateway"],
|
||||
"currency": method["currency"],
|
||||
"company": method["company"],
|
||||
},
|
||||
"name",
|
||||
):
|
||||
frappe.get_doc(method).insert(ignore_permissions=True)
|
||||
@@ -60,7 +66,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
dt="Sales Order",
|
||||
dn=so_inr.name,
|
||||
recipient_id="saurabh@erpnext.com",
|
||||
payment_gateway_account="_Test Gateway - INR",
|
||||
payment_gateway_account="_Test Gateway - INR - _TC",
|
||||
)
|
||||
|
||||
self.assertEqual(pr.reference_doctype, "Sales Order")
|
||||
@@ -74,7 +80,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
dt="Sales Invoice",
|
||||
dn=si_usd.name,
|
||||
recipient_id="saurabh@erpnext.com",
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||
)
|
||||
|
||||
self.assertEqual(pr.reference_doctype, "Sales Invoice")
|
||||
@@ -95,7 +101,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
party="_Test Supplier USD",
|
||||
recipient_id="user@example.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||
submit_doc=1,
|
||||
return_doc=1,
|
||||
)
|
||||
@@ -119,7 +125,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
dn=purchase_invoice.name,
|
||||
recipient_id="user@example.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||
return_doc=1,
|
||||
)
|
||||
|
||||
@@ -138,7 +144,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
dn=purchase_invoice.name,
|
||||
recipient_id="user@example.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||
return_doc=1,
|
||||
)
|
||||
|
||||
@@ -162,7 +168,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
dn=so_inr.name,
|
||||
recipient_id="saurabh@erpnext.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - INR",
|
||||
payment_gateway_account="_Test Gateway - INR - _TC",
|
||||
submit_doc=1,
|
||||
return_doc=1,
|
||||
)
|
||||
@@ -184,7 +190,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
dn=si_usd.name,
|
||||
recipient_id="saurabh@erpnext.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||
submit_doc=1,
|
||||
return_doc=1,
|
||||
)
|
||||
@@ -228,7 +234,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
dn=si_usd.name,
|
||||
recipient_id="saurabh@erpnext.com",
|
||||
mute_email=1,
|
||||
payment_gateway_account="_Test Gateway - USD",
|
||||
payment_gateway_account="_Test Gateway - USD - _TC",
|
||||
submit_doc=1,
|
||||
return_doc=1,
|
||||
)
|
||||
@@ -328,7 +334,7 @@ class TestPaymentRequest(FrappeTestCase):
|
||||
|
||||
self.assertEqual(pe.paid_amount, 800) # paid amount set from pr's outstanding amount
|
||||
self.assertEqual(pe.references[0].allocated_amount, 800)
|
||||
self.assertEqual(pe.references[0].outstanding_amount, 800) # for Orders it is not zero
|
||||
self.assertEqual(pe.references[0].outstanding_amount, 0) # Also for orders it will zero
|
||||
self.assertEqual(pe.references[0].payment_request, pr.name)
|
||||
|
||||
so.load_from_db()
|
||||
|
||||
@@ -75,6 +75,17 @@ class PeriodClosingVoucher(AccountsController):
|
||||
return
|
||||
|
||||
previous_fiscal_year_start_date = previous_fiscal_year[0][1]
|
||||
previous_fiscal_year_closed = frappe.db.exists(
|
||||
"Period Closing Voucher",
|
||||
{
|
||||
"period_end_date": ("between", [previous_fiscal_year_start_date, last_year_closing]),
|
||||
"docstatus": 1,
|
||||
"company": self.company,
|
||||
},
|
||||
)
|
||||
if previous_fiscal_year_closed:
|
||||
return
|
||||
|
||||
gle_exists_in_previous_year = frappe.db.exists(
|
||||
"GL Entry",
|
||||
{
|
||||
@@ -86,16 +97,7 @@ class PeriodClosingVoucher(AccountsController):
|
||||
if not gle_exists_in_previous_year:
|
||||
return
|
||||
|
||||
previous_fiscal_year_closed = frappe.db.exists(
|
||||
"Period Closing Voucher",
|
||||
{
|
||||
"period_end_date": ("between", [previous_fiscal_year_start_date, last_year_closing]),
|
||||
"docstatus": 1,
|
||||
"company": self.company,
|
||||
},
|
||||
)
|
||||
if not previous_fiscal_year_closed:
|
||||
frappe.throw(_("Previous Year is not closed, please close it first"))
|
||||
frappe.throw(_("Previous Year is not closed, please close it first"))
|
||||
|
||||
def block_if_future_closing_voucher_exists(self):
|
||||
future_closing_voucher = self.get_future_closing_voucher()
|
||||
|
||||
@@ -55,6 +55,16 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
});
|
||||
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
|
||||
if (this.frm.doc.pos_profile) {
|
||||
frappe.db
|
||||
.get_value("POS Profile", this.frm.doc.pos_profile, "disable_grand_total_to_default_mop")
|
||||
.then((r) => {
|
||||
if (!r.exc) {
|
||||
this.frm.skip_default_payment = r.message.disable_grand_total_to_default_mop;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onload_post_render(frm) {
|
||||
@@ -113,6 +123,7 @@ erpnext.selling.POSInvoiceController = class POSInvoiceController extends erpnex
|
||||
this.frm.meta.default_print_format = r.message.print_format || "";
|
||||
this.frm.doc.campaign = r.message.campaign;
|
||||
this.frm.allow_print_before_pay = r.message.allow_print_before_pay;
|
||||
this.frm.skip_default_payment = r.message.skip_default_payment;
|
||||
}
|
||||
this.frm.script_manager.trigger("update_stock");
|
||||
this.calculate_taxes_and_totals();
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
"items_section",
|
||||
"update_stock",
|
||||
"scan_barcode",
|
||||
"last_scanned_warehouse",
|
||||
"items",
|
||||
"pricing_rule_details",
|
||||
"pricing_rules",
|
||||
@@ -301,6 +302,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "Now",
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Posting Time",
|
||||
@@ -1568,12 +1570,19 @@
|
||||
"label": "Company Contact Person",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.last_scanned_warehouse",
|
||||
"fieldname": "last_scanned_warehouse",
|
||||
"fieldtype": "Data",
|
||||
"is_virtual": 1,
|
||||
"label": "Last Scanned Warehouse"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-07-17 16:51:40.886083",
|
||||
"modified": "2025-08-04 22:22:31.471752",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -216,6 +216,7 @@ class POSInvoice(SalesInvoice):
|
||||
self.validate_loyalty_transaction()
|
||||
self.validate_company_with_pos_company()
|
||||
self.validate_full_payment()
|
||||
self.update_packing_list()
|
||||
if self.coupon_code:
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import validate_coupon_code
|
||||
|
||||
@@ -367,9 +368,9 @@ class POSInvoice(SalesInvoice):
|
||||
)
|
||||
elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
|
||||
).format(d.idx, item_code, warehouse, available_stock),
|
||||
_("Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}.").format(
|
||||
d.idx, item_code, warehouse
|
||||
),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
|
||||
@@ -667,7 +668,13 @@ class POSInvoice(SalesInvoice):
|
||||
"Account", self.debit_to, "account_currency"
|
||||
)
|
||||
if not self.due_date and self.customer:
|
||||
self.due_date = get_due_date(self.posting_date, "Customer", self.customer, self.company)
|
||||
self.due_date = get_due_date(
|
||||
self.posting_date,
|
||||
"Customer",
|
||||
self.customer,
|
||||
self.company,
|
||||
template_name=self.payment_terms_template,
|
||||
)
|
||||
|
||||
super(SalesInvoice, self).set_missing_values(for_validate)
|
||||
|
||||
@@ -680,6 +687,7 @@ class POSInvoice(SalesInvoice):
|
||||
"print_format": print_format,
|
||||
"campaign": profile.get("campaign"),
|
||||
"allow_print_before_pay": profile.get("allow_print_before_pay"),
|
||||
"skip_default_payment": profile.get("disable_grand_total_to_default_mop"),
|
||||
}
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -774,10 +782,8 @@ def get_bundle_availability(bundle_item_code, warehouse):
|
||||
bundle_bin_qty = 1000000
|
||||
for item in product_bundle.items:
|
||||
item_bin_qty = get_bin_qty(item.item_code, warehouse)
|
||||
item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
|
||||
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||
|
||||
max_available_bundles = available_qty / item.qty
|
||||
max_available_bundles = item_bin_qty / item.qty
|
||||
if bundle_bin_qty > max_available_bundles and frappe.get_value(
|
||||
"Item", item.item_code, "is_stock_item"
|
||||
):
|
||||
@@ -800,13 +806,49 @@ def get_bin_qty(item_code, warehouse):
|
||||
|
||||
|
||||
def get_pos_reserved_qty(item_code, warehouse):
|
||||
"""
|
||||
Calculate total quantity reserved for the given item and warehouse.
|
||||
|
||||
Includes:
|
||||
- Direct sales of the item in submitted POS Invoices
|
||||
- Sales of the item as a component of a Product Bundle
|
||||
|
||||
Excludes consolidated invoices (already merged into Sales Invoices via
|
||||
POS Closing Entry). Used to reflect near real-time availability in the
|
||||
POS UI and to prevent overselling while multiple sessions may be active.
|
||||
"""
|
||||
pinv_item_reserved_qty = get_pos_reserved_qty_from_table("POS Invoice Item", item_code, warehouse)
|
||||
packed_item_reserved_qty = get_pos_reserved_qty_from_table("Packed Item", item_code, warehouse)
|
||||
|
||||
reserved_qty = pinv_item_reserved_qty + packed_item_reserved_qty
|
||||
|
||||
return reserved_qty
|
||||
|
||||
|
||||
def get_pos_reserved_qty_from_table(child_table, item_code, warehouse):
|
||||
"""
|
||||
Get the total reserved quantity for a given item in POS Invoices
|
||||
from a specific child table.
|
||||
|
||||
Args:
|
||||
child_table (str): Name of the child table to query
|
||||
(e.g., "POS Invoice Item", "Packed Item").
|
||||
item_code (str): The Item Code to filter by.
|
||||
warehouse (str): The Warehouse to filter by.
|
||||
|
||||
Returns:
|
||||
float: The total reserved quantity for the item in the given
|
||||
warehouse from submitted, unconsolidated POS Invoices.
|
||||
"""
|
||||
p_inv = frappe.qb.DocType("POS Invoice")
|
||||
p_item = frappe.qb.DocType("POS Invoice Item")
|
||||
p_item = frappe.qb.DocType(child_table)
|
||||
|
||||
qty_column = "qty" if child_table == "Packed Item" else "stock_qty"
|
||||
|
||||
reserved_qty = (
|
||||
frappe.qb.from_(p_inv)
|
||||
.from_(p_item)
|
||||
.select(Sum(p_item.stock_qty).as_("stock_qty"))
|
||||
.select(Sum(p_item[qty_column]).as_("stock_qty"))
|
||||
.where(
|
||||
(p_inv.name == p_item.parent)
|
||||
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
||||
|
||||
@@ -174,6 +174,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||
"fieldname": "is_cumulative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Cumulative"
|
||||
@@ -656,7 +657,7 @@
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2025-02-17 18:15:39.824639",
|
||||
"modified": "2025-08-20 11:40:07.096854",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
|
||||
@@ -583,11 +583,7 @@ def apply_pricing_rule_on_transaction(doc):
|
||||
if not d.get(pr_field):
|
||||
continue
|
||||
|
||||
if (
|
||||
d.validate_applied_rule
|
||||
and doc.get(field) is not None
|
||||
and doc.get(field) < d.get(pr_field)
|
||||
):
|
||||
if d.validate_applied_rule and (doc.get(field) or 0) < d.get(pr_field):
|
||||
frappe.msgprint(_("User has not applied rule on the invoice {0}").format(doc.name))
|
||||
else:
|
||||
if not d.coupon_code_based:
|
||||
|
||||
@@ -78,18 +78,18 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
|
||||
"depends_on": "eval:(!doc.enable_auto_email && doc.report == 'General Ledger');",
|
||||
"fieldname": "from_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "From Date",
|
||||
"mandatory_depends_on": "eval:doc.frequency == '';"
|
||||
"mandatory_depends_on": "eval:(!doc.enable_auto_email && doc.report == \"General Ledger\") "
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.enable_auto_email == 0 && doc.report == 'General Ledger');",
|
||||
"depends_on": "eval:(!doc.enable_auto_email && doc.report == 'General Ledger');",
|
||||
"fieldname": "to_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "To Date",
|
||||
"mandatory_depends_on": "eval:doc.frequency == '';"
|
||||
"mandatory_depends_on": "eval:(!doc.enable_auto_email && doc.report == \"General Ledger\") "
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
@@ -330,7 +330,8 @@
|
||||
"depends_on": "eval:(doc.report == 'Accounts Receivable');",
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Posting Date"
|
||||
"label": "Posting Date",
|
||||
"mandatory_depends_on": "eval:(doc.report == 'Accounts Receivable');"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: (doc.report == 'Accounts Receivable');",
|
||||
@@ -400,7 +401,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2025-07-08 16:52:12.602384",
|
||||
"modified": "2025-08-04 18:21:12.603623",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -550,7 +550,7 @@ def send_auto_email():
|
||||
selected = frappe.get_list(
|
||||
"Process Statement Of Accounts",
|
||||
filters={"enable_auto_email": 1},
|
||||
or_filters={"to_date": format_date(today()), "posting_date": format_date(today())},
|
||||
or_filters={"to_date": today(), "posting_date": today()},
|
||||
)
|
||||
for entry in selected:
|
||||
send_emails(entry.name, from_scheduler=True)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import getdate
|
||||
from frappe.utils import create_batch, getdate
|
||||
|
||||
from erpnext.accounts.doctype.subscription.subscription import DateTimeLikeObject, process_all
|
||||
|
||||
@@ -23,7 +23,23 @@ class ProcessSubscription(Document):
|
||||
# end: auto-generated types
|
||||
|
||||
def on_submit(self):
|
||||
process_all(subscription=self.subscription, posting_date=self.posting_date)
|
||||
self.process_all_subscription()
|
||||
|
||||
def process_all_subscription(self):
|
||||
filters = {"status": ("!=", "Cancelled")}
|
||||
|
||||
if self.subscription:
|
||||
filters["name"] = self.subscription
|
||||
|
||||
subscriptions = frappe.get_all("Subscription", filters, pluck="name")
|
||||
|
||||
for subscription in create_batch(subscriptions, 500):
|
||||
frappe.enqueue(
|
||||
method="erpnext.accounts.doctype.subscription.subscription.process_all",
|
||||
queue="long",
|
||||
subscription=subscription,
|
||||
posting_date=self.posting_date,
|
||||
)
|
||||
|
||||
|
||||
def create_subscription_process(
|
||||
|
||||
@@ -93,12 +93,14 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||
"fieldname": "mixed_conditions",
|
||||
"fieldtype": "Check",
|
||||
"label": "Mixed Conditions"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||
"fieldname": "is_cumulative",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Cumulative"
|
||||
@@ -278,7 +280,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-05-06 16:20:22.039078",
|
||||
"modified": "2025-08-20 11:48:23.231081",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Promotional Scheme",
|
||||
@@ -336,4 +338,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,10 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
this.frm.set_query("expense_account", "items", function () {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_expense_account",
|
||||
filters: { company: doc.company },
|
||||
filters: {
|
||||
company: doc.company,
|
||||
disabled: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -150,6 +153,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
per_billed: ["<", 99.99],
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
@@ -172,6 +178,9 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
company: me.frm.doc.company,
|
||||
is_return: 0,
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
@@ -635,7 +644,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
},
|
||||
|
||||
add_custom_buttons: function (frm) {
|
||||
if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) {
|
||||
if (frm.doc.docstatus == 1 && frm.doc.per_received < 100 && frm.doc.update_stock == 0) {
|
||||
frm.add_custom_button(
|
||||
__("Purchase Receipt"),
|
||||
() => {
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"ignore_pricing_rule",
|
||||
"sec_warehouse",
|
||||
"scan_barcode",
|
||||
"last_scanned_warehouse",
|
||||
"col_break_warehouse",
|
||||
"update_stock",
|
||||
"set_warehouse",
|
||||
@@ -319,6 +320,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "Now",
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Posting Time",
|
||||
@@ -1643,6 +1645,13 @@
|
||||
"label": "Select Dispatch Address ",
|
||||
"options": "Address",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.last_scanned_warehouse",
|
||||
"fieldname": "last_scanned_warehouse",
|
||||
"fieldtype": "Data",
|
||||
"is_virtual": 1,
|
||||
"label": "Last Scanned Warehouse"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1650,7 +1659,7 @@
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-04-09 16:49:22.175081",
|
||||
"modified": "2025-08-04 19:19:11.380664",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb, throw
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@@ -338,7 +340,12 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
if not self.due_date:
|
||||
self.due_date = get_due_date(
|
||||
self.posting_date, "Supplier", self.supplier, self.company, self.bill_date
|
||||
self.posting_date,
|
||||
"Supplier",
|
||||
self.supplier,
|
||||
self.company,
|
||||
self.bill_date,
|
||||
template_name=self.payment_terms_template,
|
||||
)
|
||||
|
||||
tds_category = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
|
||||
@@ -2070,7 +2077,12 @@ def make_inter_company_sales_invoice(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None):
|
||||
def make_purchase_receipt(source_name, target_doc=None, args=None):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
def update_item(obj, target, source_parent):
|
||||
target.qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
target.received_qty = flt(obj.qty) - flt(obj.received_qty)
|
||||
@@ -2080,6 +2092,11 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
(flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) * flt(source_parent.conversion_rate)
|
||||
)
|
||||
|
||||
def select_item(d):
|
||||
filtered_items = args.get("filtered_children", [])
|
||||
child_filter = d.name in filtered_items if filtered_items else True
|
||||
return child_filter
|
||||
|
||||
doc = get_mapped_doc(
|
||||
"Purchase Invoice",
|
||||
source_name,
|
||||
@@ -2103,7 +2120,7 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty),
|
||||
"condition": lambda doc: abs(doc.received_qty) < abs(doc.qty) and select_item(doc),
|
||||
},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges"},
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.desk.form.linked_with import get_child_tables_of_doctypes
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils.data import comma_and
|
||||
|
||||
@@ -169,11 +170,15 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
frappe.db.delete(
|
||||
"Payment Ledger Entry", filters={"voucher_type": doc.doctype, "voucher_no": doc.name}
|
||||
)
|
||||
frappe.db.delete(
|
||||
"Advance Payment Ledger Entry",
|
||||
filters={"voucher_type": doc.doctype, "voucher_no": doc.name},
|
||||
)
|
||||
|
||||
if doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
if not repost_doc.delete_cancelled_entries:
|
||||
doc.docstatus = 2
|
||||
doc.make_gl_entries_on_cancel()
|
||||
doc.make_gl_entries_on_cancel(from_repost=True)
|
||||
|
||||
doc.docstatus = 1
|
||||
if doc.doctype == "Sales Invoice":
|
||||
@@ -185,7 +190,7 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
elif doc.doctype == "Purchase Receipt":
|
||||
if not repost_doc.delete_cancelled_entries:
|
||||
doc.docstatus = 2
|
||||
doc.make_gl_entries_on_cancel()
|
||||
doc.make_gl_entries_on_cancel(from_repost=True)
|
||||
|
||||
doc.docstatus = 1
|
||||
doc.make_gl_entries(from_repost=True)
|
||||
@@ -204,13 +209,29 @@ def start_repost(account_repost_doc=str) -> None:
|
||||
doc.make_gl_entries()
|
||||
|
||||
|
||||
def get_allowed_types_from_settings():
|
||||
return [
|
||||
def get_allowed_types_from_settings(child_doc: bool = False):
|
||||
repost_docs = [
|
||||
x.document_type
|
||||
for x in frappe.db.get_all(
|
||||
"Repost Allowed Types", filters={"allowed": True}, fields=["distinct(document_type)"]
|
||||
)
|
||||
]
|
||||
result = repost_docs
|
||||
|
||||
if repost_docs and child_doc:
|
||||
result.extend(get_child_docs(repost_docs))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_child_docs(doc: list) -> list:
|
||||
child_doc = []
|
||||
doc = get_child_tables_of_doctypes(doc)
|
||||
for child_list in doc.values():
|
||||
for child in child_list:
|
||||
if child.get("child_table"):
|
||||
child_doc.append(child["child_table"])
|
||||
return child_doc
|
||||
|
||||
|
||||
def validate_docs_for_deferred_accounting(sales_docs, purchase_docs):
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
)
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs
|
||||
|
||||
|
||||
class RepostAccountingLedgerSettings(Document):
|
||||
# begin: auto-generated types
|
||||
@@ -17,6 +22,24 @@ class RepostAccountingLedgerSettings(Document):
|
||||
from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes
|
||||
|
||||
allowed_types: DF.Table[RepostAllowedTypes]
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
# end: auto-generated types
|
||||
def validate(self):
|
||||
self.update_property_for_accounting_dimension()
|
||||
|
||||
def update_property_for_accounting_dimension(self):
|
||||
doctypes = [entry.document_type for entry in self.allowed_types if entry.allowed]
|
||||
if not doctypes:
|
||||
return
|
||||
doctypes += get_child_docs(doctypes)
|
||||
|
||||
set_allow_on_submit_for_dimension_fields(doctypes)
|
||||
|
||||
|
||||
def set_allow_on_submit_for_dimension_fields(doctypes):
|
||||
for dt in doctypes:
|
||||
meta = frappe.get_meta(dt)
|
||||
for dimension in get_accounting_dimensions():
|
||||
df = meta.get_field(dimension)
|
||||
if df and not df.allow_on_submit:
|
||||
frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)
|
||||
|
||||
@@ -8,7 +8,7 @@ from frappe import _, qb
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
|
||||
from erpnext.accounts.utils import _delete_pl_entries, create_payment_ledger_entry
|
||||
from erpnext.accounts.utils import _delete_adv_pl_entries, _delete_pl_entries, create_payment_ledger_entry
|
||||
|
||||
VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
|
||||
|
||||
@@ -16,6 +16,7 @@ VOUCHER_TYPES = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal
|
||||
def repost_ple_for_voucher(voucher_type, voucher_no, gle_map=None):
|
||||
if voucher_type and voucher_no and gle_map:
|
||||
_delete_pl_entries(voucher_type, voucher_no)
|
||||
_delete_adv_pl_entries(voucher_type, voucher_no)
|
||||
create_payment_ledger_entry(gle_map, cancel=0)
|
||||
|
||||
|
||||
|
||||
@@ -58,6 +58,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
|
||||
me.frm.script_manager.trigger("is_pos");
|
||||
me.frm.refresh_fields();
|
||||
frappe.db
|
||||
.get_value("POS Profile", this.frm.doc.pos_profile, "disable_grand_total_to_default_mop")
|
||||
.then((r) => {
|
||||
if (!r.exc) {
|
||||
me.frm.skip_default_payment = r.message.disable_grand_total_to_default_mop;
|
||||
}
|
||||
});
|
||||
}
|
||||
erpnext.queries.setup_warehouse_query(this.frm);
|
||||
}
|
||||
@@ -259,6 +266,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
per_billed: ["<", 99.99],
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
@@ -288,6 +298,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
status: ["!=", "Lost"],
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "item_name", "qty", "rate", "amount"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
@@ -319,6 +332,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
filters: filters,
|
||||
};
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "item_name", "qty", "amount", "billed_amt"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
@@ -497,8 +513,9 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends (
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
if (r.message && r.message.print_format) {
|
||||
if (r.message) {
|
||||
me.frm.pos_print_format = r.message.print_format;
|
||||
me.frm.skip_default_payment = r.message.skip_default_payment;
|
||||
}
|
||||
me.frm.trigger("update_stock");
|
||||
if (me.frm.doc.taxes_and_charges) {
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
"items_section",
|
||||
"scan_barcode",
|
||||
"update_stock",
|
||||
"last_scanned_warehouse",
|
||||
"column_break_39",
|
||||
"set_warehouse",
|
||||
"set_target_warehouse",
|
||||
@@ -379,6 +380,7 @@
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "Now",
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"hide_days": 1,
|
||||
@@ -2176,6 +2178,13 @@
|
||||
"label": "Company Contact Person",
|
||||
"options": "Contact",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.last_scanned_warehouse",
|
||||
"fieldname": "last_scanned_warehouse",
|
||||
"fieldtype": "Data",
|
||||
"is_virtual": 1,
|
||||
"label": "Last Scanned Warehouse"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -2189,7 +2198,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2025-06-26 14:06:56.773552",
|
||||
"modified": "2025-08-04 19:20:28.732039",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -700,6 +700,7 @@ class SalesInvoice(SellingController):
|
||||
"allow_edit_discount": pos.get("allow_user_to_edit_discount"),
|
||||
"campaign": pos.get("campaign"),
|
||||
"allow_print_before_pay": pos.get("allow_print_before_pay"),
|
||||
"skip_default_payment": pos.get("disable_grand_total_to_default_mop"),
|
||||
}
|
||||
|
||||
def update_time_sheet(self, sales_invoice):
|
||||
@@ -1206,7 +1207,6 @@ class SalesInvoice(SellingController):
|
||||
|
||||
self.make_exchange_gain_loss_journal()
|
||||
elif self.docstatus == 2:
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||
|
||||
if update_outstanding == "No":
|
||||
@@ -1370,8 +1370,9 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
asset.db_set("disposal_date", None)
|
||||
add_asset_activity(asset.name, _("Asset returned"))
|
||||
asset_status = asset.get_status()
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
if asset.calculate_depreciation and not asset_status == "Fully Depreciated":
|
||||
posting_date = (
|
||||
frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||
if self.is_return
|
||||
|
||||
@@ -8,7 +8,7 @@ import frappe
|
||||
from frappe import qb
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, format_date, getdate, nowdate, today
|
||||
from frappe.utils import add_days, cint, flt, format_date, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
|
||||
@@ -4575,6 +4575,152 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
self.assertEqual(stock_ledger_entry.qty, 2.0)
|
||||
self.assertEqual(stock_ledger_entry.stock_value_difference, 0.0)
|
||||
|
||||
def test_non_batchwise_valuation_for_moving_average(self):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
item_code = "_Test Item for Non Batchwise Valuation"
|
||||
make_item_for_si(
|
||||
item_code,
|
||||
{
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TBATCH-TCNV.####",
|
||||
"valuation_method": "Moving Average",
|
||||
},
|
||||
)
|
||||
|
||||
doc = frappe.get_doc("Stock Settings")
|
||||
original_value = cint(doc.do_not_use_batchwise_valuation)
|
||||
|
||||
doc.db_set("do_not_use_batchwise_valuation", 1)
|
||||
|
||||
se = make_stock_entry(
|
||||
item_code=item_code,
|
||||
qty=10,
|
||||
target="_Test Warehouse - _TC",
|
||||
rate=13.02,
|
||||
valuation_method="Moving Average",
|
||||
use_serial_batch_fields=True,
|
||||
)
|
||||
|
||||
se_batch = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)
|
||||
|
||||
# without use serial and batch fields
|
||||
si = create_sales_invoice(
|
||||
item=item_code,
|
||||
qty=1,
|
||||
rate=120,
|
||||
update_stock=1,
|
||||
use_serial_batch_fields=False,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
si.reload()
|
||||
si_batch = get_batch_from_bundle(si.items[0].serial_and_batch_bundle)
|
||||
|
||||
self.assertEqual(se_batch, si_batch)
|
||||
self.assertEqual(si.items[0].use_serial_batch_fields, 0)
|
||||
|
||||
serial_and_batch_bundle = si.items[0].serial_and_batch_bundle
|
||||
change_in_value = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si.name,
|
||||
"item_code": item_code,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"serial_and_batch_bundle": serial_and_batch_bundle,
|
||||
},
|
||||
"stock_value_difference",
|
||||
)
|
||||
|
||||
self.assertEqual(change_in_value, 13.02 * -1)
|
||||
|
||||
# with use serial and batch fields
|
||||
si = create_sales_invoice(
|
||||
item=item_code,
|
||||
qty=1,
|
||||
rate=120,
|
||||
update_stock=1,
|
||||
use_serial_batch_fields=True,
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
)
|
||||
|
||||
si.reload()
|
||||
|
||||
self.assertEqual(si.items[0].use_serial_batch_fields, 1)
|
||||
|
||||
serial_and_batch_bundle = si.items[0].serial_and_batch_bundle
|
||||
change_in_value = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si.name,
|
||||
"item_code": item_code,
|
||||
"warehouse": "_Test Warehouse - _TC",
|
||||
"serial_and_batch_bundle": serial_and_batch_bundle,
|
||||
},
|
||||
"stock_value_difference",
|
||||
)
|
||||
|
||||
self.assertEqual(change_in_value, 13.02 * -1)
|
||||
|
||||
doc.db_set("do_not_use_batchwise_valuation", original_value)
|
||||
|
||||
def test_system_generated_exchange_gain_or_loss_je_after_repost(self):
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
|
||||
update_repost_settings,
|
||||
)
|
||||
|
||||
update_repost_settings()
|
||||
|
||||
si = create_sales_invoice(
|
||||
customer="_Test Customer USD",
|
||||
debit_to="_Test Receivable USD - _TC",
|
||||
currency="USD",
|
||||
conversion_rate=80,
|
||||
)
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name)
|
||||
pe.reference_no = "10"
|
||||
pe.reference_date = nowdate()
|
||||
pe.paid_from_account_currency = si.currency
|
||||
pe.paid_to_account_currency = "INR"
|
||||
pe.source_exchange_rate = 85
|
||||
pe.target_exchange_rate = 1
|
||||
pe.paid_amount = si.outstanding_amount
|
||||
pe.insert()
|
||||
pe.submit()
|
||||
|
||||
ral = frappe.new_doc("Repost Accounting Ledger")
|
||||
ral.company = si.company
|
||||
ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
|
||||
ral.save()
|
||||
ral.submit()
|
||||
|
||||
je = frappe.qb.DocType("Journal Entry")
|
||||
jea = frappe.qb.DocType("Journal Entry Account")
|
||||
q = (
|
||||
(
|
||||
frappe.qb.from_(je)
|
||||
.join(jea)
|
||||
.on(je.name == jea.parent)
|
||||
.select(je.docstatus)
|
||||
.where(
|
||||
(je.voucher_type == "Exchange Gain Or Loss")
|
||||
& (jea.reference_name == si.name)
|
||||
& (jea.reference_type == "Sales Invoice")
|
||||
& (je.is_system_generated == 1)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.run()
|
||||
)
|
||||
|
||||
self.assertEqual(q[0][0], 1)
|
||||
|
||||
|
||||
def make_item_for_si(item_code, properties=None):
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
@@ -4690,6 +4836,7 @@ def create_sales_invoice(**args):
|
||||
"incoming_rate": args.incoming_rate or 0,
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
"allow_zero_valuation_rate": args.allow_zero_valuation_rate or 0,
|
||||
"use_serial_batch_fields": args.use_serial_batch_fields or 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -756,18 +756,14 @@ def get_prorata_factor(
|
||||
return diff / plan_days
|
||||
|
||||
|
||||
def process_all(subscription: str | None = None, posting_date: DateTimeLikeObject | None = None) -> None:
|
||||
def process_all(subscription: list, posting_date: DateTimeLikeObject | None = None) -> None:
|
||||
"""
|
||||
Task to updates the status of all `Subscription` apart from those that are cancelled
|
||||
"""
|
||||
filters = {"status": ("!=", "Cancelled")}
|
||||
|
||||
if subscription:
|
||||
filters["name"] = subscription
|
||||
|
||||
for subscription in frappe.get_all("Subscription", filters, pluck="name"):
|
||||
for subscription_name in subscription:
|
||||
try:
|
||||
subscription = frappe.get_doc("Subscription", subscription)
|
||||
subscription = frappe.get_doc("Subscription", subscription_name)
|
||||
subscription.process(posting_date)
|
||||
frappe.db.commit()
|
||||
except frappe.ValidationError:
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Even invoices with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
|
||||
"description": "Only payment entries with apply tax withholding unchecked will be considered for checking cumulative threshold breach",
|
||||
"fieldname": "consider_party_ledger_amount",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consider Entire Party Ledger Amount",
|
||||
@@ -102,10 +102,11 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-07-27 21:47:34.396071",
|
||||
"modified": "2025-07-30 07:13:51.785735",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withholding Category",
|
||||
"naming_rule": "Set by user",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -148,4 +149,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -465,10 +465,26 @@ class TestUnreconcilePayment(AccountsTestMixin, FrappeTestCase):
|
||||
self.assertEqual(len(pr.get("invoices")), 0)
|
||||
self.assertEqual(len(pr.get("payments")), 0)
|
||||
|
||||
# Assert 'Advance Paid'
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, 1000)
|
||||
|
||||
unreconcile = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Unreconcile Payment",
|
||||
"company": self.company,
|
||||
"voucher_type": pe.doctype,
|
||||
"voucher_no": pe.name,
|
||||
}
|
||||
)
|
||||
unreconcile.add_references()
|
||||
unreconcile.allocations = [x for x in unreconcile.allocations if x.reference_name == si.name]
|
||||
unreconcile.save().submit()
|
||||
|
||||
# after unreconcilaition advance paid will be reduced
|
||||
# Assert 'Advance Paid'
|
||||
so.reload()
|
||||
self.assertEqual(so.advance_paid, 0)
|
||||
|
||||
self.disable_advance_as_liability()
|
||||
|
||||
def test_unreconcile_advance_from_journal_entry(self):
|
||||
|
||||
@@ -12,7 +12,6 @@ from frappe.utils.data import comma_and
|
||||
|
||||
from erpnext.accounts.utils import (
|
||||
cancel_exchange_gain_loss_journal,
|
||||
get_advance_payment_doctypes,
|
||||
unlink_ref_doc_from_payment_entries,
|
||||
update_voucher_outstanding,
|
||||
)
|
||||
@@ -45,31 +44,12 @@ class UnreconcilePayment(Document):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_allocations_from_payment(self):
|
||||
allocated_references = []
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
allocated_references = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.account,
|
||||
ple.party_type,
|
||||
ple.party,
|
||||
ple.against_voucher_type.as_("reference_doctype"),
|
||||
ple.against_voucher_no.as_("reference_name"),
|
||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
||||
ple.account_currency,
|
||||
)
|
||||
.where(
|
||||
(ple.docstatus == 1)
|
||||
& (ple.voucher_type == self.voucher_type)
|
||||
& (ple.voucher_no == self.voucher_no)
|
||||
& (ple.voucher_no != ple.against_voucher_no)
|
||||
)
|
||||
.groupby(ple.against_voucher_type, ple.against_voucher_no)
|
||||
.run(as_dict=True)
|
||||
return get_linked_payments_for_doc(
|
||||
company=self.company,
|
||||
doctype=self.voucher_type,
|
||||
docname=self.voucher_no,
|
||||
)
|
||||
|
||||
return allocated_references
|
||||
|
||||
def add_references(self):
|
||||
allocations = self.get_allocations_from_payment()
|
||||
|
||||
@@ -82,42 +62,43 @@ class UnreconcilePayment(Document):
|
||||
doc = frappe.get_doc(alloc.reference_doctype, alloc.reference_name)
|
||||
unlink_ref_doc_from_payment_entries(doc, self.voucher_no)
|
||||
cancel_exchange_gain_loss_journal(doc, self.voucher_type, self.voucher_no)
|
||||
|
||||
# update outstanding amounts
|
||||
update_voucher_outstanding(
|
||||
alloc.reference_doctype, alloc.reference_name, alloc.account, alloc.party_type, alloc.party
|
||||
alloc.reference_doctype,
|
||||
alloc.reference_name,
|
||||
alloc.account,
|
||||
alloc.party_type,
|
||||
alloc.party,
|
||||
)
|
||||
if doc.doctype in get_advance_payment_doctypes():
|
||||
self.make_advance_payment_ledger(alloc)
|
||||
doc.set_total_advance_paid()
|
||||
|
||||
frappe.db.set_value("Unreconcile Payment Entries", alloc.name, "unlinked", True)
|
||||
|
||||
def make_advance_payment_ledger(self, alloc):
|
||||
if alloc.allocated_amount > 0:
|
||||
doc = frappe.new_doc("Advance Payment Ledger Entry")
|
||||
doc.company = self.company
|
||||
doc.voucher_type = self.voucher_type
|
||||
doc.voucher_no = self.voucher_no
|
||||
doc.against_voucher_type = alloc.reference_doctype
|
||||
doc.against_voucher_no = alloc.reference_name
|
||||
doc.amount = -1 * alloc.allocated_amount
|
||||
doc.event = "Unreconcile"
|
||||
doc.currency = alloc.account_currency
|
||||
doc.flags.ignore_permissions = 1
|
||||
doc.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def doc_has_references(doctype: str | None = None, docname: str | None = None):
|
||||
count = 0
|
||||
if doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
return frappe.db.count(
|
||||
count = frappe.db.count(
|
||||
"Payment Ledger Entry",
|
||||
filters={"delinked": 0, "against_voucher_no": docname, "amount": ["<", 0]},
|
||||
)
|
||||
else:
|
||||
return frappe.db.count(
|
||||
count = frappe.db.count(
|
||||
"Payment Ledger Entry",
|
||||
filters={"delinked": 0, "voucher_no": docname, "against_voucher_no": ["!=", docname]},
|
||||
)
|
||||
count += frappe.db.count(
|
||||
"Advance Payment Ledger Entry",
|
||||
filters={
|
||||
"delinked": 0,
|
||||
"voucher_no": docname,
|
||||
"voucher_type": doctype,
|
||||
"event": ["=", "Submit"],
|
||||
},
|
||||
)
|
||||
|
||||
return count
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -139,9 +120,12 @@ def get_linked_payments_for_doc(
|
||||
res = (
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.account,
|
||||
ple.party_type,
|
||||
ple.party,
|
||||
ple.company,
|
||||
ple.voucher_type,
|
||||
ple.voucher_no,
|
||||
ple.voucher_type.as_("reference_doctype"),
|
||||
ple.voucher_no.as_("reference_name"),
|
||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
||||
ple.account_currency,
|
||||
)
|
||||
@@ -163,19 +147,52 @@ def get_linked_payments_for_doc(
|
||||
qb.from_(ple)
|
||||
.select(
|
||||
ple.company,
|
||||
ple.against_voucher_type.as_("voucher_type"),
|
||||
ple.against_voucher_no.as_("voucher_no"),
|
||||
ple.account,
|
||||
ple.party_type,
|
||||
ple.party,
|
||||
ple.against_voucher_type.as_("reference_doctype"),
|
||||
ple.against_voucher_no.as_("reference_name"),
|
||||
Abs(Sum(ple.amount_in_account_currency)).as_("allocated_amount"),
|
||||
ple.account_currency,
|
||||
)
|
||||
.where(Criterion.all(criteria))
|
||||
.groupby(ple.against_voucher_no)
|
||||
)
|
||||
|
||||
res = query.run(as_dict=True)
|
||||
|
||||
res += get_linked_advances(company, _dn)
|
||||
|
||||
return res
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def get_linked_advances(company, docname):
|
||||
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||
criteria = [
|
||||
(adv.company == company),
|
||||
(adv.delinked == 0),
|
||||
(adv.voucher_no == docname),
|
||||
(adv.event == "Submit"),
|
||||
]
|
||||
|
||||
return (
|
||||
qb.from_(adv)
|
||||
.select(
|
||||
adv.company,
|
||||
adv.against_voucher_type.as_("reference_doctype"),
|
||||
adv.against_voucher_no.as_("reference_name"),
|
||||
Abs(Sum(adv.amount)).as_("allocated_amount"),
|
||||
adv.currency,
|
||||
)
|
||||
.where(Criterion.all(criteria))
|
||||
.having(qb.Field("allocated_amount") > 0)
|
||||
.groupby(adv.against_voucher_no)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_unreconcile_doc_for_selection(selections=None):
|
||||
if selections:
|
||||
|
||||
@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.accounting_dimension_filter.accounting_dimension_f
|
||||
)
|
||||
from erpnext.accounts.doctype.accounting_period.accounting_period import ClosedAccountingPeriod
|
||||
from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
|
||||
from erpnext.accounts.utils import create_payment_ledger_entry
|
||||
from erpnext.accounts.utils import create_payment_ledger_entry, is_immutable_ledger_enabled
|
||||
from erpnext.exceptions import InvalidAccountDimensionError, MandatoryAccountDimensionError
|
||||
|
||||
|
||||
@@ -186,6 +186,15 @@ def process_gl_map(gl_map, merge_entries=True, precision=None, from_repost=False
|
||||
|
||||
|
||||
def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_repost=False):
|
||||
round_off_account, default_currency = frappe.get_cached_value(
|
||||
"Company", gl_map[0].company, ["round_off_account", "default_currency"]
|
||||
)
|
||||
if not precision:
|
||||
precision = get_field_precision(
|
||||
frappe.get_meta("GL Entry").get_field("debit"),
|
||||
currency=default_currency,
|
||||
)
|
||||
|
||||
new_gl_map = []
|
||||
for d in gl_map:
|
||||
cost_center = d.get("cost_center")
|
||||
@@ -203,6 +212,11 @@ def distribute_gl_based_on_cost_center_allocation(gl_map, precision=None, from_r
|
||||
new_gl_map.append(d)
|
||||
continue
|
||||
|
||||
if d.account == round_off_account:
|
||||
d.cost_center = cost_center_allocation[0][0]
|
||||
new_gl_map.append(d)
|
||||
continue
|
||||
|
||||
for sub_cost_center, percentage in cost_center_allocation:
|
||||
gle = copy.deepcopy(d)
|
||||
gle.cost_center = sub_cost_center
|
||||
@@ -305,6 +319,8 @@ def get_merge_properties(dimensions=None):
|
||||
"project",
|
||||
"finance_book",
|
||||
"voucher_no",
|
||||
"advance_voucher_type",
|
||||
"advance_voucher_no",
|
||||
]
|
||||
if dimensions:
|
||||
merge_properties.extend(dimensions)
|
||||
@@ -822,7 +838,3 @@ def validate_allowed_dimensions(gl_entry, dimension_filter_map):
|
||||
),
|
||||
InvalidAccountDimensionError,
|
||||
)
|
||||
|
||||
|
||||
def is_immutable_ledger_enabled():
|
||||
return frappe.db.get_single_value("Accounts Settings", "enable_immutable_ledger")
|
||||
|
||||
@@ -25,17 +25,25 @@ def get_group_by_asset_category_data(filters):
|
||||
|
||||
asset_categories = get_asset_categories_for_grouped_by_category(filters)
|
||||
assets = get_assets_for_grouped_by_category(filters)
|
||||
asset_value_adjustment_map = get_asset_value_adjustment_map_by_category(filters)
|
||||
|
||||
for asset_category in asset_categories:
|
||||
row = frappe._dict()
|
||||
row.update(asset_category)
|
||||
|
||||
adjustments = asset_value_adjustment_map.get(asset_category.get("asset_category"), {})
|
||||
row.adjustment_before_from_date = flt(adjustments.get("adjustment_before_from_date", 0))
|
||||
row.adjustment_till_to_date = flt(adjustments.get("adjustment_till_to_date", 0))
|
||||
row.adjustment_during_period = row.adjustment_till_to_date - row.adjustment_before_from_date
|
||||
|
||||
row.value_as_on_from_date += row.adjustment_before_from_date
|
||||
row.value_as_on_to_date = (
|
||||
flt(row.value_as_on_from_date)
|
||||
+ flt(row.value_of_new_purchase)
|
||||
- flt(row.value_of_sold_asset)
|
||||
- flt(row.value_of_scrapped_asset)
|
||||
- flt(row.value_of_capitalized_asset)
|
||||
+ flt(row.adjustment_during_period)
|
||||
)
|
||||
|
||||
row.update(
|
||||
@@ -229,26 +237,93 @@ def get_assets_for_grouped_by_category(filters):
|
||||
)
|
||||
|
||||
|
||||
def get_asset_value_adjustment_map_by_category(filters):
|
||||
asset_value_adjustments = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.asset_category AS asset_category,
|
||||
IFNULL(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN gle.posting_date < %(from_date)s
|
||||
AND (a.disposal_date IS NULL OR a.disposal_date >= %(from_date)s)
|
||||
THEN gle.debit - gle.credit
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0) AS value_adjustment_before_from_date,
|
||||
IFNULL(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN gle.posting_date <= %(to_date)s
|
||||
AND (a.disposal_date IS NULL OR a.disposal_date >= %(to_date)s)
|
||||
THEN gle.debit - gle.credit
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0) AS value_adjustment_till_to_date
|
||||
|
||||
FROM `tabGL Entry` gle
|
||||
JOIN `tabAsset` a ON gle.against_voucher = a.name
|
||||
JOIN `tabAsset Category Account` aca
|
||||
ON aca.parent = a.asset_category
|
||||
AND aca.company_name = %(company)s
|
||||
WHERE gle.is_cancelled = 0
|
||||
AND a.docstatus = 1
|
||||
AND a.company = %(company)s
|
||||
AND a.purchase_date <= %(to_date)s
|
||||
AND gle.account = aca.fixed_asset_account
|
||||
GROUP BY a.asset_category
|
||||
""",
|
||||
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
category_value_adjustment_map = {}
|
||||
|
||||
for r in asset_value_adjustments:
|
||||
category_value_adjustment_map[r["asset_category"]] = {
|
||||
"adjustment_before_from_date": flt(r.get("value_adjustment_before_from_date", 0)),
|
||||
"adjustment_till_to_date": flt(r.get("value_adjustment_till_to_date", 0)),
|
||||
}
|
||||
|
||||
return category_value_adjustment_map
|
||||
|
||||
|
||||
def get_group_by_asset_data(filters):
|
||||
data = []
|
||||
|
||||
asset_details = get_asset_details_for_grouped_by_category(filters)
|
||||
assets = get_assets_for_grouped_by_asset(filters)
|
||||
asset_value_adjustment_map = get_asset_value_adjustment_map(filters)
|
||||
|
||||
for asset_detail in asset_details:
|
||||
row = frappe._dict()
|
||||
row.update(asset_detail)
|
||||
|
||||
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
|
||||
adjustments = asset_value_adjustment_map.get(
|
||||
asset_detail.get("name", ""),
|
||||
{
|
||||
"adjustment_before_from_date": 0.0,
|
||||
"adjustment_till_to_date": 0.0,
|
||||
},
|
||||
)
|
||||
row.adjustment_before_from_date = adjustments["adjustment_before_from_date"]
|
||||
row.adjustment_till_to_date = adjustments["adjustment_till_to_date"]
|
||||
row.adjustment_during_period = flt(row.adjustment_till_to_date) - flt(row.adjustment_before_from_date)
|
||||
|
||||
row.value_as_on_from_date += row.adjustment_before_from_date
|
||||
|
||||
row.value_as_on_to_date = (
|
||||
flt(row.value_as_on_from_date)
|
||||
+ flt(row.value_of_new_purchase)
|
||||
- flt(row.value_of_sold_asset)
|
||||
- flt(row.value_of_scrapped_asset)
|
||||
- flt(row.value_of_capitalized_asset)
|
||||
+ flt(row.adjustment_during_period)
|
||||
)
|
||||
|
||||
row.update(next(asset for asset in assets if asset["asset"] == asset_detail.get("name", "")))
|
||||
|
||||
row.accumulated_depreciation_as_on_to_date = (
|
||||
flt(row.accumulated_depreciation_as_on_from_date)
|
||||
+ flt(row.depreciation_amount_during_the_period)
|
||||
@@ -432,6 +507,59 @@ def get_assets_for_grouped_by_asset(filters):
|
||||
)
|
||||
|
||||
|
||||
def get_asset_value_adjustment_map(filters):
|
||||
asset_with_value_adjustments = frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
a.name AS asset,
|
||||
IFNULL(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN gle.posting_date < %(from_date)s
|
||||
AND (a.disposal_date IS NULL OR a.disposal_date >= %(from_date)s)
|
||||
THEN gle.debit - gle.credit
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0) AS value_adjustment_before_from_date,
|
||||
IFNULL(
|
||||
SUM(
|
||||
CASE
|
||||
WHEN gle.posting_date <= %(to_date)s
|
||||
AND (a.disposal_date IS NULL OR a.disposal_date >= %(to_date)s)
|
||||
THEN gle.debit - gle.credit
|
||||
ELSE 0
|
||||
END
|
||||
),
|
||||
0) AS value_adjustment_till_to_date
|
||||
|
||||
FROM `tabGL Entry` gle
|
||||
JOIN `tabAsset` a ON gle.against_voucher = a.name
|
||||
JOIN `tabAsset Category Account` aca
|
||||
ON aca.parent = a.asset_category
|
||||
AND aca.company_name = %(company)s
|
||||
WHERE gle.is_cancelled = 0
|
||||
AND a.docstatus = 1
|
||||
AND a.company = %(company)s
|
||||
AND a.purchase_date <= %(to_date)s
|
||||
AND gle.account = aca.fixed_asset_account
|
||||
GROUP BY a.name
|
||||
""",
|
||||
{"from_date": filters.from_date, "to_date": filters.to_date, "company": filters.company},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
asset_value_adjustment_map = {}
|
||||
|
||||
for r in asset_with_value_adjustments:
|
||||
asset_value_adjustment_map[r["asset"]] = {
|
||||
"adjustment_before_from_date": flt(r.get("value_adjustment_before_from_date", 0)),
|
||||
"adjustment_till_to_date": flt(r.get("value_adjustment_till_to_date", 0)),
|
||||
}
|
||||
|
||||
return asset_value_adjustment_map
|
||||
|
||||
|
||||
def get_columns(filters):
|
||||
columns = []
|
||||
|
||||
|
||||
@@ -532,6 +532,7 @@ def get_accounting_entries(
|
||||
query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year)
|
||||
query = query.where(gl_entry.is_cancelled == 0)
|
||||
query = query.where(gl_entry.posting_date <= to_date)
|
||||
query = query.force_index("posting_date_company_index")
|
||||
|
||||
if ignore_opening_entries and not ignore_is_opening:
|
||||
query = query.where(gl_entry.is_opening == "No")
|
||||
|
||||
@@ -197,6 +197,11 @@ frappe.query_reports["General Ledger"] = {
|
||||
label: __("Show Net Values in Party Account"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
{
|
||||
fieldname: "show_amount_in_company_currency",
|
||||
label: __("Show Credit / Debit in Company Currency"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
{
|
||||
fieldname: "add_values_in_transaction_currency",
|
||||
label: __("Add Columns in Transaction Currency"),
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2013-12-06 13:22:23",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:17:51.995451",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "General Ledger",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "General Ledger",
|
||||
"report_type": "Script Report",
|
||||
"add_total_row": 1,
|
||||
"add_translate_data": 0,
|
||||
"columns": [],
|
||||
"creation": "2013-12-06 13:22:23",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 3,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2025-08-13 12:47:27.645023",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "General Ledger",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "General Ledger",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
},
|
||||
{
|
||||
"role": "Auditor"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"timeout": 0
|
||||
}
|
||||
|
||||
@@ -203,6 +203,12 @@ def get_gl_entries(filters, accounting_dimensions):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
party_name_map = get_party_name_map()
|
||||
|
||||
for gl_entry in gl_entries:
|
||||
if gl_entry.party_type and gl_entry.party:
|
||||
gl_entry.party_name = party_name_map.get(gl_entry.party_type, {}).get(gl_entry.party)
|
||||
|
||||
if filters.get("presentation_currency"):
|
||||
return convert_to_presentation_currency(gl_entries, currency_map, filters)
|
||||
else:
|
||||
@@ -337,6 +343,20 @@ def get_conditions(filters):
|
||||
return "and {}".format(" and ".join(conditions)) if conditions else ""
|
||||
|
||||
|
||||
def get_party_name_map():
|
||||
party_map = {}
|
||||
|
||||
customers = frappe.get_all("Customer", fields=["name", "customer_name"])
|
||||
party_map["Customer"] = {c.name: c.customer_name for c in customers}
|
||||
|
||||
suppliers = frappe.get_all("Supplier", fields=["name", "supplier_name"])
|
||||
party_map["Supplier"] = {s.name: s.supplier_name for s in suppliers}
|
||||
|
||||
employees = frappe.get_all("Employee", fields=["name", "employee_name"])
|
||||
party_map["Employee"] = {e.name: e.employee_name for e in employees}
|
||||
return party_map
|
||||
|
||||
|
||||
def get_accounts_with_children(accounts):
|
||||
if not isinstance(accounts, list):
|
||||
accounts = [d.strip() for d in accounts.strip().split(",") if d]
|
||||
@@ -605,6 +625,18 @@ def get_columns(filters):
|
||||
company = filters.get("company") or get_default_company()
|
||||
filters["presentation_currency"] = currency = get_company_currency(company)
|
||||
|
||||
company_currency = get_company_currency(filters.get("company") or get_default_company())
|
||||
|
||||
if (
|
||||
filters.get("show_amount_in_company_currency")
|
||||
and filters["presentation_currency"] != company_currency
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
f'Presentation Currency cannot be {frappe.bold(filters["presentation_currency"])} , When {frappe.bold("Show Credit / Debit in Company Currency")} is enabled.'
|
||||
)
|
||||
)
|
||||
|
||||
columns = [
|
||||
{
|
||||
"label": _("GL Entry"),
|
||||
@@ -689,6 +721,19 @@ def get_columns(filters):
|
||||
{"label": _("Party"), "fieldname": "party", "width": 100},
|
||||
]
|
||||
|
||||
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
|
||||
if supplier_master_name != "Supplier Name" or customer_master_name != "Customer Name":
|
||||
columns.append(
|
||||
{
|
||||
"label": _("Party Name"),
|
||||
"fieldname": "party_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 150,
|
||||
}
|
||||
)
|
||||
|
||||
if filters.get("include_dimensions"):
|
||||
columns.append({"label": _("Project"), "options": "Project", "fieldname": "project", "width": 100})
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ def execute(filters=None):
|
||||
"invoice_or_item",
|
||||
"customer",
|
||||
"customer_group",
|
||||
"customer_name",
|
||||
"posting_date",
|
||||
"item_code",
|
||||
"item_name",
|
||||
@@ -95,6 +96,7 @@ def execute(filters=None):
|
||||
"customer": [
|
||||
"customer",
|
||||
"customer_group",
|
||||
"customer_name",
|
||||
"qty",
|
||||
"base_rate",
|
||||
"buying_rate",
|
||||
@@ -250,6 +252,10 @@ def get_data_when_not_grouped_by_invoice(gross_profit_data, filters, group_wise_
|
||||
|
||||
def get_columns(group_wise_columns, filters):
|
||||
columns = []
|
||||
|
||||
supplier_master_name = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
customer_master_name = frappe.db.get_single_value("Selling Settings", "cust_master_name")
|
||||
|
||||
column_map = frappe._dict(
|
||||
{
|
||||
"parent": {
|
||||
@@ -395,6 +401,12 @@ def get_columns(group_wise_columns, filters):
|
||||
"options": "Customer Group",
|
||||
"width": 100,
|
||||
},
|
||||
"customer_name": {
|
||||
"label": _("Customer Name"),
|
||||
"fieldname": "customer_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 150,
|
||||
},
|
||||
"territory": {
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
@@ -419,6 +431,10 @@ def get_columns(group_wise_columns, filters):
|
||||
)
|
||||
|
||||
for col in group_wise_columns.get(scrub(filters.group_by)):
|
||||
if col == "customer_name" and (
|
||||
supplier_master_name == "Supplier Name" and customer_master_name == "Customer Name"
|
||||
):
|
||||
continue
|
||||
columns.append(column_map.get(col))
|
||||
|
||||
columns.append(
|
||||
@@ -440,6 +456,7 @@ def get_column_names():
|
||||
"invoice_or_item": "sales_invoice",
|
||||
"customer": "customer",
|
||||
"customer_group": "customer_group",
|
||||
"customer_name": "customer_name",
|
||||
"posting_date": "posting_date",
|
||||
"item_code": "item_code",
|
||||
"item_name": "item_name",
|
||||
@@ -905,7 +922,7 @@ class GrossProfitGenerator:
|
||||
`tabSales Invoice Item`.parenttype, `tabSales Invoice Item`.parent,
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.posting_time,
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.update_stock,
|
||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group,
|
||||
`tabSales Invoice`.customer, `tabSales Invoice`.customer_group, `tabSales Invoice`.customer_name,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice Item`.item_code,
|
||||
`tabSales Invoice`.base_net_total as "invoice_base_net_total",
|
||||
`tabSales Invoice Item`.item_name, `tabSales Invoice Item`.description,
|
||||
@@ -1003,6 +1020,7 @@ class GrossProfitGenerator:
|
||||
"update_stock": row.update_stock,
|
||||
"customer": row.customer,
|
||||
"customer_group": row.customer_group,
|
||||
"customer_name": row.customer_name,
|
||||
"item_code": None,
|
||||
"item_name": None,
|
||||
"description": None,
|
||||
@@ -1032,6 +1050,7 @@ class GrossProfitGenerator:
|
||||
"project": row.project,
|
||||
"customer": row.customer,
|
||||
"customer_group": row.customer_group,
|
||||
"customer_name": row.customer_name,
|
||||
"item_code": item.item_code,
|
||||
"item_name": item.item_name,
|
||||
"description": item.description,
|
||||
|
||||
@@ -9,7 +9,6 @@ from frappe.query_builder import functions as fn
|
||||
from frappe.utils import cstr, flt
|
||||
from frappe.utils.nestedset import get_descendants_of
|
||||
from frappe.utils.xlsxutils import handle_html
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
|
||||
from erpnext.accounts.report.utils import get_values_for_columns
|
||||
@@ -355,7 +354,13 @@ def apply_conditions(query, si, sii, sip, filters, additional_conditions=None):
|
||||
query = query.where(si.posting_date <= filters.get("to_date"))
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
query = query.where(sip.mode_of_payment == filters.get("mode_of_payment"))
|
||||
subquery = (
|
||||
frappe.qb.from_(sip)
|
||||
.select(sip.parent)
|
||||
.where(sip.mode_of_payment == filters.get("mode_of_payment"))
|
||||
.groupby(sip.parent)
|
||||
)
|
||||
query = query.where(si.name.isin(subquery))
|
||||
|
||||
if filters.get("warehouse"):
|
||||
if frappe.db.get_value("Warehouse", filters.get("warehouse"), "is_group"):
|
||||
@@ -424,8 +429,6 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
frappe.qb.from_(si)
|
||||
.join(sii)
|
||||
.on(si.name == sii.parent)
|
||||
.left_join(sip)
|
||||
.on(sip.parent == si.name)
|
||||
.left_join(item)
|
||||
.on(sii.item_code == item.name)
|
||||
.select(
|
||||
@@ -465,7 +468,6 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
si.update_stock,
|
||||
sii.uom,
|
||||
sii.qty,
|
||||
sip.mode_of_payment,
|
||||
)
|
||||
.where(si.docstatus == 1)
|
||||
.where(sii.parenttype == doctype)
|
||||
|
||||
@@ -46,6 +46,7 @@ def get_ordered_to_be_billed_data(args, filters=None):
|
||||
child_doctype.item_name,
|
||||
child_doctype.description,
|
||||
project_field,
|
||||
doctype.company,
|
||||
)
|
||||
.where(
|
||||
(doctype.docstatus == 1)
|
||||
|
||||
@@ -46,6 +46,7 @@ class PaymentLedger:
|
||||
against_voucher_no=ple.against_voucher_no,
|
||||
amount=ple.amount,
|
||||
currency=ple.account_currency,
|
||||
company=ple.company,
|
||||
)
|
||||
|
||||
if self.filters.include_account_currency:
|
||||
@@ -77,6 +78,7 @@ class PaymentLedger:
|
||||
against_voucher_no="Outstanding:",
|
||||
amount=total,
|
||||
currency=voucher_data[0].currency,
|
||||
company=voucher_data[0].company,
|
||||
)
|
||||
|
||||
if self.filters.include_account_currency:
|
||||
@@ -85,7 +87,12 @@ class PaymentLedger:
|
||||
voucher_data.append(entry)
|
||||
|
||||
# empty row
|
||||
voucher_data.append(frappe._dict())
|
||||
voucher_data.append(
|
||||
frappe._dict(
|
||||
currency=voucher_data[0].currency,
|
||||
company=voucher_data[0].company,
|
||||
)
|
||||
)
|
||||
self.data.extend(voucher_data)
|
||||
|
||||
def build_conditions(self):
|
||||
@@ -130,7 +137,6 @@ class PaymentLedger:
|
||||
)
|
||||
|
||||
def get_columns(self):
|
||||
company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency")
|
||||
options = None
|
||||
self.columns.append(
|
||||
dict(
|
||||
@@ -195,7 +201,7 @@ class PaymentLedger:
|
||||
label=_("Amount"),
|
||||
fieldname="amount",
|
||||
fieldtype="Currency",
|
||||
options=company_currency,
|
||||
options="Company:company:default_currency",
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -45,6 +45,7 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
gle_map = get_gle_map(tds_docs)
|
||||
|
||||
out = []
|
||||
entries = {}
|
||||
for name, details in gle_map.items():
|
||||
for entry in details:
|
||||
tax_amount, total_amount, grand_total, base_total = 0, 0, 0, 0
|
||||
@@ -119,8 +120,13 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
"supplier_invoice_date": bill_date,
|
||||
}
|
||||
)
|
||||
out.append(row)
|
||||
|
||||
key = entry.voucher_no
|
||||
if key in entries:
|
||||
entries[key]["tax_amount"] += tax_amount
|
||||
else:
|
||||
entries[key] = row
|
||||
out = list(entries.values())
|
||||
out.sort(key=lambda x: (x["section_code"], x["transaction_date"]))
|
||||
|
||||
return out
|
||||
|
||||
@@ -4,15 +4,25 @@
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"mandatory": 1,
|
||||
"options": "Company",
|
||||
"wildcard_filter": 0
|
||||
}
|
||||
],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2019-01-17 17:20:42.374958",
|
||||
"modified": "2025-08-28 19:06:54.273322",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Trial Balance (Simple)",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account",
|
||||
"query": "select fiscal_year as \"Fiscal Year:Data:80\",\n\tcompany as \"Company:Data:220\",\n\tposting_date as \"Posting Date:Date:100\",\n\taccount as \"Account:Data:380\",\n\tsum(debit) as \"Debit:Currency:140\",\n\tsum(credit) as \"Credit:Currency:140\",\n\tfinance_book as \"Finance Book:Link/Finance Book:140\"\nfrom `tabGL Entry`\nwhere is_cancelled = 0 and company = %(company)s\ngroup by fiscal_year, company, posting_date, account\norder by fiscal_year, company, posting_date, account",
|
||||
"ref_doctype": "GL Entry",
|
||||
"report_name": "Trial Balance (Simple)",
|
||||
"report_type": "Query Report",
|
||||
|
||||
@@ -118,7 +118,7 @@ def convert_to_presentation_currency(gl_entries, currency_info, filters=None):
|
||||
len(account_currencies) == 1
|
||||
and account_currency == presentation_currency
|
||||
and not exchange_gain_or_loss
|
||||
):
|
||||
) and not (filters and filters.get("show_amount_in_company_currency")):
|
||||
entry["debit"] = debit_in_account_currency
|
||||
entry["credit"] = credit_in_account_currency
|
||||
else:
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
from collections import defaultdict
|
||||
from json import loads
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
@@ -469,44 +470,28 @@ def reconcile_against_document(
|
||||
reconciled_entries[(row.voucher_type, row.voucher_no)] = []
|
||||
|
||||
reconciled_entries[(row.voucher_type, row.voucher_no)].append(row)
|
||||
|
||||
for key, entries in reconciled_entries.items():
|
||||
voucher_type = key[0]
|
||||
voucher_no = key[1]
|
||||
voucher_type, voucher_no = key
|
||||
|
||||
# cancel advance entry
|
||||
doc = frappe.get_doc(voucher_type, voucher_no)
|
||||
frappe.flags.ignore_party_validation = True
|
||||
|
||||
# When Advance is allocated from an Order to an Invoice
|
||||
# whole ledger must be reposted
|
||||
repost_whole_ledger = any([x.voucher_detail_no for x in entries])
|
||||
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
|
||||
if repost_whole_ledger:
|
||||
doc.make_gl_entries(cancel=1)
|
||||
else:
|
||||
doc.make_advance_gl_entries(cancel=1)
|
||||
else:
|
||||
_delete_pl_entries(voucher_type, voucher_no)
|
||||
|
||||
reposting_rows = []
|
||||
for entry in entries:
|
||||
check_if_advance_entry_modified(entry)
|
||||
validate_allocated_amount(entry)
|
||||
|
||||
dimensions_dict = _build_dimensions_dict_for_exc_gain_loss(entry, active_dimensions)
|
||||
|
||||
# update ref in advance entry
|
||||
if voucher_type == "Journal Entry":
|
||||
referenced_row, update_advance_paid = update_reference_in_journal_entry(
|
||||
entry, doc, do_not_save=False
|
||||
)
|
||||
referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
|
||||
# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
|
||||
# amount and account in args
|
||||
# referenced_row is used to deduplicate gain/loss journal
|
||||
entry.update({"referenced_row": referenced_row})
|
||||
entry.update({"referenced_row": referenced_row.name})
|
||||
doc.make_exchange_gain_loss_journal([entry], dimensions_dict)
|
||||
else:
|
||||
referenced_row, update_advance_paid = update_reference_in_payment_entry(
|
||||
referenced_row = update_reference_in_payment_entry(
|
||||
entry,
|
||||
doc,
|
||||
do_not_save=True,
|
||||
@@ -515,20 +500,16 @@ def reconcile_against_document(
|
||||
)
|
||||
if referenced_row.get("outstanding_amount"):
|
||||
referenced_row.outstanding_amount -= flt(entry.allocated_amount)
|
||||
|
||||
reposting_rows.append(referenced_row)
|
||||
|
||||
doc.save(ignore_permissions=True)
|
||||
# re-submit advance entry
|
||||
doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
|
||||
|
||||
if voucher_type == "Payment Entry" and doc.book_advance_payments_in_separate_party_account:
|
||||
# When Advance is allocated from an Order to an Invoice
|
||||
# whole ledger must be reposted
|
||||
if repost_whole_ledger:
|
||||
doc.make_gl_entries()
|
||||
else:
|
||||
# both ledgers must be posted to for `Advance` in separate account feature
|
||||
# TODO: find a more efficient way post only for the new linked vouchers
|
||||
doc.make_advance_gl_entries()
|
||||
for row in reposting_rows:
|
||||
doc.make_advance_gl_entries(entry=row)
|
||||
else:
|
||||
_delete_pl_entries(voucher_type, voucher_no)
|
||||
gl_map = doc.build_gl_map()
|
||||
# Make sure there is no overallocation
|
||||
from erpnext.accounts.general_ledger import process_debit_credit_difference
|
||||
@@ -545,11 +526,6 @@ def reconcile_against_document(
|
||||
entry.party_type,
|
||||
entry.party,
|
||||
)
|
||||
# update advance paid in Advance Receivable/Payable doctypes
|
||||
if update_advance_paid:
|
||||
for t, n in update_advance_paid:
|
||||
frappe.get_doc(t, n).set_total_advance_paid()
|
||||
|
||||
frappe.flags.ignore_party_validation = False
|
||||
|
||||
|
||||
@@ -630,12 +606,6 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
||||
"""
|
||||
jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
|
||||
|
||||
# Update Advance Paid in SO/PO since they might be getting unlinked
|
||||
update_advance_paid = []
|
||||
|
||||
if jv_detail.get("reference_type") in ["Sales Order", "Purchase Order"]:
|
||||
update_advance_paid.append((jv_detail.reference_type, jv_detail.reference_name))
|
||||
|
||||
rev_dr_or_cr = (
|
||||
"debit_in_account_currency"
|
||||
if d["dr_or_cr"] == "credit_in_account_currency"
|
||||
@@ -688,6 +658,10 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
||||
new_row.is_advance = cstr(jv_detail.is_advance)
|
||||
new_row.docstatus = 1
|
||||
|
||||
if jv_detail.get("reference_type") in get_advance_payment_doctypes():
|
||||
new_row.advance_voucher_type = jv_detail.get("reference_type")
|
||||
new_row.advance_voucher_no = jv_detail.get("reference_name")
|
||||
|
||||
# will work as update after submit
|
||||
journal_entry.flags.ignore_validate_update_after_submit = True
|
||||
# Ledgers will be reposted by Reconciliation tool
|
||||
@@ -695,7 +669,7 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
||||
if not do_not_save:
|
||||
journal_entry.save(ignore_permissions=True)
|
||||
|
||||
return new_row.name, update_advance_paid
|
||||
return new_row
|
||||
|
||||
|
||||
def update_reference_in_payment_entry(
|
||||
@@ -714,7 +688,8 @@ def update_reference_in_payment_entry(
|
||||
"account": d.account,
|
||||
"dimensions": d.dimensions,
|
||||
}
|
||||
update_advance_paid = []
|
||||
|
||||
advance_payment_doctypes = get_advance_payment_doctypes()
|
||||
|
||||
# Update Reconciliation effect date in reference
|
||||
if payment_entry.book_advance_payments_in_separate_party_account:
|
||||
@@ -726,10 +701,6 @@ def update_reference_in_payment_entry(
|
||||
if d.voucher_detail_no:
|
||||
existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
|
||||
|
||||
# Update Advance Paid in SO/PO since they are getting unlinked
|
||||
if existing_row.get("reference_doctype") in ["Sales Order", "Purchase Order"]:
|
||||
update_advance_paid.append((existing_row.reference_doctype, existing_row.reference_name))
|
||||
|
||||
if d.allocated_amount <= existing_row.allocated_amount:
|
||||
existing_row.allocated_amount -= d.allocated_amount
|
||||
|
||||
@@ -737,7 +708,13 @@ def update_reference_in_payment_entry(
|
||||
new_row.docstatus = 1
|
||||
for field in list(reference_details):
|
||||
new_row.set(field, reference_details[field])
|
||||
|
||||
if existing_row.reference_doctype in advance_payment_doctypes:
|
||||
new_row.advance_voucher_type = existing_row.reference_doctype
|
||||
new_row.advance_voucher_no = existing_row.reference_name
|
||||
|
||||
row = new_row
|
||||
|
||||
else:
|
||||
new_row = payment_entry.append("references")
|
||||
new_row.docstatus = 1
|
||||
@@ -771,7 +748,8 @@ def update_reference_in_payment_entry(
|
||||
payment_entry.flags.ignore_reposting_on_reconciliation = True
|
||||
if not do_not_save:
|
||||
payment_entry.save(ignore_permissions=True)
|
||||
return row, update_advance_paid
|
||||
|
||||
return row
|
||||
|
||||
|
||||
def get_reconciliation_effect_date(against_voucher_type, against_voucher, company, posting_date):
|
||||
@@ -949,6 +927,24 @@ def update_accounting_ledgers_after_reference_removal(
|
||||
ple_update_query = ple_update_query.where(ple.voucher_no == payment_name)
|
||||
ple_update_query.run()
|
||||
|
||||
# Advance Payment
|
||||
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||
adv_ple = (
|
||||
qb.update(adv)
|
||||
.set(adv.delinked, 1)
|
||||
.set(adv.modified, now())
|
||||
.set(adv.modified_by, frappe.session.user)
|
||||
.where(adv.delinked == 0)
|
||||
.where(
|
||||
((adv.against_voucher_type == ref_type) & (adv.against_voucher_no == ref_no))
|
||||
| ((adv.voucher_type == ref_type) & (adv.voucher_no == ref_no))
|
||||
)
|
||||
)
|
||||
if payment_name:
|
||||
adv_ple = adv_ple.where(adv.voucher_no == payment_name)
|
||||
|
||||
adv_ple.run()
|
||||
|
||||
|
||||
def remove_ref_from_advance_section(ref_doc: object = None):
|
||||
# TODO: this might need some testing
|
||||
@@ -985,6 +981,8 @@ def remove_ref_doc_link_from_jv(
|
||||
qb.update(jea)
|
||||
.set(jea.reference_type, None)
|
||||
.set(jea.reference_name, None)
|
||||
.set(jea.advance_voucher_type, None)
|
||||
.set(jea.advance_voucher_no, None)
|
||||
.set(jea.modified, now())
|
||||
.set(jea.modified_by, frappe.session.user)
|
||||
.where((jea.reference_type == ref_type) & (jea.reference_name == ref_no))
|
||||
@@ -1341,6 +1339,7 @@ def create_payment_gateway_account(gateway, payment_channel="Email"):
|
||||
"payment_account": bank_account.name,
|
||||
"currency": bank_account.account_currency,
|
||||
"payment_channel": payment_channel,
|
||||
"company": company,
|
||||
}
|
||||
).insert(ignore_permissions=True, ignore_if_duplicate=True)
|
||||
|
||||
@@ -1495,6 +1494,11 @@ def _delete_pl_entries(voucher_type, voucher_no):
|
||||
qb.from_(ple).delete().where((ple.voucher_type == voucher_type) & (ple.voucher_no == voucher_no)).run()
|
||||
|
||||
|
||||
def _delete_adv_pl_entries(voucher_type, voucher_no):
|
||||
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||
qb.from_(adv).delete().where((adv.voucher_type == voucher_type) & (adv.voucher_no == voucher_no)).run()
|
||||
|
||||
|
||||
def _delete_gl_entries(voucher_type, voucher_no):
|
||||
gle = qb.DocType("GL Entry")
|
||||
qb.from_(gle).delete().where((gle.voucher_type == voucher_type) & (gle.voucher_no == voucher_no)).run()
|
||||
@@ -1739,40 +1743,38 @@ def create_err_and_its_journals(companies: list | None = None) -> None:
|
||||
jv and frappe.get_doc("Journal Entry", jv).submit()
|
||||
|
||||
|
||||
def _auto_create_exchange_rate_revaluation_for(frequency: str) -> None:
|
||||
"""
|
||||
Internal helper to avoid code duplication and typos.
|
||||
Fetches companies by frequency and triggers ERR.
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": frequency},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_daily() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Daily"},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
_auto_create_exchange_rate_revaluation_for("Daily")
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_weekly() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Weekly"},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
_auto_create_exchange_rate_revaluation_for("Weekly")
|
||||
|
||||
|
||||
def auto_create_exchange_rate_revaluation_monthly() -> None:
|
||||
"""
|
||||
Executed by background job
|
||||
"""
|
||||
companies = frappe.db.get_all(
|
||||
"Company",
|
||||
filters={"auto_exchange_rate_revaluation": 1, "auto_err_frequency": "Montly"},
|
||||
fields=["name", "submit_err_jv"],
|
||||
)
|
||||
create_err_and_its_journals(companies)
|
||||
_auto_create_exchange_rate_revaluation_for("Monthly")
|
||||
|
||||
|
||||
def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
@@ -1800,6 +1802,7 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
|
||||
dr_or_cr = 0
|
||||
account_type = None
|
||||
|
||||
for gle in gl_entries:
|
||||
if gle.account in receivable_or_payable_accounts:
|
||||
account_type = get_account_type(gle.account)
|
||||
@@ -1814,6 +1817,11 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
dr_or_cr *= -1
|
||||
dr_or_cr_account_currency *= -1
|
||||
|
||||
against_voucher_type = (
|
||||
gle.against_voucher_type if gle.against_voucher_type else gle.voucher_type
|
||||
)
|
||||
against_voucher_no = gle.against_voucher if gle.against_voucher else gle.voucher_no
|
||||
|
||||
ple = frappe._dict(
|
||||
doctype="Payment Ledger Entry",
|
||||
posting_date=gle.posting_date,
|
||||
@@ -1828,14 +1836,12 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
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,
|
||||
against_voucher_no=gle.against_voucher if gle.against_voucher else gle.voucher_no,
|
||||
against_voucher_type=against_voucher_type,
|
||||
against_voucher_no=against_voucher_no,
|
||||
account_currency=gle.account_currency,
|
||||
amount=dr_or_cr,
|
||||
amount_in_account_currency=dr_or_cr_account_currency,
|
||||
delinked=True if cancel else False,
|
||||
delinked=cancel,
|
||||
remarks=gle.remarks,
|
||||
)
|
||||
|
||||
@@ -1844,10 +1850,40 @@ def get_payment_ledger_entries(gl_entries, cancel=0):
|
||||
for dimension in dimensions_and_defaults[0]:
|
||||
ple[dimension.fieldname] = gle.get(dimension.fieldname)
|
||||
|
||||
if gle.advance_voucher_no:
|
||||
# create advance entry
|
||||
adv = get_advance_ledger_entry(
|
||||
gle, against_voucher_type, against_voucher_no, dr_or_cr_account_currency, cancel
|
||||
)
|
||||
|
||||
ple_map.append(adv)
|
||||
|
||||
ple_map.append(ple)
|
||||
|
||||
return ple_map
|
||||
|
||||
|
||||
def get_advance_ledger_entry(gle, against_voucher_type, against_voucher_no, amount, cancel):
|
||||
event = (
|
||||
"Submit"
|
||||
if (against_voucher_type == gle.voucher_type and against_voucher_no == gle.voucher_no)
|
||||
else "Adjustment"
|
||||
)
|
||||
return frappe._dict(
|
||||
doctype="Advance Payment Ledger Entry",
|
||||
company=gle.company,
|
||||
voucher_type=gle.voucher_type,
|
||||
voucher_no=gle.voucher_no,
|
||||
voucher_detail_no=gle.voucher_detail_no,
|
||||
against_voucher_type=gle.advance_voucher_type,
|
||||
against_voucher_no=gle.advance_voucher_no,
|
||||
amount=amount,
|
||||
currency=gle.account_currency,
|
||||
event=event,
|
||||
delinked=cancel,
|
||||
)
|
||||
|
||||
|
||||
def create_payment_ledger_entry(
|
||||
gl_entries, cancel=0, adv_adj=0, update_outstanding="Yes", from_repost=0, partial_cancel=False
|
||||
):
|
||||
@@ -1859,6 +1895,9 @@ def create_payment_ledger_entry(
|
||||
|
||||
if cancel:
|
||||
delink_original_entry(ple, partial_cancel=partial_cancel)
|
||||
if is_immutable_ledger_enabled():
|
||||
ple.delinked = 0
|
||||
ple.posting_date = frappe.form_dict.get("posting_date") or getdate()
|
||||
|
||||
ple.flags.ignore_permissions = 1
|
||||
ple.flags.adv_adj = adv_adj
|
||||
@@ -1868,6 +1907,16 @@ def create_payment_ledger_entry(
|
||||
|
||||
|
||||
def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, party):
|
||||
from erpnext.accounts.doctype.dunning.dunning import update_linked_dunnings
|
||||
|
||||
if not voucher_type or not voucher_no:
|
||||
return
|
||||
|
||||
if voucher_type in get_advance_payment_doctypes():
|
||||
ref_doc = frappe.get_lazy_doc(voucher_type, voucher_no)
|
||||
ref_doc.set_total_advance_paid()
|
||||
return
|
||||
|
||||
ple = frappe.qb.DocType("Payment Ledger Entry")
|
||||
vouchers = [frappe._dict({"voucher_type": voucher_type, "voucher_no": voucher_no})]
|
||||
common_filter = []
|
||||
@@ -1892,6 +1941,7 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
|
||||
):
|
||||
outstanding = voucher_outstanding[0]
|
||||
ref_doc = frappe.get_doc(voucher_type, voucher_no)
|
||||
previous_outstanding_amount = ref_doc.outstanding_amount
|
||||
outstanding_amount = flt(
|
||||
outstanding["outstanding_in_account_currency"], ref_doc.precision("outstanding_amount")
|
||||
)
|
||||
@@ -1905,16 +1955,36 @@ def update_voucher_outstanding(voucher_type, voucher_no, account, party_type, pa
|
||||
outstanding_amount,
|
||||
)
|
||||
|
||||
update_linked_dunnings(ref_doc, previous_outstanding_amount)
|
||||
ref_doc.set_status(update=True)
|
||||
ref_doc.notify_update()
|
||||
|
||||
|
||||
def delink_original_entry(pl_entry, partial_cancel=False):
|
||||
if pl_entry:
|
||||
if not pl_entry:
|
||||
return
|
||||
|
||||
if pl_entry.doctype == "Advance Payment Ledger Entry":
|
||||
adv = qb.DocType("Advance Payment Ledger Entry")
|
||||
|
||||
(
|
||||
qb.update(adv)
|
||||
.set(adv.delinked, 1)
|
||||
.set(adv.event, "Cancel")
|
||||
.set(adv.modified, now())
|
||||
.set(adv.modified_by, frappe.session.user)
|
||||
.where(adv.voucher_type == pl_entry.voucher_type)
|
||||
.where(adv.voucher_no == pl_entry.voucher_no)
|
||||
.where(adv.against_voucher_type == pl_entry.against_voucher_type)
|
||||
.where(adv.against_voucher_no == pl_entry.against_voucher_no)
|
||||
.where(adv.event == pl_entry.event)
|
||||
.run()
|
||||
)
|
||||
|
||||
else:
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
query = (
|
||||
qb.update(ple)
|
||||
.set(ple.delinked, True)
|
||||
.set(ple.modified, now())
|
||||
.set(ple.modified_by, frappe.session.user)
|
||||
.where(
|
||||
@@ -1933,6 +2003,9 @@ def delink_original_entry(pl_entry, partial_cancel=False):
|
||||
if partial_cancel:
|
||||
query = query.where(ple.voucher_detail_no == pl_entry.voucher_detail_no)
|
||||
|
||||
if not is_immutable_ledger_enabled():
|
||||
query = query.set(ple.delinked, True)
|
||||
|
||||
query.run()
|
||||
|
||||
|
||||
@@ -2355,25 +2428,41 @@ def sync_auto_reconcile_config(auto_reconciliation_job_trigger: int = 15):
|
||||
).save()
|
||||
|
||||
|
||||
def get_link_fields_grouped_by_option(doctype):
|
||||
meta = frappe.get_meta(doctype)
|
||||
link_fields_map = defaultdict(list)
|
||||
|
||||
for df in meta.fields:
|
||||
if df.fieldtype == "Link" and df.options and not df.ignore_user_permissions:
|
||||
link_fields_map[df.options].append(df.fieldname)
|
||||
|
||||
return link_fields_map
|
||||
|
||||
|
||||
def build_qb_match_conditions(doctype, user=None) -> list:
|
||||
match_filters = build_match_conditions(doctype, user, False)
|
||||
link_fields_map = get_link_fields_grouped_by_option(doctype)
|
||||
criterion = []
|
||||
apply_strict_user_permissions = frappe.get_system_settings("apply_strict_user_permissions")
|
||||
|
||||
if match_filters:
|
||||
from frappe import qb
|
||||
|
||||
_dt = qb.DocType(doctype)
|
||||
|
||||
for filter in match_filters:
|
||||
for d, names in filter.items():
|
||||
fieldname = d.lower().replace(" ", "_")
|
||||
field = _dt[fieldname]
|
||||
for link_option, allowed_values in filter.items():
|
||||
fieldnames = link_fields_map.get(link_option, [])
|
||||
|
||||
cond = field.isin(names)
|
||||
if not apply_strict_user_permissions:
|
||||
cond = (Coalesce(field, "") == "") | field.isin(names)
|
||||
for fieldname in fieldnames:
|
||||
field = _dt[fieldname]
|
||||
cond = field.isin(allowed_values)
|
||||
|
||||
criterion.append(cond)
|
||||
if not apply_strict_user_permissions:
|
||||
cond = (Coalesce(field, "") == "") | cond
|
||||
|
||||
criterion.append(cond)
|
||||
|
||||
return criterion
|
||||
|
||||
|
||||
def is_immutable_ledger_enabled():
|
||||
return frappe.get_single_value("Accounts Settings", "enable_immutable_ledger")
|
||||
|
||||
@@ -122,6 +122,7 @@ class Asset(AccountsController):
|
||||
def validate(self):
|
||||
self.validate_category()
|
||||
self.validate_precision()
|
||||
self.validate_linked_purchase_docs()
|
||||
self.set_purchase_doc_row_item()
|
||||
self.validate_asset_values()
|
||||
self.validate_asset_and_reference()
|
||||
@@ -321,6 +322,9 @@ class Asset(AccountsController):
|
||||
finance_books = get_item_details(self.item_code, self.asset_category, self.gross_purchase_amount)
|
||||
self.set("finance_books", finance_books)
|
||||
|
||||
if self.asset_owner == "Company" and not self.asset_owner_company:
|
||||
self.asset_owner_company = self.company
|
||||
|
||||
def validate_finance_books(self):
|
||||
if not self.calculate_depreciation or len(self.finance_books) == 1:
|
||||
return
|
||||
@@ -409,6 +413,21 @@ class Asset(AccountsController):
|
||||
if self.available_for_use_date and getdate(self.available_for_use_date) < getdate(self.purchase_date):
|
||||
frappe.throw(_("Available-for-use Date should be after purchase date"))
|
||||
|
||||
def validate_linked_purchase_docs(self):
|
||||
for doctype_field, doctype_name in [
|
||||
("purchase_receipt", "Purchase Receipt"),
|
||||
("purchase_invoice", "Purchase Invoice"),
|
||||
]:
|
||||
linked_doc = getattr(self, doctype_field, None)
|
||||
if linked_doc:
|
||||
docstatus = frappe.db.get_value(doctype_name, linked_doc, "docstatus")
|
||||
if docstatus == 0:
|
||||
frappe.throw(
|
||||
_("{0} is still in Draft. Please submit it before saving the Asset.").format(
|
||||
get_link_to_form(doctype_name, linked_doc)
|
||||
)
|
||||
)
|
||||
|
||||
def validate_gross_and_purchase_amount(self):
|
||||
if self.is_existing_asset:
|
||||
return
|
||||
@@ -1083,7 +1102,7 @@ def get_asset_account(account_name, asset=None, asset_category=None, company=Non
|
||||
def make_journal_entry(asset_name):
|
||||
asset = frappe.get_doc("Asset", asset_name)
|
||||
(
|
||||
_,
|
||||
fixed_asset_account,
|
||||
accumulated_depreciation_account,
|
||||
depreciation_expense_account,
|
||||
) = get_depreciation_accounts(asset.asset_category, asset.company)
|
||||
@@ -1097,7 +1116,7 @@ def make_journal_entry(asset_name):
|
||||
je.voucher_type = "Depreciation Entry"
|
||||
je.naming_series = depreciation_series
|
||||
je.company = asset.company
|
||||
je.remark = f"Depreciation Entry against asset {asset_name}"
|
||||
je.remark = _("Depreciation Entry against asset {0}").format(asset_name)
|
||||
|
||||
je.append(
|
||||
"accounts",
|
||||
|
||||
@@ -277,7 +277,9 @@ def _make_journal_entry_for_depreciation(
|
||||
je.posting_date = depr_schedule.schedule_date
|
||||
je.company = asset.company
|
||||
je.finance_book = asset_depr_schedule_doc.finance_book
|
||||
je.remark = f"Depreciation Entry against {asset.name} worth {depr_schedule.depreciation_amount}"
|
||||
je.remark = _("Depreciation Entry against {0} worth {1}").format(
|
||||
asset.name, depr_schedule.depreciation_amount
|
||||
)
|
||||
|
||||
credit_entry = {
|
||||
"account": credit_account,
|
||||
|
||||
@@ -346,6 +346,33 @@ class TestAsset(AssetSetup):
|
||||
si.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||
|
||||
def test_asset_status_after_sales_invoice_cancel(self):
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2020-04-01",
|
||||
purchase_date="2020-04-01",
|
||||
expected_value_after_useful_life=0,
|
||||
total_number_of_depreciations=5,
|
||||
opening_number_of_booked_depreciations=2,
|
||||
frequency_of_depreciation=12,
|
||||
depreciation_start_date="2023-03-31",
|
||||
opening_accumulated_depreciation=24000,
|
||||
gross_purchase_amount=60000,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
si = create_sales_invoice(
|
||||
item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23")
|
||||
)
|
||||
asset.load_from_db()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
si.cancel()
|
||||
asset.load_from_db()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||
|
||||
def test_gle_made_by_asset_sale_for_existing_asset(self):
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
|
||||
@@ -98,20 +98,41 @@ class AssetDepreciationSchedule(Document):
|
||||
)
|
||||
|
||||
def on_submit(self):
|
||||
self.validate_asset()
|
||||
self.db_set("status", "Active")
|
||||
|
||||
def before_cancel(self):
|
||||
def validate_asset(self):
|
||||
asset = frappe.get_doc("Asset", self.asset)
|
||||
if not asset.calculate_depreciation:
|
||||
frappe.throw(
|
||||
_("Asset {0} is not set to calculate depreciation.").format(
|
||||
get_link_to_form("Asset", self.asset)
|
||||
)
|
||||
)
|
||||
if asset.docstatus != 1:
|
||||
frappe.throw(
|
||||
_("Asset {0} is not submitted. Please submit the asset before proceeding.").format(
|
||||
get_link_to_form("Asset", self.asset)
|
||||
)
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
if not self.flags.should_not_cancel_depreciation_entries:
|
||||
self.cancel_depreciation_entries()
|
||||
|
||||
def cancel_depreciation_entries(self):
|
||||
for d in self.get("depreciation_schedule"):
|
||||
if d.journal_entry:
|
||||
je_status = frappe.db.get_value("Journal Entry", d.journal_entry, "docstatus")
|
||||
if je_status == 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Cannot cancel Asset Depreciation Schedule {0} as it has a draft journal entry {1}."
|
||||
).format(self.name, d.journal_entry)
|
||||
)
|
||||
frappe.get_doc("Journal Entry", d.journal_entry).cancel()
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
|
||||
def update_shift_depr_schedule(self):
|
||||
if not self.shift_based or self.docstatus != 0:
|
||||
return
|
||||
@@ -435,7 +456,7 @@ class AssetDepreciationSchedule(Document):
|
||||
continue
|
||||
depreciation_amount = flt(depreciation_amount, asset_doc.precision("gross_purchase_amount"))
|
||||
value_after_depreciation = flt(
|
||||
value_after_depreciation - flt(depreciation_amount),
|
||||
flt(value_after_depreciation) - flt(depreciation_amount),
|
||||
asset_doc.precision("gross_purchase_amount"),
|
||||
)
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ frappe.listview_settings["Asset Maintenance Log"] = {
|
||||
has_indicator_for_draft: 1,
|
||||
get_indicator: function (doc) {
|
||||
if (doc.maintenance_status == "Planned") {
|
||||
return [__(doc.maintenance_status), "orange", "status,=," + doc.maintenance_status];
|
||||
return [__(doc.maintenance_status), "orange", "maintenance_status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status == "Completed") {
|
||||
return [__(doc.maintenance_status), "green", "status,=," + doc.maintenance_status];
|
||||
return [__(doc.maintenance_status), "green", "maintenance_status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status == "Cancelled") {
|
||||
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
|
||||
return [__(doc.maintenance_status), "red", "maintenance_status,=," + doc.maintenance_status];
|
||||
} else if (doc.maintenance_status == "Overdue") {
|
||||
return [__(doc.maintenance_status), "red", "status,=," + doc.maintenance_status];
|
||||
return [__(doc.maintenance_status), "red", "maintenance_status,=," + doc.maintenance_status];
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,6 +31,91 @@ def execute(filters=None):
|
||||
return columns, data, None, chart
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
data = []
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
pr_supplier_map = get_purchase_receipt_supplier_map()
|
||||
pi_supplier_map = get_purchase_invoice_supplier_map()
|
||||
|
||||
assets_linked_to_fb = get_assets_linked_to_fb(filters)
|
||||
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.include_default_book_assets and company_fb:
|
||||
finance_book = company_fb
|
||||
elif filters.finance_book:
|
||||
finance_book = filters.finance_book
|
||||
else:
|
||||
finance_book = None
|
||||
|
||||
depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book)
|
||||
|
||||
revaluation_amount_map = get_asset_value_adjustment_map(filters, finance_book)
|
||||
|
||||
group_by = frappe.scrub(filters.get("group_by"))
|
||||
|
||||
if group_by in ("asset_category", "location"):
|
||||
data = get_group_by_data(
|
||||
group_by, conditions, assets_linked_to_fb, depreciation_amount_map, revaluation_amount_map
|
||||
)
|
||||
return data
|
||||
|
||||
fields = [
|
||||
"name as asset_id",
|
||||
"asset_name",
|
||||
"status",
|
||||
"department",
|
||||
"company",
|
||||
"cost_center",
|
||||
"calculate_depreciation",
|
||||
"purchase_receipt",
|
||||
"asset_category",
|
||||
"purchase_date",
|
||||
"gross_purchase_amount",
|
||||
"location",
|
||||
"available_for_use_date",
|
||||
"purchase_invoice",
|
||||
"opening_accumulated_depreciation",
|
||||
]
|
||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||
|
||||
for asset in assets_record:
|
||||
if assets_linked_to_fb and asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
|
||||
continue
|
||||
|
||||
depreciation_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
|
||||
revaluation_amount = revaluation_amount_map.get(asset.asset_id, 0.0)
|
||||
asset_value = (
|
||||
asset.gross_purchase_amount
|
||||
- asset.opening_accumulated_depreciation
|
||||
- depreciation_amount
|
||||
+ revaluation_amount
|
||||
)
|
||||
|
||||
row = {
|
||||
"asset_id": asset.asset_id,
|
||||
"asset_name": asset.asset_name,
|
||||
"status": asset.status,
|
||||
"department": asset.department,
|
||||
"cost_center": asset.cost_center,
|
||||
"vendor_name": pr_supplier_map.get(asset.purchase_receipt)
|
||||
or pi_supplier_map.get(asset.purchase_invoice),
|
||||
"gross_purchase_amount": asset.gross_purchase_amount,
|
||||
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
||||
"depreciated_amount": depreciation_amount,
|
||||
"available_for_use_date": asset.available_for_use_date,
|
||||
"location": asset.location,
|
||||
"asset_category": asset.asset_category,
|
||||
"purchase_date": asset.purchase_date,
|
||||
"asset_value": asset_value,
|
||||
"company": asset.company,
|
||||
}
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = {"docstatus": 1}
|
||||
status = filters.status
|
||||
@@ -76,83 +161,6 @@ def get_conditions(filters):
|
||||
return conditions
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
data = []
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
pr_supplier_map = get_purchase_receipt_supplier_map()
|
||||
pi_supplier_map = get_purchase_invoice_supplier_map()
|
||||
|
||||
assets_linked_to_fb = get_assets_linked_to_fb(filters)
|
||||
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.include_default_book_assets and company_fb:
|
||||
finance_book = company_fb
|
||||
elif filters.finance_book:
|
||||
finance_book = filters.finance_book
|
||||
else:
|
||||
finance_book = None
|
||||
|
||||
depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book)
|
||||
|
||||
group_by = frappe.scrub(filters.get("group_by"))
|
||||
|
||||
if group_by in ("asset_category", "location"):
|
||||
data = get_group_by_data(group_by, conditions, assets_linked_to_fb, depreciation_amount_map)
|
||||
return data
|
||||
|
||||
fields = [
|
||||
"name as asset_id",
|
||||
"asset_name",
|
||||
"status",
|
||||
"department",
|
||||
"company",
|
||||
"cost_center",
|
||||
"calculate_depreciation",
|
||||
"purchase_receipt",
|
||||
"asset_category",
|
||||
"purchase_date",
|
||||
"gross_purchase_amount",
|
||||
"location",
|
||||
"available_for_use_date",
|
||||
"purchase_invoice",
|
||||
"opening_accumulated_depreciation",
|
||||
]
|
||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||
|
||||
for asset in assets_record:
|
||||
if assets_linked_to_fb and asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
|
||||
continue
|
||||
|
||||
depreciation_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
|
||||
asset_value = (
|
||||
asset.gross_purchase_amount - asset.opening_accumulated_depreciation - depreciation_amount
|
||||
)
|
||||
|
||||
row = {
|
||||
"asset_id": asset.asset_id,
|
||||
"asset_name": asset.asset_name,
|
||||
"status": asset.status,
|
||||
"department": asset.department,
|
||||
"cost_center": asset.cost_center,
|
||||
"vendor_name": pr_supplier_map.get(asset.purchase_receipt)
|
||||
or pi_supplier_map.get(asset.purchase_invoice),
|
||||
"gross_purchase_amount": asset.gross_purchase_amount,
|
||||
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
||||
"depreciated_amount": depreciation_amount,
|
||||
"available_for_use_date": asset.available_for_use_date,
|
||||
"location": asset.location,
|
||||
"asset_category": asset.asset_category,
|
||||
"purchase_date": asset.purchase_date,
|
||||
"asset_value": asset_value,
|
||||
"company": asset.company,
|
||||
}
|
||||
data.append(row)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def prepare_chart_data(data, filters):
|
||||
if not data:
|
||||
return
|
||||
@@ -290,7 +298,59 @@ def get_asset_depreciation_amount_map(filters, finance_book):
|
||||
return dict(asset_depr_amount_map)
|
||||
|
||||
|
||||
def get_group_by_data(group_by, conditions, assets_linked_to_fb, depreciation_amount_map):
|
||||
def get_asset_value_adjustment_map(filters, finance_book):
|
||||
start_date = filters.from_date if filters.filter_based_on == "Date Range" else filters.year_start_date
|
||||
end_date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
|
||||
|
||||
asset = frappe.qb.DocType("Asset")
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
aca = frappe.qb.DocType("Asset Category Account")
|
||||
company = frappe.qb.DocType("Company")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(gle)
|
||||
.join(asset)
|
||||
.on(gle.against_voucher == asset.name)
|
||||
.join(aca)
|
||||
.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
|
||||
.join(company)
|
||||
.on(company.name == asset.company)
|
||||
.select(asset.name.as_("asset"), Sum(gle.debit - gle.credit).as_("adjustment_amount"))
|
||||
.where(gle.account == aca.fixed_asset_account)
|
||||
.where(gle.is_cancelled == 0)
|
||||
.where(company.name == filters.company)
|
||||
.where(asset.docstatus == 1)
|
||||
)
|
||||
|
||||
if filters.only_existing_assets:
|
||||
query = query.where(asset.is_existing_asset == 1)
|
||||
if filters.asset_category:
|
||||
query = query.where(asset.asset_category == filters.asset_category)
|
||||
if filters.cost_center:
|
||||
query = query.where(asset.cost_center == filters.cost_center)
|
||||
if filters.status:
|
||||
if filters.status == "In Location":
|
||||
query = query.where(asset.status.notin(["Sold", "Scrapped", "Capitalized"]))
|
||||
else:
|
||||
query = query.where(asset.status.isin(["Sold", "Scrapped", "Capitalized"]))
|
||||
if finance_book:
|
||||
query = query.where((gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull()))
|
||||
else:
|
||||
query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull()))
|
||||
if filters.filter_based_on in ("Date Range", "Fiscal Year"):
|
||||
query = query.where(gle.posting_date >= start_date)
|
||||
query = query.where(gle.posting_date <= end_date)
|
||||
|
||||
query = query.groupby(asset.name)
|
||||
|
||||
asset_adjustment_map = query.run()
|
||||
|
||||
return dict(asset_adjustment_map)
|
||||
|
||||
|
||||
def get_group_by_data(
|
||||
group_by, conditions, assets_linked_to_fb, depreciation_amount_map, revaluation_amount_map
|
||||
):
|
||||
fields = [
|
||||
group_by,
|
||||
"name",
|
||||
@@ -307,8 +367,12 @@ def get_group_by_data(group_by, conditions, assets_linked_to_fb, depreciation_am
|
||||
continue
|
||||
|
||||
a["depreciated_amount"] = depreciation_amount_map.get(a["name"], 0.0)
|
||||
a["revaluation_amount"] = revaluation_amount_map.get(a["name"], 0.0)
|
||||
a["asset_value"] = (
|
||||
a["gross_purchase_amount"] - a["opening_accumulated_depreciation"] - a["depreciated_amount"]
|
||||
a["gross_purchase_amount"]
|
||||
- a["opening_accumulated_depreciation"]
|
||||
- a["depreciated_amount"]
|
||||
+ a["revaluation_amount"]
|
||||
)
|
||||
|
||||
del a["name"]
|
||||
|
||||
@@ -39,7 +39,9 @@
|
||||
"section_break_xcug",
|
||||
"auto_create_subcontracting_order",
|
||||
"column_break_izrr",
|
||||
"auto_create_purchase_receipt"
|
||||
"auto_create_purchase_receipt",
|
||||
"request_for_quotation_tab",
|
||||
"fixed_email"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -255,6 +257,19 @@
|
||||
"fieldname": "set_valuation_rate_for_rejected_materials",
|
||||
"fieldtype": "Check",
|
||||
"label": "Set Valuation Rate for Rejected Materials"
|
||||
},
|
||||
{
|
||||
"fieldname": "request_for_quotation_tab",
|
||||
"fieldtype": "Tab Break",
|
||||
"label": "Request for Quotation"
|
||||
},
|
||||
{
|
||||
"description": "If set, the system does not use the user's Email or the standard outgoing Email account for sending request for quotations.",
|
||||
"fieldname": "fixed_email",
|
||||
"fieldtype": "Link",
|
||||
"label": "Fixed Outgoing Email Account",
|
||||
"link_filters": "[[\"Email Account\",\"enable_outgoing\",\"=\",1]]",
|
||||
"options": "Email Account"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -263,7 +278,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2025-05-16 15:56:38.321369",
|
||||
"modified": "2025-08-20 22:13:38.506889",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -30,6 +30,7 @@ class BuyingSettings(Document):
|
||||
blanket_order_allowance: DF.Float
|
||||
buying_price_list: DF.Link | None
|
||||
disable_last_purchase_rate: DF.Check
|
||||
fixed_email: DF.Link | None
|
||||
maintain_same_rate: DF.Check
|
||||
maintain_same_rate_action: DF.Literal["Stop", "Warn"]
|
||||
over_transfer_allowance: DF.Float
|
||||
|
||||
@@ -463,8 +463,8 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
if (internal) {
|
||||
let button_label =
|
||||
me.frm.doc.company === me.frm.doc.represents_company
|
||||
? "Internal Sales Order"
|
||||
: "Inter Company Sales Order";
|
||||
? __("Internal Sales Order")
|
||||
: __("Inter Company Sales Order");
|
||||
|
||||
me.frm.add_custom_button(
|
||||
button_label,
|
||||
@@ -578,7 +578,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "qty", "ordered_qty"],
|
||||
child_columns: ["item_code", "item_name", "qty", "ordered_qty"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
@@ -599,6 +599,9 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends (
|
||||
docstatus: 1,
|
||||
status: ["not in", ["Stopped", "Expired"]],
|
||||
},
|
||||
allow_child_item_selection: true,
|
||||
child_fieldname: "items",
|
||||
child_columns: ["item_code", "item_name", "qty", "rate", "amount"],
|
||||
});
|
||||
},
|
||||
__("Get Items From")
|
||||
|
||||
@@ -41,8 +41,9 @@
|
||||
"ignore_pricing_rule",
|
||||
"before_items_section",
|
||||
"scan_barcode",
|
||||
"set_from_warehouse",
|
||||
"last_scanned_warehouse",
|
||||
"items_col_break",
|
||||
"set_from_warehouse",
|
||||
"set_warehouse",
|
||||
"items_section",
|
||||
"items",
|
||||
@@ -1294,6 +1295,13 @@
|
||||
"hidden": 1,
|
||||
"label": "Has Unit Price Items",
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval: doc.last_scanned_warehouse",
|
||||
"fieldname": "last_scanned_warehouse",
|
||||
"fieldtype": "Data",
|
||||
"is_virtual": 1,
|
||||
"label": "Last Scanned Warehouse"
|
||||
}
|
||||
],
|
||||
"grid_page_length": 50,
|
||||
@@ -1301,7 +1309,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2025-04-09 16:54:08.836106",
|
||||
"modified": "2025-07-31 17:19:40.816883",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -505,6 +505,7 @@ class PurchaseOrder(BuyingController):
|
||||
self.ignore_linked_doctypes = (
|
||||
"GL Entry",
|
||||
"Payment Ledger Entry",
|
||||
"Advance Payment Ledger Entry",
|
||||
"Unreconcile Payment",
|
||||
"Unreconcile Payment Entries",
|
||||
)
|
||||
@@ -719,10 +720,16 @@ def close_or_unclose_purchase_orders(names, status):
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("calculate_taxes_and_totals")
|
||||
target.run_method("set_use_serial_batch_fields")
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_receipt(source_name, target_doc=None):
|
||||
def make_purchase_receipt(source_name, target_doc=None, args=None):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
has_unit_price_items = frappe.db.get_value("Purchase Order", source_name, "has_unit_price_items")
|
||||
|
||||
def is_unit_price_row(source):
|
||||
@@ -736,6 +743,11 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
(flt(obj.qty) - flt(obj.received_qty)) * flt(obj.rate) * flt(source_parent.conversion_rate)
|
||||
)
|
||||
|
||||
def select_item(d):
|
||||
filtered_items = args.get("filtered_children", [])
|
||||
child_filter = d.name in filtered_items if filtered_items else True
|
||||
return child_filter
|
||||
|
||||
doc = get_mapped_doc(
|
||||
"Purchase Order",
|
||||
source_name,
|
||||
@@ -763,7 +775,8 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
"condition": lambda doc: (
|
||||
True if is_unit_price_row(doc) else abs(doc.received_qty) < abs(doc.qty)
|
||||
)
|
||||
and doc.delivered_by_supplier != 1,
|
||||
and doc.delivered_by_supplier != 1
|
||||
and select_item(doc),
|
||||
},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True},
|
||||
},
|
||||
@@ -775,8 +788,8 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_invoice(source_name, target_doc=None):
|
||||
return get_mapped_purchase_invoice(source_name, target_doc)
|
||||
def make_purchase_invoice(source_name, target_doc=None, args=None):
|
||||
return get_mapped_purchase_invoice(source_name, target_doc, args=args)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -790,7 +803,12 @@ def make_purchase_invoice_from_portal(purchase_order_name):
|
||||
frappe.response.location = "/purchase-invoices/" + doc.name
|
||||
|
||||
|
||||
def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions=False):
|
||||
def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions=False, args=None):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
def postprocess(source, target):
|
||||
target.flags.ignore_permissions = ignore_permissions
|
||||
set_missing_values(source, target)
|
||||
@@ -830,6 +848,11 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
or item_group.get("buying_cost_center")
|
||||
)
|
||||
|
||||
def select_item(d):
|
||||
filtered_items = args.get("filtered_children", [])
|
||||
child_filter = d.name in filtered_items if filtered_items else True
|
||||
return child_filter
|
||||
|
||||
fields = {
|
||||
"Purchase Order": {
|
||||
"doctype": "Purchase Invoice",
|
||||
@@ -852,7 +875,8 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
"wip_composite_asset": "wip_composite_asset",
|
||||
},
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount)),
|
||||
"condition": lambda doc: (doc.base_amount == 0 or abs(doc.billed_amt) < abs(doc.amount))
|
||||
and select_item(doc),
|
||||
},
|
||||
"Purchase Taxes and Charges": {"doctype": "Purchase Taxes and Charges", "reset_value": True},
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ from frappe import _
|
||||
from frappe.core.doctype.communication.email import make
|
||||
from frappe.desk.form.load import get_attachments
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.query_builder import Order
|
||||
from frappe.utils import get_url
|
||||
from frappe.utils.print_format import download_pdf
|
||||
from frappe.utils.user import get_user_fullname
|
||||
@@ -288,7 +289,11 @@ class RequestforQuotation(BuyingController):
|
||||
email_template = frappe.get_doc("Email Template", self.email_template)
|
||||
message = frappe.render_template(email_template.response_, doc_args)
|
||||
subject = frappe.render_template(email_template.subject, doc_args)
|
||||
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
|
||||
fixed_procurement_email = frappe.db.get_single_value("Buying Settings", "fixed_email")
|
||||
if fixed_procurement_email:
|
||||
sender = frappe.db.get_value("Email Account", fixed_procurement_email, "email_id")
|
||||
else:
|
||||
sender = frappe.session.user not in STANDARD_USERS and frappe.session.user or None
|
||||
|
||||
if preview:
|
||||
return {"message": message, "subject": subject}
|
||||
@@ -582,35 +587,32 @@ def get_supplier_tag():
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_rfq_containing_supplier(doctype, txt, searchfield, start, page_len, filters):
|
||||
conditions = ""
|
||||
if txt:
|
||||
conditions += "and rfq.name like '%%" + txt + "%%' "
|
||||
rfq = frappe.qb.DocType("Request for Quotation")
|
||||
rfq_supplier = frappe.qb.DocType("Request for Quotation Supplier")
|
||||
|
||||
if filters.get("transaction_date"):
|
||||
conditions += "and rfq.transaction_date = '{}'".format(filters.get("transaction_date"))
|
||||
|
||||
rfq_data = frappe.db.sql(
|
||||
f"""
|
||||
select
|
||||
distinct rfq.name, rfq.transaction_date,
|
||||
rfq.company
|
||||
from
|
||||
`tabRequest for Quotation` rfq, `tabRequest for Quotation Supplier` rfq_supplier
|
||||
where
|
||||
rfq.name = rfq_supplier.parent
|
||||
and rfq_supplier.supplier = %(supplier)s
|
||||
and rfq.docstatus = 1
|
||||
and rfq.company = %(company)s
|
||||
{conditions}
|
||||
order by rfq.transaction_date ASC
|
||||
limit %(page_len)s offset %(start)s """,
|
||||
{
|
||||
"page_len": page_len,
|
||||
"start": start,
|
||||
"company": filters.get("company"),
|
||||
"supplier": filters.get("supplier"),
|
||||
},
|
||||
as_dict=1,
|
||||
query = (
|
||||
frappe.qb.from_(rfq)
|
||||
.from_(rfq_supplier)
|
||||
.select(rfq.name)
|
||||
.distinct()
|
||||
.select(rfq.transaction_date, rfq.company)
|
||||
.where(
|
||||
(rfq.name == rfq_supplier.parent)
|
||||
& (rfq_supplier.supplier == filters.get("supplier"))
|
||||
& (rfq.docstatus == 1)
|
||||
& (rfq.company == filters.get("company"))
|
||||
)
|
||||
.orderby(rfq.transaction_date, order=Order.asc)
|
||||
.limit(page_len)
|
||||
.offset(start)
|
||||
)
|
||||
|
||||
if txt:
|
||||
query = query.where(rfq.name.like(f"%%{txt}%%"))
|
||||
|
||||
if filters.get("transaction_date"):
|
||||
query = query.where(rfq.transaction_date == filters.get("transaction_date"))
|
||||
|
||||
rfq_data = query.run(as_dict=1)
|
||||
|
||||
return rfq_data
|
||||
|
||||
@@ -64,6 +64,11 @@ frappe.ui.form.on("Supplier", {
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.make_methods = {
|
||||
"Bank Account": () => erpnext.utils.make_bank_account(frm.doc.doctype, frm.doc.name),
|
||||
"Pricing Rule": () => erpnext.utils.make_pricing_rule(frm.doc.doctype, frm.doc.name),
|
||||
};
|
||||
},
|
||||
|
||||
refresh: function (frm) {
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
@@ -235,7 +237,12 @@ def get_list_context(context=None):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_purchase_order(source_name, target_doc=None):
|
||||
def make_purchase_order(source_name, target_doc=None, args=None):
|
||||
if args is None:
|
||||
args = {}
|
||||
if isinstance(args, str):
|
||||
args = json.loads(args)
|
||||
|
||||
def set_missing_values(source, target):
|
||||
target.run_method("set_missing_values")
|
||||
target.run_method("get_schedule_dates")
|
||||
@@ -244,6 +251,11 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
def update_item(obj, target, source_parent):
|
||||
target.stock_qty = flt(obj.qty) * flt(obj.conversion_factor)
|
||||
|
||||
def select_item(d):
|
||||
filtered_items = args.get("filtered_children", [])
|
||||
child_filter = d.name in filtered_items if filtered_items else True
|
||||
return child_filter
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
"Supplier Quotation",
|
||||
source_name,
|
||||
@@ -265,6 +277,7 @@ def make_purchase_order(source_name, target_doc=None):
|
||||
["sales_order", "sales_order"],
|
||||
],
|
||||
"postprocess": update_item,
|
||||
"condition": select_item,
|
||||
},
|
||||
"Purchase Taxes and Charges": {
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
|
||||
@@ -51,7 +51,7 @@ def get_columns(filters):
|
||||
},
|
||||
{
|
||||
"label": _("Requestor"),
|
||||
"options": "Employee",
|
||||
"options": "User",
|
||||
"fieldname": "requestor",
|
||||
"fieldtype": "Link",
|
||||
"width": 140,
|
||||
|
||||
@@ -397,7 +397,6 @@ class AccountsController(TransactionBase):
|
||||
def on_trash(self):
|
||||
from erpnext.accounts.utils import delete_exchange_gain_loss_journal
|
||||
|
||||
self._remove_advance_payment_ledger_entries()
|
||||
self._remove_references_in_repost_doctypes()
|
||||
self._remove_references_in_unreconcile()
|
||||
self.remove_serial_and_batch_bundle()
|
||||
@@ -426,6 +425,8 @@ class AccountsController(TransactionBase):
|
||||
(sle.voucher_type == self.doctype) & (sle.voucher_no == self.name)
|
||||
).run()
|
||||
|
||||
self._remove_advance_payment_ledger_entries()
|
||||
|
||||
def remove_serial_and_batch_bundle(self):
|
||||
bundles = frappe.get_all(
|
||||
"Serial and Batch Bundle",
|
||||
@@ -2233,54 +2234,30 @@ class AccountsController(TransactionBase):
|
||||
|
||||
def calculate_total_advance_from_ledger(self):
|
||||
adv = frappe.qb.DocType("Advance Payment Ledger Entry")
|
||||
advance = (
|
||||
frappe.qb.from_(adv)
|
||||
.select(adv.currency.as_("account_currency"), Abs(Sum(adv.amount)).as_("amount"))
|
||||
.where(
|
||||
(adv.against_voucher_type == self.doctype)
|
||||
& (adv.against_voucher_no == self.name)
|
||||
& (adv.company == self.company)
|
||||
)
|
||||
return (
|
||||
qb.from_(adv)
|
||||
.select(Abs(Sum(adv.amount)).as_("amount"), adv.currency.as_("account_currency"))
|
||||
.where(adv.company == self.company)
|
||||
.where(adv.delinked == 0)
|
||||
.where(adv.against_voucher_type == self.doctype)
|
||||
.where(adv.against_voucher_no == self.name)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
return advance
|
||||
|
||||
def set_total_advance_paid(self):
|
||||
advance = self.calculate_total_advance_from_ledger()
|
||||
advance_paid, order_total = None, None
|
||||
advance_paid = 0
|
||||
|
||||
if advance:
|
||||
advance = advance[0]
|
||||
|
||||
advance_paid = flt(advance.amount, self.precision("advance_paid"))
|
||||
formatted_advance_paid = fmt_money(
|
||||
advance_paid, precision=self.precision("advance_paid"), currency=advance.account_currency
|
||||
)
|
||||
|
||||
if advance.account_currency:
|
||||
frappe.db.set_value(
|
||||
self.doctype, self.name, "party_account_currency", advance.account_currency
|
||||
)
|
||||
|
||||
if advance.account_currency == self.currency:
|
||||
order_total = self.get("rounded_total") or self.grand_total
|
||||
precision = "rounded_total" if self.get("rounded_total") else "grand_total"
|
||||
else:
|
||||
order_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
precision = "base_rounded_total" if self.get("base_rounded_total") else "base_grand_total"
|
||||
|
||||
formatted_order_total = fmt_money(
|
||||
order_total, precision=self.precision(precision), currency=advance.account_currency
|
||||
)
|
||||
|
||||
if self.currency == self.company_currency and advance_paid > order_total:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Total advance ({0}) against Order {1} cannot be greater than the Grand Total ({2})"
|
||||
).format(formatted_advance_paid, self.name, formatted_order_total)
|
||||
)
|
||||
|
||||
self.db_set("advance_paid", advance_paid)
|
||||
self.db_set("advance_paid", advance_paid)
|
||||
|
||||
@property
|
||||
def company_abbr(self):
|
||||
@@ -2924,64 +2901,6 @@ class AccountsController(TransactionBase):
|
||||
def get_advance_payment_doctypes(self) -> list:
|
||||
return _get_advance_payment_doctypes()
|
||||
|
||||
def make_advance_payment_ledger_for_journal(self):
|
||||
advance_payment_doctypes = self.get_advance_payment_doctypes()
|
||||
advance_doctype_references = [
|
||||
x for x in self.accounts if x.reference_type in advance_payment_doctypes
|
||||
]
|
||||
|
||||
for x in advance_doctype_references:
|
||||
# Looking for payments
|
||||
dr_or_cr = (
|
||||
"credit_in_account_currency"
|
||||
if x.account_type == "Receivable"
|
||||
else "debit_in_account_currency"
|
||||
)
|
||||
|
||||
amount = x.get(dr_or_cr)
|
||||
if amount > 0:
|
||||
doc = frappe.new_doc("Advance Payment Ledger Entry")
|
||||
doc.company = self.company
|
||||
doc.voucher_type = self.doctype
|
||||
doc.voucher_no = self.name
|
||||
doc.against_voucher_type = x.reference_type
|
||||
doc.against_voucher_no = x.reference_name
|
||||
doc.amount = amount if self.docstatus == 1 else -1 * amount
|
||||
doc.event = "Submit" if self.docstatus == 1 else "Cancel"
|
||||
doc.currency = x.account_currency
|
||||
doc.flags.ignore_permissions = 1
|
||||
doc.save()
|
||||
|
||||
def make_advance_payment_ledger_for_payment(self):
|
||||
advance_payment_doctypes = self.get_advance_payment_doctypes()
|
||||
advance_doctype_references = [
|
||||
x for x in self.references if x.reference_doctype in advance_payment_doctypes
|
||||
]
|
||||
currency = (
|
||||
self.paid_from_account_currency
|
||||
if self.payment_type == "Receive"
|
||||
else self.paid_to_account_currency
|
||||
)
|
||||
for x in advance_doctype_references:
|
||||
doc = frappe.new_doc("Advance Payment Ledger Entry")
|
||||
doc.company = self.company
|
||||
doc.voucher_type = self.doctype
|
||||
doc.voucher_no = self.name
|
||||
doc.against_voucher_type = x.reference_doctype
|
||||
doc.against_voucher_no = x.reference_name
|
||||
doc.amount = x.allocated_amount if self.docstatus == 1 else -1 * x.allocated_amount
|
||||
doc.currency = currency
|
||||
doc.event = "Submit" if self.docstatus == 1 else "Cancel"
|
||||
doc.flags.ignore_permissions = 1
|
||||
doc.save()
|
||||
|
||||
def make_advance_payment_ledger_entries(self):
|
||||
if self.docstatus != 0:
|
||||
if self.doctype == "Journal Entry":
|
||||
self.make_advance_payment_ledger_for_journal()
|
||||
elif self.doctype == "Payment Entry":
|
||||
self.make_advance_payment_ledger_for_payment()
|
||||
|
||||
def set_transaction_currency_and_rate_in_gl_map(self, gl_entries):
|
||||
for x in gl_entries:
|
||||
x["transaction_currency"] = self.currency
|
||||
|
||||
@@ -583,21 +583,27 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_blanket_orders(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select distinct bo.name, bo.blanket_order_type, bo.to_date
|
||||
from `tabBlanket Order` bo, `tabBlanket Order Item` boi
|
||||
where
|
||||
boi.parent = bo.name
|
||||
and boi.item_code = {item_code}
|
||||
and bo.blanket_order_type = '{blanket_order_type}'
|
||||
and bo.company = {company}
|
||||
and bo.docstatus = 1""".format(
|
||||
item_code=frappe.db.escape(filters.get("item")),
|
||||
blanket_order_type=filters.get("blanket_order_type"),
|
||||
company=frappe.db.escape(filters.get("company")),
|
||||
bo = frappe.qb.DocType("Blanket Order")
|
||||
bo_item = frappe.qb.DocType("Blanket Order Item")
|
||||
|
||||
blanket_orders = (
|
||||
frappe.qb.from_(bo)
|
||||
.from_(bo_item)
|
||||
.select(bo.name)
|
||||
.distinct()
|
||||
.select(bo.blanket_order_type, bo.to_date)
|
||||
.where(
|
||||
(bo_item.parent == bo.name)
|
||||
& (bo_item.item_code == filters.get("item"))
|
||||
& (bo.blanket_order_type == filters.get("blanket_order_type"))
|
||||
& (bo.company == filters.get("company"))
|
||||
& (bo.docstatus == 1)
|
||||
)
|
||||
.run()
|
||||
)
|
||||
|
||||
return blanket_orders
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
@@ -615,7 +621,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
|
||||
condition += f"and tabAccount.disabled = {filters.get('disabled', 0)}"
|
||||
condition += " and tabAccount.disabled = %(disabled)s"
|
||||
|
||||
return frappe.db.sql(
|
||||
f"""select tabAccount.name from `tabAccount`
|
||||
@@ -625,7 +631,11 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
and tabAccount.`{searchfield}` LIKE %(txt)s
|
||||
{condition} {get_match_cond(doctype)}
|
||||
order by idx desc, name""",
|
||||
{"txt": "%" + txt + "%", "company": filters.get("company", "")},
|
||||
{
|
||||
"txt": "%" + txt + "%",
|
||||
"company": filters.get("company", ""),
|
||||
"disabled": cint(filters.get("disabled", 0)),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -701,7 +711,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
where (tabAccount.report_type = "Profit and Loss"
|
||||
or tabAccount.account_type in ("Expense Account", "Fixed Asset", "Temporary", "Asset Received But Not Billed", "Capital Work in Progress"))
|
||||
and tabAccount.is_group=0
|
||||
and tabAccount.docstatus!=2
|
||||
and tabAccount.disabled = 0
|
||||
and tabAccount.{searchfield} LIKE %(txt)s
|
||||
{condition} {get_match_cond(doctype)}""",
|
||||
{"company": filters.get("company", ""), "txt": "%" + txt + "%"},
|
||||
|
||||
@@ -108,7 +108,7 @@ status_map = {
|
||||
["Pending", "eval:self.status != 'Stopped' and self.per_ordered == 0 and self.docstatus == 1"],
|
||||
[
|
||||
"Ordered",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Purchase'",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type in ['Purchase', 'Manufacture']",
|
||||
],
|
||||
[
|
||||
"Transferred",
|
||||
@@ -134,10 +134,6 @@ status_map = {
|
||||
"Partially Ordered",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered < 100 and self.per_ordered > 0 and self.docstatus == 1 and self.material_request_type != 'Material Transfer'",
|
||||
],
|
||||
[
|
||||
"Manufactured",
|
||||
"eval:self.status != 'Stopped' and self.per_ordered == 100 and self.docstatus == 1 and self.material_request_type == 'Manufacture'",
|
||||
],
|
||||
],
|
||||
"POS Opening Entry": [
|
||||
["Draft", None],
|
||||
|
||||
@@ -638,7 +638,9 @@ class StockController(AccountsController):
|
||||
).format(wh, self.company)
|
||||
)
|
||||
|
||||
return process_gl_map(gl_list, precision=precision)
|
||||
return process_gl_map(
|
||||
gl_list, precision=precision, from_repost=frappe.flags.through_repost_item_valuation
|
||||
)
|
||||
|
||||
def get_debit_field_precision(self):
|
||||
if not frappe.flags.debit_field_precision:
|
||||
@@ -957,8 +959,9 @@ class StockController(AccountsController):
|
||||
make_sl_entries(sl_entries, allow_negative_stock, via_landed_cost_voucher)
|
||||
update_batch_qty(self.doctype, self.name, via_landed_cost_voucher=via_landed_cost_voucher)
|
||||
|
||||
def make_gl_entries_on_cancel(self):
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
def make_gl_entries_on_cancel(self, from_repost=False):
|
||||
if not from_repost:
|
||||
cancel_exchange_gain_loss_journal(frappe._dict(doctype=self.doctype, name=self.name))
|
||||
if frappe.db.sql(
|
||||
"""select name from `tabGL Entry` where voucher_type=%s
|
||||
and voucher_no=%s""",
|
||||
@@ -1851,6 +1854,7 @@ def make_bundle_for_material_transfer(**kwargs):
|
||||
|
||||
row.warehouse = kwargs.warehouse
|
||||
|
||||
bundle_doc.set_incoming_rate()
|
||||
bundle_doc.calculate_qty_and_amount()
|
||||
bundle_doc.flags.ignore_permissions = True
|
||||
bundle_doc.flags.ignore_validate = True
|
||||
|
||||
@@ -284,6 +284,9 @@ erpnext.crm.Opportunity = class Opportunity extends frappe.ui.form.Controller {
|
||||
this.frm.set_value("currency", frappe.defaults.get_user_default("Currency"));
|
||||
}
|
||||
|
||||
if (this.frm.is_new() && this.frm.doc.opportunity_type === undefined) {
|
||||
this.frm.doc.opportunity_type = __("Sales");
|
||||
}
|
||||
this.setup_queries();
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user