mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-16 19:49:18 +00:00
Compare commits
430 Commits
ankush-pat
...
v14.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
217e407371 | ||
|
|
90607becb8 | ||
|
|
d6888bc8c8 | ||
|
|
063f206b9b | ||
|
|
7a8038500d | ||
|
|
8851826a57 | ||
|
|
2a82f16680 | ||
|
|
32107c4a79 | ||
|
|
bbc7559380 | ||
|
|
9c56a5ff7c | ||
|
|
c8c0f345d7 | ||
|
|
d3c151f32b | ||
|
|
f09222a9e5 | ||
|
|
ba80ba07f1 | ||
|
|
34845b7630 | ||
|
|
ecfa64f22e | ||
|
|
3e7582f55b | ||
|
|
6f571218bf | ||
|
|
8ab2f2f925 | ||
|
|
2d34761057 | ||
|
|
c0a141bfcd | ||
|
|
fd232d1db7 | ||
|
|
df9147f2e5 | ||
|
|
8e68219c5b | ||
|
|
060ce45572 | ||
|
|
432da7d1d1 | ||
|
|
9318d9a7fc | ||
|
|
faca06d6c3 | ||
|
|
1918b5c4b0 | ||
|
|
444f3b5397 | ||
|
|
3736f4ecb8 | ||
|
|
5fe2079dcc | ||
|
|
11f92797b9 | ||
|
|
17996efbc7 | ||
|
|
c2bed24c31 | ||
|
|
e5d1f59d96 | ||
|
|
793fa18225 | ||
|
|
f7ec00ef49 | ||
|
|
4d51d73797 | ||
|
|
7a096231fb | ||
|
|
db60299cf3 | ||
|
|
9ec0d104a5 | ||
|
|
7243089027 | ||
|
|
5aa9c4fd07 | ||
|
|
fd86876a0e | ||
|
|
3f0a3b702d | ||
|
|
f167abbbfd | ||
|
|
5015f38a8d | ||
|
|
381231257b | ||
|
|
f4648ed610 | ||
|
|
51fcb3c666 | ||
|
|
7b11da2373 | ||
|
|
5c0446d9fc | ||
|
|
097398914e | ||
|
|
d91ac5e549 | ||
|
|
9c915ceeb2 | ||
|
|
4b8dd1fab6 | ||
|
|
fc581464b8 | ||
|
|
b3b7650ac8 | ||
|
|
86052450a5 | ||
|
|
bad3b330f4 | ||
|
|
671033755f | ||
|
|
f5f13c4611 | ||
|
|
6b55a01834 | ||
|
|
29c4a32c33 | ||
|
|
c9c203a5eb | ||
|
|
261aac8e26 | ||
|
|
b2f280dee6 | ||
|
|
3abe4a16af | ||
|
|
32ad36ad0f | ||
|
|
e98c0ab924 | ||
|
|
4a29c7fafb | ||
|
|
4b6e1f4892 | ||
|
|
a28ae4d653 | ||
|
|
11be30d68e | ||
|
|
305eb02bfc | ||
|
|
1184fa3313 | ||
|
|
2fd0291afb | ||
|
|
959fdcd841 | ||
|
|
a83e36b801 | ||
|
|
37d0721f36 | ||
|
|
408f2bda82 | ||
|
|
ddf4d02c7d | ||
|
|
2f5276b794 | ||
|
|
e708a33684 | ||
|
|
931aa62072 | ||
|
|
8c6ddb6721 | ||
|
|
f1d774034c | ||
|
|
4a1de87822 | ||
|
|
f5a3f8b486 | ||
|
|
e464de3c5d | ||
|
|
2eeff8a00b | ||
|
|
a403e729d6 | ||
|
|
1c32bf23d7 | ||
|
|
e599b61ac5 | ||
|
|
24234d584d | ||
|
|
a69881e8e5 | ||
|
|
b3897406b0 | ||
|
|
0136d58d59 | ||
|
|
3a74ac9194 | ||
|
|
21a4556f99 | ||
|
|
bcfa9b8593 | ||
|
|
f2dd547451 | ||
|
|
4e31448e3d | ||
|
|
d5d31060c4 | ||
|
|
f5c517d08d | ||
|
|
8d5131ef8f | ||
|
|
0e7c0144cb | ||
|
|
d54bf6d91c | ||
|
|
c50d6c5e74 | ||
|
|
08daa237f2 | ||
|
|
85cda4a995 | ||
|
|
ed9a896f72 | ||
|
|
181dccd8d8 | ||
|
|
7554ac9569 | ||
|
|
fcd46aa243 | ||
|
|
d62b983057 | ||
|
|
b0131a8a80 | ||
|
|
c0003195b8 | ||
|
|
957fc440fd | ||
|
|
4939153f8c | ||
|
|
302f2d10e2 | ||
|
|
eba46dae6c | ||
|
|
27fdd41a6e | ||
|
|
f759c29d55 | ||
|
|
e8076629fa | ||
|
|
77678ecfeb | ||
|
|
273ed40cfb | ||
|
|
a56b5ed8e5 | ||
|
|
d7d75be0d7 | ||
|
|
5ba5b7bf51 | ||
|
|
dcfc11df7a | ||
|
|
f14b577766 | ||
|
|
887663129e | ||
|
|
327256a516 | ||
|
|
ff210c73eb | ||
|
|
a85dd86832 | ||
|
|
c98413c981 | ||
|
|
198d19b920 | ||
|
|
5a2e286de6 | ||
|
|
24ccebc633 | ||
|
|
908944b68b | ||
|
|
cbaffb4858 | ||
|
|
7dcb8bb674 | ||
|
|
a15f0d427c | ||
|
|
436b7e3b70 | ||
|
|
075d456417 | ||
|
|
ff78906361 | ||
|
|
5f3caf6975 | ||
|
|
5c09b28d62 | ||
|
|
b700a0be1f | ||
|
|
cceadcec33 | ||
|
|
22372e69eb | ||
|
|
88c446b0bf | ||
|
|
5cab0aa1d7 | ||
|
|
569b4c256a | ||
|
|
17ea6cc5a5 | ||
|
|
442f54a988 | ||
|
|
c07e61953a | ||
|
|
2102b83bd9 | ||
|
|
f370c7b50b | ||
|
|
f752822bb3 | ||
|
|
af5fb78fe2 | ||
|
|
85cf30cfa8 | ||
|
|
14e5f239ac | ||
|
|
64aad88684 | ||
|
|
fed23536dc | ||
|
|
f126e88e5e | ||
|
|
3f8c701083 | ||
|
|
153ef5f164 | ||
|
|
cbdc46b89e | ||
|
|
b9a249918a | ||
|
|
fb99d3f1b9 | ||
|
|
3b79e24c7e | ||
|
|
97b462538a | ||
|
|
2fe72af359 | ||
|
|
38488c13e6 | ||
|
|
740c17c231 | ||
|
|
5f4b214217 | ||
|
|
5b04bebc0a | ||
|
|
38c31077c9 | ||
|
|
cd613c6033 | ||
|
|
2e1fb5255e | ||
|
|
9fd8fffb28 | ||
|
|
53f235b630 | ||
|
|
2c4867cc98 | ||
|
|
dda057d257 | ||
|
|
ded6e7f544 | ||
|
|
7bb5f6eb23 | ||
|
|
28b1d4e2a3 | ||
|
|
027e66f637 | ||
|
|
944b8a0ed7 | ||
|
|
95da66a201 | ||
|
|
9fbf507877 | ||
|
|
3c3ab897d5 | ||
|
|
60b914b2a6 | ||
|
|
581c5cbc38 | ||
|
|
4e09203ddc | ||
|
|
af883be065 | ||
|
|
08f5f81fa8 | ||
|
|
702c16eac4 | ||
|
|
82440e3124 | ||
|
|
93e510023a | ||
|
|
0bc61b3b16 | ||
|
|
174c7503c9 | ||
|
|
3083b4c218 | ||
|
|
c06e241fbc | ||
|
|
a9e9dad66a | ||
|
|
86e5f362e3 | ||
|
|
7cc98181ec | ||
|
|
1ea778a77b | ||
|
|
a2285d5e5e | ||
|
|
ccd0449006 | ||
|
|
793ee83f39 | ||
|
|
9203217678 | ||
|
|
3c1c50f171 | ||
|
|
a408722983 | ||
|
|
16502b30a0 | ||
|
|
61a1b2b42b | ||
|
|
467ee97938 | ||
|
|
d3cc9d4fa6 | ||
|
|
844f120a56 | ||
|
|
8be12f0858 | ||
|
|
5af006278e | ||
|
|
7ce6729100 | ||
|
|
c4c43bc550 | ||
|
|
62741bf744 | ||
|
|
4568648d8c | ||
|
|
7840fed7a5 | ||
|
|
f2ab220ce3 | ||
|
|
9b6dd7e71f | ||
|
|
64831952d5 | ||
|
|
d53fd8c592 | ||
|
|
2d2eef6b29 | ||
|
|
c313c37006 | ||
|
|
a00bc2967e | ||
|
|
4bac0daf9b | ||
|
|
404668fcc9 | ||
|
|
09d8fa4b5d | ||
|
|
354aaae587 | ||
|
|
7235c3f88f | ||
|
|
15733c14e8 | ||
|
|
22b2386aa1 | ||
|
|
6ab0637b0b | ||
|
|
76ae4d87ca | ||
|
|
3c688dfa6d | ||
|
|
8b8d054ded | ||
|
|
121ec83562 | ||
|
|
26536da74b | ||
|
|
f605564094 | ||
|
|
d14a9a1e89 | ||
|
|
b7e8fbe43f | ||
|
|
00a73c7a57 | ||
|
|
edb100274b | ||
|
|
6976316ada | ||
|
|
357f74a580 | ||
|
|
aedd0397b8 | ||
|
|
4cb685a326 | ||
|
|
afcc0cbccd | ||
|
|
29bca45b1e | ||
|
|
784fb47197 | ||
|
|
5520c6b2f3 | ||
|
|
f873547447 | ||
|
|
d65d2f617d | ||
|
|
0438433a4f | ||
|
|
055556b7f1 | ||
|
|
60fa421409 | ||
|
|
dd2fd12d5f | ||
|
|
319ee41403 | ||
|
|
b96526eefd | ||
|
|
78a992c086 | ||
|
|
f871dd4ef6 | ||
|
|
79ecf7751f | ||
|
|
4698dba402 | ||
|
|
f55881aef8 | ||
|
|
f1b2ba5a84 | ||
|
|
4409f11282 | ||
|
|
84865a8421 | ||
|
|
d9632e8138 | ||
|
|
4bbd0ec985 | ||
|
|
92f8f0ec74 | ||
|
|
aea484be1f | ||
|
|
52fc10d00c | ||
|
|
1b9082e07b | ||
|
|
0e9a1fb40e | ||
|
|
32abf67c80 | ||
|
|
52b42e9492 | ||
|
|
77ac7f06d4 | ||
|
|
8717235a34 | ||
|
|
670426f428 | ||
|
|
1b5a1cbaad | ||
|
|
7ca1beb15d | ||
|
|
c660db145b | ||
|
|
0aaf9c4f05 | ||
|
|
b3b0272ec9 | ||
|
|
f7898b4954 | ||
|
|
b067eae38c | ||
|
|
cee867f941 | ||
|
|
6c4fcd80c6 | ||
|
|
11657effa5 | ||
|
|
d3b7942f32 | ||
|
|
f407c972d1 | ||
|
|
0b72295fa2 | ||
|
|
204b6c0272 | ||
|
|
1118e25b6d | ||
|
|
9b9df70632 | ||
|
|
39ff0cc6d8 | ||
|
|
c407d1e51a | ||
|
|
4d8ced6c87 | ||
|
|
4f832678cf | ||
|
|
f0f7afa669 | ||
|
|
cb6cbf7818 | ||
|
|
3aec1175df | ||
|
|
c645995ae3 | ||
|
|
f772c17f3f | ||
|
|
193502ce03 | ||
|
|
abd637a238 | ||
|
|
4cf9fb08e1 | ||
|
|
e4ca20654f | ||
|
|
bc873939eb | ||
|
|
c6d3e9f432 | ||
|
|
54f4504df6 | ||
|
|
309da96442 | ||
|
|
94ebfa765c | ||
|
|
2577747c5c | ||
|
|
fb387426d6 | ||
|
|
15915d7053 | ||
|
|
362976fa42 | ||
|
|
a7d23abc2f | ||
|
|
4b609322ba | ||
|
|
5778f227ee | ||
|
|
7ac75aab1a | ||
|
|
e831b6e054 | ||
|
|
856a64b77c | ||
|
|
3afb625ff8 | ||
|
|
b6d2de2cc1 | ||
|
|
0bfb774bdf | ||
|
|
d7a8db04a1 | ||
|
|
2d5ae811d2 | ||
|
|
be9607e27b | ||
|
|
fd45a7afbe | ||
|
|
0c73af6ee2 | ||
|
|
981add9b6f | ||
|
|
8f5736c500 | ||
|
|
052f7c3345 | ||
|
|
fa4a40812c | ||
|
|
a76732613e | ||
|
|
dd602989a8 | ||
|
|
78b39d6ca4 | ||
|
|
77fa64e100 | ||
|
|
ac04fc60ef | ||
|
|
6be77d5729 | ||
|
|
37e5b93e2d | ||
|
|
19d29d1861 | ||
|
|
0db912998a | ||
|
|
200a971743 | ||
|
|
c0f0986539 | ||
|
|
376293326b | ||
|
|
9a29e3c9f2 | ||
|
|
e099e10c8e | ||
|
|
3b222339b8 | ||
|
|
9e60dd32e8 | ||
|
|
7ff5414571 | ||
|
|
d48487ada2 | ||
|
|
da69cc5477 | ||
|
|
b2a720d847 | ||
|
|
6656d23e45 | ||
|
|
ae5c05081d | ||
|
|
028e939cca | ||
|
|
216cb9b07b | ||
|
|
50ad612453 | ||
|
|
6b71af9008 | ||
|
|
3c8412efdb | ||
|
|
623f56a95c | ||
|
|
b637d4d5f1 | ||
|
|
601bc64618 | ||
|
|
04d3571dd9 | ||
|
|
aa5aaa113e | ||
|
|
354a9d6169 | ||
|
|
c7e2217c92 | ||
|
|
ce5fc5b457 | ||
|
|
abe18945a6 | ||
|
|
010a0ca0a9 | ||
|
|
74664a34c0 | ||
|
|
82f1dd268d | ||
|
|
1aa96defda | ||
|
|
8fdbbf374d | ||
|
|
66e5202642 | ||
|
|
35e9bfca38 | ||
|
|
735a60807a | ||
|
|
c3fd802351 | ||
|
|
6046f8bc5e | ||
|
|
d7db8ed12e | ||
|
|
adcd21724b | ||
|
|
04bdff736b | ||
|
|
65bb1d8cc2 | ||
|
|
ebf766cf62 | ||
|
|
80bf47170f | ||
|
|
0faa7b0432 | ||
|
|
1d1f12f949 | ||
|
|
4c82533239 | ||
|
|
5a28ba8537 | ||
|
|
8737c10ce4 | ||
|
|
2defb89962 | ||
|
|
53b9d61c46 | ||
|
|
3092131913 | ||
|
|
6dce122825 | ||
|
|
248cc48842 | ||
|
|
ebd8f2f45b | ||
|
|
bb00d38dd7 | ||
|
|
06aead0470 | ||
|
|
cdb1800087 | ||
|
|
13f8edd43f | ||
|
|
85d1a237ce | ||
|
|
dc3c27fd1b | ||
|
|
2d9da22721 | ||
|
|
86a6293e62 | ||
|
|
c311b8ea4f | ||
|
|
e832944dfe | ||
|
|
003cfe2717 | ||
|
|
132b517584 | ||
|
|
d173e06e69 | ||
|
|
9ae0380a96 | ||
|
|
8873ef7b67 | ||
|
|
dc24a657fd | ||
|
|
8c54be7e99 | ||
|
|
7a5d75b68d | ||
|
|
3b9bc8e4ef | ||
|
|
702b5c32c1 | ||
|
|
6c748966e7 |
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -2,7 +2,7 @@ name: Generate Semantic Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- version-13
|
||||
- version-14
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
@@ -13,10 +13,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Setup Node.js v14
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 14
|
||||
node-version: 16
|
||||
|
||||
- name: Setup dependencies
|
||||
run: |
|
||||
npm install @semantic-release/git @semantic-release/exec --no-save
|
||||
@@ -28,4 +30,4 @@ jobs:
|
||||
GIT_AUTHOR_EMAIL: "developers@frappe.io"
|
||||
GIT_COMMITTER_NAME: "Frappe PR Bot"
|
||||
GIT_COMMITTER_EMAIL: "developers@frappe.io"
|
||||
run: npx semantic-release
|
||||
run: npx semantic-release
|
||||
|
||||
2
.github/workflows/server-tests-mariadb.yml
vendored
2
.github/workflows/server-tests-mariadb.yml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
fail-fast: false
|
||||
|
||||
matrix:
|
||||
container: [1, 2, 3, 4]
|
||||
container: [1, 2, 3]
|
||||
|
||||
name: Python Unit Tests
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"branches": ["version-13"],
|
||||
"branches": ["version-14"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"preset": "angular",
|
||||
@@ -10,7 +10,7 @@
|
||||
"@semantic-release/release-notes-generator",
|
||||
[
|
||||
"@semantic-release/exec", {
|
||||
"prepareCmd": 'sed -ir "s/[0-9]*\.[0-9]*\.[0-9]*/${nextRelease.version}/" erpnext/__init__.py'
|
||||
"prepareCmd": 'sed -ir -E "s/\"[0-9]+\.[0-9]+\.[0-9]+\"/\"${nextRelease.version}\"/" erpnext/__init__.py'
|
||||
}
|
||||
],
|
||||
[
|
||||
@@ -21,4 +21,4 @@
|
||||
],
|
||||
"@semantic-release/github"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,8 @@ GNU/General Public License (see [license.txt](license.txt))
|
||||
|
||||
The ERPNext code is licensed as GNU General Public License (v3) and the Documentation is licensed as Creative Commons (CC-BY-SA-3.0) and the copyright is owned by Frappe Technologies Pvt Ltd (Frappe) and Contributors.
|
||||
|
||||
By contributing to ERPNext, you agree that your contributions will be licensed under its GNU General Public License (v3).
|
||||
|
||||
## Logo and Trademark Policy
|
||||
|
||||
Please read our [Logo and Trademark Policy](TRADEMARK_POLICY.md).
|
||||
|
||||
@@ -2,7 +2,7 @@ import inspect
|
||||
|
||||
import frappe
|
||||
|
||||
__version__ = "14.0.0-dev"
|
||||
__version__ = "14.2.2"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -37,7 +37,7 @@ class Account(NestedSet):
|
||||
def autoname(self):
|
||||
from erpnext.accounts.utils import get_autoname_with_number
|
||||
|
||||
self.name = get_autoname_with_number(self.account_number, self.account_name, None, self.company)
|
||||
self.name = get_autoname_with_number(self.account_number, self.account_name, self.company)
|
||||
|
||||
def validate(self):
|
||||
from erpnext.accounts.utils import validate_field_number
|
||||
|
||||
@@ -99,7 +99,7 @@ class BankClearance(Document):
|
||||
.where(loan_disbursement.clearance_date.isnull())
|
||||
.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
|
||||
.orderby(loan_disbursement.disbursement_date)
|
||||
.orderby(loan_disbursement.name, frappe.qb.desc)
|
||||
.orderby(loan_disbursement.name, order=frappe.qb.desc)
|
||||
).run(as_dict=1)
|
||||
|
||||
loan_repayment = frappe.qb.DocType("Loan Repayment")
|
||||
@@ -126,7 +126,9 @@ class BankClearance(Document):
|
||||
if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
|
||||
query = query.where((loan_repayment.repay_from_salary == 0))
|
||||
|
||||
query = query.orderby(loan_repayment.posting_date).orderby(loan_repayment.name, frappe.qb.desc)
|
||||
query = query.orderby(loan_repayment.posting_date).orderby(
|
||||
loan_repayment.name, order=frappe.qb.desc
|
||||
)
|
||||
|
||||
loan_repayments = query.run(as_dict=True)
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
},
|
||||
|
||||
show_import_status(frm) {
|
||||
let import_log = JSON.parse(frm.doc.import_log || "[]");
|
||||
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||
let successful_records = import_log.filter((log) => log.success);
|
||||
let failed_records = import_log.filter((log) => !log.success);
|
||||
if (successful_records.length === 0) return;
|
||||
@@ -309,7 +309,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
// method: 'frappe.core.doctype.data_import.data_import.get_preview_from_template',
|
||||
|
||||
show_import_preview(frm, preview_data) {
|
||||
let import_log = JSON.parse(frm.doc.import_log || "[]");
|
||||
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||
|
||||
if (
|
||||
frm.import_preview &&
|
||||
@@ -439,7 +439,7 @@ frappe.ui.form.on("Bank Statement Import", {
|
||||
},
|
||||
|
||||
show_import_log(frm) {
|
||||
let import_log = JSON.parse(frm.doc.import_log || "[]");
|
||||
let import_log = JSON.parse(frm.doc.statement_import_log || "[]");
|
||||
let logs = import_log;
|
||||
frm.toggle_display("import_log", false);
|
||||
frm.toggle_display("import_log_section", logs.length > 0);
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"section_import_preview",
|
||||
"import_preview",
|
||||
"import_log_section",
|
||||
"import_log",
|
||||
"statement_import_log",
|
||||
"show_failed_logs",
|
||||
"import_log_preview",
|
||||
"reference_doctype",
|
||||
@@ -90,12 +90,6 @@
|
||||
"options": "JSON",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "import_log",
|
||||
"fieldtype": "Code",
|
||||
"label": "Import Log",
|
||||
"options": "JSON"
|
||||
},
|
||||
{
|
||||
"fieldname": "import_log_section",
|
||||
"fieldtype": "Section Break",
|
||||
@@ -198,11 +192,17 @@
|
||||
{
|
||||
"fieldname": "column_break_4",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "statement_import_log",
|
||||
"fieldtype": "Code",
|
||||
"label": "Statement Import Log",
|
||||
"options": "JSON"
|
||||
}
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"links": [],
|
||||
"modified": "2021-05-12 14:17:37.777246",
|
||||
"modified": "2022-09-07 11:11:40.293317",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Bank Statement Import",
|
||||
|
||||
@@ -16,7 +16,7 @@ class CostCenter(NestedSet):
|
||||
from erpnext.accounts.utils import get_autoname_with_number
|
||||
|
||||
self.name = get_autoname_with_number(
|
||||
self.cost_center_number, self.cost_center_name, None, self.company
|
||||
self.cost_center_number, self.cost_center_name, self.company
|
||||
)
|
||||
|
||||
def validate(self):
|
||||
|
||||
@@ -366,7 +366,7 @@ def update_outstanding_amt(
|
||||
if against_voucher_type in ["Sales Invoice", "Purchase Invoice", "Fees"]:
|
||||
ref_doc = frappe.get_doc(against_voucher_type, against_voucher)
|
||||
|
||||
# Didn't use db_set for optimisation purpose
|
||||
# Didn't use db_set for optimization purpose
|
||||
ref_doc.outstanding_amount = bal
|
||||
frappe.db.set_value(against_voucher_type, against_voucher, "outstanding_amount", bal)
|
||||
|
||||
|
||||
@@ -184,7 +184,9 @@ class JournalEntry(AccountsController):
|
||||
}
|
||||
)
|
||||
|
||||
tax_withholding_details = get_party_tax_withholding_details(inv, self.tax_withholding_category)
|
||||
tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details(
|
||||
inv, self.tax_withholding_category
|
||||
)
|
||||
|
||||
if not tax_withholding_details:
|
||||
return
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Journal Entry Template", {
|
||||
setup: function(frm) {
|
||||
refresh: function(frm) {
|
||||
frappe.model.set_default_values(frm.doc);
|
||||
|
||||
frm.set_query("account" ,"accounts", function(){
|
||||
|
||||
@@ -1091,7 +1091,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
|
||||
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
|
||||
|
||||
frm.doc.paid_amount_after_tax = frm.doc.paid_amount;
|
||||
frm.doc.paid_amount_after_tax = frm.doc.base_paid_amount;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1182,7 +1182,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item;
|
||||
frm.doc.paid_amount_after_tax = flt(frm.doc.paid_amount/(1+cumulated_tax_fraction))
|
||||
frm.doc.paid_amount_after_tax = flt(frm.doc.base_paid_amount/(1+cumulated_tax_fraction))
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1214,6 +1214,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.doc.total_taxes_and_charges = 0.0;
|
||||
frm.doc.base_total_taxes_and_charges = 0.0;
|
||||
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
let actual_tax_dict = {};
|
||||
|
||||
// maintain actual tax rate based on idx
|
||||
@@ -1234,8 +1235,8 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
}
|
||||
|
||||
tax.tax_amount = current_tax_amount;
|
||||
tax.base_tax_amount = tax.tax_amount * frm.doc.source_exchange_rate;
|
||||
// tax accounts are only in company currency
|
||||
tax.base_tax_amount = current_tax_amount;
|
||||
current_tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
|
||||
|
||||
if(i==0) {
|
||||
@@ -1244,9 +1245,29 @@ frappe.ui.form.on('Payment Entry', {
|
||||
tax.total = flt(frm.doc["taxes"][i-1].total + current_tax_amount, precision("total", tax));
|
||||
}
|
||||
|
||||
tax.base_total = tax.total * frm.doc.source_exchange_rate;
|
||||
frm.doc.total_taxes_and_charges += current_tax_amount;
|
||||
frm.doc.base_total_taxes_and_charges += current_tax_amount * frm.doc.source_exchange_rate;
|
||||
// tac accounts are only in company currency
|
||||
tax.base_total = tax.total
|
||||
|
||||
// calculate total taxes and base total taxes
|
||||
if(frm.doc.payment_type == "Pay") {
|
||||
// tax accounts only have company currency
|
||||
if(tax.currency != frm.doc.paid_to_account_currency) {
|
||||
//total_taxes_and_charges has the target currency. so using target conversion rate
|
||||
frm.doc.total_taxes_and_charges += flt(current_tax_amount / frm.doc.target_exchange_rate);
|
||||
|
||||
} else {
|
||||
frm.doc.total_taxes_and_charges += current_tax_amount;
|
||||
}
|
||||
} else if(frm.doc.payment_type == "Receive") {
|
||||
if(tax.currency != frm.doc.paid_from_account_currency) {
|
||||
//total_taxes_and_charges has the target currency. so using source conversion rate
|
||||
frm.doc.total_taxes_and_charges += flt(current_tax_amount / frm.doc.source_exchange_rate);
|
||||
} else {
|
||||
frm.doc.total_taxes_and_charges += current_tax_amount;
|
||||
}
|
||||
}
|
||||
|
||||
frm.doc.base_total_taxes_and_charges += tax.base_tax_amount;
|
||||
|
||||
frm.refresh_field('taxes');
|
||||
frm.refresh_field('total_taxes_and_charges');
|
||||
|
||||
@@ -181,7 +181,11 @@ class PaymentEntry(AccountsController):
|
||||
frappe.throw(_("Party is mandatory"))
|
||||
|
||||
_party_name = "title" if self.party_type == "Shareholder" else self.party_type.lower() + "_name"
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
|
||||
|
||||
if frappe.db.has_column(self.party_type, _party_name):
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party, _party_name)
|
||||
else:
|
||||
self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
|
||||
|
||||
if self.party:
|
||||
if not self.party_balance:
|
||||
@@ -295,6 +299,9 @@ class PaymentEntry(AccountsController):
|
||||
def validate_reference_documents(self):
|
||||
valid_reference_doctypes = self.get_valid_reference_doctypes()
|
||||
|
||||
if not valid_reference_doctypes:
|
||||
return
|
||||
|
||||
for d in self.get("references"):
|
||||
if not d.allocated_amount:
|
||||
continue
|
||||
@@ -362,7 +369,7 @@ class PaymentEntry(AccountsController):
|
||||
if not d.allocated_amount:
|
||||
continue
|
||||
|
||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice", "Fees"):
|
||||
if d.reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
outstanding_amount, is_return = frappe.get_cached_value(
|
||||
d.reference_doctype, d.reference_name, ["outstanding_amount", "is_return"]
|
||||
)
|
||||
@@ -933,6 +940,13 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
if not d.included_in_paid_amount:
|
||||
if get_account_currency(payment_account) != self.company_currency:
|
||||
if self.payment_type == "Receive":
|
||||
exchange_rate = self.target_exchange_rate
|
||||
elif self.payment_type in ["Pay", "Internal Transfer"]:
|
||||
exchange_rate = self.source_exchange_rate
|
||||
base_tax_amount = flt((tax_amount / exchange_rate), self.precision("paid_amount"))
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
@@ -1026,7 +1040,7 @@ class PaymentEntry(AccountsController):
|
||||
for fieldname in tax_fields:
|
||||
tax.set(fieldname, 0.0)
|
||||
|
||||
self.paid_amount_after_tax = self.paid_amount
|
||||
self.paid_amount_after_tax = self.base_paid_amount
|
||||
|
||||
def determine_exclusive_rate(self):
|
||||
if not any(cint(tax.included_in_paid_amount) for tax in self.get("taxes")):
|
||||
@@ -1045,7 +1059,7 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
cumulated_tax_fraction += tax.tax_fraction_for_current_item
|
||||
|
||||
self.paid_amount_after_tax = flt(self.paid_amount / (1 + cumulated_tax_fraction))
|
||||
self.paid_amount_after_tax = flt(self.base_paid_amount / (1 + cumulated_tax_fraction))
|
||||
|
||||
def calculate_taxes(self):
|
||||
self.total_taxes_and_charges = 0.0
|
||||
@@ -1068,7 +1082,7 @@ class PaymentEntry(AccountsController):
|
||||
current_tax_amount += actual_tax_dict[tax.idx]
|
||||
|
||||
tax.tax_amount = current_tax_amount
|
||||
tax.base_tax_amount = tax.tax_amount * self.source_exchange_rate
|
||||
tax.base_tax_amount = current_tax_amount
|
||||
|
||||
if tax.add_deduct_tax == "Deduct":
|
||||
current_tax_amount *= -1.0
|
||||
@@ -1082,14 +1096,20 @@ class PaymentEntry(AccountsController):
|
||||
self.get("taxes")[i - 1].total + current_tax_amount, self.precision("total", tax)
|
||||
)
|
||||
|
||||
tax.base_total = tax.total * self.source_exchange_rate
|
||||
tax.base_total = tax.total
|
||||
|
||||
if self.payment_type == "Pay":
|
||||
self.base_total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
|
||||
self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
|
||||
else:
|
||||
self.base_total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
|
||||
self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
|
||||
if tax.currency != self.paid_to_account_currency:
|
||||
self.total_taxes_and_charges += flt(current_tax_amount / self.target_exchange_rate)
|
||||
else:
|
||||
self.total_taxes_and_charges += current_tax_amount
|
||||
elif self.payment_type == "Receive":
|
||||
if tax.currency != self.paid_from_account_currency:
|
||||
self.total_taxes_and_charges += flt(current_tax_amount / self.source_exchange_rate)
|
||||
else:
|
||||
self.total_taxes_and_charges += current_tax_amount
|
||||
|
||||
self.base_total_taxes_and_charges += tax.base_tax_amount
|
||||
|
||||
if self.get("taxes"):
|
||||
self.paid_amount_after_tax = self.get("taxes")[-1].base_total
|
||||
@@ -1184,6 +1204,7 @@ def get_outstanding_reference_documents(args):
|
||||
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
common_filter = []
|
||||
posting_and_due_date = []
|
||||
|
||||
# confirm that Supplier is not blocked
|
||||
if args.get("party_type") == "Supplier":
|
||||
@@ -1200,7 +1221,7 @@ def get_outstanding_reference_documents(args):
|
||||
party_account_currency = get_account_currency(args.get("party_account"))
|
||||
company_currency = frappe.get_cached_value("Company", args.get("company"), "default_currency")
|
||||
|
||||
# Get positive outstanding sales /purchase invoices/ Fees
|
||||
# Get positive outstanding sales /purchase invoices
|
||||
condition = ""
|
||||
if args.get("voucher_type") and args.get("voucher_no"):
|
||||
condition = " and voucher_type={0} and voucher_no={1}".format(
|
||||
@@ -1224,7 +1245,7 @@ def get_outstanding_reference_documents(args):
|
||||
condition += " and {0} between '{1}' and '{2}'".format(
|
||||
fieldname, args.get(date_fields[0]), args.get(date_fields[1])
|
||||
)
|
||||
common_filter.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
|
||||
posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
|
||||
|
||||
if args.get("company"):
|
||||
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
|
||||
@@ -1235,6 +1256,7 @@ def get_outstanding_reference_documents(args):
|
||||
args.get("party"),
|
||||
args.get("party_account"),
|
||||
common_filter=common_filter,
|
||||
posting_date=posting_and_due_date,
|
||||
min_outstanding=args.get("outstanding_amt_greater_than"),
|
||||
max_outstanding=args.get("outstanding_amt_less_than"),
|
||||
)
|
||||
@@ -1595,10 +1617,11 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
elif reference_doctype != "Journal Entry":
|
||||
if not total_amount:
|
||||
if party_account_currency == company_currency:
|
||||
total_amount = ref_doc.base_grand_total
|
||||
# for handling cases that don't have multi-currency (base field)
|
||||
total_amount = ref_doc.get("grand_total") or ref_doc.get("base_grand_total")
|
||||
exchange_rate = 1
|
||||
else:
|
||||
total_amount = ref_doc.grand_total
|
||||
total_amount = ref_doc.get("grand_total")
|
||||
if not exchange_rate:
|
||||
# Get the exchange rate from the original ref doc
|
||||
# or get it based on the posting date of the ref doc.
|
||||
@@ -1609,7 +1632,7 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
if reference_doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
outstanding_amount = ref_doc.get("outstanding_amount")
|
||||
else:
|
||||
outstanding_amount = flt(total_amount) - flt(ref_doc.advance_paid)
|
||||
outstanding_amount = flt(total_amount) - flt(ref_doc.get("advance_paid"))
|
||||
|
||||
else:
|
||||
# Get the exchange rate based on the posting date of the ref doc.
|
||||
@@ -1627,16 +1650,23 @@ def get_reference_details(reference_doctype, reference_name, party_account_curre
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_payment_entry(dt, dn, party_amount=None, bank_account=None, bank_amount=None):
|
||||
def get_payment_entry(
|
||||
dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
|
||||
):
|
||||
reference_doc = None
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
|
||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||
|
||||
party_type = set_party_type(dt)
|
||||
if not party_type:
|
||||
party_type = set_party_type(dt)
|
||||
|
||||
party_account = set_party_account(dt, dn, doc, party_type)
|
||||
party_account_currency = set_party_account_currency(dt, party_account, doc)
|
||||
payment_type = set_payment_type(dt, doc)
|
||||
|
||||
if not payment_type:
|
||||
payment_type = set_payment_type(dt, doc)
|
||||
|
||||
grand_total, outstanding_amount = set_grand_total_and_outstanding_amount(
|
||||
party_amount, dt, party_account_currency, doc
|
||||
)
|
||||
@@ -1786,8 +1816,6 @@ def set_party_account(dt, dn, doc, party_type):
|
||||
party_account = get_party_account_based_on_invoice_discounting(dn) or doc.debit_to
|
||||
elif dt == "Purchase Invoice":
|
||||
party_account = doc.credit_to
|
||||
elif dt == "Fees":
|
||||
party_account = doc.receivable_account
|
||||
else:
|
||||
party_account = get_party_account(party_type, doc.get(party_type.lower()), doc.company)
|
||||
return party_account
|
||||
@@ -1803,8 +1831,7 @@ def set_party_account_currency(dt, party_account, doc):
|
||||
|
||||
def set_payment_type(dt, doc):
|
||||
if (
|
||||
dt == "Sales Order"
|
||||
or (dt in ("Sales Invoice", "Fees", "Dunning") and doc.outstanding_amount > 0)
|
||||
dt == "Sales Order" or (dt in ("Sales Invoice", "Dunning") and doc.outstanding_amount > 0)
|
||||
) or (dt == "Purchase Invoice" and doc.outstanding_amount < 0):
|
||||
payment_type = "Receive"
|
||||
else:
|
||||
@@ -1822,18 +1849,15 @@ def set_grand_total_and_outstanding_amount(party_amount, dt, party_account_curre
|
||||
else:
|
||||
grand_total = doc.rounded_total or doc.grand_total
|
||||
outstanding_amount = doc.outstanding_amount
|
||||
elif dt == "Fees":
|
||||
grand_total = doc.grand_total
|
||||
outstanding_amount = doc.outstanding_amount
|
||||
elif dt == "Dunning":
|
||||
grand_total = doc.grand_total
|
||||
outstanding_amount = doc.grand_total
|
||||
else:
|
||||
if party_account_currency == doc.company_currency:
|
||||
grand_total = flt(doc.get("base_rounded_total") or doc.base_grand_total)
|
||||
grand_total = flt(doc.get("base_rounded_total") or doc.get("base_grand_total"))
|
||||
else:
|
||||
grand_total = flt(doc.get("rounded_total") or doc.grand_total)
|
||||
outstanding_amount = grand_total - flt(doc.advance_paid)
|
||||
grand_total = flt(doc.get("rounded_total") or doc.get("grand_total"))
|
||||
outstanding_amount = doc.get("outstanding_amount") or (grand_total - flt(doc.advance_paid))
|
||||
return grand_total, outstanding_amount
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import flt, nowdate
|
||||
|
||||
@@ -722,6 +723,46 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
flt(payment_entry.total_taxes_and_charges, 2), flt(10 / payment_entry.target_exchange_rate, 2)
|
||||
)
|
||||
|
||||
def test_gl_of_multi_currency_payment_with_taxes(self):
|
||||
payment_entry = create_payment_entry(
|
||||
party="_Test Supplier USD", paid_to="_Test Payable USD - _TC", save=True
|
||||
)
|
||||
payment_entry.append(
|
||||
"taxes",
|
||||
{
|
||||
"account_head": "_Test Account Service Tax - _TC",
|
||||
"charge_type": "Actual",
|
||||
"tax_amount": 100,
|
||||
"add_deduct_tax": "Add",
|
||||
"description": "Test",
|
||||
},
|
||||
)
|
||||
payment_entry.target_exchange_rate = 80
|
||||
payment_entry.received_amount = 12.5
|
||||
payment_entry = payment_entry.submit()
|
||||
gle = qb.DocType("GL Entry")
|
||||
gl_entries = (
|
||||
qb.from_(gle)
|
||||
.select(
|
||||
gle.account,
|
||||
gle.debit,
|
||||
gle.credit,
|
||||
gle.debit_in_account_currency,
|
||||
gle.credit_in_account_currency,
|
||||
)
|
||||
.orderby(gle.account)
|
||||
.where(gle.voucher_no == payment_entry.name)
|
||||
.run()
|
||||
)
|
||||
|
||||
expected_gl_entries = (
|
||||
("_Test Account Service Tax - _TC", 100.0, 0.0, 100.0, 0.0),
|
||||
("_Test Bank - _TC", 0.0, 1100.0, 0.0, 1100.0),
|
||||
("_Test Payable USD - _TC", 1000.0, 0.0, 12.5, 0),
|
||||
)
|
||||
|
||||
self.assertEqual(gl_entries, expected_gl_entries)
|
||||
|
||||
def test_payment_entry_against_onhold_purchase_invoice(self):
|
||||
pi = make_purchase_invoice()
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"amount",
|
||||
"account_currency",
|
||||
"amount_in_account_currency",
|
||||
"delinked"
|
||||
"delinked",
|
||||
"remarks"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -136,12 +137,17 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname": "remarks",
|
||||
"fieldtype": "Text",
|
||||
"label": "Remarks"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2022-07-11 09:13:54.379168",
|
||||
"modified": "2022-08-22 15:32:56.629430",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Ledger Entry",
|
||||
|
||||
@@ -14,6 +14,7 @@ from erpnext.accounts.utils import (
|
||||
QueryPaymentLedger,
|
||||
get_outstanding_invoices,
|
||||
reconcile_against_document,
|
||||
update_reference_in_payment_entry,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import get_advance_payment_entries
|
||||
|
||||
@@ -22,6 +23,7 @@ class PaymentReconciliation(Document):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(PaymentReconciliation, self).__init__(*args, **kwargs)
|
||||
self.common_filter_conditions = []
|
||||
self.ple_posting_date_filter = []
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_unreconciled_entries(self):
|
||||
@@ -150,6 +152,7 @@ class PaymentReconciliation(Document):
|
||||
return_outstanding = ple_query.get_voucher_outstandings(
|
||||
vouchers=return_invoices,
|
||||
common_filter=self.common_filter_conditions,
|
||||
posting_date=self.ple_posting_date_filter,
|
||||
min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
|
||||
max_outstanding=-(self.maximum_payment_amount) if self.maximum_payment_amount else None,
|
||||
get_payments=True,
|
||||
@@ -187,6 +190,7 @@ class PaymentReconciliation(Document):
|
||||
self.party,
|
||||
self.receivable_payable_account,
|
||||
common_filter=self.common_filter_conditions,
|
||||
posting_date=self.ple_posting_date_filter,
|
||||
min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
|
||||
max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
|
||||
)
|
||||
@@ -209,6 +213,23 @@ class PaymentReconciliation(Document):
|
||||
inv.currency = entry.get("currency")
|
||||
inv.outstanding_amount = flt(entry.get("outstanding_amount"))
|
||||
|
||||
def get_difference_amount(self, allocated_entry):
|
||||
if allocated_entry.get("reference_type") != "Payment Entry":
|
||||
return
|
||||
|
||||
dr_or_cr = (
|
||||
"credit_in_account_currency"
|
||||
if erpnext.get_party_account_type(self.party_type) == "Receivable"
|
||||
else "debit_in_account_currency"
|
||||
)
|
||||
|
||||
row = self.get_payment_details(allocated_entry, dr_or_cr)
|
||||
|
||||
doc = frappe.get_doc(allocated_entry.reference_type, allocated_entry.reference_name)
|
||||
update_reference_in_payment_entry(row, doc, do_not_save=True)
|
||||
|
||||
return doc.difference_amount
|
||||
|
||||
@frappe.whitelist()
|
||||
def allocate_entries(self, args):
|
||||
self.validate_entries()
|
||||
@@ -224,12 +245,16 @@ class PaymentReconciliation(Document):
|
||||
res = self.get_allocated_entry(pay, inv, pay["amount"])
|
||||
inv["outstanding_amount"] = flt(inv.get("outstanding_amount")) - flt(pay.get("amount"))
|
||||
pay["amount"] = 0
|
||||
|
||||
res.difference_amount = self.get_difference_amount(res)
|
||||
|
||||
if pay.get("amount") == 0:
|
||||
entries.append(res)
|
||||
break
|
||||
elif inv.get("outstanding_amount") == 0:
|
||||
entries.append(res)
|
||||
continue
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -350,6 +375,7 @@ class PaymentReconciliation(Document):
|
||||
|
||||
def build_qb_filter_conditions(self, get_invoices=False, get_return_invoices=False):
|
||||
self.common_filter_conditions.clear()
|
||||
self.ple_posting_date_filter.clear()
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
|
||||
self.common_filter_conditions.append(ple.company == self.company)
|
||||
@@ -359,15 +385,15 @@ class PaymentReconciliation(Document):
|
||||
|
||||
if get_invoices:
|
||||
if self.from_invoice_date:
|
||||
self.common_filter_conditions.append(ple.posting_date.gte(self.from_invoice_date))
|
||||
self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_invoice_date))
|
||||
if self.to_invoice_date:
|
||||
self.common_filter_conditions.append(ple.posting_date.lte(self.to_invoice_date))
|
||||
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_invoice_date))
|
||||
|
||||
elif get_return_invoices:
|
||||
if self.from_payment_date:
|
||||
self.common_filter_conditions.append(ple.posting_date.gte(self.from_payment_date))
|
||||
self.ple_posting_date_filter.append(ple.posting_date.gte(self.from_payment_date))
|
||||
if self.to_payment_date:
|
||||
self.common_filter_conditions.append(ple.posting_date.lte(self.to_payment_date))
|
||||
self.ple_posting_date_filter.append(ple.posting_date.lte(self.to_payment_date))
|
||||
|
||||
def get_conditions(self, get_payments=False):
|
||||
condition = " and company = '{0}' ".format(self.company)
|
||||
|
||||
@@ -283,6 +283,41 @@ class TestPaymentReconciliation(FrappeTestCase):
|
||||
self.assertEqual(len(pr.get("invoices")), 2)
|
||||
self.assertEqual(len(pr.get("payments")), 2)
|
||||
|
||||
def test_filter_posting_date_case2(self):
|
||||
"""
|
||||
Posting date should not affect outstanding amount calculation
|
||||
"""
|
||||
|
||||
from_date = add_days(nowdate(), -30)
|
||||
to_date = nowdate()
|
||||
self.create_payment_entry(amount=25, posting_date=from_date).submit()
|
||||
self.create_sales_invoice(rate=25, qty=1, posting_date=to_date)
|
||||
|
||||
pr = self.create_payment_reconciliation()
|
||||
pr.from_invoice_date = pr.from_payment_date = from_date
|
||||
pr.to_invoice_date = pr.to_payment_date = to_date
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 1)
|
||||
self.assertEqual(len(pr.payments), 1)
|
||||
|
||||
invoices = [x.as_dict() for x in pr.invoices]
|
||||
payments = [x.as_dict() for x in pr.payments]
|
||||
pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
|
||||
pr.reconcile()
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
self.assertEqual(len(pr.payments), 0)
|
||||
|
||||
pr.from_invoice_date = pr.from_payment_date = to_date
|
||||
pr.to_invoice_date = pr.to_payment_date = to_date
|
||||
|
||||
pr.get_unreconciled_entries()
|
||||
|
||||
self.assertEqual(len(pr.invoices), 0)
|
||||
|
||||
def test_filter_invoice_limit(self):
|
||||
# check filter condition - invoice limit
|
||||
transaction_date = nowdate()
|
||||
|
||||
@@ -186,8 +186,10 @@
|
||||
{
|
||||
"fetch_from": "bank_account.bank",
|
||||
"fieldname": "bank",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "Bank"
|
||||
"fieldtype": "Link",
|
||||
"label": "Bank",
|
||||
"options": "Bank",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "bank_account.bank_account_no",
|
||||
@@ -366,10 +368,11 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-09-18 12:24:14.178853",
|
||||
"modified": "2022-09-30 16:19:43.680025",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -401,5 +404,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -39,6 +39,7 @@
|
||||
{
|
||||
"columns": 2,
|
||||
"fetch_from": "payment_term.description",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "description",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
@@ -159,7 +160,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-04-28 05:41:35.084233",
|
||||
"modified": "2022-09-16 13:57:06.382859",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Schedule",
|
||||
@@ -168,5 +169,6 @@
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -36,6 +36,15 @@ frappe.ui.form.on('POS Closing Entry', {
|
||||
});
|
||||
|
||||
set_html_data(frm);
|
||||
|
||||
if (frm.doc.docstatus == 1) {
|
||||
if (!frm.doc.posting_date) {
|
||||
frm.set_value("posting_date", frappe.datetime.nowdate());
|
||||
}
|
||||
if (!frm.doc.posting_time) {
|
||||
frm.set_value("posting_time", frappe.datetime.now_time());
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"period_end_date",
|
||||
"column_break_3",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"pos_opening_entry",
|
||||
"status",
|
||||
"section_break_5",
|
||||
@@ -51,7 +52,6 @@
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
"label": "Period End Date",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -219,6 +219,13 @@
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Error",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Posting Time",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"is_submittable": 1,
|
||||
@@ -228,10 +235,11 @@
|
||||
"link_fieldname": "pos_closing_entry"
|
||||
}
|
||||
],
|
||||
"modified": "2021-10-20 16:19:25.340565",
|
||||
"modified": "2022-08-01 11:37:14.991228",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Closing Entry",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -278,5 +286,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -15,6 +15,9 @@ from erpnext.controllers.status_updater import StatusUpdater
|
||||
|
||||
class POSClosingEntry(StatusUpdater):
|
||||
def validate(self):
|
||||
self.posting_date = self.posting_date or frappe.utils.nowdate()
|
||||
self.posting_time = self.posting_time or frappe.utils.nowtime()
|
||||
|
||||
if frappe.db.get_value("POS Opening Entry", self.pos_opening_entry, "status") != "Open":
|
||||
frappe.throw(_("Selected POS Opening Entry should be open."), title=_("Invalid Opening Entry"))
|
||||
|
||||
|
||||
@@ -1553,7 +1553,7 @@
|
||||
"icon": "fa fa-file-text",
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-03-22 13:00:24.166684",
|
||||
"modified": "2022-09-27 13:00:24.166684",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice",
|
||||
|
||||
@@ -239,14 +239,14 @@ class POSInvoice(SalesInvoice):
|
||||
frappe.bold(d.warehouse),
|
||||
frappe.bold(d.qty),
|
||||
)
|
||||
if flt(available_stock) <= 0:
|
||||
if is_stock_item and flt(available_stock) <= 0:
|
||||
frappe.throw(
|
||||
_("Row #{}: Item Code: {} is not available under warehouse {}.").format(
|
||||
d.idx, item_code, warehouse
|
||||
),
|
||||
title=_("Item Unavailable"),
|
||||
)
|
||||
elif flt(available_stock) < flt(d.qty):
|
||||
elif is_stock_item and flt(available_stock) < flt(d.qty):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
|
||||
@@ -632,11 +632,12 @@ def get_stock_availability(item_code, warehouse):
|
||||
pos_sales_qty = get_pos_reserved_qty(item_code, warehouse)
|
||||
return bin_qty - pos_sales_qty, is_stock_item
|
||||
else:
|
||||
is_stock_item = False
|
||||
is_stock_item = True
|
||||
if frappe.db.exists("Product Bundle", item_code):
|
||||
return get_bundle_availability(item_code, warehouse), is_stock_item
|
||||
else:
|
||||
# Is a service item
|
||||
is_stock_item = False
|
||||
# Is a service item or non_stock item
|
||||
return 0, is_stock_item
|
||||
|
||||
|
||||
@@ -650,7 +651,9 @@ def get_bundle_availability(bundle_item_code, warehouse):
|
||||
available_qty = item_bin_qty - item_pos_reserved_qty
|
||||
|
||||
max_available_bundles = available_qty / item.qty
|
||||
if bundle_bin_qty > max_available_bundles:
|
||||
if bundle_bin_qty > max_available_bundles and frappe.get_value(
|
||||
"Item", item.item_code, "is_stock_item"
|
||||
):
|
||||
bundle_bin_qty = max_available_bundles
|
||||
|
||||
pos_sales_qty = get_pos_reserved_qty(bundle_item_code, warehouse)
|
||||
@@ -740,3 +743,7 @@ def add_return_modes(doc, pos_profile):
|
||||
]:
|
||||
payment_mode = get_mode_of_payment_info(mode_of_payment, doc.company)
|
||||
append_payment(payment_mode[0])
|
||||
|
||||
|
||||
def on_doctype_update():
|
||||
frappe.db.add_index("POS Invoice", ["return_against"])
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"merge_invoices_based_on",
|
||||
"column_break_3",
|
||||
"pos_closing_entry",
|
||||
@@ -105,12 +106,19 @@
|
||||
"label": "Customer Group",
|
||||
"mandatory_depends_on": "eval:doc.merge_invoices_based_on == 'Customer Group'",
|
||||
"options": "Customer Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Posting Time",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-14 11:17:19.001142",
|
||||
"modified": "2022-08-01 11:36:42.456429",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Invoice Merge Log",
|
||||
@@ -173,5 +181,6 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -9,7 +9,7 @@ from frappe import _
|
||||
from frappe.core.page.background_jobs.background_jobs import get_info
|
||||
from frappe.model.document import Document
|
||||
from frappe.model.mapper import map_child_doc, map_doc
|
||||
from frappe.utils import cint, flt, getdate, nowdate
|
||||
from frappe.utils import cint, flt, get_time, getdate, nowdate, nowtime
|
||||
from frappe.utils.background_jobs import enqueue
|
||||
from frappe.utils.scheduler import is_scheduler_inactive
|
||||
|
||||
@@ -99,6 +99,7 @@ class POSInvoiceMergeLog(Document):
|
||||
sales_invoice.is_consolidated = 1
|
||||
sales_invoice.set_posting_time = 1
|
||||
sales_invoice.posting_date = getdate(self.posting_date)
|
||||
sales_invoice.posting_time = get_time(self.posting_time)
|
||||
sales_invoice.save()
|
||||
sales_invoice.submit()
|
||||
|
||||
@@ -115,6 +116,7 @@ class POSInvoiceMergeLog(Document):
|
||||
credit_note.is_consolidated = 1
|
||||
credit_note.set_posting_time = 1
|
||||
credit_note.posting_date = getdate(self.posting_date)
|
||||
credit_note.posting_time = get_time(self.posting_time)
|
||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||
# credit_note.return_against = self.consolidated_invoice
|
||||
credit_note.save()
|
||||
@@ -402,6 +404,9 @@ def create_merge_logs(invoice_by_customer, closing_entry=None):
|
||||
merge_log.posting_date = (
|
||||
getdate(closing_entry.get("posting_date")) if closing_entry else nowdate()
|
||||
)
|
||||
merge_log.posting_time = (
|
||||
get_time(closing_entry.get("posting_time")) if closing_entry else nowtime()
|
||||
)
|
||||
merge_log.customer = customer
|
||||
merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else None
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"currency",
|
||||
"write_off_account",
|
||||
"write_off_cost_center",
|
||||
"write_off_limit",
|
||||
"account_for_change_amount",
|
||||
"disable_rounded_total",
|
||||
"column_break_23",
|
||||
@@ -360,6 +361,14 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Validate Stock on Save"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"description": "Auto write off precision loss while consolidation",
|
||||
"fieldname": "write_off_limit",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Write Off Limit",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, the consolidated invoices will have rounded total disabled",
|
||||
@@ -393,7 +402,7 @@
|
||||
"link_fieldname": "pos_profile"
|
||||
}
|
||||
],
|
||||
"modified": "2022-07-21 11:16:46.911173",
|
||||
"modified": "2022-08-10 12:57:06.241439",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "POS Profile",
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "eval:doc.apply_on != 'Transaction'",
|
||||
"depends_on": "eval:doc.apply_on != 'Transaction' && !doc.mixed_conditions",
|
||||
"fieldname": "section_break_18",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount on Other Item"
|
||||
@@ -297,12 +297,12 @@
|
||||
{
|
||||
"fieldname": "min_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Min Qty"
|
||||
"label": "Min Qty (As Per Stock UOM)"
|
||||
},
|
||||
{
|
||||
"fieldname": "max_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Max Qty"
|
||||
"label": "Max Qty (As Per Stock UOM)"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_21",
|
||||
@@ -481,7 +481,7 @@
|
||||
"description": "System will notify to increase or decrease quantity or amount ",
|
||||
"fieldname": "threshold_percentage",
|
||||
"fieldtype": "Percent",
|
||||
"label": "Threshold for Suggestion"
|
||||
"label": "Threshold for Suggestion (In Percentage)"
|
||||
},
|
||||
{
|
||||
"description": "Higher the number, higher the priority",
|
||||
@@ -583,10 +583,11 @@
|
||||
"icon": "fa fa-gift",
|
||||
"idx": 1,
|
||||
"links": [],
|
||||
"modified": "2021-08-06 15:10:04.219321",
|
||||
"modified": "2022-09-16 16:00:38.356266",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Pricing Rule",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -642,5 +643,6 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title"
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,7 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
|
||||
if isinstance(pricing_rule, str):
|
||||
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
|
||||
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
|
||||
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or []
|
||||
|
||||
if pricing_rule.get("suggestion"):
|
||||
continue
|
||||
@@ -337,7 +337,6 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
if pricing_rule.mixed_conditions or pricing_rule.apply_rule_on_other:
|
||||
item_details.update(
|
||||
{
|
||||
"apply_rule_on_other_items": json.dumps(pricing_rule.apply_rule_on_other_items),
|
||||
"price_or_product_discount": pricing_rule.price_or_product_discount,
|
||||
"apply_rule_on": (
|
||||
frappe.scrub(pricing_rule.apply_rule_on_other)
|
||||
@@ -347,6 +346,9 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
}
|
||||
)
|
||||
|
||||
if pricing_rule.apply_rule_on_other_items:
|
||||
item_details["apply_rule_on_other_items"] = json.dumps(pricing_rule.apply_rule_on_other_items)
|
||||
|
||||
if pricing_rule.coupon_code_based == 1 and args.coupon_code == None:
|
||||
return item_details
|
||||
|
||||
@@ -492,7 +494,7 @@ def remove_pricing_rule_for_item(pricing_rules, item_details, item_code=None, ra
|
||||
)
|
||||
|
||||
if pricing_rule.get("mixed_conditions") or pricing_rule.get("apply_rule_on_other"):
|
||||
items = get_pricing_rule_items(pricing_rule)
|
||||
items = get_pricing_rule_items(pricing_rule, other_items=True)
|
||||
item_details.apply_on = (
|
||||
frappe.scrub(pricing_rule.apply_rule_on_other)
|
||||
if pricing_rule.apply_rule_on_other
|
||||
|
||||
@@ -766,6 +766,68 @@ class TestPricingRule(unittest.TestCase):
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 1")
|
||||
frappe.delete_doc_if_exists("Pricing Rule", "_Test Pricing Rule with Min Qty - 2")
|
||||
|
||||
def test_pricing_rule_for_other_items_cond_with_amount(self):
|
||||
item = make_item("Water Flask New")
|
||||
other_item = make_item("Other Water Flask New")
|
||||
make_item_price(item.name, "_Test Price List", 100)
|
||||
make_item_price(other_item.name, "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Water Flask Rule",
|
||||
"apply_on": "Item Code",
|
||||
"apply_rule_on_other": "Item Code",
|
||||
"price_or_product_discount": "Price",
|
||||
"rate_or_discount": "Discount Percentage",
|
||||
"other_item_code": other_item.name,
|
||||
"items": [
|
||||
{
|
||||
"item_code": item.name,
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"currency": "INR",
|
||||
"min_amt": 200,
|
||||
"discount_percentage": 10,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
rule = frappe.get_doc(pricing_rule_record)
|
||||
rule.insert()
|
||||
|
||||
si = create_sales_invoice(do_not_save=True, item_code=item.name)
|
||||
si.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": other_item.name,
|
||||
"item_name": other_item.item_name,
|
||||
"description": other_item.description,
|
||||
"stock_uom": other_item.stock_uom,
|
||||
"uom": other_item.stock_uom,
|
||||
"cost_center": si.items[0].cost_center,
|
||||
"expense_account": si.items[0].expense_account,
|
||||
"warehouse": si.items[0].warehouse,
|
||||
"conversion_factor": 1,
|
||||
"qty": 1,
|
||||
},
|
||||
)
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
self.assertEqual(si.items[0].discount_percentage, 0)
|
||||
self.assertEqual(si.items[1].discount_percentage, 0)
|
||||
|
||||
si.items[0].qty = 2
|
||||
si.save()
|
||||
|
||||
self.assertEqual(si.items[0].discount_percentage, 0)
|
||||
self.assertEqual(si.items[0].stock_qty, 2)
|
||||
self.assertEqual(si.items[0].amount, 200)
|
||||
self.assertEqual(si.items[0].price_list_rate, 100)
|
||||
self.assertEqual(si.items[1].discount_percentage, 10)
|
||||
|
||||
si.delete()
|
||||
rule.delete()
|
||||
|
||||
|
||||
test_dependencies = ["Campaign"]
|
||||
|
||||
|
||||
@@ -252,12 +252,6 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
||||
stock_qty = flt(args.get("stock_qty"))
|
||||
amount = flt(args.get("price_list_rate")) * flt(args.get("qty"))
|
||||
|
||||
if pricing_rules[0].apply_rule_on_other:
|
||||
field = frappe.scrub(pricing_rules[0].apply_rule_on_other)
|
||||
|
||||
if field and pricing_rules[0].get("other_" + field) != args.get(field):
|
||||
return
|
||||
|
||||
pr_doc = frappe.get_cached_doc("Pricing Rule", pricing_rules[0].name)
|
||||
|
||||
if pricing_rules[0].mixed_conditions and doc:
|
||||
@@ -274,7 +268,7 @@ def filter_pricing_rules(args, pricing_rules, doc=None):
|
||||
amount += data[1]
|
||||
|
||||
if pricing_rules[0].apply_rule_on_other and not pricing_rules[0].mixed_conditions and doc:
|
||||
pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules) or []
|
||||
pricing_rules = get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, args) or []
|
||||
else:
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, args)
|
||||
|
||||
@@ -352,16 +346,14 @@ def validate_quantity_and_amount_for_suggestion(args, qty, amount, item_code, tr
|
||||
if fieldname:
|
||||
msg = _(
|
||||
"If you {0} {1} quantities of the item {2}, the scheme {3} will be applied on the item."
|
||||
).format(
|
||||
type_of_transaction, args.get(fieldname), bold(item_code), bold(args.rule_description)
|
||||
)
|
||||
).format(type_of_transaction, args.get(fieldname), bold(item_code), bold(args.title))
|
||||
|
||||
if fieldname in ["min_amt", "max_amt"]:
|
||||
msg = _("If you {0} {1} worth item {2}, the scheme {3} will be applied on the item.").format(
|
||||
type_of_transaction,
|
||||
fmt_money(args.get(fieldname), currency=args.get("currency")),
|
||||
bold(item_code),
|
||||
bold(args.rule_description),
|
||||
bold(args.title),
|
||||
)
|
||||
|
||||
frappe.msgprint(msg)
|
||||
@@ -454,17 +446,29 @@ def get_qty_and_rate_for_mixed_conditions(doc, pr_doc, args):
|
||||
return sum_qty, sum_amt, items
|
||||
|
||||
|
||||
def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules):
|
||||
items = get_pricing_rule_items(pr_doc)
|
||||
def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, row_item):
|
||||
other_items = get_pricing_rule_items(pr_doc, other_items=True)
|
||||
pricing_rule_apply_on = apply_on_table.get(pr_doc.get("apply_on"))
|
||||
apply_on = frappe.scrub(pr_doc.get("apply_on"))
|
||||
|
||||
items = []
|
||||
for d in pr_doc.get(pricing_rule_apply_on):
|
||||
if apply_on == "item_group":
|
||||
items.extend(get_child_item_groups(d.get(apply_on)))
|
||||
else:
|
||||
items.append(d.get(apply_on))
|
||||
|
||||
for row in doc.items:
|
||||
if row.get(frappe.scrub(pr_doc.apply_rule_on_other)) in items:
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(
|
||||
row.get("stock_qty"), row.get("amount"), pricing_rules, row
|
||||
)
|
||||
if row.get(apply_on) in items:
|
||||
if not row.get("qty"):
|
||||
continue
|
||||
|
||||
stock_qty = row.get("qty") * (row.get("conversion_factor") or 1.0)
|
||||
amount = stock_qty * (row.get("price_list_rate") or row.get("rate"))
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, row)
|
||||
|
||||
if pricing_rules and pricing_rules[0]:
|
||||
pricing_rules[0].apply_rule_on_other_items = items
|
||||
pricing_rules[0].apply_rule_on_other_items = other_items
|
||||
return pricing_rules
|
||||
|
||||
|
||||
@@ -658,21 +662,21 @@ def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values
|
||||
doc.append("items", args)
|
||||
|
||||
|
||||
def get_pricing_rule_items(pr_doc):
|
||||
def get_pricing_rule_items(pr_doc, other_items=False) -> list:
|
||||
apply_on_data = []
|
||||
apply_on = frappe.scrub(pr_doc.get("apply_on"))
|
||||
|
||||
pricing_rule_apply_on = apply_on_table.get(pr_doc.get("apply_on"))
|
||||
|
||||
for d in pr_doc.get(pricing_rule_apply_on):
|
||||
if apply_on == "item_group":
|
||||
apply_on_data.extend(get_child_item_groups(d.get(apply_on)))
|
||||
else:
|
||||
apply_on_data.append(d.get(apply_on))
|
||||
|
||||
if pr_doc.apply_rule_on_other:
|
||||
if pr_doc.apply_rule_on_other and other_items:
|
||||
apply_on = frappe.scrub(pr_doc.apply_rule_on_other)
|
||||
apply_on_data.append(pr_doc.get("other_" + apply_on))
|
||||
else:
|
||||
for d in pr_doc.get(pricing_rule_apply_on):
|
||||
if apply_on == "item_group":
|
||||
apply_on_data.extend(get_child_item_groups(d.get(apply_on)))
|
||||
else:
|
||||
apply_on_data.append(d.get(apply_on))
|
||||
|
||||
return list(set(apply_on_data))
|
||||
|
||||
|
||||
@@ -34,4 +34,4 @@ class ProcessDeferredAccounting(Document):
|
||||
filters={"against_voucher_type": self.doctype, "against_voucher": self.name},
|
||||
)
|
||||
|
||||
make_gl_entries(gl_entries=gl_entries, cancel=1)
|
||||
make_gl_entries(gl_map=gl_entries, cancel=1)
|
||||
|
||||
@@ -57,3 +57,16 @@ class TestProcessDeferredAccounting(unittest.TestCase):
|
||||
]
|
||||
|
||||
check_gl_entries(self, si.name, expected_gle, "2019-01-10")
|
||||
|
||||
def test_pda_submission_and_cancellation(self):
|
||||
pda = frappe.get_doc(
|
||||
dict(
|
||||
doctype="Process Deferred Accounting",
|
||||
posting_date="2019-01-01",
|
||||
start_date="2019-01-01",
|
||||
end_date="2019-01-31",
|
||||
type="Income",
|
||||
)
|
||||
)
|
||||
pda.submit()
|
||||
pda.cancel()
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<table class="table table-bordered">
|
||||
<table class="table table-bordered" style="font-size: 10px">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 12%">{{ _("Date") }}</th>
|
||||
|
||||
@@ -34,8 +34,8 @@ pricing_rule_fields = [
|
||||
other_fields = [
|
||||
"min_qty",
|
||||
"max_qty",
|
||||
"min_amt",
|
||||
"max_amt",
|
||||
"min_amount",
|
||||
"max_amount",
|
||||
"priority",
|
||||
"warehouse",
|
||||
"threshold_percentage",
|
||||
@@ -246,7 +246,11 @@ def prepare_pricing_rule(
|
||||
def set_args(args, pr, doc, child_doc, discount_fields, child_doc_fields):
|
||||
pr.update(args)
|
||||
for field in other_fields + discount_fields:
|
||||
pr.set(field, child_doc_fields.get(field))
|
||||
target_field = field
|
||||
if target_field in ["min_amount", "max_amount"]:
|
||||
target_field = "min_amt" if field == "min_amount" else "max_amt"
|
||||
|
||||
pr.set(target_field, child_doc_fields.get(field))
|
||||
|
||||
pr.promotional_scheme_id = child_doc_fields.name
|
||||
pr.promotional_scheme = doc.name
|
||||
|
||||
@@ -90,6 +90,23 @@ class TestPromotionalScheme(unittest.TestCase):
|
||||
price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
|
||||
self.assertEqual(price_rules, [])
|
||||
|
||||
def test_min_max_amount_configuration(self):
|
||||
ps = make_promotional_scheme()
|
||||
ps.price_discount_slabs[0].min_amount = 10
|
||||
ps.price_discount_slabs[0].max_amount = 1000
|
||||
ps.save()
|
||||
|
||||
price_rules_data = frappe.db.get_value(
|
||||
"Pricing Rule", {"promotional_scheme": ps.name}, ["min_amt", "max_amt"], as_dict=1
|
||||
)
|
||||
|
||||
self.assertEqual(price_rules_data.min_amt, 10)
|
||||
self.assertEqual(price_rules_data.max_amt, 1000)
|
||||
|
||||
frappe.delete_doc("Promotional Scheme", ps.name)
|
||||
price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
|
||||
self.assertEqual(price_rules, [])
|
||||
|
||||
|
||||
def make_promotional_scheme(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
@@ -83,6 +83,8 @@
|
||||
"section_break_51",
|
||||
"taxes_and_charges",
|
||||
"taxes",
|
||||
"tax_withheld_vouchers_section",
|
||||
"tax_withheld_vouchers",
|
||||
"sec_tax_breakup",
|
||||
"other_charges_calculation",
|
||||
"totals",
|
||||
@@ -96,7 +98,6 @@
|
||||
"section_break_44",
|
||||
"apply_discount_on",
|
||||
"base_discount_amount",
|
||||
"additional_discount_account",
|
||||
"column_break_46",
|
||||
"additional_discount_percentage",
|
||||
"discount_amount",
|
||||
@@ -512,7 +513,6 @@
|
||||
"fieldname": "ignore_pricing_rule",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Pricing Rule",
|
||||
"no_copy": 1,
|
||||
"permlevel": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -1367,7 +1367,7 @@
|
||||
"width": "50px"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.is_subcontracted",
|
||||
"depends_on": "eval:doc.is_subcontracted",
|
||||
"fieldname": "supplier_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Supplier Warehouse",
|
||||
@@ -1386,12 +1386,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "additional_discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Additional Discount Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_default_payment_terms_template",
|
||||
@@ -1426,13 +1420,25 @@
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withheld_vouchers_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Tax Withheld Vouchers"
|
||||
},
|
||||
{
|
||||
"fieldname": "tax_withheld_vouchers",
|
||||
"fieldtype": "Table",
|
||||
"label": "Tax Withheld Vouchers",
|
||||
"options": "Tax Withheld Vouchers",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-15 15:40:58.527065",
|
||||
"modified": "2022-09-27 11:07:55.766844",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
@@ -1492,6 +1498,7 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
|
||||
@@ -575,7 +575,6 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
self.make_supplier_gl_entry(gl_entries)
|
||||
self.make_item_gl_entries(gl_entries)
|
||||
self.make_discount_gl_entries(gl_entries)
|
||||
|
||||
if self.check_asset_cwip_enabled():
|
||||
self.get_asset_gl_entry(gl_entries)
|
||||
@@ -670,9 +669,6 @@ class PurchaseInvoice(BuyingController):
|
||||
|
||||
exchange_rate_map, net_rate_map = get_purchase_document_details(self)
|
||||
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
provisional_accounting_for_non_stock_items = cint(
|
||||
frappe.db.get_value(
|
||||
"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
|
||||
@@ -807,7 +803,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if not item.is_fixed_asset:
|
||||
dummy, amount = self.get_amount_and_base_amount(item, enable_discount_accounting)
|
||||
dummy, amount = self.get_amount_and_base_amount(item, None)
|
||||
else:
|
||||
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
|
||||
|
||||
@@ -1160,12 +1156,9 @@ class PurchaseInvoice(BuyingController):
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
# tax table gl entries
|
||||
valuation_tax = {}
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
amount, base_amount = self.get_tax_amounts(tax, enable_discount_accounting)
|
||||
amount, base_amount = self.get_tax_amounts(tax, None)
|
||||
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
|
||||
account_currency = get_account_currency(tax.account_head)
|
||||
|
||||
@@ -1250,15 +1243,6 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def enable_discount_accounting(self):
|
||||
if not hasattr(self, "_enable_discount_accounting"):
|
||||
self._enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
return self._enable_discount_accounting
|
||||
|
||||
def make_internal_transfer_gl_entries(self, gl_entries):
|
||||
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
|
||||
account_currency = get_account_currency(self.unrealized_profit_loss_account)
|
||||
@@ -1520,7 +1504,7 @@ class PurchaseInvoice(BuyingController):
|
||||
if not self.tax_withholding_category:
|
||||
return
|
||||
|
||||
tax_withholding_details, advance_taxes = get_party_tax_withholding_details(
|
||||
tax_withholding_details, advance_taxes, voucher_wise_amount = get_party_tax_withholding_details(
|
||||
self, self.tax_withholding_category
|
||||
)
|
||||
|
||||
@@ -1549,6 +1533,19 @@ class PurchaseInvoice(BuyingController):
|
||||
for d in to_remove:
|
||||
self.remove(d)
|
||||
|
||||
## Add pending vouchers on which tax was withheld
|
||||
self.set("tax_withheld_vouchers", [])
|
||||
|
||||
for voucher_no, voucher_details in voucher_wise_amount.items():
|
||||
self.append(
|
||||
"tax_withheld_vouchers",
|
||||
{
|
||||
"voucher_name": voucher_no,
|
||||
"voucher_type": voucher_details.get("voucher_type"),
|
||||
"taxable_amount": voucher_details.get("amount"),
|
||||
},
|
||||
)
|
||||
|
||||
# calculate totals again after applying TDS
|
||||
self.calculate_taxes_and_totals()
|
||||
|
||||
@@ -1791,4 +1788,6 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
target_doc,
|
||||
)
|
||||
|
||||
doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -338,59 +338,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
|
||||
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
|
||||
|
||||
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
|
||||
def test_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
|
||||
discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
pi = make_purchase_invoice(discount_account=discount_account, rate=45)
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 225.0, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 25.0, nowdate()],
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
|
||||
@change_settings("Buying Settings", {"enable_discount_accounting": 1})
|
||||
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
|
||||
additional_discount_account = create_account(
|
||||
account_name="Discount Account",
|
||||
parent_account="Indirect Expenses - _TC",
|
||||
company="_Test Company",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
|
||||
pi.apply_discount_on = "Grand Total"
|
||||
pi.additional_discount_account = additional_discount_account
|
||||
pi.additional_discount_percentage = 10
|
||||
pi.disable_rounded_total = 1
|
||||
pi.append(
|
||||
"taxes",
|
||||
{
|
||||
"charge_type": "On Net Total",
|
||||
"account_head": "_Test Account VAT - _TC",
|
||||
"cost_center": "Main - _TC",
|
||||
"description": "Test",
|
||||
"rate": 10,
|
||||
},
|
||||
)
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
|
||||
["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
|
||||
["Creditors - _TC", 0.0, 247.5, nowdate()],
|
||||
["Discount Account - _TC", 0.0, 27.5, nowdate()],
|
||||
]
|
||||
|
||||
check_gl_entries(self, pi.name, expected_gle, nowdate())
|
||||
|
||||
def test_purchase_invoice_change_naming_series(self):
|
||||
pi = frappe.copy_doc(test_records[1])
|
||||
pi.insert()
|
||||
@@ -1596,6 +1543,37 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
pi.save()
|
||||
self.assertEqual(pi.items[0].conversion_factor, 1000)
|
||||
|
||||
def test_batch_expiry_for_purchase_invoice(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
|
||||
item = self.make_item(
|
||||
"_Test Batch Item For Return Check",
|
||||
{
|
||||
"is_purchase_item": 1,
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TBIRC.#####",
|
||||
},
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
qty=1,
|
||||
item_code=item.name,
|
||||
update_stock=True,
|
||||
)
|
||||
|
||||
pi.load_from_db()
|
||||
batch_no = pi.items[0].batch_no
|
||||
self.assertTrue(batch_no)
|
||||
|
||||
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(nowdate(), -1))
|
||||
|
||||
return_pi = make_return_doc(pi.doctype, pi.name)
|
||||
return_pi.save().submit()
|
||||
|
||||
self.assertTrue(return_pi.docstatus == 1)
|
||||
|
||||
|
||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
|
||||
gl_entries = frappe.db.sql(
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
"manufacturer_part_no",
|
||||
"accounting",
|
||||
"expense_account",
|
||||
"discount_account",
|
||||
"col_break5",
|
||||
"is_fixed_asset",
|
||||
"asset_location",
|
||||
@@ -860,12 +859,6 @@
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "discount_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Discount Account",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "product_bundle",
|
||||
"fieldtype": "Link",
|
||||
@@ -877,7 +870,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-17 05:31:10.520171",
|
||||
"modified": "2022-09-27 10:54:23.980713",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -479,9 +479,13 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
|
||||
is_cash_or_non_trade_discount() {
|
||||
this.frm.set_df_property("additional_discount_account", "hidden", 1 - this.frm.doc.is_cash_or_non_trade_discount);
|
||||
this.frm.set_df_property("additional_discount_account", "reqd", this.frm.doc.is_cash_or_non_trade_discount);
|
||||
|
||||
if (!this.frm.doc.is_cash_or_non_trade_discount) {
|
||||
this.frm.set_value("additional_discount_account", "");
|
||||
}
|
||||
|
||||
this.calculate_taxes_and_totals();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -649,7 +649,6 @@
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Ignore Pricing Rule",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -2022,7 +2021,7 @@
|
||||
"link_fieldname": "consolidated_invoice"
|
||||
}
|
||||
],
|
||||
"modified": "2022-07-11 17:43:56.435382",
|
||||
"modified": "2022-09-16 17:44:22.227332",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
|
||||
@@ -7,17 +7,7 @@ from frappe import _, msgprint, throw
|
||||
from frappe.contacts.doctype.address.address import get_address_display
|
||||
from frappe.model.mapper import get_mapped_doc
|
||||
from frappe.model.utils import get_fetch_values
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
cint,
|
||||
cstr,
|
||||
flt,
|
||||
formatdate,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
nowdate,
|
||||
)
|
||||
from frappe.utils import add_days, cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.deferred_revenue import validate_service_stop_date
|
||||
@@ -32,10 +22,12 @@ from erpnext.accounts.general_ledger import get_round_off_account_and_cost_cente
|
||||
from erpnext.accounts.party import get_due_date, get_party_account, get_party_details
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
depreciate_asset,
|
||||
get_disposal_account_and_cost_center,
|
||||
get_gl_entries_on_asset_disposal,
|
||||
get_gl_entries_on_asset_regain,
|
||||
make_depreciation_entry,
|
||||
reset_depreciation_schedule,
|
||||
reverse_depreciation_entry_made_after_disposal,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
@@ -186,7 +178,7 @@ class SalesInvoice(SellingController):
|
||||
if self.update_stock:
|
||||
frappe.throw(_("'Update Stock' cannot be checked for fixed asset sale"))
|
||||
|
||||
elif asset.status in ("Scrapped", "Cancelled") or (
|
||||
elif asset.status in ("Scrapped", "Cancelled", "Capitalized", "Decapitalized") or (
|
||||
asset.status == "Sold" and not self.is_return
|
||||
):
|
||||
frappe.throw(
|
||||
@@ -710,6 +702,7 @@ class SalesInvoice(SellingController):
|
||||
if (
|
||||
cint(frappe.db.get_single_value("Selling Settings", "maintain_same_sales_rate"))
|
||||
and not self.is_return
|
||||
and not self.is_internal_customer
|
||||
):
|
||||
self.validate_rate_with_reference_doc(
|
||||
[["Sales Order", "sales_order", "so_detail"], ["Delivery Note", "delivery_note", "dn_detail"]]
|
||||
@@ -1033,22 +1026,6 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
)
|
||||
|
||||
if self.apply_discount_on == "Grand Total" and self.get("is_cash_or_discount_account"):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.additional_discount_account,
|
||||
"against": self.debit_to,
|
||||
"debit": self.base_discount_amount,
|
||||
"debit_in_account_currency": self.discount_amount,
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.project,
|
||||
},
|
||||
self.currency,
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def make_tax_gl_entries(self, gl_entries):
|
||||
enable_discount_accounting = cint(
|
||||
frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
|
||||
@@ -1107,23 +1084,25 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if self.is_return:
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
|
||||
asset, item.base_net_amount, item.finance_book
|
||||
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
|
||||
)
|
||||
asset.db_set("disposal_date", None)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.reverse_depreciation_entry_made_after_sale(asset)
|
||||
self.reset_depreciation_schedule(asset)
|
||||
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
||||
reset_depreciation_schedule(asset, self.posting_date)
|
||||
|
||||
else:
|
||||
if asset.calculate_depreciation:
|
||||
depreciate_asset(asset, self.posting_date)
|
||||
asset.reload()
|
||||
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||
asset, item.base_net_amount, item.finance_book
|
||||
asset, item.base_net_amount, item.finance_book, self.get("doctype"), self.get("name")
|
||||
)
|
||||
asset.db_set("disposal_date", self.posting_date)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
self.depreciate_asset(asset)
|
||||
|
||||
for gle in fixed_asset_gl_entries:
|
||||
gle["against"] = self.customer
|
||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||
@@ -1177,101 +1156,6 @@ class SalesInvoice(SellingController):
|
||||
self.check_finance_books(item, asset)
|
||||
return asset
|
||||
|
||||
def check_finance_books(self, item, asset):
|
||||
if (
|
||||
len(asset.finance_books) > 1 and not item.finance_book and asset.finance_books[0].finance_book
|
||||
):
|
||||
frappe.throw(
|
||||
_("Select finance book for the item {0} at row {1}").format(item.item_code, item.idx)
|
||||
)
|
||||
|
||||
def depreciate_asset(self, asset):
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.prepare_depreciation_data(date_of_sale=self.posting_date)
|
||||
asset.save()
|
||||
|
||||
make_depreciation_entry(asset.name, self.posting_date)
|
||||
|
||||
def reset_depreciation_schedule(self, asset):
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
|
||||
# recreate original depreciation schedule of the asset
|
||||
asset.prepare_depreciation_data(date_of_return=self.posting_date)
|
||||
|
||||
self.modify_depreciation_schedule_for_asset_repairs(asset)
|
||||
asset.save()
|
||||
|
||||
def modify_depreciation_schedule_for_asset_repairs(self, asset):
|
||||
asset_repairs = frappe.get_all(
|
||||
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
|
||||
)
|
||||
|
||||
for repair in asset_repairs:
|
||||
if repair.increase_in_asset_life:
|
||||
asset_repair = frappe.get_doc("Asset Repair", repair.name)
|
||||
asset_repair.modify_depreciation_schedule()
|
||||
asset.prepare_depreciation_data()
|
||||
|
||||
def reverse_depreciation_entry_made_after_sale(self, asset):
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||
|
||||
posting_date_of_original_invoice = self.get_posting_date_of_sales_invoice()
|
||||
|
||||
row = -1
|
||||
finance_book = asset.get("schedules")[0].get("finance_book")
|
||||
for schedule in asset.get("schedules"):
|
||||
if schedule.finance_book != finance_book:
|
||||
row = 0
|
||||
finance_book = schedule.finance_book
|
||||
else:
|
||||
row += 1
|
||||
|
||||
if schedule.schedule_date == posting_date_of_original_invoice:
|
||||
if not self.sale_was_made_on_original_schedule_date(
|
||||
asset, schedule, row, posting_date_of_original_invoice
|
||||
) or self.sale_happens_in_the_future(posting_date_of_original_invoice):
|
||||
|
||||
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
|
||||
reverse_journal_entry.posting_date = nowdate()
|
||||
frappe.flags.is_reverse_depr_entry = True
|
||||
reverse_journal_entry.submit()
|
||||
|
||||
frappe.flags.is_reverse_depr_entry = False
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
schedule.journal_entry = None
|
||||
depreciation_amount = self.get_depreciation_amount_in_je(reverse_journal_entry)
|
||||
asset.finance_books[0].value_after_depreciation += depreciation_amount
|
||||
asset.save()
|
||||
|
||||
def get_posting_date_of_sales_invoice(self):
|
||||
return frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||
|
||||
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
|
||||
def sale_was_made_on_original_schedule_date(
|
||||
self, asset, schedule, row, posting_date_of_original_invoice
|
||||
):
|
||||
for finance_book in asset.get("finance_books"):
|
||||
if schedule.finance_book == finance_book.finance_book:
|
||||
orginal_schedule_date = add_months(
|
||||
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
|
||||
)
|
||||
|
||||
if orginal_schedule_date == posting_date_of_original_invoice:
|
||||
return True
|
||||
return False
|
||||
|
||||
def sale_happens_in_the_future(self, posting_date_of_original_invoice):
|
||||
if posting_date_of_original_invoice > getdate():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_depreciation_amount_in_je(self, journal_entry):
|
||||
if journal_entry.accounts[0].debit_in_account_currency:
|
||||
return journal_entry.accounts[0].debit_in_account_currency
|
||||
else:
|
||||
return journal_entry.accounts[0].credit_in_account_currency
|
||||
|
||||
@property
|
||||
def enable_discount_accounting(self):
|
||||
if not hasattr(self, "_enable_discount_accounting"):
|
||||
@@ -2103,13 +1987,13 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
target_detail_field = "sales_invoice_item" if doctype == "Sales Invoice" else "sales_order_item"
|
||||
source_document_warehouse_field = "target_warehouse"
|
||||
target_document_warehouse_field = "from_warehouse"
|
||||
received_items = get_received_items(source_name, target_doctype, target_detail_field)
|
||||
else:
|
||||
source_doc = frappe.get_doc(doctype, source_name)
|
||||
target_doctype = "Sales Invoice" if doctype == "Purchase Invoice" else "Sales Order"
|
||||
source_document_warehouse_field = "from_warehouse"
|
||||
target_document_warehouse_field = "target_warehouse"
|
||||
|
||||
received_items = get_received_items(source_name, target_doctype, target_detail_field)
|
||||
received_items = {}
|
||||
|
||||
validate_inter_company_transaction(source_doc, doctype)
|
||||
details = get_inter_company_details(source_doc, doctype)
|
||||
@@ -2177,6 +2061,17 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
|
||||
def update_item(source, target, source_parent):
|
||||
target.qty = flt(source.qty) - received_items.get(source.name, 0.0)
|
||||
if source.doctype == "Purchase Order Item" and target.doctype == "Sales Order Item":
|
||||
target.purchase_order = source.parent
|
||||
target.purchase_order_item = source.name
|
||||
|
||||
if (
|
||||
source.get("purchase_order")
|
||||
and source.get("purchase_order_item")
|
||||
and target.doctype == "Purchase Invoice Item"
|
||||
):
|
||||
target.purchase_order = source.purchase_order
|
||||
target.po_detail = source.purchase_order_item
|
||||
|
||||
item_field_map = {
|
||||
"doctype": target_doctype + " Item",
|
||||
@@ -2203,6 +2098,12 @@ def make_inter_company_transaction(doctype, source_name, target_doc=None):
|
||||
"serial_no": "serial_no",
|
||||
}
|
||||
)
|
||||
elif target_doctype == "Sales Order":
|
||||
item_field_map["field_map"].update(
|
||||
{
|
||||
source_document_warehouse_field: "warehouse",
|
||||
}
|
||||
)
|
||||
|
||||
doclist = get_mapped_doc(
|
||||
doctype,
|
||||
@@ -2247,6 +2148,7 @@ def get_received_items(reference_name, doctype, reference_fieldname):
|
||||
|
||||
def set_purchase_references(doc):
|
||||
# add internal PO or PR links if any
|
||||
|
||||
if doc.is_internal_transfer():
|
||||
if doc.doctype == "Purchase Receipt":
|
||||
so_item_map = get_delivery_note_details(doc.inter_company_invoice_reference)
|
||||
@@ -2276,15 +2178,6 @@ def set_purchase_references(doc):
|
||||
warehouse_map,
|
||||
)
|
||||
|
||||
if list(so_item_map.values()):
|
||||
pd_item_map, parent_child_map, warehouse_map = get_pd_details(
|
||||
"Purchase Order Item", so_item_map, "sales_order_item"
|
||||
)
|
||||
|
||||
update_pi_items(
|
||||
doc, "po_detail", "purchase_order", so_item_map, pd_item_map, parent_child_map, warehouse_map
|
||||
)
|
||||
|
||||
|
||||
def update_pi_items(
|
||||
doc,
|
||||
@@ -2300,13 +2193,19 @@ def update_pi_items(
|
||||
item.set(parent_field, parent_child_map.get(sales_item_map.get(item.sales_invoice_item)))
|
||||
if doc.update_stock:
|
||||
item.warehouse = warehouse_map.get(sales_item_map.get(item.sales_invoice_item))
|
||||
if not item.warehouse and item.get("purchase_order") and item.get("purchase_order_item"):
|
||||
item.warehouse = frappe.db.get_value(
|
||||
"Purchase Order Item", item.purchase_order_item, "warehouse"
|
||||
)
|
||||
|
||||
|
||||
def update_pr_items(doc, sales_item_map, purchase_item_map, parent_child_map, warehouse_map):
|
||||
for item in doc.get("items"):
|
||||
item.purchase_order_item = purchase_item_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
item.warehouse = warehouse_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
item.purchase_order = parent_child_map.get(sales_item_map.get(item.delivery_note_item))
|
||||
if not item.warehouse and item.get("purchase_order") and item.get("purchase_order_item"):
|
||||
item.warehouse = frappe.db.get_value(
|
||||
"Purchase Order Item", item.purchase_order_item, "warehouse"
|
||||
)
|
||||
|
||||
|
||||
def get_delivery_note_details(internal_reference):
|
||||
@@ -2535,7 +2434,6 @@ def create_dunning(source_name, target_doc=None):
|
||||
target.closing_text = letter_text.get("closing_text")
|
||||
target.language = letter_text.get("language")
|
||||
amounts = calculate_interest_and_amount(
|
||||
target.posting_date,
|
||||
target.outstanding_amount,
|
||||
target.rate_of_interest,
|
||||
target.dunning_fee,
|
||||
|
||||
@@ -8,7 +8,7 @@ import frappe
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.tests.utils import change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
|
||||
@@ -3196,6 +3196,37 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
"Accounts Settings", "Accounts Settings", "unlink_payment_on_cancel_of_invoice", unlink_enabled
|
||||
)
|
||||
|
||||
def test_batch_expiry_for_sales_invoice_return(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
from erpnext.stock.doctype.item.test_item import make_item
|
||||
|
||||
item = make_item(
|
||||
"_Test Batch Item For Return Check",
|
||||
{
|
||||
"is_purchase_item": 1,
|
||||
"is_stock_item": 1,
|
||||
"has_batch_no": 1,
|
||||
"create_new_batch": 1,
|
||||
"batch_number_series": "TBIRC.#####",
|
||||
},
|
||||
)
|
||||
|
||||
pr = make_purchase_receipt(qty=1, item_code=item.name)
|
||||
|
||||
batch_no = pr.items[0].batch_no
|
||||
si = create_sales_invoice(qty=1, item_code=item.name, update_stock=1, batch_no=batch_no)
|
||||
|
||||
si.load_from_db()
|
||||
batch_no = si.items[0].batch_no
|
||||
self.assertTrue(batch_no)
|
||||
|
||||
frappe.db.set_value("Batch", batch_no, "expiry_date", add_days(today(), -1))
|
||||
|
||||
return_si = make_return_doc(si.doctype, si.name)
|
||||
return_si.save().submit()
|
||||
|
||||
self.assertTrue(return_si.docstatus == 1)
|
||||
|
||||
|
||||
def get_sales_invoice_for_e_invoice():
|
||||
si = make_sales_invoice_for_ewaybill()
|
||||
@@ -3289,6 +3320,7 @@ def create_sales_invoice(**args):
|
||||
"serial_no": args.serial_no,
|
||||
"conversion_factor": 1,
|
||||
"incoming_rate": args.incoming_rate or 0,
|
||||
"batch_no": args.batch_no or None,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -96,6 +96,10 @@
|
||||
"delivery_note",
|
||||
"dn_detail",
|
||||
"delivered_qty",
|
||||
"internal_transfer_section",
|
||||
"purchase_order",
|
||||
"column_break_92",
|
||||
"purchase_order_item",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
@@ -282,7 +286,6 @@
|
||||
"label": "Discount (%) on Price List Rate with Margin",
|
||||
"oldfieldname": "adj_rate",
|
||||
"oldfieldtype": "Float",
|
||||
"precision": "2",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -841,12 +844,38 @@
|
||||
"fieldtype": "Check",
|
||||
"label": "Grant Commission",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"depends_on": "eval:parent.is_internal_customer == 1",
|
||||
"fieldname": "internal_transfer_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Internal Transfer"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_order",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Order",
|
||||
"options": "Purchase Order",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_92",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_order_item",
|
||||
"fieldtype": "Data",
|
||||
"label": "Purchase Order Item",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-17 05:33:15.335912",
|
||||
"modified": "2022-09-06 14:17:43.394309",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "autoincrement",
|
||||
"creation": "2022-09-13 16:18:59.404842",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"voucher_type",
|
||||
"voucher_name",
|
||||
"taxable_amount"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "voucher_type",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Voucher Type",
|
||||
"options": "DocType"
|
||||
},
|
||||
{
|
||||
"fieldname": "voucher_name",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Voucher Name",
|
||||
"options": "voucher_type"
|
||||
},
|
||||
{
|
||||
"fieldname": "taxable_amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Taxable Amount",
|
||||
"options": "Company:company:default_currency"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-13 23:40:41.479208",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Tax Withheld Vouchers",
|
||||
"naming_rule": "Autoincrement",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class TaxWithheldVouchers(Document):
|
||||
pass
|
||||
@@ -109,7 +109,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
).format(tax_withholding_category, inv.company, party)
|
||||
)
|
||||
|
||||
tax_amount, tax_deducted, tax_deducted_on_advances = get_tax_amount(
|
||||
tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount = get_tax_amount(
|
||||
party_type, parties, inv, tax_details, posting_date, pan_no
|
||||
)
|
||||
|
||||
@@ -119,7 +119,7 @@ def get_party_tax_withholding_details(inv, tax_withholding_category=None):
|
||||
tax_row = get_tax_row_for_tcs(inv, tax_details, tax_amount, tax_deducted)
|
||||
|
||||
if inv.doctype == "Purchase Invoice":
|
||||
return tax_row, tax_deducted_on_advances
|
||||
return tax_row, tax_deducted_on_advances, voucher_wise_amount
|
||||
else:
|
||||
return tax_row
|
||||
|
||||
@@ -217,7 +217,9 @@ def get_lower_deduction_certificate(tax_details, pan_no):
|
||||
|
||||
|
||||
def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=None):
|
||||
vouchers = get_invoice_vouchers(parties, tax_details, inv.company, party_type=party_type)
|
||||
vouchers, voucher_wise_amount = get_invoice_vouchers(
|
||||
parties, tax_details, inv.company, party_type=party_type
|
||||
)
|
||||
advance_vouchers = get_advance_vouchers(
|
||||
parties,
|
||||
company=inv.company,
|
||||
@@ -236,6 +238,7 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
tax_deducted = get_deducted_tax(taxable_vouchers, tax_details)
|
||||
|
||||
tax_amount = 0
|
||||
|
||||
if party_type == "Supplier":
|
||||
ldc = get_lower_deduction_certificate(tax_details, pan_no)
|
||||
if tax_deducted:
|
||||
@@ -261,12 +264,13 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
if cint(tax_details.round_off_tax_amount):
|
||||
tax_amount = round(tax_amount)
|
||||
|
||||
return tax_amount, tax_deducted, tax_deducted_on_advances
|
||||
return tax_amount, tax_deducted, tax_deducted_on_advances, voucher_wise_amount
|
||||
|
||||
|
||||
def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
dr_or_cr = "credit" if party_type == "Supplier" else "debit"
|
||||
doctype = "Purchase Invoice" if party_type == "Supplier" else "Sales Invoice"
|
||||
voucher_wise_amount = {}
|
||||
vouchers = []
|
||||
|
||||
filters = {
|
||||
"company": company,
|
||||
@@ -281,29 +285,40 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
|
||||
)
|
||||
|
||||
invoices = frappe.get_all(doctype, filters=filters, pluck="name") or [""]
|
||||
invoices_details = frappe.get_all(doctype, filters=filters, fields=["name", "base_net_total"])
|
||||
|
||||
journal_entries = frappe.db.sql(
|
||||
for d in invoices_details:
|
||||
vouchers.append(d.name)
|
||||
voucher_wise_amount.update({d.name: {"amount": d.base_net_total, "voucher_type": doctype}})
|
||||
|
||||
journal_entries_details = frappe.db.sql(
|
||||
"""
|
||||
SELECT j.name
|
||||
SELECT j.name, ja.credit - ja.debit AS amount
|
||||
FROM `tabJournal Entry` j, `tabJournal Entry Account` ja
|
||||
WHERE
|
||||
j.docstatus = 1
|
||||
j.name = ja.parent
|
||||
AND j.docstatus = 1
|
||||
AND j.is_opening = 'No'
|
||||
AND j.posting_date between %s and %s
|
||||
AND ja.{dr_or_cr} > 0
|
||||
AND ja.party in %s
|
||||
""".format(
|
||||
dr_or_cr=dr_or_cr
|
||||
AND j.apply_tds = 1
|
||||
AND j.tax_withholding_category = %s
|
||||
""",
|
||||
(
|
||||
tax_details.from_date,
|
||||
tax_details.to_date,
|
||||
tuple(parties),
|
||||
tax_details.get("tax_withholding_category"),
|
||||
),
|
||||
(tax_details.from_date, tax_details.to_date, tuple(parties)),
|
||||
as_list=1,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if journal_entries:
|
||||
journal_entries = journal_entries[0]
|
||||
if journal_entries_details:
|
||||
for d in journal_entries_details:
|
||||
vouchers.append(d.name)
|
||||
voucher_wise_amount.update({d.name: {"amount": d.amount, "voucher_type": "Journal Entry"}})
|
||||
|
||||
return invoices + journal_entries
|
||||
return vouchers, voucher_wise_amount
|
||||
|
||||
|
||||
def get_advance_vouchers(
|
||||
@@ -318,9 +333,11 @@ def get_advance_vouchers(
|
||||
"is_cancelled": 0,
|
||||
"party_type": party_type,
|
||||
"party": ["in", parties],
|
||||
"against_voucher": ["is", "not set"],
|
||||
}
|
||||
|
||||
if party_type == "Customer":
|
||||
filters.update({"against_voucher": ["is", "not set"]})
|
||||
|
||||
if company:
|
||||
filters["company"] = company
|
||||
if from_date and to_date:
|
||||
@@ -330,23 +347,25 @@ def get_advance_vouchers(
|
||||
|
||||
|
||||
def get_taxes_deducted_on_advances_allocated(inv, tax_details):
|
||||
advances = [d.reference_name for d in inv.get("advances")]
|
||||
tax_info = []
|
||||
|
||||
if advances:
|
||||
pe = frappe.qb.DocType("Payment Entry").as_("pe")
|
||||
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
|
||||
if inv.get("advances"):
|
||||
advances = [d.reference_name for d in inv.get("advances")]
|
||||
|
||||
tax_info = (
|
||||
frappe.qb.from_(at)
|
||||
.inner_join(pe)
|
||||
.on(pe.name == at.parent)
|
||||
.select(at.parent, at.name, at.tax_amount, at.allocated_amount)
|
||||
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
|
||||
.where(at.parent.isin(advances))
|
||||
.where(at.account_head == tax_details.account_head)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
if advances:
|
||||
pe = frappe.qb.DocType("Payment Entry").as_("pe")
|
||||
at = frappe.qb.DocType("Advance Taxes and Charges").as_("at")
|
||||
|
||||
tax_info = (
|
||||
frappe.qb.from_(at)
|
||||
.inner_join(pe)
|
||||
.on(pe.name == at.parent)
|
||||
.select(at.parent, at.name, at.tax_amount, at.allocated_amount)
|
||||
.where(pe.tax_withholding_category == tax_details.get("tax_withholding_category"))
|
||||
.where(at.parent.isin(advances))
|
||||
.where(at.account_head == tax_details.account_head)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
|
||||
return tax_info
|
||||
|
||||
@@ -395,11 +414,6 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
supp_credit_amt += supp_jv_credit_amt
|
||||
supp_credit_amt += inv.net_total
|
||||
|
||||
debit_note_amount = get_debit_note_amount(
|
||||
parties, tax_details.from_date, tax_details.to_date, inv.company
|
||||
)
|
||||
supp_credit_amt -= debit_note_amount
|
||||
|
||||
threshold = tax_details.get("threshold", 0)
|
||||
cumulative_threshold = tax_details.get("cumulative_threshold", 0)
|
||||
|
||||
@@ -516,22 +530,6 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
|
||||
return tds_amount
|
||||
|
||||
|
||||
def get_debit_note_amount(suppliers, from_date, to_date, company=None):
|
||||
|
||||
filters = {
|
||||
"supplier": ["in", suppliers],
|
||||
"is_return": 1,
|
||||
"docstatus": 1,
|
||||
"posting_date": ["between", (from_date, to_date)],
|
||||
}
|
||||
fields = ["abs(sum(net_total)) as net_total"]
|
||||
|
||||
if company:
|
||||
filters["company"] = company
|
||||
|
||||
return frappe.get_all("Purchase Invoice", filters, fields)[0].get("net_total") or 0.0
|
||||
|
||||
|
||||
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
|
||||
if current_amount < (certificate_limit - deducted_amount):
|
||||
return current_amount * rate / 100
|
||||
|
||||
@@ -52,7 +52,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
invoices.append(pi)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
def test_single_threshold_tds(self):
|
||||
@@ -88,7 +88,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
self.assertEqual(pi.taxes_and_charges_deducted, 1000)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
def test_tax_withholding_category_checks(self):
|
||||
@@ -114,7 +114,7 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
# TDS should be applied only on 1000
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
|
||||
|
||||
for d in invoices:
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
def test_cumulative_threshold_tcs(self):
|
||||
@@ -148,8 +148,8 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
self.assertEqual(tcs_charged, 500)
|
||||
invoices.append(si)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
# cancel invoices to avoid clashing
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
def test_tds_calculation_on_net_total(self):
|
||||
@@ -182,8 +182,8 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 4000)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
# cancel invoices to avoid clashing
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
def test_multi_category_single_supplier(self):
|
||||
@@ -207,8 +207,50 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 250)
|
||||
|
||||
# delete invoices to avoid clashing
|
||||
for d in invoices:
|
||||
# cancel invoices to avoid clashing
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
def test_tax_withholding_category_voucher_display(self):
|
||||
frappe.db.set_value(
|
||||
"Supplier", "Test TDS Supplier6", "tax_withholding_category", "Test Multi Invoice Category"
|
||||
)
|
||||
invoices = []
|
||||
|
||||
pi = create_purchase_invoice(supplier="Test TDS Supplier6", rate=4000, do_not_save=True)
|
||||
pi.apply_tds = 1
|
||||
pi.tax_withholding_category = "Test Multi Invoice Category"
|
||||
pi.save()
|
||||
pi.submit()
|
||||
invoices.append(pi)
|
||||
|
||||
pi1 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=2000, do_not_save=True)
|
||||
pi1.apply_tds = 1
|
||||
pi1.is_return = 1
|
||||
pi1.items[0].qty = -1
|
||||
pi1.tax_withholding_category = "Test Multi Invoice Category"
|
||||
pi1.save()
|
||||
pi1.submit()
|
||||
invoices.append(pi1)
|
||||
|
||||
pi2 = create_purchase_invoice(supplier="Test TDS Supplier6", rate=9000, do_not_save=True)
|
||||
pi2.apply_tds = 1
|
||||
pi2.tax_withholding_category = "Test Multi Invoice Category"
|
||||
pi2.save()
|
||||
pi2.submit()
|
||||
invoices.append(pi2)
|
||||
|
||||
pi2.load_from_db()
|
||||
|
||||
self.assertTrue(pi2.taxes[0].tax_amount, 1100)
|
||||
|
||||
self.assertTrue(pi2.tax_withheld_vouchers[0].voucher_name == pi1.name)
|
||||
self.assertTrue(pi2.tax_withheld_vouchers[0].taxable_amount == pi1.net_total)
|
||||
self.assertTrue(pi2.tax_withheld_vouchers[1].voucher_name == pi.name)
|
||||
self.assertTrue(pi2.tax_withheld_vouchers[1].taxable_amount == pi.net_total)
|
||||
|
||||
# cancel invoices to avoid clashing
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
|
||||
@@ -308,6 +350,7 @@ def create_records():
|
||||
"Test TDS Supplier3",
|
||||
"Test TDS Supplier4",
|
||||
"Test TDS Supplier5",
|
||||
"Test TDS Supplier6",
|
||||
]:
|
||||
if frappe.db.exists("Supplier", name):
|
||||
continue
|
||||
@@ -498,3 +541,22 @@ def create_tax_with_holding_category():
|
||||
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
|
||||
}
|
||||
).insert()
|
||||
|
||||
if not frappe.db.exists("Tax Withholding Category", "Test Multi Invoice Category"):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Tax Withholding Category",
|
||||
"name": "Test Multi Invoice Category",
|
||||
"category_name": "Test Multi Invoice Category",
|
||||
"rates": [
|
||||
{
|
||||
"from_date": fiscal_year[1],
|
||||
"to_date": fiscal_year[2],
|
||||
"tax_withholding_rate": 10,
|
||||
"single_threshold": 5000,
|
||||
"cumulative_threshold": 10000,
|
||||
}
|
||||
],
|
||||
"accounts": [{"company": "_Test Company", "account": "TDS - _TC"}],
|
||||
}
|
||||
).insert()
|
||||
|
||||
@@ -489,7 +489,6 @@ def make_reverse_gl_entries(
|
||||
).run(as_dict=1)
|
||||
|
||||
if gl_entries:
|
||||
create_payment_ledger_entry(gl_entries, cancel=1)
|
||||
create_payment_ledger_entry(
|
||||
gl_entries, cancel=1, adv_adj=adv_adj, update_outstanding=update_outstanding
|
||||
)
|
||||
|
||||
@@ -207,7 +207,7 @@ def set_address_details(
|
||||
)
|
||||
|
||||
if company_address:
|
||||
party_details.update({"company_address": company_address})
|
||||
party_details.company_address = company_address
|
||||
else:
|
||||
party_details.update(get_company_address(company))
|
||||
|
||||
@@ -219,12 +219,31 @@ def set_address_details(
|
||||
get_regional_address_details(party_details, doctype, company)
|
||||
|
||||
elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
|
||||
if party_details.company_address:
|
||||
party_details["shipping_address"] = shipping_address or party_details["company_address"]
|
||||
party_details.shipping_address_display = get_address_display(party_details["shipping_address"])
|
||||
if shipping_address:
|
||||
party_details.update(
|
||||
get_fetch_values(doctype, "shipping_address", party_details.shipping_address)
|
||||
shipping_address=shipping_address,
|
||||
shipping_address_display=get_address_display(shipping_address),
|
||||
**get_fetch_values(doctype, "shipping_address", shipping_address)
|
||||
)
|
||||
|
||||
if party_details.company_address:
|
||||
# billing address
|
||||
party_details.update(
|
||||
billing_address=party_details.company_address,
|
||||
billing_address_display=(
|
||||
party_details.company_address_display or get_address_display(party_details.company_address)
|
||||
),
|
||||
**get_fetch_values(doctype, "billing_address", party_details.company_address)
|
||||
)
|
||||
|
||||
# shipping address - if not already set
|
||||
if not party_details.shipping_address:
|
||||
party_details.update(
|
||||
shipping_address=party_details.billing_address,
|
||||
shipping_address_display=party_details.billing_address_display,
|
||||
**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
|
||||
)
|
||||
|
||||
get_regional_address_details(party_details, doctype, company)
|
||||
|
||||
return party_details.get(billing_address_field), party_details.shipping_address_name
|
||||
|
||||
@@ -178,6 +178,11 @@ frappe.query_reports["Accounts Receivable"] = {
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_remarks",
|
||||
"label": __("Show Remarks"),
|
||||
"fieldtype": "Check",
|
||||
},
|
||||
{
|
||||
"fieldname": "customer_name",
|
||||
"label": __("Customer Name"),
|
||||
|
||||
@@ -119,6 +119,7 @@ class ReceivablePayableReport(object):
|
||||
party_account=ple.account,
|
||||
posting_date=ple.posting_date,
|
||||
account_currency=ple.account_currency,
|
||||
remarks=ple.remarks,
|
||||
invoiced=0.0,
|
||||
paid=0.0,
|
||||
credit_note=0.0,
|
||||
@@ -165,7 +166,7 @@ class ReceivablePayableReport(object):
|
||||
"range4",
|
||||
"range5",
|
||||
"future_amount",
|
||||
"remaining_balance"
|
||||
"remaining_balance",
|
||||
]
|
||||
|
||||
def get_voucher_balance(self, ple):
|
||||
@@ -178,6 +179,11 @@ class ReceivablePayableReport(object):
|
||||
|
||||
key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
|
||||
row = self.voucher_balance.get(key)
|
||||
|
||||
if not row:
|
||||
# no invoice, this is an invoice / stand-alone payment / credit note
|
||||
row = self.voucher_balance.get((ple.voucher_type, ple.voucher_no, ple.party))
|
||||
|
||||
return row
|
||||
|
||||
def update_voucher_balance(self, ple):
|
||||
@@ -187,7 +193,11 @@ class ReceivablePayableReport(object):
|
||||
if not row:
|
||||
return
|
||||
|
||||
amount = ple.amount
|
||||
# amount in "Party Currency", if its supplied. If not, amount in company currency
|
||||
if self.filters.get(scrub(self.party_type)):
|
||||
amount = ple.amount_in_account_currency
|
||||
else:
|
||||
amount = ple.amount
|
||||
amount_in_account_currency = ple.amount_in_account_currency
|
||||
|
||||
# update voucher
|
||||
@@ -685,9 +695,10 @@ class ReceivablePayableReport(object):
|
||||
ple.party,
|
||||
ple.posting_date,
|
||||
ple.due_date,
|
||||
ple.account_currency.as_("currency"),
|
||||
ple.account_currency,
|
||||
ple.amount,
|
||||
ple.amount_in_account_currency,
|
||||
ple.remarks,
|
||||
)
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(self.qb_selection_filter))
|
||||
@@ -722,6 +733,7 @@ class ReceivablePayableReport(object):
|
||||
def prepare_conditions(self):
|
||||
self.qb_selection_filter = []
|
||||
party_type_field = scrub(self.party_type)
|
||||
self.qb_selection_filter.append(self.ple.party_type == self.party_type)
|
||||
|
||||
self.add_common_filters(party_type_field=party_type_field)
|
||||
|
||||
@@ -772,7 +784,7 @@ class ReceivablePayableReport(object):
|
||||
def add_customer_filters(
|
||||
self,
|
||||
):
|
||||
self.customter = qb.DocType("Customer")
|
||||
self.customer = qb.DocType("Customer")
|
||||
|
||||
if self.filters.get("customer_group"):
|
||||
self.get_hierarchical_filters("Customer Group", "customer_group")
|
||||
@@ -826,7 +838,7 @@ class ReceivablePayableReport(object):
|
||||
customer = self.customer
|
||||
groups = qb.from_(doc).select(doc.name).where((doc.lft >= lft) & (doc.rgt <= rgt))
|
||||
customers = qb.from_(customer).select(customer.name).where(customer[key].isin(groups))
|
||||
self.qb_selection_filter.append(ple.isin(ple.party.isin(customers)))
|
||||
self.qb_selection_filter.append(ple.party.isin(customers))
|
||||
|
||||
def add_accounting_dimensions_filters(self):
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
@@ -965,6 +977,9 @@ class ReceivablePayableReport(object):
|
||||
options="Supplier Group",
|
||||
)
|
||||
|
||||
if self.filters.show_remarks:
|
||||
self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
|
||||
|
||||
def add_column(self, label, fieldname=None, fieldtype="Currency", options=None, width=120):
|
||||
if not fieldname:
|
||||
fieldname = scrub(label)
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_days, getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.accounts.report.accounts_receivable.accounts_receivable import execute
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
|
||||
class TestAccountsReceivable(unittest.TestCase):
|
||||
def test_accounts_receivable(self):
|
||||
class TestAccountsReceivable(FrappeTestCase):
|
||||
def setUp(self):
|
||||
frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
|
||||
frappe.db.sql("delete from `tabSales Order` where company='_Test Company 2'")
|
||||
frappe.db.sql("delete from `tabPayment Entry` where company='_Test Company 2'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
|
||||
frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'")
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_accounts_receivable(self):
|
||||
filters = {
|
||||
"company": "_Test Company 2",
|
||||
"based_on_payment_terms": 1,
|
||||
@@ -66,6 +74,50 @@ class TestAccountsReceivable(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def test_payment_againt_po_in_receivable_report(self):
|
||||
"""
|
||||
Payments made against Purchase Order will show up as outstanding amount
|
||||
"""
|
||||
|
||||
so = make_sales_order(
|
||||
company="_Test Company 2",
|
||||
customer="_Test Customer 2",
|
||||
warehouse="Finished Goods - _TC2",
|
||||
currency="EUR",
|
||||
debit_to="Debtors - _TC2",
|
||||
income_account="Sales - _TC2",
|
||||
expense_account="Cost of Goods Sold - _TC2",
|
||||
cost_center="Main - _TC2",
|
||||
)
|
||||
|
||||
pe = get_payment_entry(so.doctype, so.name)
|
||||
pe = pe.save().submit()
|
||||
|
||||
filters = {
|
||||
"company": "_Test Company 2",
|
||||
"based_on_payment_terms": 0,
|
||||
"report_date": today(),
|
||||
"range1": 30,
|
||||
"range2": 60,
|
||||
"range3": 90,
|
||||
"range4": 120,
|
||||
}
|
||||
|
||||
report = execute(filters)
|
||||
|
||||
expected_data_after_payment = [0, 1000, 0, -1000]
|
||||
|
||||
row = report[1][0]
|
||||
self.assertEqual(
|
||||
expected_data_after_payment,
|
||||
[
|
||||
row.invoiced,
|
||||
row.paid,
|
||||
row.credit_note,
|
||||
row.outstanding,
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def make_sales_invoice():
|
||||
frappe.set_user("Administrator")
|
||||
|
||||
@@ -22,8 +22,7 @@ def get_columns():
|
||||
{
|
||||
"label": _("Payment Document Type"),
|
||||
"fieldname": "payment_document_type",
|
||||
"fieldtype": "Link",
|
||||
"options": "Doctype",
|
||||
"fieldtype": "Data",
|
||||
"width": 130,
|
||||
},
|
||||
{
|
||||
@@ -33,15 +32,15 @@ def get_columns():
|
||||
"options": "payment_document_type",
|
||||
"width": 140,
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 100},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 120},
|
||||
{"label": _("Cheque/Reference No"), "fieldname": "cheque_no", "width": 120},
|
||||
{"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 100},
|
||||
{"label": _("Clearance Date"), "fieldname": "clearance_date", "fieldtype": "Date", "width": 120},
|
||||
{
|
||||
"label": _("Against Account"),
|
||||
"fieldname": "against",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 170,
|
||||
"width": 200,
|
||||
},
|
||||
{"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 120},
|
||||
]
|
||||
|
||||
@@ -535,7 +535,11 @@ def get_accounts(root_type, companies):
|
||||
):
|
||||
if account.account_name not in added_accounts:
|
||||
accounts.append(account)
|
||||
added_accounts.append(account.account_name)
|
||||
if account.account_number:
|
||||
account_key = account.account_number + "-" + account.account_name
|
||||
else:
|
||||
account_key = account.account_name
|
||||
added_accounts.append(account_key)
|
||||
|
||||
return accounts
|
||||
|
||||
|
||||
@@ -237,9 +237,9 @@ def get_conditions(filters):
|
||||
or filters.get("party")
|
||||
or filters.get("group_by") in ["Group by Account", "Group by Party"]
|
||||
):
|
||||
conditions.append("posting_date >=%(from_date)s")
|
||||
conditions.append("(posting_date >=%(from_date)s or is_opening = 'Yes')")
|
||||
|
||||
conditions.append("(posting_date <=%(to_date)s or is_opening = 'Yes')")
|
||||
conditions.append("(posting_date <=%(to_date)s)")
|
||||
|
||||
if filters.get("project"):
|
||||
conditions.append("project in %(project)s")
|
||||
|
||||
@@ -155,7 +155,6 @@ def adjust_account(data, period_list, consolidated=False):
|
||||
for d in data:
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
d[key] = totals[d["account"]]
|
||||
d["total"] = totals[d["account"]]
|
||||
return data
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
frappe.query_reports["Gross Profit"] = {
|
||||
"filters": [
|
||||
{
|
||||
"fieldname":"company",
|
||||
"fieldname": "company",
|
||||
"label": __("Company"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
@@ -12,46 +12,58 @@ frappe.query_reports["Gross Profit"] = {
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"sales_invoice",
|
||||
"fieldname": "sales_invoice",
|
||||
"label": __("Sales Invoice"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname":"group_by",
|
||||
"fieldname": "group_by",
|
||||
"label": __("Group By"),
|
||||
"fieldtype": "Select",
|
||||
"options": "Invoice\nItem Code\nItem Group\nBrand\nWarehouse\nCustomer\nCustomer Group\nTerritory\nSales Person\nProject\nMonthly\nPayment Term",
|
||||
"default": "Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_group",
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "sales_person",
|
||||
"label": __("Sales Person"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Person"
|
||||
},
|
||||
],
|
||||
"tree": true,
|
||||
"name_field": "parent",
|
||||
"parent_field": "parent_invoice",
|
||||
"initial_depth": 3,
|
||||
"formatter": function(value, row, column, data, default_formatter) {
|
||||
if (column.fieldname == "sales_invoice" && column.options == "Item" && data.indent == 0) {
|
||||
if (column.fieldname == "sales_invoice" && column.options == "Item" && data && data.indent == 0) {
|
||||
column._options = "Sales Invoice";
|
||||
} else {
|
||||
column._options = "Item";
|
||||
}
|
||||
value = default_formatter(value, row, column, data);
|
||||
|
||||
if (data && (data.indent == 0.0 || row[1].content == "Total")) {
|
||||
if (data && (data.indent == 0.0 || (row[1] && row[1].content == "Total"))) {
|
||||
value = $(`<span>${value}</span>`);
|
||||
var $value = $(value).css("font-weight", "bold");
|
||||
value = $value.wrap("<p></p>").parent().html();
|
||||
|
||||
@@ -7,6 +7,7 @@ from frappe import _, scrub
|
||||
from frappe.utils import cint, flt, formatdate
|
||||
|
||||
from erpnext.controllers.queries import get_match_cond
|
||||
from erpnext.stock.report.stock_ledger.stock_ledger import get_item_group_condition
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
|
||||
@@ -616,7 +617,7 @@ class GrossProfitGenerator(object):
|
||||
previous_stock_value = len(my_sle) > i + 1 and flt(my_sle[i + 1].stock_value) or 0.0
|
||||
|
||||
if previous_stock_value:
|
||||
return (previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
||||
return abs(previous_stock_value - flt(sle.stock_value)) * flt(row.qty) / abs(flt(sle.qty))
|
||||
else:
|
||||
return flt(row.qty) * self.get_average_buying_rate(row, item_code)
|
||||
else:
|
||||
@@ -676,6 +677,17 @@ class GrossProfitGenerator(object):
|
||||
if self.filters.to_date:
|
||||
conditions += " and posting_date <= %(to_date)s"
|
||||
|
||||
if self.filters.item_group:
|
||||
conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))
|
||||
|
||||
if self.filters.sales_person:
|
||||
conditions += """
|
||||
and exists(select 1
|
||||
from `tabSales Team` st
|
||||
where st.parent = `tabSales Invoice`.name
|
||||
and st.sales_person = %(sales_person)s)
|
||||
"""
|
||||
|
||||
if self.filters.group_by == "Sales Person":
|
||||
sales_person_cols = ", sales.sales_person, sales.allocated_amount, sales.incentives"
|
||||
sales_team_table = "left join `tabSales Team` sales on sales.parent = `tabSales Invoice`.name"
|
||||
@@ -723,6 +735,7 @@ class GrossProfitGenerator(object):
|
||||
from
|
||||
`tabSales Invoice` inner join `tabSales Invoice Item`
|
||||
on `tabSales Invoice Item`.parent = `tabSales Invoice`.name
|
||||
join `tabItem` item on item.name = `tabSales Invoice Item`.item_code
|
||||
{sales_team_table}
|
||||
{payment_term_table}
|
||||
where
|
||||
|
||||
@@ -97,6 +97,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
row.update({"rate": d.base_net_rate, "amount": d.base_net_amount})
|
||||
|
||||
total_tax = 0
|
||||
total_other_charges = 0
|
||||
for tax in tax_columns:
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
row.update(
|
||||
@@ -105,10 +106,18 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
|
||||
}
|
||||
)
|
||||
total_tax += flt(item_tax.get("tax_amount"))
|
||||
if item_tax.get("is_other_charges"):
|
||||
total_other_charges += flt(item_tax.get("tax_amount"))
|
||||
else:
|
||||
total_tax += flt(item_tax.get("tax_amount"))
|
||||
|
||||
row.update(
|
||||
{"total_tax": total_tax, "total": d.base_net_amount + total_tax, "currency": company_currency}
|
||||
{
|
||||
"total_tax": total_tax,
|
||||
"total_other_charges": total_other_charges,
|
||||
"total": d.base_net_amount + total_tax,
|
||||
"currency": company_currency,
|
||||
}
|
||||
)
|
||||
|
||||
if filters.get("group_by"):
|
||||
@@ -477,7 +486,7 @@ def get_tax_accounts(
|
||||
tax_details = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name, parent, description, item_wise_tax_detail,
|
||||
name, parent, description, item_wise_tax_detail, account_head,
|
||||
charge_type, {add_deduct_tax}, base_tax_amount_after_discount_amount
|
||||
from `tab%s`
|
||||
where
|
||||
@@ -493,11 +502,22 @@ def get_tax_accounts(
|
||||
tuple([doctype] + list(invoice_item_row)),
|
||||
)
|
||||
|
||||
account_doctype = frappe.qb.DocType("Account")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(account_doctype)
|
||||
.select(account_doctype.name)
|
||||
.where((account_doctype.account_type == "Tax"))
|
||||
)
|
||||
|
||||
tax_accounts = query.run()
|
||||
|
||||
for (
|
||||
name,
|
||||
parent,
|
||||
description,
|
||||
item_wise_tax_detail,
|
||||
account_head,
|
||||
charge_type,
|
||||
add_deduct_tax,
|
||||
tax_amount,
|
||||
@@ -540,7 +560,11 @@ def get_tax_accounts(
|
||||
)
|
||||
|
||||
itemised_tax.setdefault(d.name, {})[description] = frappe._dict(
|
||||
{"tax_rate": tax_rate, "tax_amount": tax_value}
|
||||
{
|
||||
"tax_rate": tax_rate,
|
||||
"tax_amount": tax_value,
|
||||
"is_other_charges": 0 if tuple([account_head]) in tax_accounts else 1,
|
||||
}
|
||||
)
|
||||
|
||||
except ValueError:
|
||||
@@ -583,6 +607,13 @@ def get_tax_accounts(
|
||||
"options": "currency",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Total Other Charges"),
|
||||
"fieldname": "total_other_charges",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Total"),
|
||||
"fieldname": "total",
|
||||
|
||||
@@ -370,7 +370,7 @@ def get_conditions(filters):
|
||||
where parent=`tabSales Invoice`.name
|
||||
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
|
||||
|
||||
conditions += get_sales_invoice_item_field_condition("mode_of_payments", "Sales Invoice Payment")
|
||||
conditions += get_sales_invoice_item_field_condition("mode_of_payment", "Sales Invoice Payment")
|
||||
conditions += get_sales_invoice_item_field_condition("cost_center")
|
||||
conditions += get_sales_invoice_item_field_condition("warehouse")
|
||||
conditions += get_sales_invoice_item_field_condition("brand")
|
||||
|
||||
@@ -14,9 +14,9 @@ def execute(filters=None):
|
||||
filters.naming_series = frappe.db.get_single_value("Buying Settings", "supp_master_name")
|
||||
|
||||
columns = get_columns(filters)
|
||||
tds_docs, tds_accounts, tax_category_map = get_tds_docs(filters)
|
||||
tds_docs, tds_accounts, tax_category_map, journal_entry_party_map = get_tds_docs(filters)
|
||||
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map)
|
||||
res = get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_party_map)
|
||||
final_result = group_by_supplier_and_category(res)
|
||||
|
||||
return columns, final_result
|
||||
|
||||
@@ -26,7 +26,6 @@ def get_result(filters, tds_docs, tds_accounts, tax_category_map, journal_entry_
|
||||
supplier_map = get_supplier_pan_map()
|
||||
tax_rate_map = get_tax_rate_map(filters)
|
||||
gle_map = get_gle_map(tds_docs)
|
||||
print(journal_entry_party_map)
|
||||
|
||||
out = []
|
||||
for name, details in gle_map.items():
|
||||
|
||||
@@ -172,6 +172,7 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
query_filters = {
|
||||
"company": filters.company,
|
||||
"from_date": filters.from_date,
|
||||
"to_date": filters.to_date,
|
||||
"report_type": report_type,
|
||||
"year_start_date": filters.year_start_date,
|
||||
"project": filters.project,
|
||||
@@ -200,7 +201,7 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
where
|
||||
company=%(company)s
|
||||
{additional_conditions}
|
||||
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
|
||||
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
|
||||
and account in (select name from `tabAccount` where report_type=%(report_type)s)
|
||||
and is_cancelled = 0
|
||||
group by account""".format(
|
||||
|
||||
@@ -104,12 +104,17 @@ def get_opening_balances(filters):
|
||||
where company=%(company)s
|
||||
and is_cancelled=0
|
||||
and ifnull(party_type, '') = %(party_type)s and ifnull(party, '') != ''
|
||||
and (posting_date < %(from_date)s or ifnull(is_opening, 'No') = 'Yes')
|
||||
and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
|
||||
{account_filter}
|
||||
group by party""".format(
|
||||
account_filter=account_filter
|
||||
),
|
||||
{"company": filters.company, "from_date": filters.from_date, "party_type": filters.party_type},
|
||||
{
|
||||
"company": filters.company,
|
||||
"from_date": filters.from_date,
|
||||
"to_date": filters.to_date,
|
||||
"party_type": filters.party_type,
|
||||
},
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@ def get_fiscal_years(
|
||||
)
|
||||
)
|
||||
|
||||
query = query.orderby(FY.year_start_date, Order.desc)
|
||||
query = query.orderby(FY.year_start_date, order=Order.desc)
|
||||
fiscal_years = query.run(as_dict=True)
|
||||
|
||||
frappe.cache().hset("fiscal_years", company, fiscal_years)
|
||||
@@ -823,7 +823,13 @@ def get_held_invoices(party_type, party):
|
||||
|
||||
|
||||
def get_outstanding_invoices(
|
||||
party_type, party, account, common_filter=None, min_outstanding=None, max_outstanding=None
|
||||
party_type,
|
||||
party,
|
||||
account,
|
||||
common_filter=None,
|
||||
posting_date=None,
|
||||
min_outstanding=None,
|
||||
max_outstanding=None,
|
||||
):
|
||||
|
||||
ple = qb.DocType("Payment Ledger Entry")
|
||||
@@ -850,6 +856,7 @@ def get_outstanding_invoices(
|
||||
ple_query = QueryPaymentLedger()
|
||||
invoice_list = ple_query.get_voucher_outstandings(
|
||||
common_filter=common_filter,
|
||||
posting_date=posting_date,
|
||||
min_outstanding=min_outstanding,
|
||||
max_outstanding=max_outstanding,
|
||||
get_invoices=True,
|
||||
@@ -1030,7 +1037,7 @@ def update_cost_center(docname, cost_center_name, cost_center_number, company, m
|
||||
|
||||
frappe.db.set_value("Cost Center", docname, "cost_center_name", cost_center_name.strip())
|
||||
|
||||
new_name = get_autoname_with_number(cost_center_number, cost_center_name, docname, company)
|
||||
new_name = get_autoname_with_number(cost_center_number, cost_center_name, company)
|
||||
if docname != new_name:
|
||||
frappe.rename_doc("Cost Center", docname, new_name, force=1, merge=merge)
|
||||
return new_name
|
||||
@@ -1053,16 +1060,14 @@ def validate_field_number(doctype_name, docname, number_value, company, field_na
|
||||
)
|
||||
|
||||
|
||||
def get_autoname_with_number(number_value, doc_title, name, company):
|
||||
def get_autoname_with_number(number_value, doc_title, company):
|
||||
"""append title with prefix as number and suffix as company's abbreviation separated by '-'"""
|
||||
if name:
|
||||
name_split = name.split("-")
|
||||
parts = [doc_title.strip(), name_split[len(name_split) - 1].strip()]
|
||||
else:
|
||||
abbr = frappe.get_cached_value("Company", company, ["abbr"], as_dict=True)
|
||||
parts = [doc_title.strip(), abbr.abbr]
|
||||
company_abbr = frappe.get_cached_value("Company", company, "abbr")
|
||||
parts = [doc_title.strip(), company_abbr]
|
||||
|
||||
if cstr(number_value).strip():
|
||||
parts.insert(0, cstr(number_value).strip())
|
||||
|
||||
return " - ".join(parts)
|
||||
|
||||
|
||||
@@ -1417,6 +1422,7 @@ def create_payment_ledger_entry(
|
||||
"amount": dr_or_cr,
|
||||
"amount_in_account_currency": dr_or_cr_account_currency,
|
||||
"delinked": True if cancel else False,
|
||||
"remarks": gle.remarks,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1501,6 +1507,7 @@ class QueryPaymentLedger(object):
|
||||
# query filters
|
||||
self.vouchers = []
|
||||
self.common_filter = []
|
||||
self.voucher_posting_date = []
|
||||
self.min_outstanding = None
|
||||
self.max_outstanding = None
|
||||
|
||||
@@ -1571,6 +1578,7 @@ class QueryPaymentLedger(object):
|
||||
.where(ple.delinked == 0)
|
||||
.where(Criterion.all(filter_on_voucher_no))
|
||||
.where(Criterion.all(self.common_filter))
|
||||
.where(Criterion.all(self.voucher_posting_date))
|
||||
.groupby(ple.voucher_type, ple.voucher_no, ple.party_type, ple.party)
|
||||
)
|
||||
|
||||
@@ -1652,6 +1660,7 @@ class QueryPaymentLedger(object):
|
||||
self,
|
||||
vouchers=None,
|
||||
common_filter=None,
|
||||
posting_date=None,
|
||||
min_outstanding=None,
|
||||
max_outstanding=None,
|
||||
get_payments=False,
|
||||
@@ -1671,6 +1680,7 @@ class QueryPaymentLedger(object):
|
||||
self.reset()
|
||||
self.vouchers = vouchers
|
||||
self.common_filter = common_filter or []
|
||||
self.voucher_posting_date = posting_date or []
|
||||
self.min_outstanding = min_outstanding
|
||||
self.max_outstanding = max_outstanding
|
||||
self.get_payments = get_payments
|
||||
|
||||
@@ -230,7 +230,7 @@ frappe.ui.form.on('Asset', {
|
||||
datasets: [{
|
||||
color: 'green',
|
||||
values: asset_values,
|
||||
formatted: asset_values.map(d => d.toFixed(2))
|
||||
formatted: asset_values.map(d => d?.toFixed(2))
|
||||
}]
|
||||
},
|
||||
type: 'line'
|
||||
@@ -239,8 +239,10 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
|
||||
item_code: function(frm) {
|
||||
if(frm.doc.item_code) {
|
||||
if(frm.doc.item_code && frm.doc.calculate_depreciation) {
|
||||
frm.trigger('set_finance_book');
|
||||
} else {
|
||||
frm.set_value('finance_books', []);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -381,6 +383,11 @@ frappe.ui.form.on('Asset', {
|
||||
|
||||
calculate_depreciation: function(frm) {
|
||||
frm.toggle_reqd("finance_books", frm.doc.calculate_depreciation);
|
||||
if (frm.doc.item_code && frm.doc.calculate_depreciation ) {
|
||||
frm.trigger("set_finance_book");
|
||||
} else {
|
||||
frm.set_value("finance_books", []);
|
||||
}
|
||||
},
|
||||
|
||||
gross_purchase_amount: function(frm) {
|
||||
|
||||
@@ -388,7 +388,7 @@
|
||||
"in_standard_filter": 1,
|
||||
"label": "Status",
|
||||
"no_copy": 1,
|
||||
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt",
|
||||
"options": "Draft\nSubmitted\nPartially Depreciated\nFully Depreciated\nSold\nScrapped\nIn Maintenance\nOut of Order\nIssue\nReceipt\nCapitalized\nDecapitalized",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -79,12 +79,12 @@ class Asset(AccountsController):
|
||||
_("Purchase Invoice cannot be made against an existing asset {0}").format(self.name)
|
||||
)
|
||||
|
||||
def prepare_depreciation_data(self, date_of_sale=None, date_of_return=None):
|
||||
def prepare_depreciation_data(self, date_of_disposal=None, date_of_return=None):
|
||||
if self.calculate_depreciation:
|
||||
self.value_after_depreciation = 0
|
||||
self.set_depreciation_rate()
|
||||
self.make_depreciation_schedule(date_of_sale)
|
||||
self.set_accumulated_depreciation(date_of_sale, date_of_return)
|
||||
self.make_depreciation_schedule(date_of_disposal)
|
||||
self.set_accumulated_depreciation(date_of_disposal, date_of_return)
|
||||
else:
|
||||
self.finance_books = []
|
||||
self.value_after_depreciation = flt(self.gross_purchase_amount) - flt(
|
||||
@@ -223,7 +223,7 @@ class Asset(AccountsController):
|
||||
self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
|
||||
)
|
||||
|
||||
def make_depreciation_schedule(self, date_of_sale):
|
||||
def make_depreciation_schedule(self, date_of_disposal):
|
||||
if "Manual" not in [d.depreciation_method for d in self.finance_books] and not self.get(
|
||||
"schedules"
|
||||
):
|
||||
@@ -235,9 +235,9 @@ class Asset(AccountsController):
|
||||
start = self.clear_depreciation_schedule()
|
||||
|
||||
for finance_book in self.get("finance_books"):
|
||||
self._make_depreciation_schedule(finance_book, start, date_of_sale)
|
||||
self._make_depreciation_schedule(finance_book, start, date_of_disposal)
|
||||
|
||||
def _make_depreciation_schedule(self, finance_book, start, date_of_sale):
|
||||
def _make_depreciation_schedule(self, finance_book, start, date_of_disposal):
|
||||
self.validate_asset_finance_books(finance_book)
|
||||
|
||||
value_after_depreciation = self._get_value_after_depreciation(finance_book)
|
||||
@@ -274,15 +274,15 @@ class Asset(AccountsController):
|
||||
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
|
||||
|
||||
# if asset is being sold
|
||||
if date_of_sale:
|
||||
if date_of_disposal:
|
||||
from_date = self.get_from_date(finance_book.finance_book)
|
||||
depreciation_amount, days, months = self.get_pro_rata_amt(
|
||||
finance_book, depreciation_amount, from_date, date_of_sale
|
||||
finance_book, depreciation_amount, from_date, date_of_disposal
|
||||
)
|
||||
|
||||
if depreciation_amount > 0:
|
||||
self._add_depreciation_row(
|
||||
date_of_sale,
|
||||
date_of_disposal,
|
||||
depreciation_amount,
|
||||
finance_book.depreciation_method,
|
||||
finance_book.finance_book,
|
||||
@@ -828,7 +828,9 @@ class Asset(AccountsController):
|
||||
|
||||
|
||||
def update_maintenance_status():
|
||||
assets = frappe.get_all("Asset", filters={"docstatus": 1, "maintenance_required": 1})
|
||||
assets = frappe.get_all(
|
||||
"Asset", filters={"docstatus": 1, "maintenance_required": 1, "disposal_date": ("is", "not set")}
|
||||
)
|
||||
|
||||
for asset in assets:
|
||||
asset = frappe.get_doc("Asset", asset.name)
|
||||
|
||||
@@ -10,6 +10,9 @@ frappe.listview_settings['Asset'] = {
|
||||
} else if (doc.status === "Sold") {
|
||||
return [__("Sold"), "green", "status,=,Sold"];
|
||||
|
||||
} else if (["Capitalized", "Decapitalized"].includes(doc.status)) {
|
||||
return [__(doc.status), "grey", "status,=," + doc.status];
|
||||
|
||||
} else if (doc.status === "Scrapped") {
|
||||
return [__("Scrapped"), "grey", "status,=,Scrapped"];
|
||||
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt, getdate, today
|
||||
from frappe.utils import add_months, cint, flt, getdate, nowdate, today
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_checks_for_pl_and_bs_accounts,
|
||||
)
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||
|
||||
|
||||
def post_depreciation_entries(date=None):
|
||||
def post_depreciation_entries(date=None, commit=True):
|
||||
# Return if automatic booking of asset depreciation is disabled
|
||||
if not cint(
|
||||
frappe.db.get_value("Accounts Settings", None, "book_asset_depreciation_entry_automatically")
|
||||
@@ -22,7 +23,8 @@ def post_depreciation_entries(date=None):
|
||||
date = today()
|
||||
for asset in get_depreciable_assets(date):
|
||||
make_depreciation_entry(asset, date)
|
||||
frappe.db.commit()
|
||||
if commit:
|
||||
frappe.db.commit()
|
||||
|
||||
|
||||
def get_depreciable_assets(date):
|
||||
@@ -190,11 +192,16 @@ def scrap_asset(asset_name):
|
||||
|
||||
if asset.docstatus != 1:
|
||||
frappe.throw(_("Asset {0} must be submitted").format(asset.name))
|
||||
elif asset.status in ("Cancelled", "Sold", "Scrapped"):
|
||||
elif asset.status in ("Cancelled", "Sold", "Scrapped", "Capitalized", "Decapitalized"):
|
||||
frappe.throw(
|
||||
_("Asset {0} cannot be scrapped, as it is already {1}").format(asset.name, asset.status)
|
||||
)
|
||||
|
||||
date = today()
|
||||
|
||||
depreciate_asset(asset, date)
|
||||
asset.reload()
|
||||
|
||||
depreciation_series = frappe.get_cached_value(
|
||||
"Company", asset.company, "series_for_depreciation_entry"
|
||||
)
|
||||
@@ -202,7 +209,7 @@ def scrap_asset(asset_name):
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = "Journal Entry"
|
||||
je.naming_series = depreciation_series
|
||||
je.posting_date = today()
|
||||
je.posting_date = date
|
||||
je.company = asset.company
|
||||
je.remark = "Scrap Entry for asset {0}".format(asset_name)
|
||||
|
||||
@@ -213,7 +220,7 @@ def scrap_asset(asset_name):
|
||||
je.flags.ignore_permissions = True
|
||||
je.submit()
|
||||
|
||||
frappe.db.set_value("Asset", asset_name, "disposal_date", today())
|
||||
frappe.db.set_value("Asset", asset_name, "disposal_date", date)
|
||||
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
|
||||
asset.set_status("Scrapped")
|
||||
|
||||
@@ -224,6 +231,9 @@ def scrap_asset(asset_name):
|
||||
def restore_asset(asset_name):
|
||||
asset = frappe.get_doc("Asset", asset_name)
|
||||
|
||||
reverse_depreciation_entry_made_after_disposal(asset, asset.disposal_date)
|
||||
reset_depreciation_schedule(asset, asset.disposal_date)
|
||||
|
||||
je = asset.journal_entry_for_scrap
|
||||
|
||||
asset.db_set("disposal_date", None)
|
||||
@@ -234,7 +244,94 @@ def restore_asset(asset_name):
|
||||
asset.set_status()
|
||||
|
||||
|
||||
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
|
||||
def depreciate_asset(asset, date):
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.prepare_depreciation_data(date_of_disposal=date)
|
||||
asset.save()
|
||||
|
||||
make_depreciation_entry(asset.name, date)
|
||||
|
||||
|
||||
def reset_depreciation_schedule(asset, date):
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
|
||||
# recreate original depreciation schedule of the asset
|
||||
asset.prepare_depreciation_data(date_of_return=date)
|
||||
|
||||
modify_depreciation_schedule_for_asset_repairs(asset)
|
||||
asset.save()
|
||||
|
||||
|
||||
def modify_depreciation_schedule_for_asset_repairs(asset):
|
||||
asset_repairs = frappe.get_all(
|
||||
"Asset Repair", filters={"asset": asset.name}, fields=["name", "increase_in_asset_life"]
|
||||
)
|
||||
|
||||
for repair in asset_repairs:
|
||||
if repair.increase_in_asset_life:
|
||||
asset_repair = frappe.get_doc("Asset Repair", repair.name)
|
||||
asset_repair.modify_depreciation_schedule()
|
||||
asset.prepare_depreciation_data()
|
||||
|
||||
|
||||
def reverse_depreciation_entry_made_after_disposal(asset, date):
|
||||
row = -1
|
||||
finance_book = asset.get("schedules")[0].get("finance_book")
|
||||
for schedule in asset.get("schedules"):
|
||||
if schedule.finance_book != finance_book:
|
||||
row = 0
|
||||
finance_book = schedule.finance_book
|
||||
else:
|
||||
row += 1
|
||||
|
||||
if schedule.schedule_date == date:
|
||||
if not disposal_was_made_on_original_schedule_date(
|
||||
asset, schedule, row, date
|
||||
) or disposal_happens_in_the_future(date):
|
||||
|
||||
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
|
||||
reverse_journal_entry.posting_date = nowdate()
|
||||
frappe.flags.is_reverse_depr_entry = True
|
||||
reverse_journal_entry.submit()
|
||||
|
||||
frappe.flags.is_reverse_depr_entry = False
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
schedule.journal_entry = None
|
||||
depreciation_amount = get_depreciation_amount_in_je(reverse_journal_entry)
|
||||
asset.finance_books[0].value_after_depreciation += depreciation_amount
|
||||
asset.save()
|
||||
|
||||
|
||||
def get_depreciation_amount_in_je(journal_entry):
|
||||
if journal_entry.accounts[0].debit_in_account_currency:
|
||||
return journal_entry.accounts[0].debit_in_account_currency
|
||||
else:
|
||||
return journal_entry.accounts[0].credit_in_account_currency
|
||||
|
||||
|
||||
# if the invoice had been posted on the date the depreciation was initially supposed to happen, the depreciation shouldn't be undone
|
||||
def disposal_was_made_on_original_schedule_date(asset, schedule, row, posting_date_of_disposal):
|
||||
for finance_book in asset.get("finance_books"):
|
||||
if schedule.finance_book == finance_book.finance_book:
|
||||
orginal_schedule_date = add_months(
|
||||
finance_book.depreciation_start_date, row * cint(finance_book.frequency_of_depreciation)
|
||||
)
|
||||
|
||||
if orginal_schedule_date == posting_date_of_disposal:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def disposal_happens_in_the_future(posting_date_of_disposal):
|
||||
if posting_date_of_disposal > getdate():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_gl_entries_on_asset_regain(
|
||||
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
|
||||
):
|
||||
(
|
||||
fixed_asset_account,
|
||||
asset,
|
||||
@@ -246,28 +343,45 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
|
||||
) = get_asset_details(asset, finance_book)
|
||||
|
||||
gl_entries = [
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"debit_in_account_currency": asset.gross_purchase_amount,
|
||||
"debit": asset.gross_purchase_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
},
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"credit_in_account_currency": accumulated_depr_amount,
|
||||
"credit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
},
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"debit_in_account_currency": asset.gross_purchase_amount,
|
||||
"debit": asset.gross_purchase_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": getdate(),
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"credit_in_account_currency": accumulated_depr_amount,
|
||||
"credit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": getdate(),
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
]
|
||||
|
||||
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
|
||||
if profit_amount:
|
||||
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
||||
get_profit_gl_entries(
|
||||
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
|
||||
)
|
||||
|
||||
if voucher_type and voucher_no:
|
||||
for entry in gl_entries:
|
||||
entry["voucher_type"] = voucher_type
|
||||
entry["voucher_no"] = voucher_no
|
||||
|
||||
return gl_entries
|
||||
|
||||
|
||||
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
|
||||
def get_gl_entries_on_asset_disposal(
|
||||
asset, selling_amount=0, finance_book=None, voucher_type=None, voucher_no=None
|
||||
):
|
||||
(
|
||||
fixed_asset_account,
|
||||
asset,
|
||||
@@ -279,23 +393,38 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
|
||||
) = get_asset_details(asset, finance_book)
|
||||
|
||||
gl_entries = [
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"credit_in_account_currency": asset.gross_purchase_amount,
|
||||
"credit": asset.gross_purchase_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
},
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"debit_in_account_currency": accumulated_depr_amount,
|
||||
"debit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
},
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"credit_in_account_currency": asset.gross_purchase_amount,
|
||||
"credit": asset.gross_purchase_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": getdate(),
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"debit_in_account_currency": accumulated_depr_amount,
|
||||
"debit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": getdate(),
|
||||
},
|
||||
item=asset,
|
||||
),
|
||||
]
|
||||
|
||||
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
||||
if profit_amount:
|
||||
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
||||
get_profit_gl_entries(
|
||||
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
|
||||
)
|
||||
|
||||
if voucher_type and voucher_no:
|
||||
for entry in gl_entries:
|
||||
entry["voucher_type"] = voucher_type
|
||||
entry["voucher_no"] = voucher_no
|
||||
|
||||
return gl_entries
|
||||
|
||||
@@ -332,15 +461,21 @@ def get_asset_details(asset, finance_book=None):
|
||||
)
|
||||
|
||||
|
||||
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
|
||||
def get_profit_gl_entries(
|
||||
asset, profit_amount, gl_entries, disposal_account, depreciation_cost_center
|
||||
):
|
||||
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
||||
gl_entries.append(
|
||||
{
|
||||
"account": disposal_account,
|
||||
"cost_center": depreciation_cost_center,
|
||||
debit_or_credit: abs(profit_amount),
|
||||
debit_or_credit + "_in_account_currency": abs(profit_amount),
|
||||
}
|
||||
asset.get_gl_dict(
|
||||
{
|
||||
"account": disposal_account,
|
||||
"cost_center": depreciation_cost_center,
|
||||
debit_or_credit: abs(profit_amount),
|
||||
debit_or_credit + "_in_account_currency": abs(profit_amount),
|
||||
"posting_date": getdate(),
|
||||
},
|
||||
item=asset,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -358,3 +493,30 @@ def get_disposal_account_and_cost_center(company):
|
||||
frappe.throw(_("Please set 'Asset Depreciation Cost Center' in Company {0}").format(company))
|
||||
|
||||
return disposal_account, depreciation_cost_center
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_value_after_depreciation_on_disposal_date(asset, disposal_date, finance_book=None):
|
||||
asset_doc = frappe.get_doc("Asset", asset)
|
||||
|
||||
if asset_doc.calculate_depreciation:
|
||||
asset_doc.prepare_depreciation_data(getdate(disposal_date))
|
||||
|
||||
finance_book_id = 1
|
||||
if finance_book:
|
||||
for fb in asset_doc.finance_books:
|
||||
if fb.finance_book == finance_book:
|
||||
finance_book_id = fb.idx
|
||||
break
|
||||
|
||||
asset_schedules = [
|
||||
sch for sch in asset_doc.schedules if cint(sch.finance_book_id) == finance_book_id
|
||||
]
|
||||
accumulated_depr_amount = asset_schedules[-1].accumulated_depreciation_amount
|
||||
|
||||
return flt(
|
||||
flt(asset_doc.gross_purchase_amount) - accumulated_depr_amount,
|
||||
asset_doc.precision("gross_purchase_amount"),
|
||||
)
|
||||
else:
|
||||
return flt(asset_doc.value_after_depreciation)
|
||||
|
||||
@@ -4,10 +4,23 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import add_days, add_months, cstr, flt, get_last_day, getdate, nowdate
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
add_months,
|
||||
cstr,
|
||||
flt,
|
||||
get_first_day,
|
||||
get_last_day,
|
||||
getdate,
|
||||
nowdate,
|
||||
)
|
||||
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.assets.doctype.asset.asset import make_sales_invoice, split_asset
|
||||
from erpnext.assets.doctype.asset.asset import (
|
||||
make_sales_invoice,
|
||||
split_asset,
|
||||
update_maintenance_status,
|
||||
)
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
post_depreciation_entries,
|
||||
restore_asset,
|
||||
@@ -178,28 +191,48 @@ class TestAsset(AssetSetup):
|
||||
self.assertEqual(doc.items[0].is_fixed_asset, 1)
|
||||
|
||||
def test_scrap_asset(self):
|
||||
date = nowdate()
|
||||
purchase_date = add_months(get_first_day(date), -2)
|
||||
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2020-01-01",
|
||||
purchase_date="2020-01-01",
|
||||
available_for_use_date=purchase_date,
|
||||
purchase_date=purchase_date,
|
||||
expected_value_after_useful_life=10000,
|
||||
total_number_of_depreciations=10,
|
||||
frequency_of_depreciation=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
post_depreciation_entries(date=add_months("2020-01-01", 4))
|
||||
post_depreciation_entries(date=add_months(purchase_date, 2))
|
||||
asset.load_from_db()
|
||||
|
||||
accumulated_depr_amount = flt(
|
||||
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
|
||||
asset.precision("gross_purchase_amount"),
|
||||
)
|
||||
self.assertEquals(accumulated_depr_amount, 18000.0)
|
||||
|
||||
scrap_asset(asset.name)
|
||||
|
||||
asset.load_from_db()
|
||||
|
||||
accumulated_depr_amount = flt(
|
||||
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
|
||||
asset.precision("gross_purchase_amount"),
|
||||
)
|
||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
self.assertEquals(accumulated_depr_amount, 18000.00 + pro_rata_amount)
|
||||
|
||||
self.assertEqual(asset.status, "Scrapped")
|
||||
self.assertTrue(asset.journal_entry_for_scrap)
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 36000.0, 0.0),
|
||||
("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 64000.0, 0.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 82000.0 - pro_rata_amount, 0.0),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
@@ -216,7 +249,64 @@ class TestAsset(AssetSetup):
|
||||
self.assertFalse(asset.journal_entry_for_scrap)
|
||||
self.assertEqual(asset.status, "Partially Depreciated")
|
||||
|
||||
accumulated_depr_amount = flt(
|
||||
asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
|
||||
asset.precision("gross_purchase_amount"),
|
||||
)
|
||||
this_month_depr_amount = 9000.0 if get_last_day(date) == date else 0
|
||||
|
||||
self.assertEquals(accumulated_depr_amount, 18000.0 + this_month_depr_amount)
|
||||
|
||||
def test_gle_made_by_asset_sale(self):
|
||||
date = nowdate()
|
||||
purchase_date = add_months(get_first_day(date), -2)
|
||||
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date=purchase_date,
|
||||
purchase_date=purchase_date,
|
||||
expected_value_after_useful_life=10000,
|
||||
total_number_of_depreciations=10,
|
||||
frequency_of_depreciation=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
post_depreciation_entries(date=add_months(purchase_date, 2))
|
||||
|
||||
si = make_sales_invoice(asset=asset.name, item_code="Macbook Pro", company="_Test Company")
|
||||
si.customer = "_Test Customer"
|
||||
si.due_date = nowdate()
|
||||
si.get("items")[0].rate = 25000
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
pro_rata_amount, _, _ = asset.get_pro_rata_amt(
|
||||
asset.finance_books[0], 9000, add_months(get_last_day(purchase_date), 1), date
|
||||
)
|
||||
pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 18000.0 + pro_rata_amount, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 57000.0 - pro_rata_amount, 0.0),
|
||||
("Debtors - _TC", 25000.0, 0.0),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Sales Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
si.name,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
si.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||
|
||||
def test_asset_with_maintenance_required_status_after_sale(self):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2020-06-06",
|
||||
@@ -224,6 +314,7 @@ class TestAsset(AssetSetup):
|
||||
expected_value_after_useful_life=10000,
|
||||
total_number_of_depreciations=3,
|
||||
frequency_of_depreciation=10,
|
||||
maintenance_required=1,
|
||||
depreciation_start_date="2020-12-31",
|
||||
submit=1,
|
||||
)
|
||||
@@ -239,24 +330,9 @@ class TestAsset(AssetSetup):
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 20490.2, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 100000.0),
|
||||
("_Test Gain/Loss on Asset Disposal - _TC", 54509.8, 0.0),
|
||||
("Debtors - _TC", 25000.0, 0.0),
|
||||
)
|
||||
update_maintenance_status()
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Sales Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
si.name,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
si.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
def test_asset_splitting(self):
|
||||
asset = create_asset(
|
||||
@@ -1376,6 +1452,7 @@ def create_asset(**args):
|
||||
"number_of_depreciations_booked": args.number_of_depreciations_booked or 0,
|
||||
"gross_purchase_amount": args.gross_purchase_amount or 100000,
|
||||
"purchase_receipt_amount": args.purchase_receipt_amount or 100000,
|
||||
"maintenance_required": args.maintenance_required or 0,
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"available_for_use_date": args.available_for_use_date or "2020-06-06",
|
||||
"location": args.location or "Test Location",
|
||||
@@ -1425,6 +1502,16 @@ def create_asset_category():
|
||||
"depreciation_expense_account": "_Test Depreciations - _TC",
|
||||
},
|
||||
)
|
||||
asset_category.append(
|
||||
"accounts",
|
||||
{
|
||||
"company_name": "_Test Company with perpetual inventory",
|
||||
"fixed_asset_account": "_Test Fixed Asset - TCP1",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1",
|
||||
"depreciation_expense_account": "_Test Depreciations - TCP1",
|
||||
},
|
||||
)
|
||||
|
||||
asset_category.insert()
|
||||
|
||||
|
||||
@@ -1454,12 +1541,14 @@ def create_fixed_asset_item(item_code=None, auto_create_assets=1, is_grouped_ass
|
||||
return item
|
||||
|
||||
|
||||
def set_depreciation_settings_in_company():
|
||||
company = frappe.get_doc("Company", "_Test Company")
|
||||
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - _TC"
|
||||
company.depreciation_expense_account = "_Test Depreciations - _TC"
|
||||
company.disposal_account = "_Test Gain/Loss on Asset Disposal - _TC"
|
||||
company.depreciation_cost_center = "_Test Cost Center - _TC"
|
||||
def set_depreciation_settings_in_company(company=None):
|
||||
if not company:
|
||||
company = "_Test Company"
|
||||
company = frappe.get_doc("Company", company)
|
||||
company.accumulated_depreciation_account = "_Test Accumulated Depreciations - " + company.abbr
|
||||
company.depreciation_expense_account = "_Test Depreciations - " + company.abbr
|
||||
company.disposal_account = "_Test Gain/Loss on Asset Disposal - " + company.abbr
|
||||
company.depreciation_cost_center = "Main - " + company.abbr
|
||||
company.save()
|
||||
|
||||
# Enable booking asset depreciation entry automatically
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.provide("erpnext.assets");
|
||||
|
||||
|
||||
erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.stock.StockController {
|
||||
setup() {
|
||||
this.setup_posting_date_time_check();
|
||||
}
|
||||
|
||||
onload() {
|
||||
this.setup_queries();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
erpnext.hide_company();
|
||||
this.show_general_ledger();
|
||||
if ((this.frm.doc.stock_items && this.frm.doc.stock_items.length) || !this.frm.doc.target_is_fixed_asset) {
|
||||
this.show_stock_ledger();
|
||||
}
|
||||
}
|
||||
|
||||
setup_queries() {
|
||||
var me = this;
|
||||
|
||||
me.setup_warehouse_query();
|
||||
|
||||
me.frm.set_query("target_item_code", function() {
|
||||
if (me.frm.doc.entry_type == "Capitalization") {
|
||||
return erpnext.queries.item({"is_stock_item": 0, "is_fixed_asset": 1});
|
||||
} else {
|
||||
return erpnext.queries.item({"is_stock_item": 1, "is_fixed_asset": 0});
|
||||
}
|
||||
});
|
||||
|
||||
me.frm.set_query("target_asset", function() {
|
||||
var filters = {};
|
||||
|
||||
if (me.frm.doc.target_item_code) {
|
||||
filters['item_code'] = me.frm.doc.target_item_code;
|
||||
}
|
||||
|
||||
filters['status'] = ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]];
|
||||
filters['docstatus'] = 1;
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
};
|
||||
});
|
||||
|
||||
me.frm.set_query("asset", "asset_items", function() {
|
||||
var filters = {
|
||||
'status': ["not in", ["Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"]],
|
||||
'docstatus': 1
|
||||
};
|
||||
|
||||
if (me.frm.doc.target_asset) {
|
||||
filters['name'] = ['!=', me.frm.doc.target_asset];
|
||||
}
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
};
|
||||
});
|
||||
|
||||
me.frm.set_query("item_code", "stock_items", function() {
|
||||
return erpnext.queries.item({"is_stock_item": 1});
|
||||
});
|
||||
|
||||
me.frm.set_query("item_code", "service_items", function() {
|
||||
return erpnext.queries.item({"is_stock_item": 0, "is_fixed_asset": 0});
|
||||
});
|
||||
|
||||
me.frm.set_query('batch_no', 'stock_items', function(doc, cdt, cdn) {
|
||||
var item = locals[cdt][cdn];
|
||||
if (!item.item_code) {
|
||||
frappe.throw(__("Please enter Item Code to get Batch Number"));
|
||||
} else {
|
||||
var filters = {
|
||||
'item_code': item.item_code,
|
||||
'posting_date': me.frm.doc.posting_date || frappe.datetime.nowdate(),
|
||||
'warehouse': item.warehouse
|
||||
};
|
||||
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_batch_no",
|
||||
filters: filters
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
me.frm.set_query('expense_account', 'service_items', function() {
|
||||
return {
|
||||
filters: {
|
||||
"account_type": ['in', ["Tax", "Expense Account", "Income Account", "Expenses Included In Valuation", "Expenses Included In Asset Valuation"]],
|
||||
"is_group": 0,
|
||||
"company": me.frm.doc.company
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
target_item_code() {
|
||||
return this.get_target_item_details();
|
||||
}
|
||||
|
||||
target_asset() {
|
||||
return this.get_target_asset_details();
|
||||
}
|
||||
|
||||
item_code(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
if (cdt === "Asset Capitalization Stock Item") {
|
||||
this.get_consumed_stock_item_details(row);
|
||||
} else if (cdt == "Asset Capitalization Service Item") {
|
||||
this.get_service_item_details(row);
|
||||
}
|
||||
}
|
||||
|
||||
warehouse(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
if (cdt === "Asset Capitalization Stock Item") {
|
||||
this.get_warehouse_details(row);
|
||||
}
|
||||
}
|
||||
|
||||
asset(doc, cdt, cdn) {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
if (cdt === "Asset Capitalization Asset Item") {
|
||||
this.get_consumed_asset_details(row);
|
||||
}
|
||||
}
|
||||
|
||||
posting_date() {
|
||||
if (this.frm.doc.posting_date) {
|
||||
frappe.run_serially([
|
||||
() => this.get_all_item_warehouse_details(),
|
||||
() => this.get_all_asset_values()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
posting_time() {
|
||||
if (this.frm.doc.posting_time) {
|
||||
this.get_all_item_warehouse_details();
|
||||
}
|
||||
}
|
||||
|
||||
finance_book(doc, cdt, cdn) {
|
||||
if (cdt === "Asset Capitalization Asset Item") {
|
||||
var row = frappe.get_doc(cdt, cdn);
|
||||
this.get_consumed_asset_details(row);
|
||||
} else {
|
||||
this.get_all_asset_values();
|
||||
}
|
||||
}
|
||||
|
||||
stock_qty() {
|
||||
this.calculate_totals();
|
||||
}
|
||||
|
||||
qty() {
|
||||
this.calculate_totals();
|
||||
}
|
||||
|
||||
target_qty() {
|
||||
this.calculate_totals();
|
||||
}
|
||||
|
||||
rate() {
|
||||
this.calculate_totals();
|
||||
}
|
||||
|
||||
company() {
|
||||
var me = this;
|
||||
|
||||
if (me.frm.doc.company) {
|
||||
frappe.model.set_value(me.frm.doc.doctype, me.frm.doc.name, "cost_center", null);
|
||||
$.each(me.frm.doc.stock_items || [], function (i, d) {
|
||||
frappe.model.set_value(d.doctype, d.name, "cost_center", null);
|
||||
});
|
||||
$.each(me.frm.doc.asset_items || [], function (i, d) {
|
||||
frappe.model.set_value(d.doctype, d.name, "cost_center", null);
|
||||
});
|
||||
$.each(me.frm.doc.service_items || [], function (i, d) {
|
||||
frappe.model.set_value(d.doctype, d.name, "cost_center", null);
|
||||
});
|
||||
}
|
||||
|
||||
erpnext.accounts.dimensions.update_dimension(me.frm, me.frm.doctype);
|
||||
}
|
||||
|
||||
stock_items_add(doc, cdt, cdn) {
|
||||
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'stock_items');
|
||||
}
|
||||
|
||||
asset_items_add(doc, cdt, cdn) {
|
||||
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'asset_items');
|
||||
}
|
||||
|
||||
serivce_items_add(doc, cdt, cdn) {
|
||||
erpnext.accounts.dimensions.copy_dimension_from_first_row(this.frm, cdt, cdn, 'service_items');
|
||||
}
|
||||
|
||||
get_target_item_details() {
|
||||
var me = this;
|
||||
|
||||
if (me.frm.doc.target_item_code) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_item_details",
|
||||
child: me.frm.doc,
|
||||
args: {
|
||||
item_code: me.frm.doc.target_item_code,
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_target_asset_details() {
|
||||
var me = this;
|
||||
|
||||
if (me.frm.doc.target_asset) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_target_asset_details",
|
||||
child: me.frm.doc,
|
||||
args: {
|
||||
asset: me.frm.doc.target_asset,
|
||||
company: me.frm.doc.company,
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_consumed_stock_item_details(row) {
|
||||
var me = this;
|
||||
|
||||
if (row && row.item_code) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_consumed_stock_item_details",
|
||||
child: row,
|
||||
args: {
|
||||
args: {
|
||||
item_code: row.item_code,
|
||||
warehouse: row.warehouse,
|
||||
stock_qty: flt(row.stock_qty),
|
||||
doctype: me.frm.doc.doctype,
|
||||
name: me.frm.doc.name,
|
||||
company: me.frm.doc.company,
|
||||
posting_date: me.frm.doc.posting_date,
|
||||
posting_time: me.frm.doc.posting_time,
|
||||
}
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
me.calculate_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_consumed_asset_details(row) {
|
||||
var me = this;
|
||||
|
||||
if (row && row.asset) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_consumed_asset_details",
|
||||
child: row,
|
||||
args: {
|
||||
args: {
|
||||
asset: row.asset,
|
||||
doctype: me.frm.doc.doctype,
|
||||
name: me.frm.doc.name,
|
||||
company: me.frm.doc.company,
|
||||
finance_book: row.finance_book || me.frm.doc.finance_book,
|
||||
posting_date: me.frm.doc.posting_date,
|
||||
posting_time: me.frm.doc.posting_time,
|
||||
}
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
me.calculate_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_service_item_details(row) {
|
||||
var me = this;
|
||||
|
||||
if (row && row.item_code) {
|
||||
return me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_service_item_details",
|
||||
child: row,
|
||||
args: {
|
||||
args: {
|
||||
item_code: row.item_code,
|
||||
qty: flt(row.qty),
|
||||
expense_account: row.expense_account,
|
||||
company: me.frm.doc.company,
|
||||
}
|
||||
},
|
||||
callback: function (r) {
|
||||
if (!r.exc) {
|
||||
me.calculate_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_warehouse_details(item) {
|
||||
var me = this;
|
||||
if (item.item_code && item.warehouse) {
|
||||
me.frm.call({
|
||||
method: "erpnext.assets.doctype.asset_capitalization.asset_capitalization.get_warehouse_details",
|
||||
child: item,
|
||||
args: {
|
||||
args: {
|
||||
'item_code': item.item_code,
|
||||
'warehouse': cstr(item.warehouse),
|
||||
'qty': flt(item.stock_qty),
|
||||
'serial_no': item.serial_no,
|
||||
'posting_date': me.frm.doc.posting_date,
|
||||
'posting_time': me.frm.doc.posting_time,
|
||||
'company': me.frm.doc.company,
|
||||
'voucher_type': me.frm.doc.doctype,
|
||||
'voucher_no': me.frm.doc.name,
|
||||
'allow_zero_valuation': 1
|
||||
}
|
||||
},
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
me.calculate_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
get_all_item_warehouse_details() {
|
||||
var me = this;
|
||||
return me.frm.call({
|
||||
method: "set_warehouse_details",
|
||||
doc: me.frm.doc,
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
me.calculate_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
get_all_asset_values() {
|
||||
var me = this;
|
||||
return me.frm.call({
|
||||
method: "set_asset_values",
|
||||
doc: me.frm.doc,
|
||||
callback: function(r) {
|
||||
if (!r.exc) {
|
||||
me.calculate_totals();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
calculate_totals() {
|
||||
var me = this;
|
||||
|
||||
me.frm.doc.stock_items_total = 0;
|
||||
me.frm.doc.asset_items_total = 0;
|
||||
me.frm.doc.service_items_total = 0;
|
||||
|
||||
$.each(me.frm.doc.stock_items || [], function (i, d) {
|
||||
d.amount = flt(flt(d.stock_qty) * flt(d.valuation_rate), precision('amount', d));
|
||||
me.frm.doc.stock_items_total += d.amount;
|
||||
});
|
||||
|
||||
$.each(me.frm.doc.asset_items || [], function (i, d) {
|
||||
d.asset_value = flt(flt(d.asset_value), precision('asset_value', d));
|
||||
me.frm.doc.asset_items_total += d.asset_value;
|
||||
});
|
||||
|
||||
$.each(me.frm.doc.service_items || [], function (i, d) {
|
||||
d.amount = flt(flt(d.qty) * flt(d.rate), precision('amount', d));
|
||||
me.frm.doc.service_items_total += d.amount;
|
||||
});
|
||||
|
||||
me.frm.doc.stock_items_total = flt(me.frm.doc.stock_items_total, precision('stock_items_total'));
|
||||
me.frm.doc.asset_items_total = flt(me.frm.doc.asset_items_total, precision('asset_items_total'));
|
||||
me.frm.doc.service_items_total = flt(me.frm.doc.service_items_total, precision('service_items_total'));
|
||||
|
||||
me.frm.doc.total_value = me.frm.doc.stock_items_total + me.frm.doc.asset_items_total + me.frm.doc.service_items_total;
|
||||
me.frm.doc.total_value = flt(me.frm.doc.total_value, precision('total_value'));
|
||||
|
||||
me.frm.doc.target_qty = flt(me.frm.doc.target_qty, precision('target_qty'));
|
||||
me.frm.doc.target_incoming_rate = me.frm.doc.target_qty ? me.frm.doc.total_value / flt(me.frm.doc.target_qty)
|
||||
: me.frm.doc.total_value;
|
||||
|
||||
me.frm.refresh_fields();
|
||||
}
|
||||
};
|
||||
|
||||
cur_frm.cscript = new erpnext.assets.AssetCapitalization({frm: cur_frm});
|
||||
@@ -0,0 +1,381 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2021-09-04 13:38:04.217187",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Document",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"title",
|
||||
"naming_series",
|
||||
"entry_type",
|
||||
"target_item_code",
|
||||
"target_item_name",
|
||||
"target_is_fixed_asset",
|
||||
"target_has_batch_no",
|
||||
"target_has_serial_no",
|
||||
"column_break_9",
|
||||
"target_asset",
|
||||
"target_asset_name",
|
||||
"target_warehouse",
|
||||
"target_qty",
|
||||
"target_stock_uom",
|
||||
"target_batch_no",
|
||||
"target_serial_no",
|
||||
"column_break_5",
|
||||
"company",
|
||||
"finance_book",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"set_posting_time",
|
||||
"amended_from",
|
||||
"section_break_16",
|
||||
"stock_items",
|
||||
"stock_items_total",
|
||||
"section_break_26",
|
||||
"asset_items",
|
||||
"asset_items_total",
|
||||
"service_expenses_section",
|
||||
"service_items",
|
||||
"service_items_total",
|
||||
"totals_section",
|
||||
"total_value",
|
||||
"column_break_36",
|
||||
"target_incoming_rate",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"target_fixed_asset_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Title"
|
||||
},
|
||||
{
|
||||
"fieldname": "target_item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Target Item Code",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.target_item_code && doc.target_item_name != doc.target_item_code",
|
||||
"fetch_from": "target_item_code.item_name",
|
||||
"fieldname": "target_item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Target Item Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "target_item_code.is_fixed_asset",
|
||||
"fieldname": "target_is_fixed_asset",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Target Is Fixed Asset",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_5",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"fieldname": "target_asset",
|
||||
"fieldtype": "Link",
|
||||
"in_standard_filter": 1,
|
||||
"label": "Target Asset",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"no_copy": 1,
|
||||
"options": "Asset"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Capitalization'",
|
||||
"fetch_from": "target_asset.asset_name",
|
||||
"fieldname": "target_asset_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Asset Name",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "asset.company",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Today",
|
||||
"fieldname": "posting_date",
|
||||
"fieldtype": "Date",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Posting Date",
|
||||
"no_copy": 1,
|
||||
"reqd": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"default": "Now",
|
||||
"fieldname": "posting_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "Posting Time",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:doc.docstatus==0",
|
||||
"fieldname": "set_posting_time",
|
||||
"fieldtype": "Check",
|
||||
"label": "Edit Posting Date and Time"
|
||||
},
|
||||
{
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"label": "Series",
|
||||
"options": "ACC-ASC-.YYYY.-",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "amended_from",
|
||||
"fieldtype": "Link",
|
||||
"label": "Amended From",
|
||||
"no_copy": 1,
|
||||
"options": "Asset Capitalization",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.stock_items && doc.stock_items.length)",
|
||||
"fieldname": "section_break_16",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Consumed Stock Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Stock Items",
|
||||
"options": "Asset Capitalization Stock Item"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.entry_type=='Decapitalization'",
|
||||
"fieldname": "target_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Warehouse",
|
||||
"mandatory_depends_on": "eval:doc.entry_type=='Decapitalization'",
|
||||
"options": "Warehouse"
|
||||
},
|
||||
{
|
||||
"depends_on": "target_has_batch_no",
|
||||
"fieldname": "target_batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Batch No",
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"default": "1",
|
||||
"fieldname": "target_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Target Qty",
|
||||
"read_only_depends_on": "target_is_fixed_asset"
|
||||
},
|
||||
{
|
||||
"fetch_from": "target_item_code.stock_uom",
|
||||
"fieldname": "target_stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock UOM",
|
||||
"options": "UOM",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "target_item_code.has_batch_no",
|
||||
"fieldname": "target_has_batch_no",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Target Has Batch No",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fetch_from": "target_item_code.has_serial_no",
|
||||
"fieldname": "target_has_serial_no",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 1,
|
||||
"label": "Target Has Serial No",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "target_has_serial_no",
|
||||
"fieldname": "target_serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Target Serial No"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.asset_items && doc.asset_items.length)",
|
||||
"fieldname": "section_break_26",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Consumed Asset Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "asset_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Assets",
|
||||
"options": "Asset Capitalization Asset Item"
|
||||
},
|
||||
{
|
||||
"default": "Capitalization",
|
||||
"fieldname": "entry_type",
|
||||
"fieldtype": "Select",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Entry Type",
|
||||
"options": "Capitalization\nDecapitalization",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "stock_items_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumed Stock Total Value",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "asset_items_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Consumed Asset Total Value",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus == 0 || (doc.service_items && doc.service_items.length)",
|
||||
"fieldname": "service_expenses_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Service Expenses"
|
||||
},
|
||||
{
|
||||
"fieldname": "service_items",
|
||||
"fieldtype": "Table",
|
||||
"label": "Services",
|
||||
"options": "Asset Capitalization Service Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "service_items_total",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Service Expense Total Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "totals_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Totals"
|
||||
},
|
||||
{
|
||||
"fieldname": "total_value",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Total Value",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_36",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "target_incoming_rate",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Target Incoming Rate",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "target_fixed_asset_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Target Fixed Asset Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-09-12 15:09:40.771332",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Manufacturing Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"amend": 1,
|
||||
"cancel": 1,
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Quality Manager",
|
||||
"share": 1,
|
||||
"submit": 1,
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "title",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
}
|
||||
@@ -0,0 +1,756 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import json
|
||||
|
||||
import frappe
|
||||
|
||||
# import erpnext
|
||||
from frappe import _
|
||||
from frappe.utils import cint, flt
|
||||
from six import string_types
|
||||
|
||||
import erpnext
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
depreciate_asset,
|
||||
get_gl_entries_on_asset_disposal,
|
||||
get_value_after_depreciation_on_disposal_date,
|
||||
reset_depreciation_schedule,
|
||||
reverse_depreciation_entry_made_after_disposal,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment import (
|
||||
get_current_asset_value,
|
||||
)
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
from erpnext.stock import get_warehouse_account_map
|
||||
from erpnext.stock.doctype.item.item import get_item_defaults
|
||||
from erpnext.stock.get_item_details import (
|
||||
get_default_cost_center,
|
||||
get_default_expense_account,
|
||||
get_item_warehouse,
|
||||
)
|
||||
from erpnext.stock.stock_ledger import get_previous_sle
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
|
||||
force_fields = [
|
||||
"target_item_name",
|
||||
"target_asset_name",
|
||||
"item_name",
|
||||
"asset_name",
|
||||
"target_is_fixed_asset",
|
||||
"target_has_serial_no",
|
||||
"target_has_batch_no",
|
||||
"target_stock_uom",
|
||||
"stock_uom",
|
||||
"target_fixed_asset_account",
|
||||
"fixed_asset_account",
|
||||
"valuation_rate",
|
||||
]
|
||||
|
||||
|
||||
class AssetCapitalization(StockController):
|
||||
def validate(self):
|
||||
self.validate_posting_time()
|
||||
self.set_missing_values(for_validate=True)
|
||||
self.validate_target_item()
|
||||
self.validate_target_asset()
|
||||
self.validate_consumed_stock_item()
|
||||
self.validate_consumed_asset_item()
|
||||
self.validate_service_item()
|
||||
self.set_warehouse_details()
|
||||
self.set_asset_values()
|
||||
self.calculate_totals()
|
||||
self.set_title()
|
||||
|
||||
def before_submit(self):
|
||||
self.validate_source_mandatory()
|
||||
|
||||
def on_submit(self):
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.update_target_asset()
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||
self.update_stock_ledger()
|
||||
self.make_gl_entries()
|
||||
self.update_target_asset()
|
||||
|
||||
def set_title(self):
|
||||
self.title = self.target_asset_name or self.target_item_name or self.target_item_code
|
||||
|
||||
def set_missing_values(self, for_validate=False):
|
||||
target_item_details = get_target_item_details(self.target_item_code, self.company)
|
||||
for k, v in target_item_details.items():
|
||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||
self.set(k, v)
|
||||
|
||||
# Remove asset if item not a fixed asset
|
||||
if not self.target_is_fixed_asset:
|
||||
self.target_asset = None
|
||||
|
||||
target_asset_details = get_target_asset_details(self.target_asset, self.company)
|
||||
for k, v in target_asset_details.items():
|
||||
if self.meta.has_field(k) and (not self.get(k) or k in force_fields):
|
||||
self.set(k, v)
|
||||
|
||||
for d in self.stock_items:
|
||||
args = self.as_dict()
|
||||
args.update(d.as_dict())
|
||||
args.doctype = self.doctype
|
||||
args.name = self.name
|
||||
consumed_stock_item_details = get_consumed_stock_item_details(args)
|
||||
for k, v in consumed_stock_item_details.items():
|
||||
if d.meta.has_field(k) and (not d.get(k) or k in force_fields):
|
||||
d.set(k, v)
|
||||
|
||||
for d in self.asset_items:
|
||||
args = self.as_dict()
|
||||
args.update(d.as_dict())
|
||||
args.doctype = self.doctype
|
||||
args.name = self.name
|
||||
args.finance_book = d.get("finance_book") or self.get("finance_book")
|
||||
consumed_asset_details = get_consumed_asset_details(args)
|
||||
for k, v in consumed_asset_details.items():
|
||||
if d.meta.has_field(k) and (not d.get(k) or k in force_fields):
|
||||
d.set(k, v)
|
||||
|
||||
for d in self.service_items:
|
||||
args = self.as_dict()
|
||||
args.update(d.as_dict())
|
||||
args.doctype = self.doctype
|
||||
args.name = self.name
|
||||
service_item_details = get_service_item_details(args)
|
||||
for k, v in service_item_details.items():
|
||||
if d.meta.has_field(k) and (not d.get(k) or k in force_fields):
|
||||
d.set(k, v)
|
||||
|
||||
def validate_target_item(self):
|
||||
target_item = frappe.get_cached_doc("Item", self.target_item_code)
|
||||
|
||||
if not target_item.is_fixed_asset and not target_item.is_stock_item:
|
||||
frappe.throw(
|
||||
_("Target Item {0} is neither a Fixed Asset nor a Stock Item").format(target_item.name)
|
||||
)
|
||||
|
||||
if self.entry_type == "Capitalization" and not target_item.is_fixed_asset:
|
||||
frappe.throw(_("Target Item {0} must be a Fixed Asset item").format(target_item.name))
|
||||
elif self.entry_type == "Decapitalization" and not target_item.is_stock_item:
|
||||
frappe.throw(_("Target Item {0} must be a Stock Item").format(target_item.name))
|
||||
|
||||
if target_item.is_fixed_asset:
|
||||
self.target_qty = 1
|
||||
if flt(self.target_qty) <= 0:
|
||||
frappe.throw(_("Target Qty must be a positive number"))
|
||||
|
||||
if not target_item.is_stock_item:
|
||||
self.target_warehouse = None
|
||||
if not target_item.is_fixed_asset:
|
||||
self.target_asset = None
|
||||
self.target_fixed_asset_account = None
|
||||
if not target_item.has_batch_no:
|
||||
self.target_batch_no = None
|
||||
if not target_item.has_serial_no:
|
||||
self.target_serial_no = ""
|
||||
|
||||
if target_item.is_stock_item and not self.target_warehouse:
|
||||
frappe.throw(_("Target Warehouse is mandatory for Decapitalization"))
|
||||
|
||||
self.validate_item(target_item)
|
||||
|
||||
def validate_target_asset(self):
|
||||
if self.target_asset:
|
||||
target_asset = self.get_asset_for_validation(self.target_asset)
|
||||
|
||||
if target_asset.item_code != self.target_item_code:
|
||||
frappe.throw(
|
||||
_("Asset {0} does not belong to Item {1}").format(self.target_asset, self.target_item_code)
|
||||
)
|
||||
|
||||
self.validate_asset(target_asset)
|
||||
|
||||
def validate_consumed_stock_item(self):
|
||||
for d in self.stock_items:
|
||||
if d.item_code:
|
||||
item = frappe.get_cached_doc("Item", d.item_code)
|
||||
|
||||
if not item.is_stock_item:
|
||||
frappe.throw(_("Row #{0}: Item {1} is not a stock item").format(d.idx, d.item_code))
|
||||
|
||||
if flt(d.stock_qty) <= 0:
|
||||
frappe.throw(_("Row #{0}: Qty must be a positive number").format(d.idx))
|
||||
|
||||
self.validate_item(item)
|
||||
|
||||
def validate_consumed_asset_item(self):
|
||||
for d in self.asset_items:
|
||||
if d.asset:
|
||||
if d.asset == self.target_asset:
|
||||
frappe.throw(
|
||||
_("Row #{0}: Consumed Asset {1} cannot be the same as the Target Asset").format(
|
||||
d.idx, d.asset
|
||||
)
|
||||
)
|
||||
|
||||
asset = self.get_asset_for_validation(d.asset)
|
||||
self.validate_asset(asset)
|
||||
|
||||
def validate_service_item(self):
|
||||
for d in self.service_items:
|
||||
if d.item_code:
|
||||
item = frappe.get_cached_doc("Item", d.item_code)
|
||||
|
||||
if item.is_stock_item or item.is_fixed_asset:
|
||||
frappe.throw(_("Row #{0}: Item {1} is not a service item").format(d.idx, d.item_code))
|
||||
|
||||
if flt(d.qty) <= 0:
|
||||
frappe.throw(_("Row #{0}: Qty must be a positive number").format(d.idx))
|
||||
|
||||
if flt(d.rate) <= 0:
|
||||
frappe.throw(_("Row #{0}: Amount must be a positive number").format(d.idx))
|
||||
|
||||
self.validate_item(item)
|
||||
|
||||
if not d.cost_center:
|
||||
d.cost_center = frappe.get_cached_value("Company", self.company, "cost_center")
|
||||
|
||||
def validate_source_mandatory(self):
|
||||
if not self.target_is_fixed_asset and not self.get("asset_items"):
|
||||
frappe.throw(_("Consumed Asset Items is mandatory for Decapitalization"))
|
||||
|
||||
if not self.get("stock_items") and not self.get("asset_items"):
|
||||
frappe.throw(_("Consumed Stock Items or Consumed Asset Items is mandatory for Capitalization"))
|
||||
|
||||
def validate_item(self, item):
|
||||
from erpnext.stock.doctype.item.item import validate_end_of_life
|
||||
|
||||
validate_end_of_life(item.name, item.end_of_life, item.disabled)
|
||||
|
||||
def get_asset_for_validation(self, asset):
|
||||
return frappe.db.get_value(
|
||||
"Asset", asset, ["name", "item_code", "company", "status", "docstatus"], as_dict=1
|
||||
)
|
||||
|
||||
def validate_asset(self, asset):
|
||||
if asset.status in ("Draft", "Scrapped", "Sold", "Capitalized", "Decapitalized"):
|
||||
frappe.throw(_("Asset {0} is {1}").format(asset.name, asset.status))
|
||||
|
||||
if asset.docstatus == 0:
|
||||
frappe.throw(_("Asset {0} is Draft").format(asset.name))
|
||||
if asset.docstatus == 2:
|
||||
frappe.throw(_("Asset {0} is cancelled").format(asset.name))
|
||||
|
||||
if asset.company != self.company:
|
||||
frappe.throw(_("Asset {0} does not belong to company {1}").format(asset.name, self.company))
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_warehouse_details(self):
|
||||
for d in self.get("stock_items"):
|
||||
if d.item_code and d.warehouse:
|
||||
args = self.get_args_for_incoming_rate(d)
|
||||
warehouse_details = get_warehouse_details(args)
|
||||
d.update(warehouse_details)
|
||||
|
||||
@frappe.whitelist()
|
||||
def set_asset_values(self):
|
||||
for d in self.get("asset_items"):
|
||||
if d.asset:
|
||||
finance_book = d.get("finance_book") or self.get("finance_book")
|
||||
d.current_asset_value = flt(get_current_asset_value(d.asset, finance_book=finance_book))
|
||||
d.asset_value = get_value_after_depreciation_on_disposal_date(
|
||||
d.asset, self.posting_date, finance_book=finance_book
|
||||
)
|
||||
|
||||
def get_args_for_incoming_rate(self, item):
|
||||
return frappe._dict(
|
||||
{
|
||||
"item_code": item.item_code,
|
||||
"warehouse": item.warehouse,
|
||||
"posting_date": self.posting_date,
|
||||
"posting_time": self.posting_time,
|
||||
"qty": -1 * flt(item.stock_qty),
|
||||
"serial_no": item.serial_no,
|
||||
"batch_no": item.batch_no,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"company": self.company,
|
||||
"allow_zero_valuation": cint(item.get("allow_zero_valuation_rate")),
|
||||
}
|
||||
)
|
||||
|
||||
def calculate_totals(self):
|
||||
self.stock_items_total = 0
|
||||
self.asset_items_total = 0
|
||||
self.service_items_total = 0
|
||||
|
||||
for d in self.stock_items:
|
||||
d.amount = flt(flt(d.stock_qty) * flt(d.valuation_rate), d.precision("amount"))
|
||||
self.stock_items_total += d.amount
|
||||
|
||||
for d in self.asset_items:
|
||||
d.asset_value = flt(flt(d.asset_value), d.precision("asset_value"))
|
||||
self.asset_items_total += d.asset_value
|
||||
|
||||
for d in self.service_items:
|
||||
d.amount = flt(flt(d.qty) * flt(d.rate), d.precision("amount"))
|
||||
self.service_items_total += d.amount
|
||||
|
||||
self.stock_items_total = flt(self.stock_items_total, self.precision("stock_items_total"))
|
||||
self.asset_items_total = flt(self.asset_items_total, self.precision("asset_items_total"))
|
||||
self.service_items_total = flt(self.service_items_total, self.precision("service_items_total"))
|
||||
|
||||
self.total_value = self.stock_items_total + self.asset_items_total + self.service_items_total
|
||||
self.total_value = flt(self.total_value, self.precision("total_value"))
|
||||
|
||||
self.target_qty = flt(self.target_qty, self.precision("target_qty"))
|
||||
self.target_incoming_rate = self.total_value / self.target_qty
|
||||
|
||||
def update_stock_ledger(self):
|
||||
sl_entries = []
|
||||
|
||||
for d in self.stock_items:
|
||||
sle = self.get_sl_entries(
|
||||
d,
|
||||
{
|
||||
"actual_qty": -flt(d.stock_qty),
|
||||
},
|
||||
)
|
||||
sl_entries.append(sle)
|
||||
|
||||
if self.entry_type == "Decapitalization" and not self.target_is_fixed_asset:
|
||||
sle = self.get_sl_entries(
|
||||
self,
|
||||
{
|
||||
"item_code": self.target_item_code,
|
||||
"warehouse": self.target_warehouse,
|
||||
"batch_no": self.target_batch_no,
|
||||
"serial_no": self.target_serial_no,
|
||||
"actual_qty": flt(self.target_qty),
|
||||
"incoming_rate": flt(self.target_incoming_rate),
|
||||
},
|
||||
)
|
||||
sl_entries.append(sle)
|
||||
|
||||
# reverse sl entries if cancel
|
||||
if self.docstatus == 2:
|
||||
sl_entries.reverse()
|
||||
|
||||
if sl_entries:
|
||||
self.make_sl_entries(sl_entries)
|
||||
|
||||
def make_gl_entries(self, gl_entries=None, from_repost=False):
|
||||
from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries
|
||||
|
||||
if self.docstatus == 1:
|
||||
if not gl_entries:
|
||||
gl_entries = self.get_gl_entries()
|
||||
|
||||
if gl_entries:
|
||||
make_gl_entries(gl_entries, from_repost=from_repost)
|
||||
elif self.docstatus == 2:
|
||||
make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
|
||||
|
||||
def get_gl_entries(
|
||||
self, warehouse_account=None, default_expense_account=None, default_cost_center=None
|
||||
):
|
||||
# Stock GL Entries
|
||||
gl_entries = []
|
||||
|
||||
self.warehouse_account = warehouse_account
|
||||
if not self.warehouse_account:
|
||||
self.warehouse_account = get_warehouse_account_map(self.company)
|
||||
|
||||
precision = self.get_debit_field_precision()
|
||||
self.sle_map = self.get_stock_ledger_details()
|
||||
|
||||
target_account = self.get_target_account()
|
||||
target_against = set()
|
||||
|
||||
self.get_gl_entries_for_consumed_stock_items(
|
||||
gl_entries, target_account, target_against, precision
|
||||
)
|
||||
self.get_gl_entries_for_consumed_asset_items(
|
||||
gl_entries, target_account, target_against, precision
|
||||
)
|
||||
self.get_gl_entries_for_consumed_service_items(
|
||||
gl_entries, target_account, target_against, precision
|
||||
)
|
||||
|
||||
self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
|
||||
return gl_entries
|
||||
|
||||
def get_target_account(self):
|
||||
if self.target_is_fixed_asset:
|
||||
return self.target_fixed_asset_account
|
||||
else:
|
||||
return self.warehouse_account[self.target_warehouse]["account"]
|
||||
|
||||
def get_gl_entries_for_consumed_stock_items(
|
||||
self, gl_entries, target_account, target_against, precision
|
||||
):
|
||||
# Consumed Stock Items
|
||||
for item_row in self.stock_items:
|
||||
sle_list = self.sle_map.get(item_row.name)
|
||||
if sle_list:
|
||||
for sle in sle_list:
|
||||
stock_value_difference = flt(sle.stock_value_difference, precision)
|
||||
|
||||
if erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
account = self.warehouse_account[sle.warehouse]["account"]
|
||||
else:
|
||||
account = self.get_company_default("default_expense_account")
|
||||
|
||||
target_against.add(account)
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": account,
|
||||
"against": target_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.get("project") or self.get("project"),
|
||||
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
||||
"credit": -1 * stock_value_difference,
|
||||
},
|
||||
self.warehouse_account[sle.warehouse]["account_currency"],
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
def get_gl_entries_for_consumed_asset_items(
|
||||
self, gl_entries, target_account, target_against, precision
|
||||
):
|
||||
# Consumed Assets
|
||||
for item in self.asset_items:
|
||||
asset = self.get_asset(item)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
depreciate_asset(asset, self.posting_date)
|
||||
asset.reload()
|
||||
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||
asset,
|
||||
item.asset_value,
|
||||
item.get("finance_book") or self.get("finance_book"),
|
||||
self.get("doctype"),
|
||||
self.get("name"),
|
||||
)
|
||||
|
||||
asset.db_set("disposal_date", self.posting_date)
|
||||
|
||||
self.set_consumed_asset_status(asset)
|
||||
|
||||
for gle in fixed_asset_gl_entries:
|
||||
gle["against"] = target_account
|
||||
gl_entries.append(self.get_gl_dict(gle, item=item))
|
||||
target_against.add(gle["account"])
|
||||
|
||||
def get_gl_entries_for_consumed_service_items(
|
||||
self, gl_entries, target_account, target_against, precision
|
||||
):
|
||||
# Service Expenses
|
||||
for item_row in self.service_items:
|
||||
expense_amount = flt(item_row.amount, precision)
|
||||
target_against.add(item_row.expense_account)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item_row.expense_account,
|
||||
"against": target_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.get("project") or self.get("project"),
|
||||
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
||||
"credit": expense_amount,
|
||||
},
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
def get_gl_entries_for_target_item(self, gl_entries, target_against, precision):
|
||||
if self.target_is_fixed_asset:
|
||||
# Capitalization
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.target_fixed_asset_account,
|
||||
"against": ", ".join(target_against),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": flt(self.total_value, precision),
|
||||
"cost_center": self.get("cost_center"),
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Target Stock Item
|
||||
sle_list = self.sle_map.get(self.name)
|
||||
for sle in sle_list:
|
||||
stock_value_difference = flt(sle.stock_value_difference, precision)
|
||||
account = self.warehouse_account[sle.warehouse]["account"]
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": account,
|
||||
"against": ", ".join(target_against),
|
||||
"cost_center": self.cost_center,
|
||||
"project": self.get("project"),
|
||||
"remarks": self.get("remarks") or "Accounting Entry for Stock",
|
||||
"debit": stock_value_difference,
|
||||
},
|
||||
self.warehouse_account[sle.warehouse]["account_currency"],
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def update_target_asset(self):
|
||||
total_target_asset_value = flt(self.total_value, self.precision("total_value"))
|
||||
if self.docstatus == 1 and self.entry_type == "Capitalization":
|
||||
asset_doc = frappe.get_doc("Asset", self.target_asset)
|
||||
asset_doc.purchase_date = self.posting_date
|
||||
asset_doc.gross_purchase_amount = total_target_asset_value
|
||||
asset_doc.purchase_receipt_amount = total_target_asset_value
|
||||
asset_doc.prepare_depreciation_data()
|
||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
asset_doc.save()
|
||||
elif self.docstatus == 2:
|
||||
for item in self.asset_items:
|
||||
asset = self.get_asset(item)
|
||||
asset.db_set("disposal_date", None)
|
||||
self.set_consumed_asset_status(asset)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
reverse_depreciation_entry_made_after_disposal(asset, self.posting_date)
|
||||
reset_depreciation_schedule(asset, self.posting_date)
|
||||
|
||||
def get_asset(self, item):
|
||||
asset = frappe.get_doc("Asset", item.asset)
|
||||
self.check_finance_books(item, asset)
|
||||
return asset
|
||||
|
||||
def set_consumed_asset_status(self, asset):
|
||||
if self.docstatus == 1:
|
||||
asset.set_status("Capitalized" if self.target_is_fixed_asset else "Decapitalized")
|
||||
else:
|
||||
asset.set_status()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_target_item_details(item_code=None, company=None):
|
||||
out = frappe._dict()
|
||||
|
||||
# Get Item Details
|
||||
item = frappe._dict()
|
||||
if item_code:
|
||||
item = frappe.get_cached_doc("Item", item_code)
|
||||
|
||||
# Set Item Details
|
||||
out.target_item_name = item.item_name
|
||||
out.target_stock_uom = item.stock_uom
|
||||
out.target_is_fixed_asset = cint(item.is_fixed_asset)
|
||||
out.target_has_batch_no = cint(item.has_batch_no)
|
||||
out.target_has_serial_no = cint(item.has_serial_no)
|
||||
|
||||
if out.target_is_fixed_asset:
|
||||
out.target_qty = 1
|
||||
out.target_warehouse = None
|
||||
else:
|
||||
out.target_asset = None
|
||||
|
||||
if not out.target_has_batch_no:
|
||||
out.target_batch_no = None
|
||||
if not out.target_has_serial_no:
|
||||
out.target_serial_no = ""
|
||||
|
||||
# Cost Center
|
||||
item_defaults = get_item_defaults(item.name, company)
|
||||
item_group_defaults = get_item_group_defaults(item.name, company)
|
||||
brand_defaults = get_brand_defaults(item.name, company)
|
||||
out.cost_center = get_default_cost_center(
|
||||
frappe._dict({"item_code": item.name, "company": company}),
|
||||
item_defaults,
|
||||
item_group_defaults,
|
||||
brand_defaults,
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_target_asset_details(asset=None, company=None):
|
||||
out = frappe._dict()
|
||||
|
||||
# Get Asset Details
|
||||
asset_details = frappe._dict()
|
||||
if asset:
|
||||
asset_details = frappe.db.get_value("Asset", asset, ["asset_name", "item_code"], as_dict=1)
|
||||
if not asset_details:
|
||||
frappe.throw(_("Asset {0} does not exist").format(asset))
|
||||
|
||||
# Re-set item code from Asset
|
||||
out.target_item_code = asset_details.item_code
|
||||
|
||||
# Set Asset Details
|
||||
out.asset_name = asset_details.asset_name
|
||||
|
||||
if asset_details.item_code:
|
||||
out.target_fixed_asset_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=asset_details.item_code, company=company
|
||||
)
|
||||
else:
|
||||
out.target_fixed_asset_account = None
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_consumed_stock_item_details(args):
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
out = frappe._dict()
|
||||
|
||||
item = frappe._dict()
|
||||
if args.item_code:
|
||||
item = frappe.get_cached_doc("Item", args.item_code)
|
||||
|
||||
out.item_name = item.item_name
|
||||
out.batch_no = None
|
||||
out.serial_no = ""
|
||||
|
||||
out.stock_qty = flt(args.stock_qty) or 1
|
||||
out.stock_uom = item.stock_uom
|
||||
|
||||
out.warehouse = get_item_warehouse(item, args, overwrite_warehouse=True) if item else None
|
||||
|
||||
# Cost Center
|
||||
item_defaults = get_item_defaults(item.name, args.company)
|
||||
item_group_defaults = get_item_group_defaults(item.name, args.company)
|
||||
brand_defaults = get_brand_defaults(item.name, args.company)
|
||||
out.cost_center = get_default_cost_center(
|
||||
args, item_defaults, item_group_defaults, brand_defaults
|
||||
)
|
||||
|
||||
if args.item_code and out.warehouse:
|
||||
incoming_rate_args = frappe._dict(
|
||||
{
|
||||
"item_code": args.item_code,
|
||||
"warehouse": out.warehouse,
|
||||
"posting_date": args.posting_date,
|
||||
"posting_time": args.posting_time,
|
||||
"qty": -1 * flt(out.stock_qty),
|
||||
"voucher_type": args.doctype,
|
||||
"voucher_no": args.name,
|
||||
"company": args.company,
|
||||
"serial_no": args.serial_no,
|
||||
"batch_no": args.batch_no,
|
||||
}
|
||||
)
|
||||
out.update(get_warehouse_details(incoming_rate_args))
|
||||
else:
|
||||
out.valuation_rate = 0
|
||||
out.actual_qty = 0
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_warehouse_details(args):
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
||||
out = {}
|
||||
if args.warehouse and args.item_code:
|
||||
out = {
|
||||
"actual_qty": get_previous_sle(args).get("qty_after_transaction") or 0,
|
||||
"valuation_rate": get_incoming_rate(args, raise_error_if_no_rate=False),
|
||||
}
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_consumed_asset_details(args):
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
out = frappe._dict()
|
||||
|
||||
asset_details = frappe._dict()
|
||||
if args.asset:
|
||||
asset_details = frappe.db.get_value(
|
||||
"Asset", args.asset, ["asset_name", "item_code", "item_name"], as_dict=1
|
||||
)
|
||||
if not asset_details:
|
||||
frappe.throw(_("Asset {0} does not exist").format(args.asset))
|
||||
|
||||
out.item_code = asset_details.item_code
|
||||
out.asset_name = asset_details.asset_name
|
||||
out.item_name = asset_details.item_name
|
||||
|
||||
if args.asset:
|
||||
out.current_asset_value = flt(
|
||||
get_current_asset_value(args.asset, finance_book=args.finance_book)
|
||||
)
|
||||
out.asset_value = get_value_after_depreciation_on_disposal_date(
|
||||
args.asset, args.posting_date, finance_book=args.finance_book
|
||||
)
|
||||
else:
|
||||
out.current_asset_value = 0
|
||||
out.asset_value = 0
|
||||
|
||||
# Account
|
||||
if asset_details.item_code:
|
||||
out.fixed_asset_account = get_asset_category_account(
|
||||
"fixed_asset_account", item=asset_details.item_code, company=args.company
|
||||
)
|
||||
else:
|
||||
out.fixed_asset_account = None
|
||||
|
||||
# Cost Center
|
||||
if asset_details.item_code:
|
||||
item = frappe.get_cached_doc("Item", asset_details.item_code)
|
||||
item_defaults = get_item_defaults(item.name, args.company)
|
||||
item_group_defaults = get_item_group_defaults(item.name, args.company)
|
||||
brand_defaults = get_brand_defaults(item.name, args.company)
|
||||
out.cost_center = get_default_cost_center(
|
||||
args, item_defaults, item_group_defaults, brand_defaults
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_service_item_details(args):
|
||||
if isinstance(args, string_types):
|
||||
args = json.loads(args)
|
||||
|
||||
args = frappe._dict(args)
|
||||
out = frappe._dict()
|
||||
|
||||
item = frappe._dict()
|
||||
if args.item_code:
|
||||
item = frappe.get_cached_doc("Item", args.item_code)
|
||||
|
||||
out.item_name = item.item_name
|
||||
out.qty = flt(args.qty) or 1
|
||||
out.uom = item.purchase_uom or item.stock_uom
|
||||
|
||||
item_defaults = get_item_defaults(item.name, args.company)
|
||||
item_group_defaults = get_item_group_defaults(item.name, args.company)
|
||||
brand_defaults = get_brand_defaults(item.name, args.company)
|
||||
|
||||
out.expense_account = get_default_expense_account(
|
||||
args, item_defaults, item_group_defaults, brand_defaults
|
||||
)
|
||||
out.cost_center = get_default_cost_center(
|
||||
args, item_defaults, item_group_defaults, brand_defaults
|
||||
)
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,494 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.utils import cint, flt, getdate, now_datetime
|
||||
|
||||
from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
|
||||
from erpnext.assets.doctype.asset.test_asset import (
|
||||
create_asset,
|
||||
create_asset_data,
|
||||
set_depreciation_settings_in_company,
|
||||
)
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
|
||||
class TestAssetCapitalization(unittest.TestCase):
|
||||
def setUp(self):
|
||||
set_depreciation_settings_in_company()
|
||||
create_asset_data()
|
||||
create_asset_capitalization_data()
|
||||
frappe.db.sql("delete from `tabTax Rule`")
|
||||
|
||||
def test_capitalization_with_perpetual_inventory(self):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
set_depreciation_settings_in_company(company=company)
|
||||
|
||||
# Variables
|
||||
consumed_asset_value = 100000
|
||||
|
||||
stock_rate = 1000
|
||||
stock_qty = 2
|
||||
stock_amount = 2000
|
||||
|
||||
service_rate = 500
|
||||
service_qty = 2
|
||||
service_amount = 1000
|
||||
|
||||
total_amount = 103000
|
||||
|
||||
# Create assets
|
||||
target_asset = create_asset(
|
||||
asset_name="Asset Capitalization Target Asset",
|
||||
submit=1,
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
consumed_asset = create_asset(
|
||||
asset_name="Asset Capitalization Consumable Asset",
|
||||
asset_value=consumed_asset_value,
|
||||
submit=1,
|
||||
warehouse="Stores - TCP1",
|
||||
company=company,
|
||||
)
|
||||
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
entry_type="Capitalization",
|
||||
target_asset=target_asset.name,
|
||||
stock_qty=stock_qty,
|
||||
stock_rate=stock_rate,
|
||||
consumed_asset=consumed_asset.name,
|
||||
service_qty=service_qty,
|
||||
service_rate=service_rate,
|
||||
service_expense_account="Expenses Included In Asset Valuation - TCP1",
|
||||
company=company,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
# Test Asset Capitalization values
|
||||
self.assertEqual(asset_capitalization.entry_type, "Capitalization")
|
||||
self.assertEqual(asset_capitalization.target_qty, 1)
|
||||
|
||||
self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate)
|
||||
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
|
||||
self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
|
||||
|
||||
self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value)
|
||||
self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value)
|
||||
|
||||
self.assertEqual(asset_capitalization.service_items[0].amount, service_amount)
|
||||
self.assertEqual(asset_capitalization.service_items_total, service_amount)
|
||||
|
||||
self.assertEqual(asset_capitalization.total_value, total_amount)
|
||||
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
||||
|
||||
# Test Target Asset values
|
||||
target_asset.reload()
|
||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
||||
|
||||
# Test Consumed Asset values
|
||||
self.assertEqual(consumed_asset.db_get("status"), "Capitalized")
|
||||
|
||||
# Test General Ledger Entries
|
||||
expected_gle = {
|
||||
"_Test Fixed Asset - TCP1": 3000,
|
||||
"Expenses Included In Asset Valuation - TCP1": -1000,
|
||||
"_Test Warehouse - TCP1": -2000,
|
||||
}
|
||||
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
||||
|
||||
self.assertEqual(actual_gle, expected_gle)
|
||||
|
||||
# Test Stock Ledger Entries
|
||||
expected_sle = {
|
||||
("Capitalization Source Stock Item", "_Test Warehouse - TCP1"): {
|
||||
"actual_qty": -stock_qty,
|
||||
"stock_value_difference": -stock_amount,
|
||||
}
|
||||
}
|
||||
actual_sle = get_actual_sle_dict(asset_capitalization.name)
|
||||
self.assertEqual(actual_sle, expected_sle)
|
||||
|
||||
# Cancel Asset Capitalization and make test entries and status are reversed
|
||||
asset_capitalization.cancel()
|
||||
self.assertEqual(consumed_asset.db_get("status"), "Submitted")
|
||||
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
|
||||
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
|
||||
|
||||
def test_capitalization_with_periodical_inventory(self):
|
||||
company = "_Test Company"
|
||||
# Variables
|
||||
consumed_asset_value = 100000
|
||||
|
||||
stock_rate = 1000
|
||||
stock_qty = 2
|
||||
stock_amount = 2000
|
||||
|
||||
service_rate = 500
|
||||
service_qty = 2
|
||||
service_amount = 1000
|
||||
|
||||
total_amount = 103000
|
||||
|
||||
# Create assets
|
||||
target_asset = create_asset(
|
||||
asset_name="Asset Capitalization Target Asset",
|
||||
submit=1,
|
||||
warehouse="Stores - _TC",
|
||||
company=company,
|
||||
)
|
||||
consumed_asset = create_asset(
|
||||
asset_name="Asset Capitalization Consumable Asset",
|
||||
asset_value=consumed_asset_value,
|
||||
submit=1,
|
||||
warehouse="Stores - _TC",
|
||||
company=company,
|
||||
)
|
||||
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
entry_type="Capitalization",
|
||||
target_asset=target_asset.name,
|
||||
stock_qty=stock_qty,
|
||||
stock_rate=stock_rate,
|
||||
consumed_asset=consumed_asset.name,
|
||||
service_qty=service_qty,
|
||||
service_rate=service_rate,
|
||||
service_expense_account="Expenses Included In Asset Valuation - _TC",
|
||||
company=company,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
# Test Asset Capitalization values
|
||||
self.assertEqual(asset_capitalization.entry_type, "Capitalization")
|
||||
self.assertEqual(asset_capitalization.target_qty, 1)
|
||||
|
||||
self.assertEqual(asset_capitalization.stock_items[0].valuation_rate, stock_rate)
|
||||
self.assertEqual(asset_capitalization.stock_items[0].amount, stock_amount)
|
||||
self.assertEqual(asset_capitalization.stock_items_total, stock_amount)
|
||||
|
||||
self.assertEqual(asset_capitalization.asset_items[0].asset_value, consumed_asset_value)
|
||||
self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value)
|
||||
|
||||
self.assertEqual(asset_capitalization.service_items[0].amount, service_amount)
|
||||
self.assertEqual(asset_capitalization.service_items_total, service_amount)
|
||||
|
||||
self.assertEqual(asset_capitalization.total_value, total_amount)
|
||||
self.assertEqual(asset_capitalization.target_incoming_rate, total_amount)
|
||||
|
||||
# Test Target Asset values
|
||||
target_asset.reload()
|
||||
self.assertEqual(target_asset.gross_purchase_amount, total_amount)
|
||||
self.assertEqual(target_asset.purchase_receipt_amount, total_amount)
|
||||
|
||||
# Test Consumed Asset values
|
||||
self.assertEqual(consumed_asset.db_get("status"), "Capitalized")
|
||||
|
||||
# Test General Ledger Entries
|
||||
default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
|
||||
expected_gle = {
|
||||
"_Test Fixed Asset - _TC": 3000,
|
||||
"Expenses Included In Asset Valuation - _TC": -1000,
|
||||
default_expense_account: -2000,
|
||||
}
|
||||
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
||||
|
||||
self.assertEqual(actual_gle, expected_gle)
|
||||
|
||||
# Test Stock Ledger Entries
|
||||
expected_sle = {
|
||||
("Capitalization Source Stock Item", "_Test Warehouse - _TC"): {
|
||||
"actual_qty": -stock_qty,
|
||||
"stock_value_difference": -stock_amount,
|
||||
}
|
||||
}
|
||||
actual_sle = get_actual_sle_dict(asset_capitalization.name)
|
||||
self.assertEqual(actual_sle, expected_sle)
|
||||
|
||||
# Cancel Asset Capitalization and make test entries and status are reversed
|
||||
asset_capitalization.cancel()
|
||||
self.assertEqual(consumed_asset.db_get("status"), "Submitted")
|
||||
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
|
||||
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
|
||||
|
||||
def test_decapitalization_with_depreciation(self):
|
||||
# Variables
|
||||
purchase_date = "2020-01-01"
|
||||
depreciation_start_date = "2020-12-31"
|
||||
capitalization_date = "2021-06-30"
|
||||
|
||||
total_number_of_depreciations = 3
|
||||
expected_value_after_useful_life = 10_000
|
||||
consumed_asset_purchase_value = 100_000
|
||||
consumed_asset_current_value = 70_000
|
||||
consumed_asset_value_before_disposal = 55_000
|
||||
|
||||
target_qty = 10
|
||||
target_incoming_rate = 5500
|
||||
|
||||
depreciation_before_disposal_amount = 15_000
|
||||
accumulated_depreciation = 45_000
|
||||
|
||||
# to accomodate for depreciation on disposal calculation minor difference
|
||||
consumed_asset_value_before_disposal = 55_123.29
|
||||
target_incoming_rate = 5512.329
|
||||
depreciation_before_disposal_amount = 14_876.71
|
||||
accumulated_depreciation = 44_876.71
|
||||
|
||||
# Create assets
|
||||
consumed_asset = create_depreciation_asset(
|
||||
asset_name="Asset Capitalization Consumable Asset",
|
||||
asset_value=consumed_asset_purchase_value,
|
||||
purchase_date=purchase_date,
|
||||
depreciation_start_date=depreciation_start_date,
|
||||
depreciation_method="Straight Line",
|
||||
total_number_of_depreciations=total_number_of_depreciations,
|
||||
frequency_of_depreciation=12,
|
||||
expected_value_after_useful_life=expected_value_after_useful_life,
|
||||
company="_Test Company with perpetual inventory",
|
||||
submit=1,
|
||||
)
|
||||
|
||||
# Create and submit Asset Captitalization
|
||||
asset_capitalization = create_asset_capitalization(
|
||||
entry_type="Decapitalization",
|
||||
posting_date=capitalization_date, # half a year
|
||||
target_item_code="Capitalization Target Stock Item",
|
||||
target_qty=target_qty,
|
||||
consumed_asset=consumed_asset.name,
|
||||
company="_Test Company with perpetual inventory",
|
||||
submit=1,
|
||||
)
|
||||
|
||||
# Test Asset Capitalization values
|
||||
self.assertEqual(asset_capitalization.entry_type, "Decapitalization")
|
||||
|
||||
self.assertEqual(
|
||||
asset_capitalization.asset_items[0].current_asset_value, consumed_asset_current_value
|
||||
)
|
||||
self.assertEqual(
|
||||
asset_capitalization.asset_items[0].asset_value, consumed_asset_value_before_disposal
|
||||
)
|
||||
self.assertEqual(asset_capitalization.asset_items_total, consumed_asset_value_before_disposal)
|
||||
|
||||
self.assertEqual(asset_capitalization.total_value, consumed_asset_value_before_disposal)
|
||||
self.assertEqual(asset_capitalization.target_incoming_rate, target_incoming_rate)
|
||||
|
||||
# Test Consumed Asset values
|
||||
consumed_asset.reload()
|
||||
self.assertEqual(consumed_asset.status, "Decapitalized")
|
||||
|
||||
consumed_depreciation_schedule = [
|
||||
d for d in consumed_asset.schedules if getdate(d.schedule_date) == getdate(capitalization_date)
|
||||
]
|
||||
self.assertTrue(
|
||||
consumed_depreciation_schedule and consumed_depreciation_schedule[0].journal_entry
|
||||
)
|
||||
self.assertEqual(
|
||||
consumed_depreciation_schedule[0].depreciation_amount, depreciation_before_disposal_amount
|
||||
)
|
||||
|
||||
# Test General Ledger Entries
|
||||
expected_gle = {
|
||||
"_Test Warehouse - TCP1": consumed_asset_value_before_disposal,
|
||||
"_Test Accumulated Depreciations - TCP1": accumulated_depreciation,
|
||||
"_Test Fixed Asset - TCP1": -consumed_asset_purchase_value,
|
||||
}
|
||||
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
||||
self.assertEqual(actual_gle, expected_gle)
|
||||
|
||||
# Cancel Asset Capitalization and make test entries and status are reversed
|
||||
asset_capitalization.reload()
|
||||
asset_capitalization.cancel()
|
||||
self.assertEqual(consumed_asset.db_get("status"), "Partially Depreciated")
|
||||
self.assertFalse(get_actual_gle_dict(asset_capitalization.name))
|
||||
self.assertFalse(get_actual_sle_dict(asset_capitalization.name))
|
||||
|
||||
|
||||
def create_asset_capitalization_data():
|
||||
create_item(
|
||||
"Capitalization Target Stock Item", is_stock_item=1, is_fixed_asset=0, is_purchase_item=0
|
||||
)
|
||||
create_item(
|
||||
"Capitalization Source Stock Item", is_stock_item=1, is_fixed_asset=0, is_purchase_item=0
|
||||
)
|
||||
create_item(
|
||||
"Capitalization Source Service Item", is_stock_item=0, is_fixed_asset=0, is_purchase_item=0
|
||||
)
|
||||
|
||||
|
||||
def create_asset_capitalization(**args):
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
args = frappe._dict(args)
|
||||
|
||||
now = now_datetime()
|
||||
target_asset = frappe.get_doc("Asset", args.target_asset) if args.target_asset else frappe._dict()
|
||||
target_item_code = target_asset.item_code or args.target_item_code
|
||||
company = target_asset.company or args.company or "_Test Company"
|
||||
warehouse = args.warehouse or create_warehouse("_Test Warehouse", company=company)
|
||||
target_warehouse = args.target_warehouse or warehouse
|
||||
source_warehouse = args.source_warehouse or warehouse
|
||||
|
||||
asset_capitalization = frappe.new_doc("Asset Capitalization")
|
||||
asset_capitalization.update(
|
||||
{
|
||||
"entry_type": args.entry_type or "Capitalization",
|
||||
"company": company,
|
||||
"posting_date": args.posting_date or now.strftime("%Y-%m-%d"),
|
||||
"posting_time": args.posting_time or now.strftime("%H:%M:%S.%f"),
|
||||
"target_item_code": target_item_code,
|
||||
"target_asset": target_asset.name,
|
||||
"target_warehouse": target_warehouse,
|
||||
"target_qty": flt(args.target_qty) or 1,
|
||||
"target_batch_no": args.target_batch_no,
|
||||
"target_serial_no": args.target_serial_no,
|
||||
"finance_book": args.finance_book,
|
||||
}
|
||||
)
|
||||
|
||||
if args.posting_date or args.posting_time:
|
||||
asset_capitalization.set_posting_time = 1
|
||||
|
||||
if flt(args.stock_rate):
|
||||
asset_capitalization.append(
|
||||
"stock_items",
|
||||
{
|
||||
"item_code": args.stock_item or "Capitalization Source Stock Item",
|
||||
"warehouse": source_warehouse,
|
||||
"stock_qty": flt(args.stock_qty) or 1,
|
||||
"batch_no": args.stock_batch_no,
|
||||
"serial_no": args.stock_serial_no,
|
||||
},
|
||||
)
|
||||
|
||||
if args.consumed_asset:
|
||||
asset_capitalization.append(
|
||||
"asset_items",
|
||||
{
|
||||
"asset": args.consumed_asset,
|
||||
},
|
||||
)
|
||||
|
||||
if flt(args.service_rate):
|
||||
asset_capitalization.append(
|
||||
"service_items",
|
||||
{
|
||||
"item_code": args.service_item or "Capitalization Source Service Item",
|
||||
"expense_account": args.service_expense_account,
|
||||
"qty": flt(args.service_qty) or 1,
|
||||
"rate": flt(args.service_rate),
|
||||
},
|
||||
)
|
||||
|
||||
if args.submit:
|
||||
create_stock_reconciliation(asset_capitalization, stock_rate=args.stock_rate)
|
||||
|
||||
asset_capitalization.insert()
|
||||
|
||||
if args.submit:
|
||||
asset_capitalization.submit()
|
||||
|
||||
return asset_capitalization
|
||||
|
||||
|
||||
def create_stock_reconciliation(asset_capitalization, stock_rate=0):
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
EmptyStockReconciliationItemsError,
|
||||
create_stock_reconciliation,
|
||||
)
|
||||
|
||||
if not asset_capitalization.get("stock_items"):
|
||||
return
|
||||
|
||||
try:
|
||||
create_stock_reconciliation(
|
||||
item_code=asset_capitalization.stock_items[0].item_code,
|
||||
warehouse=asset_capitalization.stock_items[0].warehouse,
|
||||
qty=flt(asset_capitalization.stock_items[0].stock_qty),
|
||||
rate=flt(stock_rate),
|
||||
company=asset_capitalization.company,
|
||||
)
|
||||
except EmptyStockReconciliationItemsError:
|
||||
pass
|
||||
|
||||
|
||||
def create_depreciation_asset(**args):
|
||||
args = frappe._dict(args)
|
||||
|
||||
asset = frappe.new_doc("Asset")
|
||||
asset.is_existing_asset = 1
|
||||
asset.calculate_depreciation = 1
|
||||
asset.asset_owner = "Company"
|
||||
|
||||
asset.company = args.company or "_Test Company"
|
||||
asset.item_code = args.item_code or "Macbook Pro"
|
||||
asset.asset_name = args.asset_name or asset.item_code
|
||||
asset.location = args.location or "Test Location"
|
||||
|
||||
asset.purchase_date = args.purchase_date or "2020-01-01"
|
||||
asset.available_for_use_date = args.available_for_use_date or asset.purchase_date
|
||||
|
||||
asset.gross_purchase_amount = args.asset_value or 100000
|
||||
asset.purchase_receipt_amount = asset.gross_purchase_amount
|
||||
|
||||
finance_book = asset.append("finance_books")
|
||||
finance_book.depreciation_start_date = args.depreciation_start_date or "2020-12-31"
|
||||
finance_book.depreciation_method = args.depreciation_method or "Straight Line"
|
||||
finance_book.total_number_of_depreciations = cint(args.total_number_of_depreciations) or 3
|
||||
finance_book.frequency_of_depreciation = cint(args.frequency_of_depreciation) or 12
|
||||
finance_book.expected_value_after_useful_life = flt(args.expected_value_after_useful_life)
|
||||
|
||||
if args.submit:
|
||||
asset.submit()
|
||||
|
||||
frappe.db.set_value("Company", "_Test Company", "series_for_depreciation_entry", "DEPR-")
|
||||
post_depreciation_entries(date=finance_book.depreciation_start_date)
|
||||
asset.load_from_db()
|
||||
|
||||
return asset
|
||||
|
||||
|
||||
def get_actual_gle_dict(name):
|
||||
return dict(
|
||||
frappe.db.sql(
|
||||
"""
|
||||
select account, sum(debit-credit) as diff
|
||||
from `tabGL Entry`
|
||||
where voucher_type = 'Asset Capitalization' and voucher_no = %s
|
||||
group by account
|
||||
having diff != 0
|
||||
""",
|
||||
name,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_actual_sle_dict(name):
|
||||
sles = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
item_code, warehouse,
|
||||
sum(actual_qty) as actual_qty,
|
||||
sum(stock_value_difference) as stock_value_difference
|
||||
from `tabStock Ledger Entry`
|
||||
where voucher_type = 'Asset Capitalization' and voucher_no = %s
|
||||
group by item_code, warehouse
|
||||
having actual_qty != 0
|
||||
""",
|
||||
name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
sle_dict = {}
|
||||
for d in sles:
|
||||
sle_dict[(d.item_code, d.warehouse)] = {
|
||||
"actual_qty": d.actual_qty,
|
||||
"stock_value_difference": d.stock_value_difference,
|
||||
}
|
||||
|
||||
return sle_dict
|
||||
@@ -0,0 +1,128 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-09-05 15:52:10.124538",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"asset",
|
||||
"asset_name",
|
||||
"finance_book",
|
||||
"column_break_3",
|
||||
"item_code",
|
||||
"item_name",
|
||||
"section_break_6",
|
||||
"current_asset_value",
|
||||
"asset_value",
|
||||
"column_break_9",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break",
|
||||
"fixed_asset_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "asset",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Asset",
|
||||
"options": "Asset",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "asset.asset_name",
|
||||
"fieldname": "asset_name",
|
||||
"fieldtype": "Data",
|
||||
"in_list_view": 1,
|
||||
"label": "Asset Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "asset.item_code",
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Value"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "asset_value",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Asset Value",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "fixed_asset_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Fixed Asset Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "finance_book",
|
||||
"fieldtype": "Link",
|
||||
"label": "Finance Book",
|
||||
"options": "Finance Book"
|
||||
},
|
||||
{
|
||||
"fieldname": "current_asset_value",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Current Asset Value",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-12 14:30:02.915132",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization Asset Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AssetCapitalizationAssetItem(Document):
|
||||
pass
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-09-06 13:32:08.642060",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"column_break_3",
|
||||
"expense_account",
|
||||
"section_break_6",
|
||||
"qty",
|
||||
"uom",
|
||||
"column_break_9",
|
||||
"rate",
|
||||
"amount",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"bold": 1,
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item"
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "expense_account",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Expense Account",
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Qty and Rate"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"default": "1",
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Qty",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fetch_from": "stock_item_code.stock_uom",
|
||||
"fieldname": "uom",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "UOM",
|
||||
"options": "UOM"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Rate",
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-08 15:52:08.598100",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization Service Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AssetCapitalizationServiceItem(Document):
|
||||
pass
|
||||
@@ -0,0 +1,156 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2021-09-05 15:23:23.492310",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"item_code",
|
||||
"item_name",
|
||||
"column_break_3",
|
||||
"warehouse",
|
||||
"section_break_6",
|
||||
"stock_qty",
|
||||
"stock_uom",
|
||||
"actual_qty",
|
||||
"column_break_9",
|
||||
"valuation_rate",
|
||||
"amount",
|
||||
"batch_and_serial_no_section",
|
||||
"batch_no",
|
||||
"column_break_13",
|
||||
"serial_no",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
"dimension_col_break"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Warehouse",
|
||||
"options": "Warehouse",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "batch_no",
|
||||
"fieldtype": "Link",
|
||||
"label": "Batch No",
|
||||
"options": "Batch"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Qty and Rate"
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fieldname": "stock_qty",
|
||||
"fieldtype": "Float",
|
||||
"in_list_view": 1,
|
||||
"label": "Qty",
|
||||
"non_negative": 1
|
||||
},
|
||||
{
|
||||
"columns": 1,
|
||||
"fetch_from": "stock_item_code.stock_uom",
|
||||
"fieldname": "stock_uom",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Stock UOM",
|
||||
"options": "UOM",
|
||||
"read_only": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "valuation_rate",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Valuation Rate",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "amount",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Amount",
|
||||
"options": "Company:company:default_currency",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "batch_and_serial_no_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Batch and Serial No"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_13",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "serial_no",
|
||||
"fieldtype": "Small Text",
|
||||
"label": "Serial No"
|
||||
},
|
||||
{
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Item Code",
|
||||
"options": "Item",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fetch_from": "item_code.item_name",
|
||||
"fieldname": "item_name",
|
||||
"fieldtype": "Data",
|
||||
"label": "Item Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "actual_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Actual Qty in Warehouse",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"fieldname": "accounting_dimensions_section",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Accounting Dimensions"
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"label": "Cost Center",
|
||||
"options": "Cost Center"
|
||||
},
|
||||
{
|
||||
"fieldname": "dimension_col_break",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-09-08 15:56:20.230548",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Capitalization Stock Item",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AssetCapitalizationStockItem(Document):
|
||||
pass
|
||||
@@ -76,7 +76,7 @@ frappe.ui.form.on('Asset Repair Consumed Item', {
|
||||
'warehouse': frm.doc.warehouse,
|
||||
'qty': item.consumed_quantity,
|
||||
'serial_no': item.serial_no,
|
||||
'company': frm.doc.company
|
||||
'company': frm.doc.company,
|
||||
};
|
||||
|
||||
frappe.call({
|
||||
|
||||
@@ -238,7 +238,6 @@
|
||||
"no_copy": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:!doc.__islocal",
|
||||
"fieldname": "purchase_invoice",
|
||||
"fieldtype": "Link",
|
||||
"label": "Purchase Invoice",
|
||||
@@ -257,6 +256,7 @@
|
||||
"fieldname": "stock_entry",
|
||||
"fieldtype": "Link",
|
||||
"label": "Stock Entry",
|
||||
"no_copy": 1,
|
||||
"options": "Stock Entry",
|
||||
"read_only": 1
|
||||
}
|
||||
@@ -264,10 +264,11 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-06-25 13:14:38.307723",
|
||||
"modified": "2022-08-16 15:55:25.023471",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Repair",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -303,6 +304,7 @@
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"title_field": "asset_name",
|
||||
"track_changes": 1,
|
||||
"track_seen": 1
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.utils import add_months, cint, flt, getdate, time_diff_in_hours
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account
|
||||
from erpnext.controllers.accounts_controller import AccountsController
|
||||
@@ -17,7 +17,7 @@ class AssetRepair(AccountsController):
|
||||
self.update_status()
|
||||
|
||||
if self.get("stock_items"):
|
||||
self.set_total_value()
|
||||
self.set_stock_items_cost()
|
||||
self.calculate_total_repair_cost()
|
||||
|
||||
def update_status(self):
|
||||
@@ -26,7 +26,7 @@ class AssetRepair(AccountsController):
|
||||
else:
|
||||
self.asset_doc.set_status()
|
||||
|
||||
def set_total_value(self):
|
||||
def set_stock_items_cost(self):
|
||||
for item in self.get("stock_items"):
|
||||
item.total_value = flt(item.valuation_rate) * flt(item.consumed_quantity)
|
||||
|
||||
@@ -66,6 +66,7 @@ class AssetRepair(AccountsController):
|
||||
if self.get("capitalize_repair_cost"):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
||||
self.make_gl_entries(cancel=True)
|
||||
self.db_set("stock_entry", None)
|
||||
if (
|
||||
frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
|
||||
and self.increase_in_asset_life
|
||||
@@ -133,6 +134,7 @@ class AssetRepair(AccountsController):
|
||||
"qty": stock_item.consumed_quantity,
|
||||
"basic_rate": stock_item.valuation_rate,
|
||||
"serial_no": stock_item.serial_no,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -142,72 +144,42 @@ class AssetRepair(AccountsController):
|
||||
self.db_set("stock_entry", stock_entry.name)
|
||||
|
||||
def increase_stock_quantity(self):
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
stock_entry.flags.ignore_links = True
|
||||
stock_entry.cancel()
|
||||
if self.stock_entry:
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
stock_entry.flags.ignore_links = True
|
||||
stock_entry.cancel()
|
||||
|
||||
def make_gl_entries(self, cancel=False):
|
||||
if flt(self.repair_cost) > 0:
|
||||
if flt(self.total_repair_cost) > 0:
|
||||
gl_entries = self.get_gl_entries()
|
||||
make_gl_entries(gl_entries, cancel)
|
||||
|
||||
def get_gl_entries(self):
|
||||
gl_entries = []
|
||||
repair_and_maintenance_account = frappe.db.get_value(
|
||||
"Company", self.company, "repair_and_maintenance_account"
|
||||
)
|
||||
|
||||
fixed_asset_account = get_asset_account(
|
||||
"fixed_asset_account", asset=self.asset, company=self.company
|
||||
)
|
||||
expense_account = (
|
||||
self.get_gl_entries_for_repair_cost(gl_entries, fixed_asset_account)
|
||||
self.get_gl_entries_for_consumed_items(gl_entries, fixed_asset_account)
|
||||
|
||||
return gl_entries
|
||||
|
||||
def get_gl_entries_for_repair_cost(self, gl_entries, fixed_asset_account):
|
||||
if flt(self.repair_cost) <= 0:
|
||||
return
|
||||
|
||||
pi_expense_account = (
|
||||
frappe.get_doc("Purchase Invoice", self.purchase_invoice).items[0].expense_account
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"credit": self.repair_cost,
|
||||
"credit_in_account_currency": self.repair_cost,
|
||||
"against": repair_and_maintenance_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("stock_consumption"):
|
||||
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
for item in stock_entry.items:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account,
|
||||
"credit": item.amount,
|
||||
"credit_in_account_currency": item.amount,
|
||||
"against": repair_and_maintenance_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"debit": self.total_repair_cost,
|
||||
"debit_in_account_currency": self.total_repair_cost,
|
||||
"against": expense_account,
|
||||
"debit": self.repair_cost,
|
||||
"debit_in_account_currency": self.repair_cost,
|
||||
"against": pi_expense_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
@@ -220,7 +192,75 @@ class AssetRepair(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
return gl_entries
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": pi_expense_account,
|
||||
"credit": self.repair_cost,
|
||||
"credit_in_account_currency": self.repair_cost,
|
||||
"against": fixed_asset_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def get_gl_entries_for_consumed_items(self, gl_entries, fixed_asset_account):
|
||||
if not (self.get("stock_consumption") and self.get("stock_items")):
|
||||
return
|
||||
|
||||
# creating GL Entries for each row in Stock Items based on the Stock Entry created for it
|
||||
stock_entry = frappe.get_doc("Stock Entry", self.stock_entry)
|
||||
|
||||
default_expense_account = None
|
||||
if not erpnext.is_perpetual_inventory_enabled(self.company):
|
||||
default_expense_account = frappe.get_cached_value(
|
||||
"Company", self.company, "default_expense_account"
|
||||
)
|
||||
if not default_expense_account:
|
||||
frappe.throw(_("Please set default Expense Account in Company {0}").format(self.company))
|
||||
|
||||
for item in stock_entry.items:
|
||||
if flt(item.amount) > 0:
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": item.expense_account or default_expense_account,
|
||||
"credit": item.amount,
|
||||
"credit_in_account_currency": item.amount,
|
||||
"against": fixed_asset_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": fixed_asset_account,
|
||||
"debit": item.amount,
|
||||
"debit_in_account_currency": item.amount,
|
||||
"against": item.expense_account or default_expense_account,
|
||||
"voucher_type": self.doctype,
|
||||
"voucher_no": self.name,
|
||||
"cost_center": self.cost_center,
|
||||
"posting_date": getdate(),
|
||||
"against_voucher_type": "Stock Entry",
|
||||
"against_voucher": self.stock_entry,
|
||||
"company": self.company,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def modify_depreciation_schedule(self):
|
||||
for row in self.asset_doc.finance_books:
|
||||
|
||||
@@ -6,6 +6,7 @@ import unittest
|
||||
import frappe
|
||||
from frappe.utils import flt, nowdate
|
||||
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account
|
||||
from erpnext.assets.doctype.asset.test_asset import (
|
||||
create_asset,
|
||||
create_asset_data,
|
||||
@@ -125,10 +126,110 @@ class TestAssetRepair(unittest.TestCase):
|
||||
asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
|
||||
self.assertTrue(asset_repair.purchase_invoice)
|
||||
|
||||
def test_gl_entries(self):
|
||||
asset_repair = create_asset_repair(capitalize_repair_cost=1, submit=1)
|
||||
gl_entry = frappe.get_last_doc("GL Entry")
|
||||
self.assertEqual(asset_repair.name, gl_entry.voucher_no)
|
||||
def test_gl_entries_with_perpetual_inventory(self):
|
||||
set_depreciation_settings_in_company(company="_Test Company with perpetual inventory")
|
||||
|
||||
asset_category = frappe.get_doc("Asset Category", "Computers")
|
||||
asset_category.enable_cwip_accounting = 0
|
||||
asset_category.append(
|
||||
"accounts",
|
||||
{
|
||||
"company_name": "_Test Company with perpetual inventory",
|
||||
"fixed_asset_account": "_Test Fixed Asset - TCP1",
|
||||
"accumulated_depreciation_account": "_Test Accumulated Depreciations - TCP1",
|
||||
"depreciation_expense_account": "_Test Depreciations - TCP1",
|
||||
},
|
||||
)
|
||||
asset_category.save()
|
||||
|
||||
asset_repair = create_asset_repair(
|
||||
capitalize_repair_cost=1,
|
||||
stock_consumption=1,
|
||||
warehouse="Stores - TCP1",
|
||||
company="_Test Company with perpetual inventory",
|
||||
submit=1,
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
account,
|
||||
sum(debit) as debit,
|
||||
sum(credit) as credit
|
||||
from `tabGL Entry`
|
||||
where
|
||||
voucher_type='Asset Repair'
|
||||
and voucher_no=%s
|
||||
group by
|
||||
account
|
||||
""",
|
||||
asset_repair.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
fixed_asset_account = get_asset_account(
|
||||
"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
|
||||
)
|
||||
pi_expense_account = (
|
||||
frappe.get_doc("Purchase Invoice", asset_repair.purchase_invoice).items[0].expense_account
|
||||
)
|
||||
stock_entry_expense_account = (
|
||||
frappe.get_doc("Stock Entry", asset_repair.stock_entry).get("items")[0].expense_account
|
||||
)
|
||||
|
||||
expected_values = {
|
||||
fixed_asset_account: [asset_repair.total_repair_cost, 0],
|
||||
pi_expense_account: [0, asset_repair.repair_cost],
|
||||
stock_entry_expense_account: [0, 100],
|
||||
}
|
||||
|
||||
for d in gl_entries:
|
||||
self.assertEqual(expected_values[d.account][0], d.debit)
|
||||
self.assertEqual(expected_values[d.account][1], d.credit)
|
||||
|
||||
def test_gl_entries_with_periodical_inventory(self):
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company", "default_expense_account", "Cost of Goods Sold - _TC"
|
||||
)
|
||||
asset_repair = create_asset_repair(
|
||||
capitalize_repair_cost=1,
|
||||
stock_consumption=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
gl_entries = frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
account,
|
||||
sum(debit) as debit,
|
||||
sum(credit) as credit
|
||||
from `tabGL Entry`
|
||||
where
|
||||
voucher_type='Asset Repair'
|
||||
and voucher_no=%s
|
||||
group by
|
||||
account
|
||||
""",
|
||||
asset_repair.name,
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
self.assertTrue(gl_entries)
|
||||
|
||||
fixed_asset_account = get_asset_account(
|
||||
"fixed_asset_account", asset=asset_repair.asset, company=asset_repair.company
|
||||
)
|
||||
default_expense_account = frappe.get_cached_value(
|
||||
"Company", asset_repair.company, "default_expense_account"
|
||||
)
|
||||
|
||||
expected_values = {fixed_asset_account: [1100, 0], default_expense_account: [0, 1100]}
|
||||
|
||||
for d in gl_entries:
|
||||
self.assertEqual(expected_values[d.account][0], d.debit)
|
||||
self.assertEqual(expected_values[d.account][1], d.credit)
|
||||
|
||||
def test_increase_in_asset_life(self):
|
||||
asset = create_asset(calculate_depreciation=1, submit=1)
|
||||
@@ -160,7 +261,7 @@ def create_asset_repair(**args):
|
||||
if args.asset:
|
||||
asset = args.asset
|
||||
else:
|
||||
asset = create_asset(is_existing_asset=1, submit=1)
|
||||
asset = create_asset(is_existing_asset=1, submit=1, company=args.company)
|
||||
asset_repair = frappe.new_doc("Asset Repair")
|
||||
asset_repair.update(
|
||||
{
|
||||
@@ -192,7 +293,7 @@ def create_asset_repair(**args):
|
||||
|
||||
if args.submit:
|
||||
asset_repair.repair_status = "Completed"
|
||||
asset_repair.cost_center = "_Test Cost Center - _TC"
|
||||
asset_repair.cost_center = frappe.db.get_value("Company", asset.company, "cost_center")
|
||||
|
||||
if args.stock_consumption:
|
||||
stock_entry = frappe.get_doc(
|
||||
@@ -204,6 +305,8 @@ def create_asset_repair(**args):
|
||||
"t_warehouse": asset_repair.warehouse,
|
||||
"item_code": asset_repair.stock_items[0].item_code,
|
||||
"qty": asset_repair.stock_items[0].consumed_quantity,
|
||||
"basic_rate": args.rate if args.get("rate") is not None else 100,
|
||||
"cost_center": asset_repair.cost_center,
|
||||
},
|
||||
)
|
||||
stock_entry.submit()
|
||||
@@ -213,7 +316,13 @@ def create_asset_repair(**args):
|
||||
asset_repair.repair_cost = 1000
|
||||
if asset.calculate_depreciation:
|
||||
asset_repair.increase_in_asset_life = 12
|
||||
asset_repair.purchase_invoice = make_purchase_invoice().name
|
||||
pi = make_purchase_invoice(
|
||||
company=asset.company,
|
||||
expense_account=frappe.db.get_value("Company", asset.company, "default_expense_account"),
|
||||
cost_center=asset_repair.cost_center,
|
||||
warehouse=asset_repair.warehouse,
|
||||
)
|
||||
asset_repair.purchase_invoice = pi.name
|
||||
|
||||
asset_repair.submit()
|
||||
return asset_repair
|
||||
|
||||
@@ -130,6 +130,17 @@
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Asset",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Asset Capitalization",
|
||||
"link_count": 0,
|
||||
"link_to": "Asset Capitalization",
|
||||
"link_type": "DocType",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
@@ -172,7 +183,7 @@
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2022-01-13 17:25:41.730628",
|
||||
"modified": "2021-10-04 12:15:54.839454",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Assets",
|
||||
@@ -205,4 +216,4 @@
|
||||
}
|
||||
],
|
||||
"title": "Assets"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"maintain_same_rate",
|
||||
"allow_multiple_items",
|
||||
"bill_for_rejected_quantity_in_purchase_invoice",
|
||||
"enable_discount_accounting",
|
||||
"subcontract",
|
||||
"backflush_raw_materials_of_subcontract_based_on",
|
||||
"column_break_11",
|
||||
@@ -76,7 +75,7 @@
|
||||
"label": "Subcontracting Settings"
|
||||
},
|
||||
{
|
||||
"default": "Material Transferred for Subcontract",
|
||||
"default": "BOM",
|
||||
"fieldname": "backflush_raw_materials_of_subcontract_based_on",
|
||||
"fieldtype": "Select",
|
||||
"label": "Backflush Raw Materials of Subcontract Based On",
|
||||
@@ -134,13 +133,6 @@
|
||||
{
|
||||
"fieldname": "column_break_12",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
|
||||
"fieldname": "enable_discount_accounting",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Discount Accounting for Buying"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-cog",
|
||||
@@ -148,7 +140,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-31 19:40:26.103909",
|
||||
"modified": "2022-09-27 10:50:27.050252",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Buying Settings",
|
||||
|
||||
@@ -5,15 +5,10 @@
|
||||
|
||||
|
||||
import frappe
|
||||
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint
|
||||
|
||||
|
||||
class BuyingSettings(Document):
|
||||
def on_update(self):
|
||||
self.toggle_discount_accounting_fields()
|
||||
|
||||
def validate(self):
|
||||
for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
|
||||
frappe.db.set_default(key, self.get(key, ""))
|
||||
@@ -26,60 +21,3 @@ class BuyingSettings(Document):
|
||||
self.get("supp_master_name") == "Naming Series",
|
||||
hide_name_field=False,
|
||||
)
|
||||
|
||||
def toggle_discount_accounting_fields(self):
|
||||
enable_discount_accounting = cint(self.enable_discount_accounting)
|
||||
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"hidden",
|
||||
not (enable_discount_accounting),
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"mandatory_depends_on",
|
||||
"eval: doc.discount_amount",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
else:
|
||||
make_property_setter(
|
||||
"Purchase Invoice Item",
|
||||
"discount_account",
|
||||
"mandatory_depends_on",
|
||||
"",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"hidden",
|
||||
not (enable_discount_accounting),
|
||||
"Check",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
if enable_discount_accounting:
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"mandatory_depends_on",
|
||||
"eval: doc.discount_amount",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
else:
|
||||
make_property_setter(
|
||||
"Purchase Invoice",
|
||||
"additional_discount_account",
|
||||
"mandatory_depends_on",
|
||||
"",
|
||||
"Code",
|
||||
validate_fields_for_doctype=False,
|
||||
)
|
||||
|
||||
@@ -33,6 +33,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
frm.set_query("fg_item", "items", function() {
|
||||
return {
|
||||
filters: {
|
||||
'is_stock_item': 1,
|
||||
'is_sub_contracted_item': 1,
|
||||
'default_bom': ['!=', '']
|
||||
}
|
||||
@@ -295,131 +296,12 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
}
|
||||
|
||||
make_stock_entry() {
|
||||
var items = $.map(cur_frm.doc.items, function(d) { return d.bom ? d.item_code : false; });
|
||||
var me = this;
|
||||
|
||||
if(items.length >= 1){
|
||||
me.raw_material_data = [];
|
||||
me.show_dialog = 1;
|
||||
let title = __('Transfer Material to Supplier');
|
||||
let fields = [
|
||||
{fieldtype:'Section Break', label: __('Raw Materials')},
|
||||
{fieldname: 'sub_con_rm_items', fieldtype: 'Table', label: __('Items'),
|
||||
fields: [
|
||||
{
|
||||
fieldtype:'Data',
|
||||
fieldname:'item_code',
|
||||
label: __('Item'),
|
||||
read_only:1,
|
||||
in_list_view:1
|
||||
},
|
||||
{
|
||||
fieldtype:'Data',
|
||||
fieldname:'rm_item_code',
|
||||
label: __('Raw Material'),
|
||||
read_only:1,
|
||||
in_list_view:1
|
||||
},
|
||||
{
|
||||
fieldtype:'Float',
|
||||
read_only:1,
|
||||
fieldname:'qty',
|
||||
label: __('Quantity'),
|
||||
read_only:1,
|
||||
in_list_view:1
|
||||
},
|
||||
{
|
||||
fieldtype:'Data',
|
||||
read_only:1,
|
||||
fieldname:'warehouse',
|
||||
label: __('Reserve Warehouse'),
|
||||
in_list_view:1
|
||||
},
|
||||
{
|
||||
fieldtype:'Float',
|
||||
read_only:1,
|
||||
fieldname:'rate',
|
||||
label: __('Rate'),
|
||||
hidden:1
|
||||
},
|
||||
{
|
||||
fieldtype:'Float',
|
||||
read_only:1,
|
||||
fieldname:'amount',
|
||||
label: __('Amount'),
|
||||
hidden:1
|
||||
},
|
||||
{
|
||||
fieldtype:'Link',
|
||||
read_only:1,
|
||||
fieldname:'uom',
|
||||
label: __('UOM'),
|
||||
hidden:1
|
||||
}
|
||||
],
|
||||
data: me.raw_material_data,
|
||||
get_data: function() {
|
||||
return me.raw_material_data;
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
me.dialog = new frappe.ui.Dialog({
|
||||
title: title, fields: fields
|
||||
});
|
||||
|
||||
if (me.frm.doc['supplied_items']) {
|
||||
me.frm.doc['supplied_items'].forEach((item, index) => {
|
||||
if (item.rm_item_code && item.main_item_code && item.required_qty - item.supplied_qty != 0) {
|
||||
me.raw_material_data.push ({
|
||||
'name':item.name,
|
||||
'item_code': item.main_item_code,
|
||||
'rm_item_code': item.rm_item_code,
|
||||
'item_name': item.rm_item_code,
|
||||
'qty': item.required_qty - item.supplied_qty,
|
||||
'warehouse':item.reserve_warehouse,
|
||||
'rate':item.rate,
|
||||
'amount':item.amount,
|
||||
'stock_uom':item.stock_uom
|
||||
});
|
||||
me.dialog.fields_dict.sub_con_rm_items.grid.refresh();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
me.dialog.get_field('sub_con_rm_items').check_all_rows()
|
||||
|
||||
me.dialog.show()
|
||||
this.dialog.set_primary_action(__('Transfer'), function() {
|
||||
me.values = me.dialog.get_values();
|
||||
if(me.values) {
|
||||
me.values.sub_con_rm_items.map((row,i) => {
|
||||
if (!row.item_code || !row.rm_item_code || !row.warehouse || !row.qty || row.qty === 0) {
|
||||
let row_id = i+1;
|
||||
frappe.throw(__("Item Code, warehouse and quantity are required on row {0}", [row_id]));
|
||||
}
|
||||
})
|
||||
me._make_rm_stock_entry(me.dialog.fields_dict.sub_con_rm_items.grid.get_selected_children())
|
||||
me.dialog.hide()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
me.dialog.get_close_btn().on('click', () => {
|
||||
me.dialog.hide();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
_make_rm_stock_entry(rm_items) {
|
||||
frappe.call({
|
||||
method:"erpnext.controllers.subcontracting_controller.make_rm_stock_entry",
|
||||
args: {
|
||||
subcontract_order: cur_frm.doc.name,
|
||||
rm_items: rm_items,
|
||||
order_doctype: cur_frm.doc.doctype
|
||||
}
|
||||
,
|
||||
},
|
||||
callback: function(r) {
|
||||
var doclist = frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"section_break_45",
|
||||
"before_items_section",
|
||||
"scan_barcode",
|
||||
"set_from_warehouse",
|
||||
"items_col_break",
|
||||
"set_warehouse",
|
||||
"items_section",
|
||||
@@ -440,7 +441,6 @@
|
||||
"fieldname": "ignore_pricing_rule",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Pricing Rule",
|
||||
"no_copy": 1,
|
||||
"permlevel": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -1166,13 +1166,20 @@
|
||||
"hidden": 1,
|
||||
"label": "Is Old Subcontracting Flow",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "is_internal_supplier",
|
||||
"fieldname": "set_from_warehouse",
|
||||
"fieldtype": "Link",
|
||||
"label": "Set From Warehouse",
|
||||
"options": "Warehouse"
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-15 15:40:58.527065",
|
||||
"modified": "2022-09-16 17:45:04.954055",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -23,5 +23,6 @@ def get_data():
|
||||
"items": ["Material Request", "Supplier Quotation", "Project", "Auto Repeat"],
|
||||
},
|
||||
{"label": _("Sub-contracting"), "items": ["Subcontracting Order", "Stock Entry"]},
|
||||
{"label": _("Internal"), "items": ["Sales Order"]},
|
||||
],
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user