mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-21 13:44:03 +00:00
Compare commits
405 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ba029b140 | ||
|
|
cf74d24400 | ||
|
|
b8d97b82b8 | ||
|
|
56c4c3186b | ||
|
|
015a221d8d | ||
|
|
eec770a4a8 | ||
|
|
44f5ccc9dc | ||
|
|
0ae2a4f1c4 | ||
|
|
5fdaddad86 | ||
|
|
03ccddc1cc | ||
|
|
e00ebcd9e5 | ||
|
|
7e7122b668 | ||
|
|
b1c70af4db | ||
|
|
1609129e1e | ||
|
|
9b02455c09 | ||
|
|
ff3bfb060a | ||
|
|
63e087d31e | ||
|
|
b62a397397 | ||
|
|
48efcc82b6 | ||
|
|
4383980bcc | ||
|
|
aa0552d788 | ||
|
|
3b5889ef85 | ||
|
|
8238b89907 | ||
|
|
6ae2f90683 | ||
|
|
cb7aef505d | ||
|
|
4a8c42d62b | ||
|
|
c67bdcf3c6 | ||
|
|
7a9e7b66ac | ||
|
|
151b9d4d7b | ||
|
|
b966f10711 | ||
|
|
0b48f13873 | ||
|
|
b531a38efe | ||
|
|
dd26ef96e0 | ||
|
|
227ce5f8a2 | ||
|
|
acd64ba7c1 | ||
|
|
6f43133c04 | ||
|
|
f72602ebf3 | ||
|
|
49c9c68e14 | ||
|
|
9ce765b268 | ||
|
|
002ae8ae13 | ||
|
|
558dc57b94 | ||
|
|
cd942d36a8 | ||
|
|
3257533ee3 | ||
|
|
2763d06307 | ||
|
|
a0772ea8d7 | ||
|
|
d67b44fcec | ||
|
|
257a2a3d71 | ||
|
|
e6abbd1c83 | ||
|
|
41599cf29f | ||
|
|
c66ebcdf9a | ||
|
|
4623b986a3 | ||
|
|
611dd5c073 | ||
|
|
be404b77a1 | ||
|
|
6e9b52d268 | ||
|
|
997990c69f | ||
|
|
2bf76f64bd | ||
|
|
e1c41b9195 | ||
|
|
32a9575f07 | ||
|
|
e031e095dd | ||
|
|
39ce3756c8 | ||
|
|
f2515d9c9c | ||
|
|
27fe9c9179 | ||
|
|
503183cb0b | ||
|
|
f1ea5de022 | ||
|
|
baa4fec611 | ||
|
|
476175b307 | ||
|
|
de584535e9 | ||
|
|
d95d234aa4 | ||
|
|
3648745e5e | ||
|
|
8e1e6a194b | ||
|
|
663e9b403f | ||
|
|
3219b7c766 | ||
|
|
3a6e4c8da7 | ||
|
|
96a6db0422 | ||
|
|
4757a65863 | ||
|
|
0f8e34e972 | ||
|
|
2fd4485f2f | ||
|
|
299175e6fe | ||
|
|
8251b843ec | ||
|
|
60745705a8 | ||
|
|
caedc9f6ad | ||
|
|
fbeb86b9c0 | ||
|
|
ad43b18ace | ||
|
|
34284216cd | ||
|
|
5c6c9812c0 | ||
|
|
4afd2b7470 | ||
|
|
bc21ce412b | ||
|
|
a668d4e789 | ||
|
|
9f8d3e43ea | ||
|
|
9dbb2bf512 | ||
|
|
8c52c71299 | ||
|
|
c4c52c1cd3 | ||
|
|
b7fbf75e10 | ||
|
|
6dabfbedf1 | ||
|
|
68c592377b | ||
|
|
d69d2217f6 | ||
|
|
cdc8297083 | ||
|
|
f24f71f387 | ||
|
|
552c5951bd | ||
|
|
562025eb45 | ||
|
|
f0a37f545a | ||
|
|
001afb96ba | ||
|
|
61d3370527 | ||
|
|
32456bff30 | ||
|
|
1aed76f778 | ||
|
|
308c400c6a | ||
|
|
a9546dd01f | ||
|
|
cb4fbd5432 | ||
|
|
6f8d62088e | ||
|
|
5d66646fcd | ||
|
|
6a58d15497 | ||
|
|
6b21deedae | ||
|
|
7124328640 | ||
|
|
430a4c98a0 | ||
|
|
c65fb64578 | ||
|
|
14ab9d9158 | ||
|
|
1095052479 | ||
|
|
b4a511cbb4 | ||
|
|
a77388d520 | ||
|
|
eda6076a43 | ||
|
|
0b2405bbdf | ||
|
|
255aa7a84a | ||
|
|
a604eed1b9 | ||
|
|
96fa14be88 | ||
|
|
ac8100f1e5 | ||
|
|
07cc05785e | ||
|
|
f03fbc0e6d | ||
|
|
447c553954 | ||
|
|
b2e9dccc8c | ||
|
|
9e8e7aab70 | ||
|
|
6c528d469d | ||
|
|
988c5b95e6 | ||
|
|
1f2887d601 | ||
|
|
ed4ac100ba | ||
|
|
a194d28b69 | ||
|
|
3217924242 | ||
|
|
02468a902f | ||
|
|
a13eecc961 | ||
|
|
776ee53a25 | ||
|
|
ddf5565c67 | ||
|
|
4152a9f026 | ||
|
|
8f961abe8b | ||
|
|
70c68f011a | ||
|
|
9066009e89 | ||
|
|
9332e7f96f | ||
|
|
d6a335e59f | ||
|
|
a66002f774 | ||
|
|
6eb3428f8e | ||
|
|
d158784472 | ||
|
|
80d046a38c | ||
|
|
d68cdb4f5e | ||
|
|
9f988910b5 | ||
|
|
09cd480c81 | ||
|
|
4777991a74 | ||
|
|
1301a6ff7f | ||
|
|
a6a5f63af2 | ||
|
|
1cfeb9371c | ||
|
|
638d5e9dc3 | ||
|
|
b4ee72e15e | ||
|
|
2a9b519773 | ||
|
|
57136fa921 | ||
|
|
882542c2d5 | ||
|
|
0a4025e7e0 | ||
|
|
54dfd50391 | ||
|
|
45d02ceb4e | ||
|
|
f72bb18da7 | ||
|
|
f371008cd9 | ||
|
|
82e5a784cb | ||
|
|
d267372334 | ||
|
|
92c7d074dd | ||
|
|
cedc8d28e4 | ||
|
|
be4fdca6c7 | ||
|
|
2694438163 | ||
|
|
d0bd78ddcd | ||
|
|
e2912caeae | ||
|
|
6f9c2e6c80 | ||
|
|
96bf1e2a0a | ||
|
|
1f633b293d | ||
|
|
0cd1cacc29 | ||
|
|
21154c8bee | ||
|
|
9270e58969 | ||
|
|
de8f44bbff | ||
|
|
e9bf74e589 | ||
|
|
8abfdb6598 | ||
|
|
305693b562 | ||
|
|
794eeebabc | ||
|
|
75768d780f | ||
|
|
fe252b48f7 | ||
|
|
ca0cce7599 | ||
|
|
e07fd46a46 | ||
|
|
51d7c0bfe4 | ||
|
|
4165fee6a7 | ||
|
|
d251ef9ce5 | ||
|
|
afda4bdb17 | ||
|
|
c4452360da | ||
|
|
13527d6154 | ||
|
|
5c0a46110e | ||
|
|
d9d6a07bbb | ||
|
|
e65990eb34 | ||
|
|
07a7fdbe6c | ||
|
|
2fca8b541e | ||
|
|
7fc460bb32 | ||
|
|
e2e69dced7 | ||
|
|
5e49b6ea0f | ||
|
|
2e3445fad9 | ||
|
|
3c10e5066a | ||
|
|
e8d2e49155 | ||
|
|
5690e9771a | ||
|
|
ec6cac8043 | ||
|
|
0329642116 | ||
|
|
3c3e3cfcf8 | ||
|
|
7c4f5fa5c5 | ||
|
|
367e05f808 | ||
|
|
2dcb35da33 | ||
|
|
94732479f5 | ||
|
|
b4dec4630d | ||
|
|
c415a47d25 | ||
|
|
1e0cc65c61 | ||
|
|
37dbc7043a | ||
|
|
0e88496607 | ||
|
|
ef86b437cb | ||
|
|
47aa191004 | ||
|
|
5840913320 | ||
|
|
b7b0076743 | ||
|
|
ef5dd1d693 | ||
|
|
a53b40ba93 | ||
|
|
e78a7679a4 | ||
|
|
6a5beecb36 | ||
|
|
2b900e2f0e | ||
|
|
bc14dbbcad | ||
|
|
f4e53a5c91 | ||
|
|
3f99522764 | ||
|
|
81dd9722fe | ||
|
|
9c5d335360 | ||
|
|
c5c28615f5 | ||
|
|
ab32c09ff9 | ||
|
|
f2e63bc491 | ||
|
|
882aa96973 | ||
|
|
d29a033c09 | ||
|
|
b3125a56ed | ||
|
|
adfc57487b | ||
|
|
ac320e4d55 | ||
|
|
3256e2b8b7 | ||
|
|
b4ec4ccc56 | ||
|
|
a1826f215a | ||
|
|
181c0acf99 | ||
|
|
638d6b7177 | ||
|
|
f5132411eb | ||
|
|
e177f1e51b | ||
|
|
6f2a567c95 | ||
|
|
bb41d8bc47 | ||
|
|
d79aacd1cb | ||
|
|
44c3a322d3 | ||
|
|
2af489d69d | ||
|
|
bb1c450c33 | ||
|
|
5707118d58 | ||
|
|
8964612b8e | ||
|
|
514d280ee7 | ||
|
|
b10a2b87b6 | ||
|
|
3664a12bb9 | ||
|
|
0605cbf26b | ||
|
|
25d2847881 | ||
|
|
66cb3fd63f | ||
|
|
d22104ad40 | ||
|
|
0527393b09 | ||
|
|
741d6fcb9a | ||
|
|
1870dbf9a8 | ||
|
|
69a9724422 | ||
|
|
814dd36a3e | ||
|
|
17ad96998e | ||
|
|
20919c8626 | ||
|
|
56d8962e40 | ||
|
|
0a3ac82232 | ||
|
|
b736df3f0b | ||
|
|
ea995de4a3 | ||
|
|
b7c94e38a6 | ||
|
|
a3698524ca | ||
|
|
0f1f67dcae | ||
|
|
11b04acf8b | ||
|
|
4c1156a816 | ||
|
|
570729b73e | ||
|
|
a46dca57cb | ||
|
|
821c1cddfc | ||
|
|
8217c6dd9f | ||
|
|
fc030a7de9 | ||
|
|
1157bf887f | ||
|
|
e64e812679 | ||
|
|
6025df97ef | ||
|
|
4793adfefd | ||
|
|
7686c9e450 | ||
|
|
074d484d3c | ||
|
|
332e86af7e | ||
|
|
8eded437d2 | ||
|
|
bef7f6114d | ||
|
|
4095a3dae2 | ||
|
|
0b40117bfd | ||
|
|
09de1cba92 | ||
|
|
51a40ad2fc | ||
|
|
261405cec1 | ||
|
|
1ac9d7f43e | ||
|
|
69b3145be4 | ||
|
|
5e1d367d7a | ||
|
|
10f0baf6ad | ||
|
|
20694eb509 | ||
|
|
c2b8d1bd9a | ||
|
|
49bf05c1c2 | ||
|
|
2cf2885470 | ||
|
|
edfaf99388 | ||
|
|
3a7a53ab72 | ||
|
|
34537c9dc2 | ||
|
|
f6248d0259 | ||
|
|
4e6ef5d6fa | ||
|
|
154adcbb58 | ||
|
|
6a9d13ff28 | ||
|
|
7c85b487cd | ||
|
|
20e9599fc1 | ||
|
|
b4c992dd4d | ||
|
|
d46ce05dcd | ||
|
|
6bc6681f92 | ||
|
|
75d57c1f77 | ||
|
|
fc7285cc85 | ||
|
|
23a441252b | ||
|
|
474f34c04c | ||
|
|
6606028062 | ||
|
|
f58427bffe | ||
|
|
89f889068e | ||
|
|
9263d5ff4d | ||
|
|
4df197a282 | ||
|
|
b495969eb8 | ||
|
|
5f4760985c | ||
|
|
4a61840e39 | ||
|
|
ca48b9de54 | ||
|
|
baab96d02c | ||
|
|
f5b967a947 | ||
|
|
06b1bcbf59 | ||
|
|
aa8a063103 | ||
|
|
3f46b2a0ce | ||
|
|
b52962751c | ||
|
|
b94154f00a | ||
|
|
90ed14a055 | ||
|
|
b65bce8f98 | ||
|
|
66578a58cf | ||
|
|
476ded2972 | ||
|
|
ffb1196516 | ||
|
|
a452143782 | ||
|
|
6405a9378d | ||
|
|
2471ca58ee | ||
|
|
a4828407d0 | ||
|
|
bafa3ce150 | ||
|
|
cff140db3d | ||
|
|
36130c6292 | ||
|
|
4775c42593 | ||
|
|
b0917aaf8a | ||
|
|
d349a337e1 | ||
|
|
ba5ee1ca96 | ||
|
|
41b8563b7a | ||
|
|
7ee75ff762 | ||
|
|
3274e7681d | ||
|
|
7ddb332f47 | ||
|
|
2a615af00a | ||
|
|
ba8dc8925f | ||
|
|
14891ccf18 | ||
|
|
15654aae8b | ||
|
|
d1de4b027c | ||
|
|
a7d66fa352 | ||
|
|
8dea238d12 | ||
|
|
acbed434c2 | ||
|
|
9751f1060e | ||
|
|
c15cff05b0 | ||
|
|
b80526f226 | ||
|
|
0569f8a857 | ||
|
|
63715bf229 | ||
|
|
63cd4349a6 | ||
|
|
42b395916d | ||
|
|
0057c10a7d | ||
|
|
3c8c5d01fb | ||
|
|
d820757359 | ||
|
|
14e59c86aa | ||
|
|
9e16f4e412 | ||
|
|
6d269a4d89 | ||
|
|
4e8b39ab3f | ||
|
|
9925eb9982 | ||
|
|
301d199ece | ||
|
|
6c574fbf33 | ||
|
|
80981d025f | ||
|
|
a420d0242c | ||
|
|
809d5caf80 | ||
|
|
6f442d14cf | ||
|
|
a17acfdaa2 | ||
|
|
3673dea03b | ||
|
|
fb3725752f | ||
|
|
16c94d292c | ||
|
|
0e46b33ee3 | ||
|
|
df716fbd0c | ||
|
|
0dbfb1589e | ||
|
|
473a43b6b1 | ||
|
|
1a3b3b96c2 | ||
|
|
e9e53a74c9 | ||
|
|
26aef4fb1c | ||
|
|
b145fe3b3e | ||
|
|
d83c869d73 | ||
|
|
08c69c7a76 | ||
|
|
18d93f8398 | ||
|
|
c9443123f9 | ||
|
|
0fdec8fac8 |
2
.github/workflows/server-tests.yml
vendored
2
.github/workflows/server-tests.yml
vendored
@@ -93,7 +93,7 @@ jobs:
|
||||
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
|
||||
|
||||
- name: Run Tests
|
||||
run: cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --use-orchestrator
|
||||
run: 'cd ~/frappe-bench/ && bench --site test_site run-parallel-tests --app erpnext --total-builds 2 --build-number ${{ matrix.container }}'
|
||||
env:
|
||||
TYPE: server
|
||||
CI_BUILD_ID: ${{ github.run_id }}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
],
|
||||
[
|
||||
|
||||
18
CODEOWNERS
18
CODEOWNERS
@@ -12,17 +12,13 @@ erpnext/selling @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
|
||||
erpnext/support/ @nextchamp-saqib @deepeshgarg007
|
||||
pos* @nextchamp-saqib
|
||||
|
||||
erpnext/buying/ @marination @rohitwaghchaure @s-aga-r
|
||||
erpnext/e_commerce/ @marination
|
||||
erpnext/maintenance/ @marination @rohitwaghchaure @s-aga-r
|
||||
erpnext/manufacturing/ @marination @rohitwaghchaure @s-aga-r
|
||||
erpnext/portal/ @marination
|
||||
erpnext/quality_management/ @marination @rohitwaghchaure @s-aga-r
|
||||
erpnext/shopping_cart/ @marination
|
||||
erpnext/stock/ @marination @rohitwaghchaure @s-aga-r
|
||||
erpnext/buying/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/maintenance/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/manufacturing/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/quality_management/ @rohitwaghchaure @s-aga-r
|
||||
erpnext/stock/ @rohitwaghchaure @s-aga-r
|
||||
|
||||
|
||||
erpnext/crm/ @NagariaHussain
|
||||
erpnext/education/ @rutwikhdev
|
||||
erpnext/healthcare/ @chillaranand
|
||||
erpnext/hr/ @ruchamahabal
|
||||
erpnext/non_profit/ @ruchamahabal
|
||||
@@ -30,7 +26,7 @@ erpnext/payroll @ruchamahabal
|
||||
erpnext/projects/ @ruchamahabal
|
||||
|
||||
erpnext/controllers @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure @marination
|
||||
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @marination rohitwaghchaure
|
||||
erpnext/patches/ @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
|
||||
erpnext/public/ @nextchamp-saqib @marination
|
||||
|
||||
.github/ @ankush
|
||||
|
||||
@@ -65,6 +65,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).
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -4,7 +4,7 @@ import frappe
|
||||
|
||||
from erpnext.hooks import regional_overrides
|
||||
|
||||
__version__ = "13.36.2"
|
||||
__version__ = "13.40.2"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -358,7 +358,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)
|
||||
|
||||
|
||||
@@ -173,8 +173,8 @@ frappe.ui.form.on("Journal Entry", {
|
||||
var update_jv_details = function(doc, r) {
|
||||
$.each(r, function(i, d) {
|
||||
var row = frappe.model.add_child(doc, "Journal Entry Account", "accounts");
|
||||
row.account = d.account;
|
||||
row.balance = d.balance;
|
||||
frappe.model.set_value(row.doctype, row.name, "account", d.account)
|
||||
frappe.model.set_value(row.doctype, row.name, "balance", d.balance)
|
||||
});
|
||||
refresh_field("accounts");
|
||||
}
|
||||
|
||||
@@ -194,7 +194,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(){
|
||||
|
||||
@@ -1111,7 +1111,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;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1202,7 +1202,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))
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1234,6 +1234,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
|
||||
@@ -1254,8 +1255,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) {
|
||||
@@ -1264,9 +1265,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');
|
||||
|
||||
@@ -944,6 +944,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(
|
||||
{
|
||||
@@ -1059,7 +1066,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"))):
|
||||
@@ -1078,7 +1085,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
|
||||
@@ -1101,7 +1108,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
|
||||
@@ -1115,14 +1122,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
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.utils import flt, nowdate
|
||||
|
||||
from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
@@ -743,6 +744,46 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
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()
|
||||
|
||||
|
||||
@@ -8,7 +8,11 @@ from frappe.model.document import Document
|
||||
from frappe.utils import flt, getdate, nowdate, today
|
||||
|
||||
import erpnext
|
||||
from erpnext.accounts.utils import get_outstanding_invoices, reconcile_against_document
|
||||
from erpnext.accounts.utils import (
|
||||
get_outstanding_invoices,
|
||||
reconcile_against_document,
|
||||
update_reference_in_payment_entry,
|
||||
)
|
||||
from erpnext.controllers.accounts_controller import get_advance_payment_entries
|
||||
|
||||
|
||||
@@ -190,6 +194,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()
|
||||
@@ -205,12 +226,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
|
||||
|
||||
@@ -332,7 +357,7 @@ class PaymentReconciliation(Document):
|
||||
def get_conditions(self, get_invoices=False, get_payments=False, get_return_invoices=False):
|
||||
condition = " and company = '{0}' ".format(self.company)
|
||||
|
||||
if self.get("cost_center") and (get_invoices or get_payments or get_return_invoices):
|
||||
if self.get("cost_center"):
|
||||
condition = " and cost_center = '{0}' ".format(self.cost_center)
|
||||
|
||||
if get_invoices:
|
||||
|
||||
@@ -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"))
|
||||
|
||||
|
||||
@@ -1572,7 +1572,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",
|
||||
|
||||
@@ -222,9 +222,6 @@ class POSInvoice(SalesInvoice):
|
||||
allow_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
|
||||
|
||||
for d in self.get("items"):
|
||||
is_service_item = not (frappe.db.get_value("Item", d.get("item_code"), "is_stock_item"))
|
||||
if is_service_item:
|
||||
return
|
||||
if d.serial_no:
|
||||
self.validate_pos_reserved_serial_nos(d)
|
||||
self.validate_delivered_serial_nos(d)
|
||||
@@ -242,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 {}."
|
||||
@@ -637,11 +634,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
|
||||
|
||||
|
||||
@@ -655,7 +653,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)
|
||||
@@ -747,3 +747,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"])
|
||||
|
||||
@@ -495,6 +495,67 @@ class TestPOSInvoice(unittest.TestCase):
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pos.submit)
|
||||
|
||||
def test_value_error_on_serial_no_validation(self):
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_serialized_item
|
||||
|
||||
se = make_serialized_item(
|
||||
company="_Test Company",
|
||||
target_warehouse="Stores - _TC",
|
||||
cost_center="Main - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
)
|
||||
serial_nos = se.get("items")[0].serial_no
|
||||
|
||||
# make a pos invoice
|
||||
pos = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
debit_to="Debtors - _TC",
|
||||
account_for_change_amount="Cash - _TC",
|
||||
warehouse="Stores - _TC",
|
||||
income_account="Sales - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
qty=1,
|
||||
do_not_save=1,
|
||||
)
|
||||
pos.get("items")[0].has_serial_no = 1
|
||||
pos.get("items")[0].serial_no = serial_nos.split("\n")[0]
|
||||
pos.set("payments", [])
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 1000, "default": 1}
|
||||
)
|
||||
pos = pos.save().submit()
|
||||
|
||||
# make a return
|
||||
pos_return = make_sales_return(pos.name)
|
||||
pos_return.paid_amount = pos_return.grand_total
|
||||
pos_return.save()
|
||||
pos_return.submit()
|
||||
|
||||
# set docstatus to 2 for pos to trigger this issue
|
||||
frappe.db.set_value("POS Invoice", pos.name, "docstatus", 2)
|
||||
|
||||
pos2 = create_pos_invoice(
|
||||
company="_Test Company",
|
||||
debit_to="Debtors - _TC",
|
||||
account_for_change_amount="Cash - _TC",
|
||||
warehouse="Stores - _TC",
|
||||
income_account="Sales - _TC",
|
||||
expense_account="Cost of Goods Sold - _TC",
|
||||
cost_center="Main - _TC",
|
||||
item=se.get("items")[0].item_code,
|
||||
rate=1000,
|
||||
qty=1,
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
pos2.get("items")[0].has_serial_no = 1
|
||||
pos2.get("items")[0].serial_no = serial_nos.split("\n")[0]
|
||||
# Value error should not be triggered on validation
|
||||
pos2.save()
|
||||
|
||||
def test_loyalty_points(self):
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||
get_loyalty_program_details_with_points,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -79,6 +79,7 @@ class POSInvoiceMergeLog(Document):
|
||||
if sales:
|
||||
sales_invoice = self.process_merging_into_sales_invoice(sales)
|
||||
|
||||
self.flags.ignore_validate_update_after_submit = True
|
||||
self.save() # save consolidated_sales_invoice & consolidated_credit_note ref in merge log
|
||||
|
||||
self.update_pos_invoices(pos_invoice_docs, sales_invoice, credit_note)
|
||||
@@ -99,6 +100,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 +117,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 +405,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",
|
||||
|
||||
@@ -269,6 +269,18 @@ def get_serial_no_for_item(args):
|
||||
return item_details
|
||||
|
||||
|
||||
def update_pricing_rule_uom(pricing_rule, args):
|
||||
child_doc = {"Item Code": "items", "Item Group": "item_groups", "Brand": "brands"}.get(
|
||||
pricing_rule.apply_on
|
||||
)
|
||||
|
||||
apply_on_field = frappe.scrub(pricing_rule.apply_on)
|
||||
|
||||
for row in pricing_rule.get(child_doc):
|
||||
if row.get(apply_on_field) == args.get(apply_on_field):
|
||||
pricing_rule.uom = row.uom
|
||||
|
||||
|
||||
def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=False):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (
|
||||
get_applied_pricing_rules,
|
||||
@@ -325,7 +337,8 @@ def get_pricing_rule_for_item(args, price_list_rate=0, doc=None, for_validate=Fa
|
||||
|
||||
if isinstance(pricing_rule, string_types):
|
||||
pricing_rule = frappe.get_cached_doc("Pricing Rule", pricing_rule)
|
||||
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule)
|
||||
update_pricing_rule_uom(pricing_rule, args)
|
||||
pricing_rule.apply_rule_on_other_items = get_pricing_rule_items(pricing_rule) or []
|
||||
|
||||
if pricing_rule.get("suggestion"):
|
||||
continue
|
||||
@@ -439,12 +452,15 @@ def apply_price_discount_rule(pricing_rule, item_details, args):
|
||||
if pricing_rule.currency == args.currency:
|
||||
pricing_rule_rate = pricing_rule.rate
|
||||
|
||||
# TODO https://github.com/frappe/erpnext/pull/23636 solve this in some other way.
|
||||
if pricing_rule_rate:
|
||||
is_blank_uom = pricing_rule.get("uom") != args.get("uom")
|
||||
# Override already set price list rate (from item price)
|
||||
# if pricing_rule_rate > 0
|
||||
item_details.update(
|
||||
{
|
||||
"price_list_rate": pricing_rule_rate * args.get("conversion_factor", 1),
|
||||
"price_list_rate": pricing_rule_rate
|
||||
* (args.get("conversion_factor", 1) if is_blank_uom else 1),
|
||||
}
|
||||
)
|
||||
item_details.update({"discount_percentage": 0.0})
|
||||
|
||||
@@ -597,6 +597,121 @@ class TestPricingRule(unittest.TestCase):
|
||||
frappe.get_doc("Item Price", {"item_code": "Water Flask"}).delete()
|
||||
item.delete()
|
||||
|
||||
def test_item_price_with_blank_uom_pricing_rule(self):
|
||||
properties = {
|
||||
"item_code": "Item Blank UOM",
|
||||
"stock_uom": "Nos",
|
||||
"sales_uom": "Box",
|
||||
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||
}
|
||||
item = make_item(properties=properties)
|
||||
|
||||
make_item_price("Item Blank UOM", "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Item Blank UOM Rule",
|
||||
"apply_on": "Item Code",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "Item Blank UOM",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"currency": "INR",
|
||||
"rate_or_discount": "Rate",
|
||||
"rate": 101,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
rule = frappe.get_doc(pricing_rule_record)
|
||||
rule.insert()
|
||||
|
||||
si = create_sales_invoice(
|
||||
do_not_save=True, item_code="Item Blank UOM", uom="Box", conversion_factor=10
|
||||
)
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# If UOM is blank consider it as stock UOM and apply pricing_rule on all UOM.
|
||||
# rate is 101, Selling UOM is Box that have conversion_factor of 10 so 101 * 10 = 1010
|
||||
self.assertEqual(si.items[0].price_list_rate, 1010)
|
||||
self.assertEqual(si.items[0].rate, 1010)
|
||||
|
||||
si.delete()
|
||||
|
||||
si = create_sales_invoice(do_not_save=True, item_code="Item Blank UOM", uom="Nos")
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is blank so consider it as stock UOM and apply pricing_rule on all UOM.
|
||||
# rate is 101, Selling UOM is Nos that have conversion_factor of 1 so 101 * 1 = 101
|
||||
self.assertEqual(si.items[0].price_list_rate, 101)
|
||||
self.assertEqual(si.items[0].rate, 101)
|
||||
|
||||
si.delete()
|
||||
rule.delete()
|
||||
frappe.get_doc("Item Price", {"item_code": "Item Blank UOM"}).delete()
|
||||
|
||||
item.delete()
|
||||
|
||||
def test_item_price_with_selling_uom_pricing_rule(self):
|
||||
properties = {
|
||||
"item_code": "Item UOM other than Stock",
|
||||
"stock_uom": "Nos",
|
||||
"sales_uom": "Box",
|
||||
"uoms": [dict(uom="Box", conversion_factor=10)],
|
||||
}
|
||||
item = make_item(properties=properties)
|
||||
|
||||
make_item_price("Item UOM other than Stock", "_Test Price List", 100)
|
||||
|
||||
pricing_rule_record = {
|
||||
"doctype": "Pricing Rule",
|
||||
"title": "_Test Item UOM other than Stock Rule",
|
||||
"apply_on": "Item Code",
|
||||
"items": [
|
||||
{
|
||||
"item_code": "Item UOM other than Stock",
|
||||
"uom": "Box",
|
||||
}
|
||||
],
|
||||
"selling": 1,
|
||||
"currency": "INR",
|
||||
"rate_or_discount": "Rate",
|
||||
"rate": 101,
|
||||
"company": "_Test Company",
|
||||
}
|
||||
rule = frappe.get_doc(pricing_rule_record)
|
||||
rule.insert()
|
||||
|
||||
si = create_sales_invoice(
|
||||
do_not_save=True, item_code="Item UOM other than Stock", uom="Box", conversion_factor=10
|
||||
)
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is Box so apply pricing_rule only on Box UOM.
|
||||
# Selling UOM is Box and as both UOM are same no need to multiply by conversion_factor.
|
||||
self.assertEqual(si.items[0].price_list_rate, 101)
|
||||
self.assertEqual(si.items[0].rate, 101)
|
||||
|
||||
si.delete()
|
||||
|
||||
si = create_sales_invoice(do_not_save=True, item_code="Item UOM other than Stock", uom="Nos")
|
||||
si.selling_price_list = "_Test Price List"
|
||||
si.save()
|
||||
|
||||
# UOM is Box so pricing_rule won't apply as selling_uom is Nos.
|
||||
# As Pricing Rule is not applied price of 100 will be fetched from Item Price List.
|
||||
self.assertEqual(si.items[0].price_list_rate, 100)
|
||||
self.assertEqual(si.items[0].rate, 100)
|
||||
|
||||
si.delete()
|
||||
rule.delete()
|
||||
frappe.get_doc("Item Price", {"item_code": "Item UOM other than Stock"}).delete()
|
||||
|
||||
item.delete()
|
||||
|
||||
def test_pricing_rule_for_different_currency(self):
|
||||
make_item("Test Sanitizer Item")
|
||||
|
||||
|
||||
@@ -111,6 +111,12 @@ def _get_pricing_rules(apply_on, args, values):
|
||||
)
|
||||
|
||||
if apply_on_field == "item_code":
|
||||
if args.get("uom", None):
|
||||
item_conditions += (
|
||||
" and ({child_doc}.uom='{item_uom}' or IFNULL({child_doc}.uom, '')='')".format(
|
||||
child_doc=child_doc, item_uom=args.get("uom")
|
||||
)
|
||||
)
|
||||
if "variant_of" not in args:
|
||||
args.variant_of = frappe.get_cached_value("Item", args.item_code, "variant_of")
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -31,7 +31,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
this._super();
|
||||
|
||||
// Ignore linked advances
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry'];
|
||||
this.frm.ignore_doctypes_on_cancel_all = ['Journal Entry', 'Payment Entry', 'Purchase Invoice'];
|
||||
|
||||
if(!this.frm.doc.__islocal) {
|
||||
// show credit_to in print format
|
||||
@@ -539,7 +539,7 @@ frappe.ui.form.on("Purchase Invoice", {
|
||||
},
|
||||
|
||||
add_custom_buttons: function(frm) {
|
||||
if (frm.doc.per_received < 100) {
|
||||
if (frm.doc.docstatus == 1 && frm.doc.per_received < 100) {
|
||||
frm.add_custom_button(__('Purchase Receipt'), () => {
|
||||
frm.events.make_purchase_receipt(frm);
|
||||
}, __('Create'));
|
||||
|
||||
@@ -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",
|
||||
@@ -511,7 +513,6 @@
|
||||
"fieldname": "ignore_pricing_rule",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Pricing Rule",
|
||||
"no_copy": 1,
|
||||
"permlevel": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -1417,13 +1418,26 @@
|
||||
"label": "Advance Tax",
|
||||
"options": "Advance Tax",
|
||||
"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",
|
||||
"no_copy": 1,
|
||||
"options": "Tax Withheld Vouchers",
|
||||
"read_only": 1
|
||||
}
|
||||
],
|
||||
"icon": "fa fa-file-text",
|
||||
"idx": 204,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-25 13:31:02.716727",
|
||||
"modified": "2022-10-07 14:19:14.214157",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
@@ -1483,7 +1497,8 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"timeline_field": "supplier",
|
||||
"title_field": "title",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,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)
|
||||
@@ -696,6 +695,10 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
)
|
||||
|
||||
credit_amount = item.base_net_amount
|
||||
if self.is_internal_supplier and item.valuation_rate:
|
||||
credit_amount = flt(item.valuation_rate * item.stock_qty)
|
||||
|
||||
# Intentionally passed negative debit amount to avoid incorrect GL Entry validation
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
@@ -705,7 +708,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"cost_center": item.cost_center,
|
||||
"project": item.project or self.project,
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Stock"),
|
||||
"debit": -1 * flt(item.base_net_amount, item.precision("base_net_amount")),
|
||||
"debit": -1 * flt(credit_amount, item.precision("base_net_amount")),
|
||||
},
|
||||
warehouse_account[item.from_warehouse]["account_currency"],
|
||||
item=item,
|
||||
@@ -794,7 +797,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if not item.is_fixed_asset:
|
||||
dummy, amount = self.get_amount_and_base_amount(item, self.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"))
|
||||
|
||||
@@ -1110,7 +1113,7 @@ class PurchaseInvoice(BuyingController):
|
||||
valuation_tax = {}
|
||||
|
||||
for tax in self.get("taxes"):
|
||||
amount, base_amount = self.get_tax_amounts(tax, self.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)
|
||||
|
||||
@@ -1365,7 +1368,14 @@ class PurchaseInvoice(BuyingController):
|
||||
frappe.db.set(self, "status", "Cancelled")
|
||||
|
||||
unlink_inter_company_doc(self.doctype, self.name, self.inter_company_invoice_reference)
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Repost Item Valuation")
|
||||
|
||||
self.ignore_linked_doctypes = (
|
||||
"GL Entry",
|
||||
"Stock Ledger Entry",
|
||||
"Repost Item Valuation",
|
||||
"Purchase Invoice",
|
||||
)
|
||||
|
||||
self.update_advance_tax_references(cancel=1)
|
||||
|
||||
def update_project(self):
|
||||
@@ -1458,7 +1468,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
|
||||
)
|
||||
|
||||
@@ -1487,6 +1497,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()
|
||||
|
||||
@@ -1689,4 +1712,6 @@ def make_purchase_receipt(source_name, target_doc=None):
|
||||
target_doc,
|
||||
)
|
||||
|
||||
doc.set_onload("ignore_price_list", True)
|
||||
|
||||
return doc
|
||||
|
||||
@@ -304,59 +304,6 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
self.assertEqual(expected_values[gle.account][1], gle.debit)
|
||||
self.assertEqual(expected_values[gle.account][2], gle.credit)
|
||||
|
||||
def test_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
enable_discount_accounting()
|
||||
|
||||
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())
|
||||
enable_discount_accounting(enable=0)
|
||||
|
||||
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
|
||||
enable_discount_accounting()
|
||||
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()
|
||||
@@ -1602,6 +1549,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(
|
||||
|
||||
@@ -706,6 +706,7 @@
|
||||
"label": "Valuation Rate",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"precision": "6",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
@@ -871,7 +872,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-15 17:04:07.191013",
|
||||
"modified": "2022-10-12 03:37:29.032732",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -480,9 +480,13 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -651,7 +651,6 @@
|
||||
"hide_days": 1,
|
||||
"hide_seconds": 1,
|
||||
"label": "Ignore Pricing Rule",
|
||||
"no_copy": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -2046,7 +2045,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",
|
||||
|
||||
@@ -1065,22 +1065,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):
|
||||
for tax in self.get("taxes"):
|
||||
amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting)
|
||||
@@ -2173,13 +2157,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)
|
||||
|
||||
@@ -7,7 +7,7 @@ import unittest
|
||||
import frappe
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.utils import add_days, flt, getdate, nowdate
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
from six import iteritems
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import create_account, get_inventory_account
|
||||
@@ -31,10 +31,20 @@ from erpnext.stock.doctype.stock_entry.test_stock_entry import (
|
||||
get_qty_after_transaction,
|
||||
make_stock_entry,
|
||||
)
|
||||
from erpnext.stock.utils import get_incoming_rate
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_stock_reconciliation,
|
||||
)
|
||||
from erpnext.stock.utils import get_incoming_rate, get_stock_balance
|
||||
|
||||
|
||||
class TestSalesInvoice(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from erpnext.stock.doctype.stock_ledger_entry.test_stock_ledger_entry import create_items
|
||||
|
||||
create_items(["_Test Internal Transfer Item"], uoms=[{"uom": "Box", "conversion_factor": 10}])
|
||||
create_internal_parties()
|
||||
setup_accounts()
|
||||
|
||||
def make(self):
|
||||
w = frappe.copy_doc(test_records[0])
|
||||
w.is_pos = 0
|
||||
@@ -1687,7 +1697,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.save()
|
||||
self.assertEqual(si.get("items")[0].rate, flt((price_list_rate * 25) / 100 + price_list_rate))
|
||||
|
||||
def test_outstanding_amount_after_advance_jv_cancelation(self):
|
||||
def test_outstanding_amount_after_advance_jv_cancellation(self):
|
||||
from erpnext.accounts.doctype.journal_entry.test_journal_entry import (
|
||||
test_records as jv_test_records,
|
||||
)
|
||||
@@ -1731,7 +1741,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
flt(si.rounded_total + si.total_advance, si.precision("outstanding_amount")),
|
||||
)
|
||||
|
||||
def test_outstanding_amount_after_advance_payment_entry_cancelation(self):
|
||||
def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
|
||||
pe = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Payment Entry",
|
||||
@@ -2369,29 +2379,6 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
acc_settings.save()
|
||||
|
||||
def test_inter_company_transaction(self):
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer",
|
||||
represents_company="_Test Company 1",
|
||||
allowed_to_interact_with="Wind Power LLC",
|
||||
)
|
||||
|
||||
if not frappe.db.exists("Supplier", "_Test Internal Supplier"):
|
||||
supplier = frappe.get_doc(
|
||||
{
|
||||
"supplier_group": "_Test Supplier Group",
|
||||
"supplier_name": "_Test Internal Supplier",
|
||||
"doctype": "Supplier",
|
||||
"is_internal_supplier": 1,
|
||||
"represents_company": "Wind Power LLC",
|
||||
}
|
||||
)
|
||||
|
||||
supplier.append("companies", {"company": "_Test Company 1"})
|
||||
|
||||
supplier.insert()
|
||||
|
||||
si = create_sales_invoice(
|
||||
company="Wind Power LLC",
|
||||
customer="_Test Internal Customer",
|
||||
@@ -2451,34 +2438,9 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
se.cancel()
|
||||
|
||||
def test_internal_transfer_gl_entry(self):
|
||||
## Create internal transfer account
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
|
||||
account = create_account(
|
||||
account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - TCP1",
|
||||
company="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
|
||||
)
|
||||
|
||||
customer = create_internal_customer(
|
||||
"_Test Internal Customer 2",
|
||||
"_Test Company with perpetual inventory",
|
||||
"_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
create_internal_supplier(
|
||||
"_Test Internal Supplier 2",
|
||||
"_Test Company with perpetual inventory",
|
||||
"_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company="_Test Company with perpetual inventory",
|
||||
customer=customer,
|
||||
customer="_Test Internal Customer 2",
|
||||
debit_to="Debtors - TCP1",
|
||||
warehouse="Stores - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
@@ -2492,7 +2454,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
si.update_stock = 1
|
||||
si.items[0].target_warehouse = "Work In Progress - TCP1"
|
||||
|
||||
# Add stock to stores for succesful stock transfer
|
||||
# Add stock to stores for successful stock transfer
|
||||
make_stock_entry(
|
||||
target="Stores - TCP1", company="_Test Company with perpetual inventory", qty=1, basic_rate=100
|
||||
)
|
||||
@@ -2830,6 +2792,77 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
self.assertEqual(einvoice["ItemList"][1]["Discount"], 0)
|
||||
self.assertEqual(einvoice["ItemList"][1]["UnitPrice"], 20)
|
||||
|
||||
def test_internal_transfer_gl_precision_issues(self):
|
||||
# Make a stock queue of an item with two valuations
|
||||
|
||||
# Remove all existing stock for this
|
||||
if get_stock_balance("_Test Internal Transfer Item", "Stores - TCP1", "2022-04-10"):
|
||||
create_stock_reconciliation(
|
||||
item_code="_Test Internal Transfer Item",
|
||||
warehouse="Stores - TCP1",
|
||||
qty=0,
|
||||
rate=0,
|
||||
company="_Test Company with perpetual inventory",
|
||||
expense_account="Stock Adjustment - TCP1"
|
||||
if frappe.get_all("Stock Ledger Entry")
|
||||
else "Temporary Opening - TCP1",
|
||||
posting_date="2020-04-10",
|
||||
posting_time="14:00",
|
||||
)
|
||||
|
||||
make_stock_entry(
|
||||
item_code="_Test Internal Transfer Item",
|
||||
target="Stores - TCP1",
|
||||
qty=9000000,
|
||||
basic_rate=52.0,
|
||||
posting_date="2020-04-10",
|
||||
posting_time="14:00",
|
||||
)
|
||||
make_stock_entry(
|
||||
item_code="_Test Internal Transfer Item",
|
||||
target="Stores - TCP1",
|
||||
qty=60000000,
|
||||
basic_rate=52.349777,
|
||||
posting_date="2020-04-10",
|
||||
posting_time="14:00",
|
||||
)
|
||||
|
||||
# Make an internal transfer Sales Invoice Stock in non stock uom to check
|
||||
# for rounding errors while converting to stock uom
|
||||
si = create_sales_invoice(
|
||||
company="_Test Company with perpetual inventory",
|
||||
customer="_Test Internal Customer 2",
|
||||
item_code="_Test Internal Transfer Item",
|
||||
qty=5000000,
|
||||
uom="Box",
|
||||
debit_to="Debtors - TCP1",
|
||||
warehouse="Stores - TCP1",
|
||||
income_account="Sales - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
cost_center="Main - TCP1",
|
||||
currency="INR",
|
||||
do_not_save=1,
|
||||
)
|
||||
|
||||
# Check GL Entries with precision
|
||||
si.update_stock = 1
|
||||
si.items[0].target_warehouse = "Work In Progress - TCP1"
|
||||
si.items[0].conversion_factor = 10
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
# Check if adjustment entry is created
|
||||
self.assertTrue(
|
||||
frappe.db.exists(
|
||||
"GL Entry",
|
||||
{
|
||||
"voucher_type": "Sales Invoice",
|
||||
"voucher_no": si.name,
|
||||
"remarks": "Rounding gain/loss Entry for Stock Transfer",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def test_item_tax_net_range(self):
|
||||
item = create_item("T Shirt")
|
||||
|
||||
@@ -3278,7 +3311,7 @@ class TestSalesInvoice(unittest.TestCase):
|
||||
[deferred_account, 2022.47, 0.0, "2019-03-15"],
|
||||
]
|
||||
|
||||
gl_entries = gl_entries = frappe.db.sql(
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select account, debit, credit, posting_date
|
||||
from `tabGL Entry`
|
||||
where voucher_type='Journal Entry' and voucher_detail_no=%s and posting_date <= %s
|
||||
@@ -3396,6 +3429,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()
|
||||
@@ -3652,6 +3716,7 @@ def create_sales_invoice(**args):
|
||||
"description": args.description or "_Test Item",
|
||||
"gst_hsn_code": "999800",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"target_warehouse": args.target_warehouse,
|
||||
"qty": args.qty or 1,
|
||||
"uom": args.uom or "Nos",
|
||||
"stock_uom": args.uom or "Nos",
|
||||
@@ -3664,8 +3729,9 @@ def create_sales_invoice(**args):
|
||||
"discount_amount": args.discount_amount or 0,
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_no": args.serial_no,
|
||||
"conversion_factor": 1,
|
||||
"conversion_factor": args.get("conversion_factor", 1),
|
||||
"incoming_rate": args.incoming_rate or 0,
|
||||
"batch_no": args.batch_no or None,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -3777,6 +3843,34 @@ def get_taxes_and_charges():
|
||||
]
|
||||
|
||||
|
||||
def create_internal_parties():
|
||||
from erpnext.selling.doctype.customer.test_customer import create_internal_customer
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer",
|
||||
represents_company="_Test Company 1",
|
||||
allowed_to_interact_with="Wind Power LLC",
|
||||
)
|
||||
|
||||
create_internal_customer(
|
||||
customer_name="_Test Internal Customer 2",
|
||||
represents_company="_Test Company with perpetual inventory",
|
||||
allowed_to_interact_with="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
create_internal_supplier(
|
||||
supplier_name="_Test Internal Supplier",
|
||||
represents_company="Wind Power LLC",
|
||||
allowed_to_interact_with="_Test Company 1",
|
||||
)
|
||||
|
||||
create_internal_supplier(
|
||||
supplier_name="_Test Internal Supplier 2",
|
||||
represents_company="_Test Company with perpetual inventory",
|
||||
allowed_to_interact_with="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
|
||||
def create_internal_supplier(supplier_name, represents_company, allowed_to_interact_with):
|
||||
if not frappe.db.exists("Supplier", supplier_name):
|
||||
supplier = frappe.get_doc(
|
||||
@@ -3799,6 +3893,19 @@ def create_internal_supplier(supplier_name, represents_company, allowed_to_inter
|
||||
return supplier_name
|
||||
|
||||
|
||||
def setup_accounts():
|
||||
## Create internal transfer account
|
||||
account = create_account(
|
||||
account_name="Unrealized Profit",
|
||||
parent_account="Current Liabilities - TCP1",
|
||||
company="_Test Company with perpetual inventory",
|
||||
)
|
||||
|
||||
frappe.db.set_value(
|
||||
"Company", "_Test Company with perpetual inventory", "unrealized_profit_loss_account", account
|
||||
)
|
||||
|
||||
|
||||
def add_taxes(doc):
|
||||
doc.append(
|
||||
"taxes",
|
||||
|
||||
@@ -279,7 +279,6 @@
|
||||
"label": "Discount (%) on Price List Rate with Margin",
|
||||
"oldfieldname": "adj_rate",
|
||||
"oldfieldtype": "Float",
|
||||
"precision": "2",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -813,6 +812,8 @@
|
||||
"fieldtype": "Currency",
|
||||
"label": "Incoming Rate (Costing)",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"precision": "6",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
@@ -842,7 +843,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-24 14:41:36.392560",
|
||||
"modified": "2022-10-10 20:57:38.340026",
|
||||
"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
|
||||
@@ -100,7 +100,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
|
||||
)
|
||||
|
||||
@@ -110,7 +110,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
|
||||
|
||||
@@ -208,7 +208,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,
|
||||
@@ -227,6 +229,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:
|
||||
@@ -237,6 +240,9 @@ def get_tax_amount(party_type, parties, inv, tax_details, posting_date, pan_no=N
|
||||
)
|
||||
else:
|
||||
tax_amount = net_total * tax_details.rate / 100 if net_total > 0 else 0
|
||||
|
||||
# once tds is deducted, not need to add vouchers in the invoice
|
||||
voucher_wise_amount = {}
|
||||
else:
|
||||
tax_amount = get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers)
|
||||
|
||||
@@ -252,12 +258,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,
|
||||
@@ -272,29 +279,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(
|
||||
@@ -309,9 +327,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:
|
||||
@@ -321,23 +341,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
|
||||
|
||||
@@ -359,6 +381,9 @@ def get_deducted_tax(taxable_vouchers, tax_details):
|
||||
|
||||
def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
tds_amount = 0
|
||||
supp_credit_amt = 0.0
|
||||
supp_jv_credit_amt = 0.0
|
||||
|
||||
invoice_filters = {"name": ("in", vouchers), "docstatus": 1, "apply_tds": 1}
|
||||
|
||||
field = "sum(net_total)"
|
||||
@@ -367,30 +392,25 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
invoice_filters.pop("apply_tds", None)
|
||||
field = "sum(grand_total)"
|
||||
|
||||
supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0
|
||||
if vouchers:
|
||||
supp_credit_amt = frappe.db.get_value("Purchase Invoice", invoice_filters, field) or 0.0
|
||||
|
||||
supp_jv_credit_amt = (
|
||||
frappe.db.get_value(
|
||||
"Journal Entry Account",
|
||||
{
|
||||
"parent": ("in", vouchers),
|
||||
"docstatus": 1,
|
||||
"party": ("in", parties),
|
||||
"reference_type": ("!=", "Purchase Invoice"),
|
||||
},
|
||||
"sum(credit_in_account_currency)",
|
||||
)
|
||||
or 0.0
|
||||
)
|
||||
supp_jv_credit_amt = (
|
||||
frappe.db.get_value(
|
||||
"Journal Entry Account",
|
||||
{
|
||||
"parent": ("in", vouchers),
|
||||
"docstatus": 1,
|
||||
"party": ("in", parties),
|
||||
"reference_type": ("!=", "Purchase Invoice"),
|
||||
},
|
||||
"sum(credit_in_account_currency)",
|
||||
)
|
||||
) or 0.0
|
||||
|
||||
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)
|
||||
|
||||
@@ -402,7 +422,10 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
):
|
||||
# Get net total again as TDS is calculated on net total
|
||||
# Grand is used to just check for threshold breach
|
||||
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)") or 0.0
|
||||
net_total = 0
|
||||
if vouchers:
|
||||
net_total = frappe.db.get_value("Purchase Invoice", invoice_filters, "sum(net_total)")
|
||||
|
||||
net_total += inv.net_total
|
||||
supp_credit_amt = net_total - cumulative_threshold
|
||||
|
||||
@@ -423,36 +446,40 @@ def get_tds_amount(ldc, parties, inv, tax_details, tax_deducted, vouchers):
|
||||
|
||||
def get_tcs_amount(parties, inv, tax_details, vouchers, adv_vouchers):
|
||||
tcs_amount = 0
|
||||
invoiced_amt = 0
|
||||
advance_amt = 0
|
||||
|
||||
# sum of debit entries made from sales invoices
|
||||
invoiced_amt = (
|
||||
frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"is_cancelled": 0,
|
||||
"party": ["in", parties],
|
||||
"company": inv.company,
|
||||
"voucher_no": ["in", vouchers],
|
||||
},
|
||||
"sum(debit)",
|
||||
if vouchers:
|
||||
invoiced_amt = (
|
||||
frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"is_cancelled": 0,
|
||||
"party": ["in", parties],
|
||||
"company": inv.company,
|
||||
"voucher_no": ["in", vouchers],
|
||||
},
|
||||
"sum(debit)",
|
||||
)
|
||||
or 0.0
|
||||
)
|
||||
or 0.0
|
||||
)
|
||||
|
||||
# sum of credit entries made from PE / JV with unset 'against voucher'
|
||||
advance_amt = (
|
||||
frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"is_cancelled": 0,
|
||||
"party": ["in", parties],
|
||||
"company": inv.company,
|
||||
"voucher_no": ["in", adv_vouchers],
|
||||
},
|
||||
"sum(credit)",
|
||||
if advance_amt:
|
||||
advance_amt = (
|
||||
frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"is_cancelled": 0,
|
||||
"party": ["in", parties],
|
||||
"company": inv.company,
|
||||
"voucher_no": ["in", adv_vouchers],
|
||||
},
|
||||
"sum(credit)",
|
||||
)
|
||||
or 0.0
|
||||
)
|
||||
or 0.0
|
||||
)
|
||||
|
||||
# sum of credit entries made from sales invoice
|
||||
credit_note_amt = sum(
|
||||
@@ -507,22 +534,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()
|
||||
|
||||
@@ -208,7 +208,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))
|
||||
|
||||
@@ -220,12 +220,37 @@ 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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -280,9 +280,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
|
||||
|
||||
|
||||
@@ -44,14 +44,14 @@ frappe.query_reports["Gross Profit"] = {
|
||||
"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();
|
||||
|
||||
@@ -561,7 +561,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:
|
||||
|
||||
@@ -19,14 +19,19 @@ def execute(filters=None):
|
||||
return _execute(filters)
|
||||
|
||||
|
||||
def _execute(filters=None, additional_table_columns=None, additional_query_columns=None):
|
||||
def _execute(
|
||||
filters=None,
|
||||
additional_table_columns=None,
|
||||
additional_query_columns=None,
|
||||
additional_conditions=None,
|
||||
):
|
||||
if not filters:
|
||||
filters = {}
|
||||
columns = get_columns(additional_table_columns, filters)
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||
|
||||
item_list = get_items(filters, additional_query_columns)
|
||||
item_list = get_items(filters, additional_query_columns, additional_conditions)
|
||||
if item_list:
|
||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||
|
||||
@@ -97,6 +102,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 +111,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"):
|
||||
@@ -319,7 +333,7 @@ def get_columns(additional_table_columns, filters):
|
||||
return columns
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
def get_conditions(filters, additional_conditions=None):
|
||||
conditions = ""
|
||||
|
||||
for opts in (
|
||||
@@ -332,6 +346,9 @@ def get_conditions(filters):
|
||||
if filters.get(opts[0]):
|
||||
conditions += opts[1]
|
||||
|
||||
if additional_conditions:
|
||||
conditions += additional_conditions
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
conditions += """ and exists(select name from `tabSales Invoice Payment`
|
||||
where parent=`tabSales Invoice`.name
|
||||
@@ -367,8 +384,8 @@ def get_group_by_conditions(filters, doctype):
|
||||
return "ORDER BY `tab{0}`.{1}".format(doctype, frappe.scrub(filters.get("group_by")))
|
||||
|
||||
|
||||
def get_items(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
conditions = get_conditions(filters, additional_conditions)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = ", " + ", ".join(additional_query_columns)
|
||||
@@ -477,7 +494,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 +510,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 +568,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 +615,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(
|
||||
|
||||
@@ -106,12 +106,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,
|
||||
)
|
||||
|
||||
|
||||
@@ -818,6 +818,31 @@ def get_held_invoices(party_type, party):
|
||||
return held_invoices
|
||||
|
||||
|
||||
def remove_return_pos_invoices(party_type, party, invoice_list):
|
||||
if invoice_list:
|
||||
|
||||
if party_type == "Customer":
|
||||
sinv = frappe.qb.DocType("Sales Invoice")
|
||||
return_pos = (
|
||||
frappe.qb.from_(sinv)
|
||||
.select(sinv.name)
|
||||
.where((sinv.is_pos == 1) & (sinv.docstatus == 1) & (sinv.is_return == 1))
|
||||
.run()
|
||||
)
|
||||
|
||||
if return_pos:
|
||||
return_pos = [x[0] for x in return_pos]
|
||||
else:
|
||||
return invoice_list
|
||||
|
||||
# remove pos return invoices from invoice_list
|
||||
for idx, inv in enumerate(invoice_list, 0):
|
||||
if inv.voucher_no in return_pos:
|
||||
del invoice_list[idx]
|
||||
|
||||
return invoice_list
|
||||
|
||||
|
||||
def get_outstanding_invoices(party_type, party, account, condition=None, filters=None):
|
||||
outstanding_invoices = []
|
||||
precision = frappe.get_precision("Sales Invoice", "outstanding_amount") or 2
|
||||
@@ -868,6 +893,8 @@ def get_outstanding_invoices(party_type, party, account, condition=None, filters
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
invoice_list = remove_return_pos_invoices(party_type, party, invoice_list)
|
||||
|
||||
payment_entries = frappe.db.sql(
|
||||
"""
|
||||
select against_voucher_type, against_voucher,
|
||||
|
||||
@@ -819,7 +819,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)
|
||||
|
||||
@@ -7,7 +7,7 @@ import frappe
|
||||
from frappe.utils import add_days, add_months, cstr, flt, 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
|
||||
from erpnext.assets.doctype.asset.asset import make_sales_invoice, update_maintenance_status
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
post_depreciation_entries,
|
||||
restore_asset,
|
||||
@@ -238,6 +238,34 @@ class TestAsset(AssetSetup):
|
||||
|
||||
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",
|
||||
purchase_date="2020-01-01",
|
||||
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,
|
||||
)
|
||||
|
||||
post_depreciation_entries(date="2021-01-01")
|
||||
|
||||
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")
|
||||
|
||||
update_maintenance_status()
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
def test_expense_head(self):
|
||||
pr = make_purchase_receipt(
|
||||
item_code="Macbook Pro", qty=2, rate=200000.0, location="Test Location"
|
||||
@@ -1353,6 +1381,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",
|
||||
|
||||
@@ -439,7 +439,6 @@
|
||||
"fieldname": "ignore_pricing_rule",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Pricing Rule",
|
||||
"no_copy": 1,
|
||||
"permlevel": 1,
|
||||
"print_hide": 1
|
||||
},
|
||||
@@ -1170,7 +1169,7 @@
|
||||
"idx": 105,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-04-26 12:16:38.694276",
|
||||
"modified": "2022-09-16 17:45:04.954055",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Buying",
|
||||
"name": "Purchase Order",
|
||||
|
||||
@@ -18,7 +18,7 @@ from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
|
||||
from erpnext.accounts.doctype.tax_withholding_category.tax_withholding_category import (
|
||||
get_party_tax_withholding_details,
|
||||
)
|
||||
from erpnext.accounts.party import get_party_account_currency
|
||||
from erpnext.accounts.party import get_party_account, get_party_account_currency
|
||||
from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
|
||||
from erpnext.controllers.buying_controller import BuyingController
|
||||
from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
|
||||
@@ -323,6 +323,7 @@ class PurchaseOrder(BuyingController):
|
||||
update_linked_doc(self.doctype, self.name, self.inter_company_order_reference)
|
||||
|
||||
def on_cancel(self):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Payment Ledger Entry")
|
||||
super(PurchaseOrder, self).on_cancel()
|
||||
|
||||
if self.is_against_so():
|
||||
@@ -532,6 +533,7 @@ def get_mapped_purchase_invoice(source_name, target_doc=None, ignore_permissions
|
||||
target.set_advances()
|
||||
|
||||
target.set_payment_schedule()
|
||||
target.credit_to = get_party_account("Supplier", source.supplier, source.company)
|
||||
|
||||
def update_item(obj, target, source_parent):
|
||||
target.amount = flt(obj.amount) - flt(obj.billed_amt)
|
||||
|
||||
@@ -15,9 +15,12 @@ frappe.ui.form.on("Request for Quotation",{
|
||||
frm.fields_dict["suppliers"].grid.get_field("contact").get_query = function(doc, cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
return {
|
||||
query: "erpnext.buying.doctype.request_for_quotation.request_for_quotation.get_supplier_contacts",
|
||||
filters: {'supplier': d.supplier}
|
||||
}
|
||||
query: "frappe.contacts.doctype.contact.contact.contact_query",
|
||||
filters: {
|
||||
link_doctype: "Supplier",
|
||||
link_name: d.supplier || ""
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ class RequestforQuotation(BuyingController):
|
||||
|
||||
def get_link(self):
|
||||
# RFQ link for supplier portal
|
||||
return get_url("/rfq/" + self.name)
|
||||
return get_url("/app/request-for-quotation/" + self.name)
|
||||
|
||||
def update_supplier_part_no(self, supplier):
|
||||
self.vendor = supplier
|
||||
@@ -287,18 +287,6 @@ def get_list_context(context=None):
|
||||
return list_context
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_supplier_contacts(doctype, txt, searchfield, start, page_len, filters):
|
||||
return frappe.db.sql(
|
||||
"""select `tabContact`.name from `tabContact`, `tabDynamic Link`
|
||||
where `tabDynamic Link`.link_doctype = 'Supplier' and (`tabDynamic Link`.link_name=%(name)s
|
||||
and `tabDynamic Link`.link_name like %(txt)s) and `tabContact`.name = `tabDynamic Link`.parent
|
||||
limit %(start)s, %(page_len)s""",
|
||||
{"start": start, "page_len": page_len, "txt": "%%%s%%" % txt, "name": filters.get("supplier")},
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def make_supplier_quotation_from_rfq(source_name, target_doc=None, for_supplier=None):
|
||||
def postprocess(source, target_doc):
|
||||
|
||||
@@ -65,7 +65,6 @@ class TestRequestforQuotation(FrappeTestCase):
|
||||
)
|
||||
sq.submit()
|
||||
|
||||
frappe.form_dict = frappe.local("form_dict")
|
||||
frappe.form_dict.name = rfq.name
|
||||
|
||||
self.assertEqual(check_supplier_has_docname_access(supplier_wt_appos[0].get("supplier")), True)
|
||||
|
||||
@@ -1105,17 +1105,17 @@ class AccountsController(TransactionBase):
|
||||
frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
|
||||
)
|
||||
|
||||
if self.doctype == "Purchase Invoice":
|
||||
dr_or_cr = "credit"
|
||||
rev_dr_cr = "debit"
|
||||
supplier_or_customer = self.supplier
|
||||
|
||||
else:
|
||||
dr_or_cr = "debit"
|
||||
rev_dr_cr = "credit"
|
||||
supplier_or_customer = self.customer
|
||||
|
||||
if enable_discount_accounting:
|
||||
if self.doctype == "Purchase Invoice":
|
||||
dr_or_cr = "credit"
|
||||
rev_dr_cr = "debit"
|
||||
supplier_or_customer = self.supplier
|
||||
|
||||
else:
|
||||
dr_or_cr = "debit"
|
||||
rev_dr_cr = "credit"
|
||||
supplier_or_customer = self.customer
|
||||
|
||||
for item in self.get("items"):
|
||||
if item.get("discount_amount") and item.get("discount_account"):
|
||||
discount_amount = item.discount_amount * item.qty
|
||||
@@ -1169,18 +1169,22 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
)
|
||||
|
||||
if self.get("discount_amount") and self.get("additional_discount_account"):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.additional_discount_account,
|
||||
"against": supplier_or_customer,
|
||||
dr_or_cr: self.discount_amount,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
if (
|
||||
(enable_discount_accounting or self.get("is_cash_or_non_trade_discount"))
|
||||
and self.get("additional_discount_account")
|
||||
and self.get("discount_amount")
|
||||
):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.additional_discount_account,
|
||||
"against": supplier_or_customer,
|
||||
dr_or_cr: self.discount_amount,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
item=self,
|
||||
)
|
||||
)
|
||||
|
||||
def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
|
||||
from erpnext.controllers.status_updater import get_allowance_for
|
||||
|
||||
@@ -86,6 +86,7 @@ class BuyingController(StockController, Subcontracting):
|
||||
company=self.company,
|
||||
party_address=self.get("supplier_address"),
|
||||
shipping_address=self.get("shipping_address"),
|
||||
company_address=self.get("billing_address"),
|
||||
fetch_payment_terms_template=not self.get("ignore_default_payment_terms_template"),
|
||||
ignore_permissions=self.flags.ignore_permissions,
|
||||
)
|
||||
@@ -192,16 +193,16 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
if self.meta.get_field("base_in_words"):
|
||||
if self.meta.get_field("base_rounded_total") and not self.is_rounded_total_disabled():
|
||||
amount = self.base_rounded_total
|
||||
amount = abs(self.base_rounded_total)
|
||||
else:
|
||||
amount = self.base_grand_total
|
||||
amount = abs(self.base_grand_total)
|
||||
self.base_in_words = money_in_words(amount, self.company_currency)
|
||||
|
||||
if self.meta.get_field("in_words"):
|
||||
if self.meta.get_field("rounded_total") and not self.is_rounded_total_disabled():
|
||||
amount = self.rounded_total
|
||||
amount = abs(self.rounded_total)
|
||||
else:
|
||||
amount = self.grand_total
|
||||
amount = abs(self.grand_total)
|
||||
|
||||
self.in_words = money_in_words(amount, self.currency)
|
||||
|
||||
@@ -301,7 +302,12 @@ class BuyingController(StockController, Subcontracting):
|
||||
|
||||
rate = flt(outgoing_rate * (d.conversion_factor or 1), d.precision("rate"))
|
||||
else:
|
||||
rate = frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), "rate")
|
||||
field = "incoming_rate" if self.get("is_internal_supplier") else "rate"
|
||||
rate = flt(
|
||||
frappe.db.get_value(ref_doctype, d.get(frappe.scrub(ref_doctype)), field)
|
||||
* (d.conversion_factor or 1),
|
||||
d.precision("rate"),
|
||||
)
|
||||
|
||||
if self.is_internal_transfer():
|
||||
if rate != d.rate:
|
||||
|
||||
@@ -18,8 +18,9 @@ from erpnext.stock.get_item_details import _get_item_tax_template
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Employee"
|
||||
conditions = []
|
||||
fields = get_fields("Employee", ["name", "employee_name"])
|
||||
fields = get_fields(doctype, ["name", "employee_name"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabEmployee`
|
||||
@@ -49,7 +50,8 @@ def employee_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
fields = get_fields("Lead", ["name", "lead_name", "company_name"])
|
||||
doctype = "Lead"
|
||||
fields = get_fields(doctype, ["name", "lead_name", "company_name"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabLead`
|
||||
@@ -77,6 +79,7 @@ def lead_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Customer"
|
||||
conditions = []
|
||||
cust_master_name = frappe.defaults.get_user_default("cust_master_name")
|
||||
|
||||
@@ -85,9 +88,9 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
else:
|
||||
fields = ["name", "customer_name", "customer_group", "territory"]
|
||||
|
||||
fields = get_fields("Customer", fields)
|
||||
fields = get_fields(doctype, fields)
|
||||
|
||||
searchfields = frappe.get_meta("Customer").get_search_fields()
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
|
||||
|
||||
return frappe.db.sql(
|
||||
@@ -116,6 +119,7 @@ def customer_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Supplier"
|
||||
supp_master_name = frappe.defaults.get_user_default("supp_master_name")
|
||||
|
||||
if supp_master_name == "Supplier Name":
|
||||
@@ -123,7 +127,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
else:
|
||||
fields = ["name", "supplier_name", "supplier_group"]
|
||||
|
||||
fields = get_fields("Supplier", fields)
|
||||
fields = get_fields(doctype, fields)
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {field} from `tabSupplier`
|
||||
@@ -147,6 +151,7 @@ def supplier_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Account"
|
||||
company_currency = erpnext.get_company_currency(filters.get("company"))
|
||||
|
||||
def get_accounts(with_account_type_filter):
|
||||
@@ -197,13 +202,14 @@ def tax_account_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
|
||||
doctype = "Item"
|
||||
conditions = []
|
||||
|
||||
if isinstance(filters, str):
|
||||
filters = json.loads(filters)
|
||||
|
||||
# Get searchfields from meta and use in Item Link field query
|
||||
meta = frappe.get_meta("Item", cached=True)
|
||||
meta = frappe.get_meta(doctype, cached=True)
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
# these are handled separately
|
||||
@@ -257,7 +263,7 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
filters.pop("supplier", None)
|
||||
|
||||
description_cond = ""
|
||||
if frappe.db.count("Item", cache=True) < 50000:
|
||||
if frappe.db.count(doctype, cache=True) < 50000:
|
||||
# scan description only if items are less than 50000
|
||||
description_cond = "or tabItem.description LIKE %(txt)s"
|
||||
return frappe.db.sql(
|
||||
@@ -300,8 +306,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters, as_dict=Fals
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "BOM"
|
||||
conditions = []
|
||||
fields = get_fields("BOM", ["name", "item"])
|
||||
fields = get_fields(doctype, ["name", "item"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields}
|
||||
@@ -331,6 +338,7 @@ def bom(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Project"
|
||||
cond = ""
|
||||
if filters and filters.get("customer"):
|
||||
cond = """(`tabProject`.customer = %s or
|
||||
@@ -338,9 +346,9 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
frappe.db.escape(filters.get("customer"))
|
||||
)
|
||||
|
||||
fields = get_fields("Project", ["name", "project_name"])
|
||||
searchfields = frappe.get_meta("Project").get_search_fields()
|
||||
searchfields = " or ".join([field + " like %(txt)s" for field in searchfields])
|
||||
fields = get_fields(doctype, ["name", "project_name"])
|
||||
searchfields = frappe.get_meta(doctype).get_search_fields()
|
||||
searchfields = " or ".join(["`tabProject`." + field + " like %(txt)s" for field in searchfields])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""select {fields} from `tabProject`
|
||||
@@ -366,7 +374,8 @@ def get_project_name(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len, filters, as_dict):
|
||||
fields = get_fields("Delivery Note", ["name", "customer", "posting_date"])
|
||||
doctype = "Delivery Note"
|
||||
fields = get_fields(doctype, ["name", "customer", "posting_date"])
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
@@ -402,6 +411,7 @@ def get_delivery_notes_to_be_billed(doctype, txt, searchfield, start, page_len,
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Batch"
|
||||
cond = ""
|
||||
if filters.get("posting_date"):
|
||||
cond = "and (batch.expiry_date is null or batch.expiry_date >= %(posting_date)s)"
|
||||
@@ -420,7 +430,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
if filters.get("is_return"):
|
||||
having_clause = ""
|
||||
|
||||
meta = frappe.get_meta("Batch", cached=True)
|
||||
meta = frappe.get_meta(doctype, cached=True)
|
||||
searchfields = meta.get_search_fields()
|
||||
|
||||
search_columns = ""
|
||||
@@ -496,6 +506,7 @@ def get_batch_no(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.whitelist()
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
doctype = "Account"
|
||||
filter_list = []
|
||||
|
||||
if isinstance(filters, dict):
|
||||
@@ -514,7 +525,7 @@ def get_account_list(doctype, txt, searchfield, start, page_len, filters):
|
||||
filter_list.append([doctype, searchfield, "like", "%%%s%%" % txt])
|
||||
|
||||
return frappe.desk.reportview.execute(
|
||||
"Account",
|
||||
doctype,
|
||||
filters=filter_list,
|
||||
fields=["name", "parent_account"],
|
||||
limit_start=start,
|
||||
@@ -553,6 +564,7 @@ def get_income_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
doctype = "Account"
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
@@ -628,6 +640,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
doctype = "Account"
|
||||
condition = ""
|
||||
if filters.get("company"):
|
||||
condition += "and tabAccount.company = %(company)s"
|
||||
@@ -650,6 +663,7 @@ def get_expense_account(doctype, txt, searchfield, start, page_len, filters):
|
||||
@frappe.validate_and_sanitize_search_inputs
|
||||
def warehouse_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
# Should be used when item code is passed in filters.
|
||||
doctype = "Warehouse"
|
||||
conditions, bin_conditions = [], []
|
||||
filter_dict = get_doctype_wise_filters(filters)
|
||||
|
||||
|
||||
@@ -439,11 +439,17 @@ class SellingController(StockController):
|
||||
# For internal transfers use incoming rate as the valuation rate
|
||||
if self.is_internal_transfer():
|
||||
if d.doctype == "Packed Item":
|
||||
incoming_rate = flt(d.incoming_rate * d.conversion_factor, d.precision("incoming_rate"))
|
||||
incoming_rate = flt(
|
||||
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
|
||||
d.precision("incoming_rate"),
|
||||
)
|
||||
if d.incoming_rate != incoming_rate:
|
||||
d.incoming_rate = incoming_rate
|
||||
else:
|
||||
rate = flt(d.incoming_rate * d.conversion_factor, d.precision("rate"))
|
||||
rate = flt(
|
||||
flt(d.incoming_rate, d.precision("incoming_rate")) * d.conversion_factor,
|
||||
d.precision("rate"),
|
||||
)
|
||||
if d.rate != rate:
|
||||
d.rate = rate
|
||||
frappe.msgprint(
|
||||
|
||||
@@ -33,6 +33,10 @@ class QualityInspectionNotSubmittedError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class BatchExpiredError(frappe.ValidationError):
|
||||
pass
|
||||
|
||||
|
||||
class StockController(AccountsController):
|
||||
def validate(self):
|
||||
super(StockController, self).validate()
|
||||
@@ -74,6 +78,10 @@ class StockController(AccountsController):
|
||||
def validate_serialized_batch(self):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
|
||||
is_material_issue = False
|
||||
if self.doctype == "Stock Entry" and self.purpose == "Material Issue":
|
||||
is_material_issue = True
|
||||
|
||||
for d in self.get("items"):
|
||||
if hasattr(d, "serial_no") and hasattr(d, "batch_no") and d.serial_no and d.batch_no:
|
||||
serial_nos = frappe.get_all(
|
||||
@@ -90,6 +98,9 @@ class StockController(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
if is_material_issue:
|
||||
continue
|
||||
|
||||
if flt(d.qty) > 0.0 and d.get("batch_no") and self.get("posting_date") and self.docstatus < 2:
|
||||
expiry_date = frappe.get_cached_value("Batch", d.get("batch_no"), "expiry_date")
|
||||
|
||||
@@ -97,7 +108,8 @@ class StockController(AccountsController):
|
||||
frappe.throw(
|
||||
_("Row #{0}: The batch {1} has already expired.").format(
|
||||
d.idx, get_link_to_form("Batch", d.get("batch_no"))
|
||||
)
|
||||
),
|
||||
BatchExpiredError,
|
||||
)
|
||||
|
||||
def clean_serial_nos(self):
|
||||
@@ -127,13 +139,15 @@ class StockController(AccountsController):
|
||||
warehouse_with_no_account = []
|
||||
precision = self.get_debit_field_precision()
|
||||
for item_row in voucher_details:
|
||||
|
||||
sle_list = sle_map.get(item_row.name)
|
||||
sle_rounding_diff = 0.0
|
||||
if sle_list:
|
||||
for sle in sle_list:
|
||||
if warehouse_account.get(sle.warehouse):
|
||||
# from warehouse account
|
||||
|
||||
sle_rounding_diff += flt(sle.stock_value_difference)
|
||||
|
||||
self.check_expense_account(item_row)
|
||||
|
||||
# expense account/ target_warehouse / source_warehouse
|
||||
@@ -176,6 +190,46 @@ class StockController(AccountsController):
|
||||
elif sle.warehouse not in warehouse_with_no_account:
|
||||
warehouse_with_no_account.append(sle.warehouse)
|
||||
|
||||
if abs(sle_rounding_diff) > (1.0 / (10**precision)) and self.is_internal_transfer():
|
||||
warehouse_asset_account = ""
|
||||
if self.get("is_internal_customer"):
|
||||
warehouse_asset_account = warehouse_account[item_row.get("target_warehouse")]["account"]
|
||||
elif self.get("is_internal_supplier"):
|
||||
warehouse_asset_account = warehouse_account[item_row.get("warehouse")]["account"]
|
||||
|
||||
expense_account = frappe.db.get_value("Company", self.company, "default_expense_account")
|
||||
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": expense_account,
|
||||
"against": warehouse_asset_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"project": item_row.project or self.get("project"),
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"debit": sle_rounding_diff,
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
},
|
||||
warehouse_account[sle.warehouse]["account_currency"],
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
gl_list.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": warehouse_asset_account,
|
||||
"against": expense_account,
|
||||
"cost_center": item_row.cost_center,
|
||||
"remarks": _("Rounding gain/loss Entry for Stock Transfer"),
|
||||
"credit": sle_rounding_diff,
|
||||
"project": item_row.get("project") or self.get("project"),
|
||||
"is_opening": item_row.get("is_opening") or self.get("is_opening") or "No",
|
||||
},
|
||||
item=item_row,
|
||||
)
|
||||
)
|
||||
|
||||
if warehouse_with_no_account:
|
||||
for wh in warehouse_with_no_account:
|
||||
if frappe.db.get_value("Warehouse", wh, "company"):
|
||||
|
||||
@@ -37,6 +37,12 @@ class calculate_taxes_and_totals(object):
|
||||
self.set_discount_amount()
|
||||
self.apply_discount_amount()
|
||||
|
||||
# Update grand total as per cash and non trade discount
|
||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||
self.doc.grand_total -= self.doc.discount_amount
|
||||
self.doc.base_grand_total -= self.doc.base_discount_amount
|
||||
self.set_rounded_total()
|
||||
|
||||
self.calculate_shipping_charges()
|
||||
|
||||
if self.doc.doctype in ["Sales Invoice", "Purchase Invoice"]:
|
||||
@@ -500,9 +506,6 @@ class calculate_taxes_and_totals(object):
|
||||
else:
|
||||
self.doc.grand_total = flt(self.doc.net_total)
|
||||
|
||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get("is_cash_or_non_trade_discount"):
|
||||
self.doc.grand_total -= self.doc.discount_amount
|
||||
|
||||
if self.doc.get("taxes"):
|
||||
self.doc.total_taxes_and_charges = flt(
|
||||
self.doc.grand_total - self.doc.net_total - flt(self.doc.rounding_adjustment),
|
||||
@@ -597,16 +600,16 @@ class calculate_taxes_and_totals(object):
|
||||
if not self.doc.apply_discount_on:
|
||||
frappe.throw(_("Please select Apply Discount On"))
|
||||
|
||||
self.doc.base_discount_amount = flt(
|
||||
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
||||
)
|
||||
|
||||
if self.doc.apply_discount_on == "Grand Total" and self.doc.get(
|
||||
"is_cash_or_non_trade_discount"
|
||||
):
|
||||
self.discount_amount_applied = True
|
||||
return
|
||||
|
||||
self.doc.base_discount_amount = flt(
|
||||
self.doc.discount_amount * self.doc.conversion_rate, self.doc.precision("base_discount_amount")
|
||||
)
|
||||
|
||||
total_for_discount_amount = self.get_total_for_discount_amount()
|
||||
taxes = self.doc.get("taxes")
|
||||
net_total = 0
|
||||
@@ -767,6 +770,18 @@ class calculate_taxes_and_totals(object):
|
||||
self.doc.precision("outstanding_amount"),
|
||||
)
|
||||
|
||||
if (
|
||||
self.doc.doctype == "Sales Invoice"
|
||||
and self.doc.get("is_pos")
|
||||
and self.doc.get("pos_profile")
|
||||
and self.doc.get("is_consolidated")
|
||||
):
|
||||
write_off_limit = flt(
|
||||
frappe.db.get_value("POS Profile", self.doc.pos_profile, "write_off_limit")
|
||||
)
|
||||
if write_off_limit and abs(self.doc.outstanding_amount) <= write_off_limit:
|
||||
self.doc.write_off_outstanding_amount_automatically = 1
|
||||
|
||||
if (
|
||||
self.doc.doctype == "Sales Invoice"
|
||||
and self.doc.get("is_pos")
|
||||
|
||||
@@ -11,9 +11,12 @@ frappe.ui.form.on('Course Scheduling Tool', {
|
||||
},
|
||||
refresh(frm) {
|
||||
frm.disable_save();
|
||||
frm.trigger("render_days");
|
||||
frm.page.set_primary_action(__('Schedule Course'), () => {
|
||||
frm.call('schedule_course')
|
||||
frappe.dom.freeze(__("Scheduling..."));
|
||||
frm.call('schedule_course', { days: frm.days.get_checked_options() })
|
||||
.then(r => {
|
||||
frappe.dom.unfreeze();
|
||||
if (!r.message) {
|
||||
frappe.throw(__('There were errors creating Course Schedule'));
|
||||
}
|
||||
@@ -40,5 +43,60 @@ frappe.ui.form.on('Course Scheduling Tool', {
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
render_days: function(frm) {
|
||||
const days_html = $('<div class="days-editor">').appendTo(
|
||||
frm.fields_dict.days_html.wrapper
|
||||
);
|
||||
|
||||
if (!frm.days) {
|
||||
frm.days = frappe.ui.form.make_control({
|
||||
parent: days_html,
|
||||
df: {
|
||||
fieldname: "days",
|
||||
fieldtype: "MultiCheck",
|
||||
select_all: true,
|
||||
columns: 4,
|
||||
options: [
|
||||
{
|
||||
label: __("Monday"),
|
||||
value: "Monday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Tuesday"),
|
||||
value: "Tuesday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Wednesday"),
|
||||
value: "Wednesday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Thursday"),
|
||||
value: "Thursday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Friday"),
|
||||
value: "Friday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Saturday"),
|
||||
value: "Saturday",
|
||||
checked: 0,
|
||||
},
|
||||
{
|
||||
label: __("Sunday"),
|
||||
value: "Sunday",
|
||||
checked: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
render_input: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,661 +1,171 @@
|
||||
{
|
||||
"allow_copy": 1,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 0,
|
||||
"beta": 0,
|
||||
"creation": "2015-09-23 15:37:38.108475",
|
||||
"custom": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 0,
|
||||
"engine": "InnoDB",
|
||||
"actions": [],
|
||||
"allow_copy": 1,
|
||||
"creation": "2015-09-23 15:37:38.108475",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"student_group",
|
||||
"course",
|
||||
"program",
|
||||
"column_break_3",
|
||||
"academic_year",
|
||||
"academic_term",
|
||||
"section_break_6",
|
||||
"instructor",
|
||||
"instructor_name",
|
||||
"column_break_9",
|
||||
"room",
|
||||
"section_break_7",
|
||||
"days_html",
|
||||
"section_break_14",
|
||||
"from_time",
|
||||
"course_start_date",
|
||||
"column_break_15",
|
||||
"to_time",
|
||||
"course_end_date",
|
||||
"reschedule"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "student_group",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Student Group",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Student Group",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "student_group",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Student Group",
|
||||
"options": "Student Group",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Course",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Course",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "course",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Course",
|
||||
"options": "Course",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Program",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Program",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "program",
|
||||
"fieldtype": "Link",
|
||||
"label": "Program",
|
||||
"options": "Program",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_3",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Academic Year",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Academic Year",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "academic_year",
|
||||
"fieldtype": "Link",
|
||||
"label": "Academic Year",
|
||||
"options": "Academic Year",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "academic_term",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Academic Term",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Academic Term",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "academic_term",
|
||||
"fieldtype": "Link",
|
||||
"label": "Academic Term",
|
||||
"options": "Academic Term",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_6",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "instructor",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Instructor",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Instructor",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "instructor",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Instructor",
|
||||
"options": "Instructor",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fetch_from": "instructor.instructor_name",
|
||||
"fieldname": "instructor_name",
|
||||
"fieldtype": "Read Only",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Instructor Name",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 1,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "instructor_name",
|
||||
"fieldtype": "Read Only",
|
||||
"label": "Instructor Name",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_9",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "room",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Room",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "Room",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "room",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "Room",
|
||||
"options": "Room",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "section_break_7",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "From Time",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "from_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "From Time",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "course_start_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Course Start Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "course_start_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Course Start Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "day",
|
||||
"fieldtype": "Select",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Day",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"options": "\nMonday\nTuesday\nWednesday\nThursday\nFriday\nSaturday\nSunday",
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"default": "0",
|
||||
"fieldname": "reschedule",
|
||||
"fieldtype": "Check",
|
||||
"label": "Reschedule"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "reschedule",
|
||||
"fieldtype": "Check",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Reschedule",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "column_break_15",
|
||||
"fieldtype": "Column Break",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Time",
|
||||
"label": "To TIme",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"fieldname": "to_time",
|
||||
"fieldtype": "Time",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "To TIme",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
},
|
||||
"fieldname": "course_end_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Course End Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"columns": 0,
|
||||
"default": "",
|
||||
"fieldname": "course_end_date",
|
||||
"fieldtype": "Date",
|
||||
"hidden": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_standard_filter": 0,
|
||||
"label": "Course End Date",
|
||||
"length": 0,
|
||||
"no_copy": 0,
|
||||
"permlevel": 0,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"read_only": 0,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"translatable": 0,
|
||||
"unique": 0
|
||||
"fieldname": "days_html",
|
||||
"fieldtype": "HTML",
|
||||
"label": "Days HTML"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_14",
|
||||
"fieldtype": "Section Break"
|
||||
}
|
||||
],
|
||||
"has_web_view": 0,
|
||||
"hide_heading": 1,
|
||||
"hide_toolbar": 1,
|
||||
"idx": 0,
|
||||
"image_view": 0,
|
||||
"in_create": 0,
|
||||
"is_submittable": 0,
|
||||
"issingle": 1,
|
||||
"istable": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": 0,
|
||||
"modified": "2018-05-16 22:43:29.363798",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Course Scheduling Tool",
|
||||
"name_case": "",
|
||||
"owner": "Administrator",
|
||||
],
|
||||
"hide_toolbar": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-01 17:08:07.180557",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Education",
|
||||
"name": "Course Scheduling Tool",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"amend": 0,
|
||||
"cancel": 0,
|
||||
"create": 1,
|
||||
"delete": 0,
|
||||
"email": 0,
|
||||
"export": 0,
|
||||
"if_owner": 0,
|
||||
"import": 0,
|
||||
"permlevel": 0,
|
||||
"print": 0,
|
||||
"read": 1,
|
||||
"report": 0,
|
||||
"role": "Academics User",
|
||||
"set_user_permissions": 0,
|
||||
"share": 0,
|
||||
"submit": 0,
|
||||
"create": 1,
|
||||
"read": 1,
|
||||
"role": "Academics User",
|
||||
"write": 1
|
||||
}
|
||||
],
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"read_only_onload": 0,
|
||||
"restrict_to_domain": "Education",
|
||||
"show_name_in_global_search": 0,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1,
|
||||
"track_seen": 0
|
||||
],
|
||||
"restrict_to_domain": "Education",
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -14,7 +14,7 @@ from erpnext.education.utils import OverlapError
|
||||
|
||||
class CourseSchedulingTool(Document):
|
||||
@frappe.whitelist()
|
||||
def schedule_course(self):
|
||||
def schedule_course(self, days):
|
||||
"""Creates course schedules as per specified parameters"""
|
||||
|
||||
course_schedules = []
|
||||
@@ -22,7 +22,7 @@ class CourseSchedulingTool(Document):
|
||||
rescheduled = []
|
||||
reschedule_errors = []
|
||||
|
||||
self.validate_mandatory()
|
||||
self.validate_mandatory(days)
|
||||
self.validate_date()
|
||||
self.instructor_name = frappe.db.get_value("Instructor", self.instructor, "instructor_name")
|
||||
|
||||
@@ -34,24 +34,22 @@ class CourseSchedulingTool(Document):
|
||||
self.course = course
|
||||
|
||||
if self.reschedule:
|
||||
rescheduled, reschedule_errors = self.delete_course_schedule(rescheduled, reschedule_errors)
|
||||
rescheduled, reschedule_errors = self.delete_course_schedule(
|
||||
rescheduled, reschedule_errors, days
|
||||
)
|
||||
|
||||
date = self.course_start_date
|
||||
while date < self.course_end_date:
|
||||
if self.day == calendar.day_name[getdate(date).weekday()]:
|
||||
if calendar.day_name[getdate(date).weekday()] in days:
|
||||
course_schedule = self.make_course_schedule(date)
|
||||
try:
|
||||
print("pass")
|
||||
course_schedule.save()
|
||||
except OverlapError:
|
||||
print("fail")
|
||||
course_schedules_errors.append(date)
|
||||
else:
|
||||
course_schedules.append(course_schedule)
|
||||
|
||||
date = add_days(date, 7)
|
||||
else:
|
||||
date = add_days(date, 1)
|
||||
date = add_days(date, 1)
|
||||
|
||||
return dict(
|
||||
course_schedules=course_schedules,
|
||||
@@ -60,8 +58,10 @@ class CourseSchedulingTool(Document):
|
||||
reschedule_errors=reschedule_errors,
|
||||
)
|
||||
|
||||
def validate_mandatory(self):
|
||||
def validate_mandatory(self, days):
|
||||
"""Validates all mandatory fields"""
|
||||
if not days:
|
||||
frappe.throw(_("Please select at least one day to schedule the course."))
|
||||
|
||||
fields = [
|
||||
"course",
|
||||
@@ -71,7 +71,6 @@ class CourseSchedulingTool(Document):
|
||||
"to_time",
|
||||
"course_start_date",
|
||||
"course_end_date",
|
||||
"day",
|
||||
]
|
||||
for d in fields:
|
||||
if not self.get(d):
|
||||
@@ -82,9 +81,8 @@ class CourseSchedulingTool(Document):
|
||||
if self.course_start_date > self.course_end_date:
|
||||
frappe.throw(_("Course Start Date cannot be greater than Course End Date."))
|
||||
|
||||
def delete_course_schedule(self, rescheduled, reschedule_errors):
|
||||
def delete_course_schedule(self, rescheduled, reschedule_errors, days):
|
||||
"""Delete all course schedule within the Date range and specified filters"""
|
||||
|
||||
schedules = frappe.get_list(
|
||||
"Course Schedule",
|
||||
fields=["name", "schedule_date"],
|
||||
@@ -98,7 +96,7 @@ class CourseSchedulingTool(Document):
|
||||
|
||||
for d in schedules:
|
||||
try:
|
||||
if self.day == calendar.day_name[getdate(d.schedule_date).weekday()]:
|
||||
if calendar.day_name[getdate(d.schedule_date).weekday()] in days:
|
||||
frappe.delete_doc("Course Schedule", d.name)
|
||||
rescheduled.append(d.name)
|
||||
except Exception:
|
||||
@@ -108,7 +106,6 @@ class CourseSchedulingTool(Document):
|
||||
def make_course_schedule(self, date):
|
||||
"""Makes a new Course Schedule.
|
||||
:param date: Date on which Course Schedule will be created."""
|
||||
|
||||
course_schedule = frappe.new_doc("Course Schedule")
|
||||
course_schedule.student_group = self.student_group
|
||||
course_schedule.course = self.course
|
||||
|
||||
@@ -37,11 +37,26 @@ def handle_end_call(**kwargs):
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
def handle_missed_call(**kwargs):
|
||||
update_call_log(kwargs, "Missed")
|
||||
status = ""
|
||||
call_type = kwargs.get("CallType")
|
||||
dial_call_status = kwargs.get("DialCallStatus")
|
||||
|
||||
if call_type == "incomplete" and dial_call_status == "no-answer":
|
||||
status = "No Answer"
|
||||
elif call_type == "client-hangup" and dial_call_status == "canceled":
|
||||
status = "Canceled"
|
||||
elif call_type == "incomplete" and dial_call_status == "failed":
|
||||
status = "Failed"
|
||||
|
||||
update_call_log(kwargs, status)
|
||||
|
||||
|
||||
def update_call_log(call_payload, status="Ringing", call_log=None):
|
||||
call_log = call_log or get_call_log(call_payload)
|
||||
|
||||
# for a new sid, call_log and get_call_log will be empty so create a new log
|
||||
if not call_log:
|
||||
call_log = create_call_log(call_payload)
|
||||
if call_log:
|
||||
call_log.status = status
|
||||
call_log.to = call_payload.get("DialWhomNumber")
|
||||
@@ -53,16 +68,9 @@ def update_call_log(call_payload, status="Ringing", call_log=None):
|
||||
|
||||
|
||||
def get_call_log(call_payload):
|
||||
call_log = frappe.get_all(
|
||||
"Call Log",
|
||||
{
|
||||
"id": call_payload.get("CallSid"),
|
||||
},
|
||||
limit=1,
|
||||
)
|
||||
|
||||
if call_log:
|
||||
return frappe.get_doc("Call Log", call_log[0].name)
|
||||
call_log_id = call_payload.get("CallSid")
|
||||
if frappe.db.exists("Call Log", call_log_id):
|
||||
return frappe.get_doc("Call Log", call_log_id)
|
||||
|
||||
|
||||
def create_call_log(call_payload):
|
||||
|
||||
@@ -334,8 +334,6 @@ has_website_permission = {
|
||||
"Patient": "erpnext.healthcare.web_form.personal_details.personal_details.has_website_permission",
|
||||
}
|
||||
|
||||
dump_report_map = "erpnext.startup.report_data_map.data_map"
|
||||
|
||||
before_tests = "erpnext.setup.utils.before_tests"
|
||||
|
||||
standard_queries = {
|
||||
@@ -478,7 +476,6 @@ scheduler_events = {
|
||||
],
|
||||
"hourly": [
|
||||
"erpnext.hr.doctype.daily_work_summary_group.daily_work_summary_group.trigger_emails",
|
||||
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
||||
"erpnext.erpnext_integrations.doctype.amazon_mws_settings.amazon_mws_settings.schedule_get_order_details",
|
||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
||||
"erpnext.projects.doctype.project.project.hourly_reminder",
|
||||
@@ -487,6 +484,7 @@ scheduler_events = {
|
||||
"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
|
||||
],
|
||||
"hourly_long": [
|
||||
"erpnext.accounts.doctype.subscription.subscription.process_all",
|
||||
"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries",
|
||||
"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
|
||||
],
|
||||
@@ -589,6 +587,7 @@ accounting_dimension_doctypes = [
|
||||
"Shipping Rule",
|
||||
"Landed Cost Item",
|
||||
"Asset Value Adjustment",
|
||||
"Asset Repair",
|
||||
"Loyalty Program",
|
||||
"Fee Schedule",
|
||||
"Fee Structure",
|
||||
|
||||
@@ -253,10 +253,12 @@ def get_unmarked_days(employee, month, exclude_holidays=0):
|
||||
start_day = 1
|
||||
end_day = calendar.monthrange(today.year, month_map[month])[1] + 1
|
||||
|
||||
if joining_date and joining_date.month == month_map[month]:
|
||||
if joining_date and joining_date.year == today.year and joining_date.month == month_map[month]:
|
||||
start_day = joining_date.day
|
||||
|
||||
if relieving_date and relieving_date.month == month_map[month]:
|
||||
if (
|
||||
relieving_date and relieving_date.year == today.year and relieving_date.month == month_map[month]
|
||||
):
|
||||
end_day = relieving_date.day + 1
|
||||
|
||||
dates_of_month = [
|
||||
|
||||
@@ -13,6 +13,8 @@ frappe.listview_settings['Attendance'] = {
|
||||
onload: function(list_view) {
|
||||
let me = this;
|
||||
const months = moment.months();
|
||||
const curMonth = moment().format("MMMM");
|
||||
months.splice(months.indexOf(curMonth) + 1);
|
||||
list_view.page.add_inner_button(__("Mark Attendance"), function() {
|
||||
let dialog = new frappe.ui.Dialog({
|
||||
title: __("Mark Attendance"),
|
||||
|
||||
@@ -19,7 +19,7 @@ from erpnext.hr.doctype.attendance.attendance import (
|
||||
mark_attendance,
|
||||
)
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.doctype.leave_application.test_leave_application import get_first_sunday
|
||||
from erpnext.hr.tests.test_utils import get_first_sunday
|
||||
|
||||
test_records = frappe.get_test_records("Attendance")
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"allow_import": 1,
|
||||
"allow_rename": 1,
|
||||
"autoname": "naming_series:",
|
||||
"creation": "2013-03-07 09:04:18",
|
||||
"creation": "2022-02-21 11:54:09.632218",
|
||||
"doctype": "DocType",
|
||||
"document_type": "Setup",
|
||||
"editable_grid": 1,
|
||||
@@ -813,11 +813,12 @@
|
||||
"idx": 24,
|
||||
"image_field": "image",
|
||||
"links": [],
|
||||
"modified": "2022-07-18 20:03:43.188705",
|
||||
"modified": "2022-08-20 13:44:37.088519",
|
||||
"modified_by": "Administrator",
|
||||
"module": "HR",
|
||||
"name": "Employee",
|
||||
"name_case": "Title Case",
|
||||
"naming_rule": "By \"Naming Series\" field",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ from frappe.permissions import (
|
||||
remove_user_permission,
|
||||
set_user_permission_if_allowed,
|
||||
)
|
||||
from frappe.utils import add_years, cstr, getdate, today, validate_email_address
|
||||
from frappe.utils import add_days, add_years, cstr, getdate, today, validate_email_address
|
||||
from frappe.utils.nestedset import NestedSet
|
||||
|
||||
from erpnext.utilities.transaction_base import delete_events
|
||||
@@ -64,6 +64,8 @@ class Employee(NestedSet):
|
||||
if existing_user_id:
|
||||
remove_user_permission("Employee", self.name, existing_user_id)
|
||||
|
||||
self.update_to_date_in_work_history()
|
||||
|
||||
def after_rename(self, old, new, merge):
|
||||
self.db_set("employee", new)
|
||||
|
||||
@@ -166,6 +168,18 @@ class Employee(NestedSet):
|
||||
user.flags.ignore_permissions = True
|
||||
user.add_roles("Expense Approver")
|
||||
|
||||
def update_to_date_in_work_history(self):
|
||||
if not self.internal_work_history:
|
||||
return
|
||||
|
||||
for idx, row in enumerate(self.internal_work_history):
|
||||
if not row.from_date or idx == 0:
|
||||
continue
|
||||
|
||||
self.internal_work_history[idx - 1].to_date = add_days(row.from_date, -1)
|
||||
|
||||
self.internal_work_history[-1].to_date = None
|
||||
|
||||
def validate_date(self):
|
||||
if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
|
||||
throw(_("Date of Birth cannot be greater than today."))
|
||||
|
||||
@@ -80,12 +80,14 @@ class TestEmployeeTransfer(unittest.TestCase):
|
||||
department = ["Accounts - TC", "Management - TC"]
|
||||
designation = ["Accountant", "Manager"]
|
||||
dt = [getdate("01-10-2021"), getdate()]
|
||||
to_date = [add_days(dt[1], -1), None]
|
||||
|
||||
employee = frappe.get_doc("Employee", employee)
|
||||
for data in employee.internal_work_history:
|
||||
self.assertEqual(data.department, department[count])
|
||||
self.assertEqual(data.designation, designation[count])
|
||||
self.assertEqual(data.from_date, dt[count])
|
||||
self.assertEqual(data.to_date, to_date[count])
|
||||
count = count + 1
|
||||
|
||||
transfer.cancel()
|
||||
@@ -95,6 +97,7 @@ class TestEmployeeTransfer(unittest.TestCase):
|
||||
self.assertEqual(data.designation, designation[0])
|
||||
self.assertEqual(data.department, department[0])
|
||||
self.assertEqual(data.from_date, dt[0])
|
||||
self.assertEqual(data.to_date, None)
|
||||
|
||||
|
||||
def create_company():
|
||||
|
||||
@@ -254,9 +254,11 @@ frappe.ui.form.on("Expense Claim", {
|
||||
}, __("View"));
|
||||
}
|
||||
|
||||
if (frm.doc.docstatus===1 && !cint(frm.doc.is_paid) && cint(frm.doc.grand_total) > 0
|
||||
&& (cint(frm.doc.total_amount_reimbursed) < cint(frm.doc.total_sanctioned_amount))
|
||||
&& frappe.model.can_create("Payment Entry")) {
|
||||
if (
|
||||
frm.doc.docstatus === 1
|
||||
&& frm.doc.status !== "Paid"
|
||||
&& frappe.model.can_create("Payment Entry")
|
||||
) {
|
||||
frm.add_custom_button(__('Payment'),
|
||||
function() { frm.events.make_payment_entry(frm); }, __('Create'));
|
||||
}
|
||||
|
||||
@@ -305,8 +305,9 @@ class ExpenseClaim(AccountsController):
|
||||
|
||||
if self.total_advance_amount:
|
||||
precision = self.precision("total_advance_amount")
|
||||
amount_with_taxes = flt(self.total_sanctioned_amount, precision) + flt(
|
||||
self.total_taxes_and_charges, precision
|
||||
amount_with_taxes = flt(
|
||||
(flt(self.total_sanctioned_amount, precision) + flt(self.total_taxes_and_charges, precision)),
|
||||
precision,
|
||||
)
|
||||
|
||||
if flt(self.total_advance_amount, precision) > amount_with_taxes:
|
||||
|
||||
@@ -94,8 +94,8 @@ class Interview(Document):
|
||||
@frappe.whitelist()
|
||||
def reschedule_interview(self, scheduled_on, from_time, to_time):
|
||||
original_date = self.scheduled_on
|
||||
from_time = self.from_time
|
||||
to_time = self.to_time
|
||||
original_from_time = self.from_time
|
||||
original_to_time = self.to_time
|
||||
|
||||
self.db_set({"scheduled_on": scheduled_on, "from_time": from_time, "to_time": to_time})
|
||||
self.notify_update()
|
||||
@@ -107,7 +107,12 @@ class Interview(Document):
|
||||
recipients=recipients,
|
||||
subject=_("Interview: {0} Rescheduled").format(self.name),
|
||||
message=_("Your Interview session is rescheduled from {0} {1} - {2} to {3} {4} - {5}").format(
|
||||
original_date, from_time, to_time, self.scheduled_on, self.from_time, self.to_time
|
||||
original_date,
|
||||
original_from_time,
|
||||
original_to_time,
|
||||
self.scheduled_on,
|
||||
self.from_time,
|
||||
self.to_time,
|
||||
),
|
||||
reference_doctype=self.doctype,
|
||||
reference_name=self.name,
|
||||
|
||||
@@ -8,7 +8,7 @@ import unittest
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.core.doctype.user_permission.test_user_permission import create_user
|
||||
from frappe.utils import add_days, getdate, nowtime
|
||||
from frappe.utils import add_days, get_time, getdate, nowtime
|
||||
|
||||
from erpnext.hr.doctype.designation.test_designation import create_designation
|
||||
from erpnext.hr.doctype.interview.interview import DuplicateInterviewRoundError
|
||||
@@ -26,18 +26,23 @@ class TestInterview(unittest.TestCase):
|
||||
def test_notification_on_rescheduling(self):
|
||||
job_applicant = create_job_applicant()
|
||||
interview = create_interview_and_dependencies(
|
||||
job_applicant.name, scheduled_on=add_days(getdate(), -4)
|
||||
job_applicant.name,
|
||||
scheduled_on=add_days(getdate(), -4),
|
||||
from_time="10:00:00",
|
||||
to_time="11:00:00",
|
||||
)
|
||||
|
||||
previous_scheduled_date = interview.scheduled_on
|
||||
frappe.db.sql("DELETE FROM `tabEmail Queue`")
|
||||
|
||||
interview.reschedule_interview(
|
||||
add_days(getdate(previous_scheduled_date), 2), from_time=nowtime(), to_time=nowtime()
|
||||
add_days(getdate(previous_scheduled_date), 2), from_time="11:00:00", to_time="12:00:00"
|
||||
)
|
||||
interview.reload()
|
||||
|
||||
self.assertEqual(interview.scheduled_on, add_days(getdate(previous_scheduled_date), 2))
|
||||
self.assertEqual(get_time(interview.from_time), get_time("11:00:00"))
|
||||
self.assertEqual(get_time(interview.to_time), get_time("12:00:00"))
|
||||
|
||||
notification = frappe.get_all(
|
||||
"Email Queue", filters={"message": ("like", "%Your Interview session is rescheduled from%")}
|
||||
|
||||
@@ -52,7 +52,7 @@ frappe.ui.form.on("Leave Application", {
|
||||
make_dashboard: function(frm) {
|
||||
var leave_details;
|
||||
let lwps;
|
||||
if (frm.doc.employee && frm.doc.from_date) {
|
||||
if (frm.doc.employee) {
|
||||
frappe.call({
|
||||
method: "erpnext.hr.doctype.leave_application.leave_application.get_leave_details",
|
||||
async: false,
|
||||
|
||||
@@ -33,6 +33,7 @@ from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import (
|
||||
create_assignment_for_multiple_employees,
|
||||
)
|
||||
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||
from erpnext.hr.tests.test_utils import get_first_sunday
|
||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
|
||||
make_holiday_list,
|
||||
make_leave_application,
|
||||
@@ -1105,23 +1106,6 @@ def allocate_leaves(employee, leave_period, leave_type, new_leaves_allocated, el
|
||||
allocate_leave.submit()
|
||||
|
||||
|
||||
def get_first_sunday(holiday_list, for_date=None):
|
||||
date = for_date or getdate()
|
||||
month_start_date = get_first_day(date)
|
||||
month_end_date = get_last_day(date)
|
||||
first_sunday = frappe.db.sql(
|
||||
"""
|
||||
select holiday_date from `tabHoliday`
|
||||
where parent = %s
|
||||
and holiday_date between %s and %s
|
||||
order by holiday_date
|
||||
""",
|
||||
(holiday_list, month_start_date, month_end_date),
|
||||
)[0][0]
|
||||
|
||||
return first_sunday
|
||||
|
||||
|
||||
def make_policy_assignment(employee, leave_type, leave_period):
|
||||
frappe.delete_doc_if_exists("Leave Type", leave_type, force=1)
|
||||
frappe.get_doc(
|
||||
|
||||
@@ -8,11 +8,11 @@ frappe.ui.form.on(cur_frm.doctype, {
|
||||
};
|
||||
});
|
||||
},
|
||||
onload: function(frm){
|
||||
if(frm.doc.__islocal){
|
||||
if(frm.doctype == "Employee Promotion"){
|
||||
onload: function(frm) {
|
||||
if (frm.doc.__islocal && !frm.doc.amended_from) {
|
||||
if (frm.doctype == "Employee Promotion") {
|
||||
frm.doc.promotion_details = [];
|
||||
}else if (frm.doctype == "Employee Transfer") {
|
||||
} else if (frm.doctype == "Employee Transfer") {
|
||||
frm.doc.transfer_details = [];
|
||||
}
|
||||
}
|
||||
@@ -106,12 +106,12 @@ var render_dynamic_field = function(d, fieldtype, options, fieldname) {
|
||||
|
||||
var add_to_details = function(frm, d, table) {
|
||||
let data = d.data;
|
||||
if(data.fieldname){
|
||||
if(validate_duplicate(frm, table, data.fieldname)){
|
||||
if (data.fieldname) {
|
||||
if (validate_duplicate(frm, table, data.fieldname)) {
|
||||
frappe.show_alert({message:__("Property already added"), indicator:'orange'});
|
||||
return false;
|
||||
}
|
||||
if(data.current == data.new){
|
||||
if (data.current == data.new) {
|
||||
frappe.show_alert({message:__("Nothing to change"), indicator:'orange'});
|
||||
d.get_primary_btn().attr('disabled', false);
|
||||
return false;
|
||||
@@ -123,12 +123,14 @@ var add_to_details = function(frm, d, table) {
|
||||
new: data.new
|
||||
});
|
||||
frm.refresh_field(table);
|
||||
frm.fields_dict[table].grid.wrapper.find(".grid-add-row").hide();
|
||||
|
||||
d.fields_dict.field_html.$wrapper.html("");
|
||||
d.set_value("property", "");
|
||||
d.set_value('current', "");
|
||||
frappe.show_alert({message:__("Added to details"),indicator:'green'});
|
||||
d.data = {};
|
||||
}else {
|
||||
} else {
|
||||
frappe.show_alert({message:__("Value missing"),indicator:'red'});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,13 +9,11 @@ from frappe.utils import add_days, add_months, flt, get_year_ending, get_year_st
|
||||
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
|
||||
from erpnext.hr.doctype.leave_application.test_leave_application import (
|
||||
get_first_sunday,
|
||||
make_allocation_record,
|
||||
)
|
||||
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
|
||||
from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
|
||||
from erpnext.hr.report.employee_leave_balance.employee_leave_balance import execute
|
||||
from erpnext.hr.tests.test_utils import get_first_sunday
|
||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
|
||||
make_holiday_list,
|
||||
make_leave_application,
|
||||
|
||||
@@ -22,9 +22,9 @@ def execute(filters=None):
|
||||
|
||||
def get_columns(leave_types):
|
||||
columns = [
|
||||
_("Employee") + ":Link.Employee:150",
|
||||
_("Employee") + ":Link/Employee:150",
|
||||
_("Employee Name") + "::200",
|
||||
_("Department") + "::150",
|
||||
_("Department") + ":Link/Department:150",
|
||||
]
|
||||
|
||||
for leave_type in leave_types:
|
||||
|
||||
@@ -9,12 +9,10 @@ from frappe.utils import add_days, flt, get_year_ending, get_year_start, getdate
|
||||
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.doctype.holiday_list.test_holiday_list import set_holiday_list
|
||||
from erpnext.hr.doctype.leave_application.test_leave_application import (
|
||||
get_first_sunday,
|
||||
make_allocation_record,
|
||||
)
|
||||
from erpnext.hr.doctype.leave_application.test_leave_application import make_allocation_record
|
||||
from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation
|
||||
from erpnext.hr.report.employee_leave_balance_summary.employee_leave_balance_summary import execute
|
||||
from erpnext.hr.tests.test_utils import get_first_sunday
|
||||
from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
|
||||
make_holiday_list,
|
||||
make_leave_application,
|
||||
|
||||
@@ -181,7 +181,6 @@ def add_data(
|
||||
total_l += 1
|
||||
elif status == "Half Day":
|
||||
total_p += 0.5
|
||||
total_a += 0.5
|
||||
total_l += 0.5
|
||||
elif not status:
|
||||
total_um += 1
|
||||
|
||||
19
erpnext/hr/tests/test_utils.py
Normal file
19
erpnext/hr/tests/test_utils.py
Normal file
@@ -0,0 +1,19 @@
|
||||
import frappe
|
||||
from frappe.utils import get_first_day, get_last_day, getdate
|
||||
|
||||
|
||||
def get_first_sunday(holiday_list="Salary Slip Test Holiday List", for_date=None):
|
||||
date = for_date or getdate()
|
||||
month_start_date = get_first_day(date)
|
||||
month_end_date = get_last_day(date)
|
||||
first_sunday = frappe.db.sql(
|
||||
"""
|
||||
select holiday_date from `tabHoliday`
|
||||
where parent = %s
|
||||
and holiday_date between %s and %s
|
||||
order by holiday_date
|
||||
""",
|
||||
(holiday_list, month_start_date, month_end_date),
|
||||
)[0][0]
|
||||
|
||||
return first_sunday
|
||||
@@ -224,6 +224,7 @@ def delete_employee_work_history(details, employee, date):
|
||||
filters["from_date"] = date
|
||||
if filters:
|
||||
frappe.db.delete("Employee Internal Work History", filters)
|
||||
employee.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"posting_date",
|
||||
"status",
|
||||
"repay_from_salary",
|
||||
"manually_update_paid_amount_in_salary_slip",
|
||||
"section_break_8",
|
||||
"loan_type",
|
||||
"loan_amount",
|
||||
@@ -51,10 +52,10 @@
|
||||
"refund_amount",
|
||||
"debit_adjustment_amount",
|
||||
"credit_adjustment_amount",
|
||||
"is_npa",
|
||||
"column_break_19",
|
||||
"total_interest_payable",
|
||||
"total_amount_paid",
|
||||
"is_npa",
|
||||
"amended_from"
|
||||
],
|
||||
"fields": [
|
||||
@@ -410,16 +411,23 @@
|
||||
"fieldname": "is_npa",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is NPA"
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"default": "0",
|
||||
"depends_on": "repay_from_salary",
|
||||
"fieldname": "manually_update_paid_amount_in_salary_slip",
|
||||
"fieldtype": "Check",
|
||||
"label": "Manually Update Paid Amount in Salary Slip"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-06-30 12:04:13.728880",
|
||||
"modified": "2022-09-13 02:05:25.017190",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan",
|
||||
"naming_rule": "Expression (old style)",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -445,6 +453,5 @@
|
||||
"search_fields": "posting_date",
|
||||
"sort_field": "creation",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ class LoanBalanceAdjustment(AccountsController):
|
||||
loan_account = frappe.db.get_value("Loan", self.loan, "loan_account")
|
||||
remarks = "{} against loan {}".format(self.adjustment_type.capitalize(), self.loan)
|
||||
if self.reference_number:
|
||||
remarks += "with reference no. {}".format(self.reference_number)
|
||||
remarks += " with reference no. {}".format(self.reference_number)
|
||||
|
||||
loan_entry = {
|
||||
"account": loan_account,
|
||||
|
||||
@@ -163,11 +163,11 @@
|
||||
},
|
||||
{
|
||||
"fetch_from": "against_loan.disbursement_account",
|
||||
"fetch_if_empty": 1,
|
||||
"fieldname": "disbursement_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Disbursement Account",
|
||||
"options": "Account",
|
||||
"read_only": 1
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_16",
|
||||
@@ -185,7 +185,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-17 18:23:44.157598",
|
||||
"modified": "2022-08-04 17:16:04.922444",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Loan Management",
|
||||
"name": "Loan Disbursement",
|
||||
|
||||
@@ -209,6 +209,9 @@ def get_disbursal_amount(loan, on_current_security_price=0):
|
||||
"loan_amount",
|
||||
"disbursed_amount",
|
||||
"total_payment",
|
||||
"debit_adjustment_amount",
|
||||
"credit_adjustment_amount",
|
||||
"refund_amount",
|
||||
"total_principal_paid",
|
||||
"total_interest_payable",
|
||||
"status",
|
||||
|
||||
@@ -135,7 +135,11 @@ def calculate_accrual_amount_for_demand_loans(
|
||||
def make_accrual_interest_entry_for_demand_loans(
|
||||
posting_date, process_loan_interest, open_loans=None, loan_type=None, accrual_type="Regular"
|
||||
):
|
||||
query_filters = {"status": ("in", ["Disbursed", "Partially Disbursed"]), "docstatus": 1}
|
||||
query_filters = {
|
||||
"status": ("in", ["Disbursed", "Partially Disbursed"]),
|
||||
"docstatus": 1,
|
||||
"is_term_loan": 0,
|
||||
}
|
||||
|
||||
if loan_type:
|
||||
query_filters.update({"loan_type": loan_type})
|
||||
@@ -147,6 +151,9 @@ def make_accrual_interest_entry_for_demand_loans(
|
||||
"name",
|
||||
"total_payment",
|
||||
"total_amount_paid",
|
||||
"debit_adjustment_amount",
|
||||
"credit_adjustment_amount",
|
||||
"refund_amount",
|
||||
"loan_account",
|
||||
"interest_income_account",
|
||||
"loan_amount",
|
||||
@@ -229,6 +236,7 @@ def get_term_loans(date, term_loan=None, loan_type=None):
|
||||
AND l.is_term_loan =1
|
||||
AND rs.payment_date <= %s
|
||||
AND rs.is_accrued=0 {0}
|
||||
AND rs.principal_amount > 0
|
||||
AND l.status = 'Disbursed'
|
||||
ORDER BY rs.payment_date""".format(
|
||||
condition
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user