mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-23 21:00:38 +00:00
Compare commits
313 Commits
michelleal
...
payments/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9ecd99049 | ||
|
|
c0323890a8 | ||
|
|
19c6bf2f92 | ||
|
|
aa54516444 | ||
|
|
74fb070959 | ||
|
|
7ca9ec14a4 | ||
|
|
3413bad51d | ||
|
|
70ed75a397 | ||
|
|
873571272d | ||
|
|
48b5567af4 | ||
|
|
340e35f209 | ||
|
|
2ae1704d11 | ||
|
|
e93ff9b267 | ||
|
|
efd8f1e978 | ||
|
|
4bd26b845e | ||
|
|
ad02e4112f | ||
|
|
c914ea4adc | ||
|
|
81dc5872c4 | ||
|
|
618e737b0b | ||
|
|
b38b8ded7b | ||
|
|
613830bfba | ||
|
|
d72d858efa | ||
|
|
00b70d7654 | ||
|
|
5b3e0c5543 | ||
|
|
257e13c299 | ||
|
|
eedf22b07a | ||
|
|
c13a147df1 | ||
|
|
a8055a6da9 | ||
|
|
5f25cde788 | ||
|
|
a464bd861b | ||
|
|
0bab6f34c1 | ||
|
|
146ce59b87 | ||
|
|
6fdb8659cc | ||
|
|
ef0682a216 | ||
|
|
9d899f3c4a | ||
|
|
5442487b6c | ||
|
|
38f925b376 | ||
|
|
4da289ec61 | ||
|
|
b7525ca4e5 | ||
|
|
890016695f | ||
|
|
80ace72541 | ||
|
|
4a7cc4da87 | ||
|
|
fb6b960806 | ||
|
|
2fdc56cebf | ||
|
|
07339e2ddf | ||
|
|
418f7e97b9 | ||
|
|
baff301e8a | ||
|
|
861edd3041 | ||
|
|
77ac87494a | ||
|
|
0a5b21d002 | ||
|
|
c50aba8333 | ||
|
|
c595bf684c | ||
|
|
f51e9df921 | ||
|
|
6ebd52bdfe | ||
|
|
aa57a98872 | ||
|
|
f9d1bdd9f9 | ||
|
|
0bdffdfa98 | ||
|
|
f369b583b9 | ||
|
|
f3b91d4d62 | ||
|
|
a84a5b43e2 | ||
|
|
78be5fc276 | ||
|
|
01b345e046 | ||
|
|
e21fcbae6a | ||
|
|
a5314165c1 | ||
|
|
d45e6781c1 | ||
|
|
d4fdada83c | ||
|
|
9b4bb30af2 | ||
|
|
ab2d7f7279 | ||
|
|
9d4ad987e1 | ||
|
|
1d9ed27a89 | ||
|
|
e8f8fb8a8f | ||
|
|
b01ac4d6fd | ||
|
|
272f42eaf2 | ||
|
|
3baffffd1b | ||
|
|
83fe78bcda | ||
|
|
4500f22671 | ||
|
|
00eac65712 | ||
|
|
5d99f17583 | ||
|
|
ac01686ded | ||
|
|
e3008843d1 | ||
|
|
735558d073 | ||
|
|
6a06e26d04 | ||
|
|
4e29654458 | ||
|
|
f1d2138258 | ||
|
|
a07100d5b3 | ||
|
|
d51f9ce815 | ||
|
|
c73ea6bad9 | ||
|
|
7c5b88a4b9 | ||
|
|
598e9c1390 | ||
|
|
bb29fc4c3d | ||
|
|
df3267ad3c | ||
|
|
e9cf8937cd | ||
|
|
663a08e4cd | ||
|
|
758d1606a9 | ||
|
|
c34737f4c6 | ||
|
|
193d13be0d | ||
|
|
5d0c25f5b5 | ||
|
|
1064d7636d | ||
|
|
33e37cfbff | ||
|
|
d3d0af3815 | ||
|
|
6310497403 | ||
|
|
f960aac639 | ||
|
|
ab2b2dc5d4 | ||
|
|
f88d5dcf5c | ||
|
|
520db864b5 | ||
|
|
62873309e5 | ||
|
|
86d3a9ab03 | ||
|
|
d855a8ec1c | ||
|
|
adf1e487e1 | ||
|
|
45804c68f0 | ||
|
|
fb32d2cafb | ||
|
|
ca2fde891e | ||
|
|
a132ca1857 | ||
|
|
a1aaffbebe | ||
|
|
8dcb1ab63b | ||
|
|
6b88777829 | ||
|
|
b3a901b631 | ||
|
|
c9015f7c04 | ||
|
|
27364b7e6b | ||
|
|
10434742e9 | ||
|
|
c64deac4d3 | ||
|
|
92bde71ab1 | ||
|
|
0aae621d0e | ||
|
|
94629e0b1b | ||
|
|
2144e0337d | ||
|
|
06e5a3fe23 | ||
|
|
74b36db24e | ||
|
|
092411b54f | ||
|
|
ef7d9004d5 | ||
|
|
8bdb8e71d4 | ||
|
|
66d7b18204 | ||
|
|
784dec24c8 | ||
|
|
4cd023444a | ||
|
|
ceaa1be729 | ||
|
|
28890fa833 | ||
|
|
6721ae76de | ||
|
|
0d57a7d4a4 | ||
|
|
18bc1df6b7 | ||
|
|
13eb9ea421 | ||
|
|
fd51dd8999 | ||
|
|
cc4afc53e3 | ||
|
|
ce7f6ee71c | ||
|
|
63ca1025bc | ||
|
|
f6371f4a0d | ||
|
|
b64f70d95a | ||
|
|
e7350f18f6 | ||
|
|
2ca0f416a9 | ||
|
|
1efdc04e46 | ||
|
|
b4d22c2936 | ||
|
|
96df19149d | ||
|
|
930286e105 | ||
|
|
f440243b75 | ||
|
|
28df8aa9cf | ||
|
|
fb846ffa12 | ||
|
|
5b9309cf34 | ||
|
|
63ad9f4f86 | ||
|
|
6cbf98294a | ||
|
|
9c82c2b5d3 | ||
|
|
ee9c3e12b4 | ||
|
|
a1183f0165 | ||
|
|
74afa57a9f | ||
|
|
85641fd21c | ||
|
|
c039cfd5c3 | ||
|
|
f48b3c41de | ||
|
|
b7f5012f15 | ||
|
|
0b7657ecf1 | ||
|
|
5c5e2c3ac2 | ||
|
|
6d19aae423 | ||
|
|
3ddb18de41 | ||
|
|
7ceb294aab | ||
|
|
fbfc5b4b1d | ||
|
|
0448ea3daa | ||
|
|
9cfbd8c8e3 | ||
|
|
2238ca55a5 | ||
|
|
6e84fc5143 | ||
|
|
5404b21c7d | ||
|
|
1511280464 | ||
|
|
949c0cab72 | ||
|
|
4d41e1538d | ||
|
|
5e8700f0b4 | ||
|
|
73c4d8a8e6 | ||
|
|
5ae756a93b | ||
|
|
c9d474bc87 | ||
|
|
226095c6b8 | ||
|
|
200a43e41f | ||
|
|
834d8c64c2 | ||
|
|
d32fdaaff5 | ||
|
|
63b772a7a3 | ||
|
|
2142f8b4a3 | ||
|
|
fa20320f39 | ||
|
|
0e52ef4fd9 | ||
|
|
5f3963b3e6 | ||
|
|
4db7054542 | ||
|
|
97c2853497 | ||
|
|
48513b1087 | ||
|
|
505cd95150 | ||
|
|
c08cd938bc | ||
|
|
11f6e570a5 | ||
|
|
77e5787036 | ||
|
|
60aaeefd70 | ||
|
|
af0ae930ca | ||
|
|
9d4165b9f9 | ||
|
|
1bdeddcb04 | ||
|
|
48c3b0d094 | ||
|
|
1721175a20 | ||
|
|
13bb48434f | ||
|
|
32c4fab14f | ||
|
|
c1a5a0510e | ||
|
|
5011b76de9 | ||
|
|
4c0c0d3eb3 | ||
|
|
164498bafb | ||
|
|
066e935892 | ||
|
|
3dfee0ae43 | ||
|
|
85c47b9a5a | ||
|
|
6f8eb4d9ed | ||
|
|
3231bedfb3 | ||
|
|
e79b57975e | ||
|
|
c1812f74e8 | ||
|
|
0db82ec93a | ||
|
|
ad4991dc47 | ||
|
|
0d42793397 | ||
|
|
6fa29376a0 | ||
|
|
2fd8de2f76 | ||
|
|
9e8f25d020 | ||
|
|
144cad81a4 | ||
|
|
da2286802a | ||
|
|
3fb0858321 | ||
|
|
ad5aadde0f | ||
|
|
88a5824e31 | ||
|
|
c085b6159b | ||
|
|
8aadc18ee8 | ||
|
|
f6933111ac | ||
|
|
7eb811c71a | ||
|
|
fd680380bb | ||
|
|
54ed6a9868 | ||
|
|
f65292612b | ||
|
|
616ce2dbec | ||
|
|
af11480b7f | ||
|
|
44261223d5 | ||
|
|
d8b6767697 | ||
|
|
3a11577411 | ||
|
|
1ef890db73 | ||
|
|
4dc0d3a003 | ||
|
|
487d0a55f5 | ||
|
|
2cd9b28e5b | ||
|
|
6c2700cd8f | ||
|
|
90880c8c01 | ||
|
|
0cf478c4c2 | ||
|
|
4cc5cd5a71 | ||
|
|
245549951d | ||
|
|
4b7f1f2d67 | ||
|
|
18b4b09371 | ||
|
|
5280132423 | ||
|
|
1d52ef7afe | ||
|
|
4abdb28a1a | ||
|
|
61bc0925d5 | ||
|
|
0a4db47c69 | ||
|
|
f194615057 | ||
|
|
5a68e918cd | ||
|
|
17ba0cff44 | ||
|
|
61a57aaed5 | ||
|
|
deccb007c1 | ||
|
|
2a999e8f58 | ||
|
|
bf39e3ab32 | ||
|
|
534243a4c8 | ||
|
|
d8de6cb708 | ||
|
|
14c40d17d3 | ||
|
|
aa652c8e15 | ||
|
|
54d6d346c8 | ||
|
|
4070359174 | ||
|
|
908ca511e7 | ||
|
|
2ee70c3968 | ||
|
|
2c6938120e | ||
|
|
f913c0fde1 | ||
|
|
d6d0a1b38d | ||
|
|
2028e52c33 | ||
|
|
b65072cd98 | ||
|
|
2312757a1d | ||
|
|
1dcd1b2a23 | ||
|
|
9cdca0d662 | ||
|
|
70ff4e7644 | ||
|
|
1d6d383ec8 | ||
|
|
34089ba230 | ||
|
|
38568a3e15 | ||
|
|
8bd84e9d51 | ||
|
|
dc29fb8759 | ||
|
|
29fe8377a1 | ||
|
|
657acab32f | ||
|
|
91ce9fce9b | ||
|
|
7b0dfb2a05 | ||
|
|
12834ccf9a | ||
|
|
42885a81f0 | ||
|
|
f7c0ccce0a | ||
|
|
a1bcc6b418 | ||
|
|
5d0a38dfc7 | ||
|
|
41e395a7e3 | ||
|
|
b94af28587 | ||
|
|
0b27b19c35 | ||
|
|
2533808f1e | ||
|
|
45a6ecbd38 | ||
|
|
ee44022249 | ||
|
|
29d50b770e | ||
|
|
2f63fae31d | ||
|
|
6e2ac09821 | ||
|
|
2851764ed6 | ||
|
|
7bae18aba8 | ||
|
|
f740c94363 | ||
|
|
003a9608dc | ||
|
|
49331e6109 | ||
|
|
5de91cf55e | ||
|
|
18993a97ce | ||
|
|
882227a460 | ||
|
|
39d6df7c7d |
@@ -18,4 +18,4 @@ max_line_length = 110
|
||||
[{*.json}]
|
||||
insert_final_newline = false
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
indent_size = 1
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -14,5 +14,8 @@ __pycache__
|
||||
*~
|
||||
.idea/
|
||||
.vscode/
|
||||
.helix/
|
||||
node_modules/
|
||||
.backportrc.json
|
||||
.backportrc.json
|
||||
# Aider AI Chat
|
||||
.aider*
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
**/setup/setup_wizard/data/uom_data.json,erpnext.gettext.extractors.uom_data.extract
|
||||
**/setup/doctype/incoterm/incoterms.csv,erpnext.gettext.extractors.incoterms.extract
|
||||
**/setup/setup_wizard/data/*.txt,erpnext.gettext.extractors.lines_from_txt_file.extract
|
||||
|
||||
|
@@ -2,6 +2,7 @@ import functools
|
||||
import inspect
|
||||
|
||||
import frappe
|
||||
from frappe.utils.user import is_website_user
|
||||
|
||||
__version__ = "16.0.0-dev"
|
||||
|
||||
@@ -149,3 +150,13 @@ def allow_regional(fn):
|
||||
return frappe.get_attr(overrides[function_path][-1])(*args, **kwargs)
|
||||
|
||||
return caller
|
||||
|
||||
|
||||
def check_app_permission():
|
||||
if frappe.session.user == "Administrator":
|
||||
return True
|
||||
|
||||
if is_website_user():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -200,7 +200,7 @@ class Account(NestedSet):
|
||||
msg = _(
|
||||
"There are ledger entries against this account. Changing {0} to non-{1} in live system will cause incorrect output in 'Accounts {2}' report"
|
||||
).format(
|
||||
frappe.bold("Account Type"), doc_before_save.account_type, doc_before_save.account_type
|
||||
frappe.bold(_("Account Type")), doc_before_save.account_type, doc_before_save.account_type
|
||||
)
|
||||
frappe.msgprint(msg)
|
||||
self.add_comment("Comment", msg)
|
||||
|
||||
@@ -38,6 +38,11 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
frm.add_custom_button(__("Get Payment Entries"), () => frm.trigger("get_payment_entries"));
|
||||
|
||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
|
||||
if (frm.doc.payment_entries.length) {
|
||||
frm.add_custom_button(__("Update Clearance Date"), () => frm.trigger("update_clearance_date"));
|
||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "default");
|
||||
frm.change_custom_button_type(__("Update Clearance Date"), null, "primary");
|
||||
}
|
||||
},
|
||||
|
||||
update_clearance_date: function (frm) {
|
||||
@@ -45,13 +50,7 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
method: "update_clearance_date",
|
||||
doc: frm.doc,
|
||||
callback: function (r, rt) {
|
||||
frm.refresh_field("payment_entries");
|
||||
frm.refresh_fields();
|
||||
|
||||
if (!frm.doc.payment_entries.length) {
|
||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "primary");
|
||||
frm.change_custom_button_type(__("Update Clearance Date"), null, "default");
|
||||
}
|
||||
frm.refresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -60,17 +59,8 @@ frappe.ui.form.on("Bank Clearance", {
|
||||
return frappe.call({
|
||||
method: "get_payment_entries",
|
||||
doc: frm.doc,
|
||||
callback: function (r, rt) {
|
||||
frm.refresh_field("payment_entries");
|
||||
|
||||
if (frm.doc.payment_entries.length) {
|
||||
frm.add_custom_button(__("Update Clearance Date"), () =>
|
||||
frm.trigger("update_clearance_date")
|
||||
);
|
||||
|
||||
frm.change_custom_button_type(__("Get Payment Entries"), null, "default");
|
||||
frm.change_custom_button_type(__("Update Clearance Date"), null, "primary");
|
||||
}
|
||||
callback: function () {
|
||||
frm.refresh();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.document import Document
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, fmt_money, getdate
|
||||
from frappe.utils import flt, fmt_money, get_link_to_form, getdate
|
||||
from pypika import Order
|
||||
|
||||
import erpnext
|
||||
@@ -96,8 +96,11 @@ class BankClearance(Document):
|
||||
|
||||
if d.cheque_date and getdate(d.clearance_date) < getdate(d.cheque_date):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Clearance date {1} cannot be before Cheque Date {2}").format(
|
||||
d.idx, d.clearance_date, d.cheque_date
|
||||
_("Row #{0}: For {1} Clearance date {2} cannot be before Cheque Date {3}").format(
|
||||
d.idx,
|
||||
get_link_to_form(d.payment_document, d.payment_entry),
|
||||
d.clearance_date,
|
||||
d.cheque_date,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -47,9 +47,11 @@ def validate_columns(data):
|
||||
|
||||
no_of_columns = max([len(d) for d in data])
|
||||
|
||||
if no_of_columns > 8:
|
||||
if no_of_columns != 8:
|
||||
frappe.throw(
|
||||
_("More columns found than expected. Please compare the uploaded file with standard template"),
|
||||
_(
|
||||
"Columns are not according to template. Please compare the uploaded file with standard template"
|
||||
),
|
||||
title=(_("Wrong Template")),
|
||||
)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"customer",
|
||||
"column_break_4",
|
||||
"coupon_code",
|
||||
"from_external_ecomm_platform",
|
||||
"pricing_rule",
|
||||
"uses",
|
||||
"valid_from",
|
||||
@@ -61,11 +62,12 @@
|
||||
"unique": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval !doc.from_external_ecomm_platform",
|
||||
"fieldname": "pricing_rule",
|
||||
"fieldtype": "Link",
|
||||
"label": "Pricing Rule",
|
||||
"options": "Pricing Rule",
|
||||
"reqd": 1
|
||||
"mandatory_depends_on": "eval: !doc.from_external_ecomm_platform",
|
||||
"options": "Pricing Rule"
|
||||
},
|
||||
{
|
||||
"fieldname": "uses",
|
||||
@@ -114,13 +116,20 @@
|
||||
"options": "Coupon Code",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "from_external_ecomm_platform",
|
||||
"fieldtype": "Check",
|
||||
"label": "From External Ecomm Platform"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:47.220931",
|
||||
"modified": "2024-06-28 06:17:01.833399",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Coupon Code",
|
||||
"naming_rule": "By fieldname",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -177,4 +186,4 @@
|
||||
"states": [],
|
||||
"title_field": "coupon_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ class CouponCode(Document):
|
||||
coupon_type: DF.Literal["Promotional", "Gift Card"]
|
||||
customer: DF.Link | None
|
||||
description: DF.TextEditor | None
|
||||
from_external_ecomm_platform: DF.Check
|
||||
maximum_use: DF.Int
|
||||
pricing_rule: DF.Link
|
||||
pricing_rule: DF.Link | None
|
||||
used: DF.Int
|
||||
valid_from: DF.Date | None
|
||||
valid_upto: DF.Date | None
|
||||
|
||||
@@ -142,3 +142,39 @@ class TestCouponCode(unittest.TestCase):
|
||||
|
||||
so.submit()
|
||||
self.assertEqual(frappe.db.get_value("Coupon Code", "SAVE30", "used"), 1)
|
||||
|
||||
def test_coupon_without_max_use(self):
|
||||
from erpnext.accounts.doctype.pricing_rule.utils import (
|
||||
update_coupon_code_count,
|
||||
validate_coupon_code,
|
||||
)
|
||||
|
||||
coupon = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Coupon Code",
|
||||
"coupon_name": "_Test Coupon Without Max Use",
|
||||
"coupon_code": "TESTUNLIMITED",
|
||||
"from_external_ecomm_platform": 1, # avoids requirement for pricing rule
|
||||
"valid_from": frappe.utils.nowdate(),
|
||||
"maximum_use": 0,
|
||||
"used": 0,
|
||||
}
|
||||
)
|
||||
coupon.insert(ignore_permissions=True)
|
||||
|
||||
# Validate initial state
|
||||
self.assertEqual(coupon.used, 0)
|
||||
self.assertEqual(coupon.maximum_use, 0)
|
||||
|
||||
# Use coupon multiple times
|
||||
for _ in range(5):
|
||||
validate_coupon_code(coupon.name)
|
||||
update_coupon_code_count(coupon.name, "used")
|
||||
coupon.reload()
|
||||
|
||||
# Check that the coupon is still valid and usage count increased
|
||||
self.assertEqual(coupon.used, 5)
|
||||
validate_coupon_code(coupon.name) # This should not raise an error
|
||||
|
||||
# Clean up
|
||||
coupon.delete()
|
||||
|
||||
@@ -85,18 +85,16 @@ frappe.ui.form.on("Exchange Rate Revaluation", {
|
||||
},
|
||||
|
||||
make_jv: function (frm) {
|
||||
let revaluation_journal = null;
|
||||
let zero_balance_journal = null;
|
||||
frappe.call({
|
||||
method: "make_jv_entries",
|
||||
doc: frm.doc,
|
||||
freeze: true,
|
||||
freeze_message: "Making Journal Entries...",
|
||||
freeze_message: __("Creating Journal Entries..."),
|
||||
callback: function (r) {
|
||||
if (r.message) {
|
||||
let response = r.message;
|
||||
if (response["revaluation_jv"] || response["zero_balance_jv"]) {
|
||||
frappe.msgprint(__("Journals have been created"));
|
||||
frappe.msgprint(__("Journal entries have been created"));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -36,7 +36,17 @@ class LoyaltyProgram(Document):
|
||||
to_date: DF.Date | None
|
||||
# end: auto-generated types
|
||||
|
||||
pass
|
||||
def validate(self):
|
||||
self.validate_lowest_tier()
|
||||
|
||||
def validate_lowest_tier(self):
|
||||
tiers = sorted(self.collection_rules, key=lambda x: x.min_spent)
|
||||
if tiers and tiers[0].min_spent != 0:
|
||||
frappe.throw(
|
||||
_(
|
||||
"The lowest tier must have a minimum spent amount of 0. Customers need to be part of a tier as soon as they are enrolled in the program."
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def get_loyalty_details(
|
||||
@@ -79,17 +89,17 @@ def get_loyalty_program_details_with_points(
|
||||
):
|
||||
lp_details = get_loyalty_program_details(customer, loyalty_program, company=company, silent=silent)
|
||||
loyalty_program = frappe.get_doc("Loyalty Program", loyalty_program)
|
||||
lp_details.update(
|
||||
get_loyalty_details(customer, loyalty_program.name, expiry_date, company, include_expired_entry)
|
||||
loyalty_details = get_loyalty_details(
|
||||
customer, loyalty_program.name, expiry_date, company, include_expired_entry
|
||||
)
|
||||
lp_details.update(loyalty_details)
|
||||
|
||||
tier_spent_level = sorted(
|
||||
[d.as_dict() for d in loyalty_program.collection_rules],
|
||||
key=lambda rule: rule.min_spent,
|
||||
reverse=True,
|
||||
)
|
||||
for i, d in enumerate(tier_spent_level):
|
||||
if i == 0 or (lp_details.total_spent + current_transaction_amount) <= d.min_spent:
|
||||
if i == 0 or (lp_details.total_spent + current_transaction_amount) >= d.min_spent:
|
||||
lp_details.tier_name = d.tier_name
|
||||
lp_details.collection_factor = d.collection_factor
|
||||
else:
|
||||
@@ -173,6 +183,8 @@ def validate_loyalty_points(ref_doc, points_to_redeem):
|
||||
|
||||
if not ref_doc.loyalty_amount and ref_doc.loyalty_amount != loyalty_amount:
|
||||
ref_doc.loyalty_amount = loyalty_amount
|
||||
if not ref_doc.loyalty_points and ref_doc.loyalty_points != points_to_redeem:
|
||||
ref_doc.loyalty_points = points_to_redeem
|
||||
|
||||
if ref_doc.doctype == "Sales Invoice":
|
||||
ref_doc.loyalty_program = loyalty_program
|
||||
|
||||
@@ -7,6 +7,7 @@ import frappe
|
||||
from frappe.utils import cint, flt, getdate, today
|
||||
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import (
|
||||
get_loyalty_details,
|
||||
get_loyalty_program_details_with_points,
|
||||
)
|
||||
from erpnext.accounts.party import get_dashboard_info
|
||||
@@ -38,6 +39,7 @@ class TestLoyaltyProgram(unittest.TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(si_original.get("loyalty_program"), customer.loyalty_program)
|
||||
self.assertEqual(lpe.get("loyalty_program_tier"), "Bronce") # is always in the first tier
|
||||
self.assertEqual(lpe.get("loyalty_program_tier"), customer.loyalty_program_tier)
|
||||
self.assertEqual(lpe.loyalty_points, earned_points)
|
||||
|
||||
@@ -79,6 +81,7 @@ class TestLoyaltyProgram(unittest.TestCase):
|
||||
si_original = create_sales_invoice_record()
|
||||
si_original.insert()
|
||||
si_original.submit()
|
||||
customer.reload()
|
||||
|
||||
earned_points = get_points_earned(si_original)
|
||||
|
||||
@@ -101,8 +104,8 @@ class TestLoyaltyProgram(unittest.TestCase):
|
||||
si_redeem.loyalty_points = earned_points
|
||||
si_redeem.insert()
|
||||
si_redeem.submit()
|
||||
customer.reload()
|
||||
|
||||
customer = frappe.get_doc("Customer", {"customer_name": "Test Loyalty Customer"})
|
||||
earned_after_redemption = get_points_earned(si_redeem)
|
||||
|
||||
lpe_redeem = frappe.get_doc(
|
||||
@@ -197,6 +200,70 @@ class TestLoyaltyProgram(unittest.TestCase):
|
||||
for d in company_wise_info:
|
||||
self.assertTrue(d.get("loyalty_points"))
|
||||
|
||||
@unittest.mock.patch("erpnext.accounts.doctype.loyalty_program.loyalty_program.get_loyalty_details")
|
||||
def test_tier_selection(self, mock_get_loyalty_details):
|
||||
# Create a new loyalty program with multiple tiers
|
||||
loyalty_program = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Loyalty Program",
|
||||
"loyalty_program_name": "Test Tier Selection",
|
||||
"auto_opt_in": 1,
|
||||
"from_date": today(),
|
||||
"loyalty_program_type": "Multiple Tier Program",
|
||||
"conversion_factor": 1,
|
||||
"expiry_duration": 10,
|
||||
"company": "_Test Company",
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Loyalty - _TC",
|
||||
"collection_rules": [
|
||||
{"tier_name": "Gold", "collection_factor": 1000, "min_spent": 20000},
|
||||
{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000},
|
||||
{"tier_name": "Bronze", "collection_factor": 1000, "min_spent": 0},
|
||||
],
|
||||
}
|
||||
)
|
||||
loyalty_program.insert()
|
||||
|
||||
# Test cases with different total_spent and current_transaction_amount combinations
|
||||
test_cases = [
|
||||
(0, 6000, "Bronze"),
|
||||
(0, 15000, "Silver"),
|
||||
(0, 25000, "Gold"),
|
||||
(4000, 500, "Bronze"),
|
||||
(8000, 3000, "Silver"),
|
||||
(18000, 3000, "Gold"),
|
||||
(22000, 5000, "Gold"),
|
||||
]
|
||||
|
||||
for total_spent, current_transaction_amount, expected_tier in test_cases:
|
||||
with self.subTest(total_spent=total_spent, current_transaction_amount=current_transaction_amount):
|
||||
# Mock the get_loyalty_details function to update the total_spent
|
||||
def side_effect(*args, **kwargs):
|
||||
result = get_loyalty_details(*args, **kwargs)
|
||||
result.update({"total_spent": total_spent})
|
||||
return result
|
||||
|
||||
mock_get_loyalty_details.side_effect = side_effect
|
||||
|
||||
lp_details = get_loyalty_program_details_with_points(
|
||||
"Test Loyalty Customer",
|
||||
loyalty_program=loyalty_program.name,
|
||||
company="_Test Company",
|
||||
current_transaction_amount=current_transaction_amount,
|
||||
)
|
||||
|
||||
# Get the selected tier based on the current implementation
|
||||
selected_tier = lp_details.tier_name
|
||||
|
||||
self.assertEqual(
|
||||
selected_tier,
|
||||
expected_tier,
|
||||
f"Expected tier {expected_tier} for total_spent {total_spent} and current_transaction_amount {current_transaction_amount}, but got {selected_tier}",
|
||||
)
|
||||
|
||||
# Clean up
|
||||
loyalty_program.delete()
|
||||
|
||||
|
||||
def get_points_earned(self):
|
||||
def get_returned_amount():
|
||||
@@ -285,7 +352,7 @@ def create_records():
|
||||
"company": "_Test Company",
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Loyalty - _TC",
|
||||
"collection_rules": [{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 1000}],
|
||||
"collection_rules": [{"tier_name": "Bronce", "collection_factor": 1000, "min_spent": 0}],
|
||||
}
|
||||
).insert()
|
||||
|
||||
@@ -316,6 +383,7 @@ def create_records():
|
||||
"cost_center": "Main - _TC",
|
||||
"expense_account": "Loyalty - _TC",
|
||||
"collection_rules": [
|
||||
{"tier_name": "Bronze", "collection_factor": 1000, "min_spent": 0},
|
||||
{"tier_name": "Silver", "collection_factor": 1000, "min_spent": 10000},
|
||||
{"tier_name": "Gold", "collection_factor": 1000, "min_spent": 19000},
|
||||
],
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
{
|
||||
"fieldname": "min_spent",
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "Minimum Total Spent"
|
||||
},
|
||||
{
|
||||
@@ -37,7 +38,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:03.536071",
|
||||
"modified": "2024-09-05 07:41:25.694041",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Loyalty Program Collection",
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"idx": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:05.597711",
|
||||
"modified": "2024-08-16 19:22:42.942264",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Mode of Payment",
|
||||
@@ -74,5 +74,6 @@
|
||||
"show_name_in_global_search": 1,
|
||||
"sort_field": "creation",
|
||||
"sort_order": "ASC",
|
||||
"states": []
|
||||
"states": [],
|
||||
"translated_doctype": 1
|
||||
}
|
||||
@@ -35,6 +35,11 @@ frappe.ui.form.on("Payment Entry", {
|
||||
var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
|
||||
? ["Bank", "Cash"]
|
||||
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
|
||||
if (frm.doc.party_type == "Shareholder") {
|
||||
account_types.push("Equity");
|
||||
}
|
||||
|
||||
return {
|
||||
filters: {
|
||||
account_type: ["in", account_types],
|
||||
@@ -90,6 +95,9 @@ frappe.ui.form.on("Payment Entry", {
|
||||
var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
|
||||
? ["Bank", "Cash"]
|
||||
: [frappe.boot.party_account_types[frm.doc.party_type]];
|
||||
if (frm.doc.party_type == "Shareholder") {
|
||||
account_types.push("Equity");
|
||||
}
|
||||
return {
|
||||
filters: {
|
||||
account_type: ["in", account_types],
|
||||
@@ -230,7 +238,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
hide_unhide_fields: function (frm) {
|
||||
var company_currency = frm.doc.company
|
||||
? frappe.get_doc(":Company", frm.doc.company).default_currency
|
||||
? frappe.get_doc(":Company", frm.doc.company)?.default_currency
|
||||
: "";
|
||||
|
||||
frm.toggle_display(
|
||||
@@ -297,7 +305,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
|
||||
set_dynamic_labels: function (frm) {
|
||||
var company_currency = frm.doc.company
|
||||
? frappe.get_doc(":Company", frm.doc.company).default_currency
|
||||
? frappe.get_doc(":Company", frm.doc.company)?.default_currency
|
||||
: "";
|
||||
|
||||
frm.set_currency_labels(
|
||||
@@ -412,6 +420,12 @@ frappe.ui.form.on("Payment Entry", {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.employee_query",
|
||||
};
|
||||
} else if (frm.doc.party_type == "Shareholder") {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -644,7 +658,7 @@ frappe.ui.form.on("Payment Entry", {
|
||||
frm.set_value("source_exchange_rate", 1);
|
||||
} else if (frm.doc.paid_from) {
|
||||
if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
|
||||
let company_currency = frappe.get_doc(":Company", frm.doc.company)?.default_currency;
|
||||
frappe.call({
|
||||
method: "erpnext.setup.utils.get_exchange_rate",
|
||||
args: {
|
||||
|
||||
@@ -1838,7 +1838,7 @@ def get_outstanding_reference_documents(args, validate=False):
|
||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
if args.get("party_type") != "Employee" and not args.get("voucher_no"):
|
||||
if args.get("party_type") != "Employee":
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
|
||||
119
erpnext/accounts/doctype/payment_request/payment_gateway_v1.py
Normal file
119
erpnext/accounts/doctype/payment_request/payment_gateway_v1.py
Normal file
@@ -0,0 +1,119 @@
|
||||
"""Compatipility methods for v1 implementations of payment gateways
|
||||
|
||||
"""
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe.utils import deprecated, flt
|
||||
|
||||
from erpnext.utilities import payment_app_import_guard
|
||||
|
||||
|
||||
def _get_payment_controller(*args, **kwargs):
|
||||
with payment_app_import_guard():
|
||||
try:
|
||||
from payments.utils import get_payment_controller
|
||||
except Exception:
|
||||
from payments.utils import get_payment_gateway_controller as get_payment_controller
|
||||
|
||||
return get_payment_controller(*args, **kwargs)
|
||||
|
||||
|
||||
def get_request_amount(self):
|
||||
data_of_completed_requests = frappe.get_all(
|
||||
"Integration Request",
|
||||
filters={
|
||||
"reference_doctype": self.doctype,
|
||||
"reference_docname": self.name,
|
||||
"status": "Completed",
|
||||
},
|
||||
pluck="data",
|
||||
)
|
||||
if not data_of_completed_requests:
|
||||
return self.grand_total
|
||||
request_amounts = sum(json.loads(d).get("request_amount") for d in data_of_completed_requests)
|
||||
return request_amounts
|
||||
|
||||
|
||||
def request_phone_payment(self, controller):
|
||||
request_amount = get_request_amount(self)
|
||||
|
||||
payment_record = dict(
|
||||
reference_doctype="Payment Request",
|
||||
reference_docname=self.name,
|
||||
payment_reference=self.reference_name,
|
||||
request_amount=request_amount,
|
||||
sender=self.email_to,
|
||||
currency=self.currency,
|
||||
payment_gateway=self.payment_gateway,
|
||||
)
|
||||
|
||||
controller.validate_transaction_currency(self.currency)
|
||||
controller.request_for_payment(**payment_record)
|
||||
|
||||
|
||||
def payment_gateway_validation(self, controller):
|
||||
try:
|
||||
if hasattr(controller, "on_payment_request_submission"):
|
||||
return controller.on_payment_request_submission(self)
|
||||
else:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def get_payment_url(self, controller):
|
||||
if self.reference_doctype != "Fees":
|
||||
data = frappe.db.get_value(
|
||||
self.reference_doctype, self.reference_name, ["company", "customer_name"], as_dict=1
|
||||
)
|
||||
else:
|
||||
data = frappe.db.get_value(self.reference_doctype, self.reference_name, ["student_name"], as_dict=1)
|
||||
data.update({"company": frappe.defaults.get_defaults().company})
|
||||
|
||||
controller.validate_transaction_currency(self.currency)
|
||||
|
||||
if hasattr(controller, "validate_minimum_transaction_amount"):
|
||||
controller.validate_minimum_transaction_amount(self.currency, self.grand_total)
|
||||
|
||||
return controller.get_payment_url(
|
||||
**{
|
||||
"amount": flt(self.grand_total, self.precision("grand_total")),
|
||||
"title": data.company.encode("utf-8"),
|
||||
"description": self.subject.encode("utf-8"),
|
||||
"reference_doctype": "Payment Request",
|
||||
"reference_docname": self.name,
|
||||
"payer_email": self.email_to or frappe.session.user,
|
||||
"payer_name": frappe.safe_encode(data.customer_name),
|
||||
"order_id": self.name,
|
||||
"currency": self.currency,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def set_payment_request_url(self, controller):
|
||||
if self.payment_account and self.payment_gateway and payment_gateway_validation(self, controller):
|
||||
self.payment_url = get_payment_url(self, controller)
|
||||
|
||||
|
||||
def v1_gateway_before_submit(self, payment_gateway):
|
||||
try:
|
||||
controller = _get_payment_controller(payment_gateway)
|
||||
except Exception:
|
||||
frappe.warnings.warn(f"{payment_gateway} is not a valid gateway; this is normal during tests.")
|
||||
return False
|
||||
if self.payment_channel == "Phone":
|
||||
request_phone_payment(self, controller)
|
||||
else:
|
||||
set_payment_request_url(self, controller)
|
||||
if not (self.mute_email or self.flags.mute_email):
|
||||
self.send_email()
|
||||
self.make_communication_entry()
|
||||
|
||||
|
||||
def v1_create_subscription(payment_provider, gateway_controller, data):
|
||||
if payment_provider == "stripe":
|
||||
with payment_app_import_guard():
|
||||
from payments.payment_gateways.stripe_integration import create_stripe_subscription
|
||||
|
||||
return create_stripe_subscription(gateway_controller, data)
|
||||
@@ -10,6 +10,7 @@
|
||||
"failed_reason",
|
||||
"column_break_2",
|
||||
"naming_series",
|
||||
"company",
|
||||
"mode_of_payment",
|
||||
"party_details",
|
||||
"party_type",
|
||||
@@ -49,13 +50,14 @@
|
||||
"message",
|
||||
"message_examples",
|
||||
"mute_email",
|
||||
"payment_url",
|
||||
"section_break_7",
|
||||
"payment_gateway",
|
||||
"payment_account",
|
||||
"payment_channel",
|
||||
"payment_order",
|
||||
"amended_from"
|
||||
"amended_from",
|
||||
"column_break_pnyv",
|
||||
"payment_url"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -342,7 +344,7 @@
|
||||
{
|
||||
"fieldname": "payment_url",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"label": "Payment URL",
|
||||
"length": 500,
|
||||
"options": "URL",
|
||||
"read_only": 1
|
||||
@@ -400,13 +402,24 @@
|
||||
"no_copy": 1,
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"label": "Company",
|
||||
"options": "Company",
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_pnyv",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-06-20 13:54:55.245774",
|
||||
"modified": "2024-08-07 16:39:54.288002",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Request",
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
@@ -16,15 +14,10 @@ from erpnext.accounts.doctype.payment_entry.payment_entry import (
|
||||
)
|
||||
from erpnext.accounts.doctype.subscription_plan.subscription_plan import get_plan_rate
|
||||
from erpnext.accounts.party import get_party_account, get_party_bank_account
|
||||
from erpnext.accounts.utils import get_account_currency
|
||||
from erpnext.accounts.utils import get_account_currency, get_currency_precision
|
||||
from erpnext.utilities import payment_app_import_guard
|
||||
|
||||
|
||||
def _get_payment_gateway_controller(*args, **kwargs):
|
||||
with payment_app_import_guard():
|
||||
from payments.utils import get_payment_gateway_controller
|
||||
|
||||
return get_payment_gateway_controller(*args, **kwargs)
|
||||
from .payment_gateway_v1 import v1_create_subscription, v1_gateway_before_submit
|
||||
|
||||
|
||||
class PaymentRequest(Document):
|
||||
@@ -85,6 +78,7 @@ class PaymentRequest(Document):
|
||||
subscription_plans: DF.Table[SubscriptionPlanDetail]
|
||||
swift_number: DF.ReadOnly | None
|
||||
transaction_date: DF.Date | None
|
||||
company: DF.Link | None
|
||||
# end: auto-generated types
|
||||
|
||||
def validate(self):
|
||||
@@ -164,48 +158,8 @@ class PaymentRequest(Document):
|
||||
elif self.payment_request_type == "Inward":
|
||||
self.status = "Requested"
|
||||
|
||||
if self.payment_request_type == "Inward":
|
||||
if self.payment_channel == "Phone":
|
||||
self.request_phone_payment()
|
||||
else:
|
||||
self.set_payment_request_url()
|
||||
if not (self.mute_email or self.flags.mute_email):
|
||||
self.send_email()
|
||||
self.make_communication_entry()
|
||||
|
||||
def request_phone_payment(self):
|
||||
controller = _get_payment_gateway_controller(self.payment_gateway)
|
||||
request_amount = self.get_request_amount()
|
||||
|
||||
payment_record = dict(
|
||||
reference_doctype="Payment Request",
|
||||
reference_docname=self.name,
|
||||
payment_reference=self.reference_name,
|
||||
request_amount=request_amount,
|
||||
sender=self.email_to,
|
||||
currency=self.currency,
|
||||
payment_gateway=self.payment_gateway,
|
||||
)
|
||||
|
||||
controller.validate_transaction_currency(self.currency)
|
||||
controller.request_for_payment(**payment_record)
|
||||
|
||||
def get_request_amount(self):
|
||||
data_of_completed_requests = frappe.get_all(
|
||||
"Integration Request",
|
||||
filters={
|
||||
"reference_doctype": self.doctype,
|
||||
"reference_docname": self.name,
|
||||
"status": "Completed",
|
||||
},
|
||||
pluck="data",
|
||||
)
|
||||
|
||||
if not data_of_completed_requests:
|
||||
return self.grand_total
|
||||
|
||||
request_amounts = sum(json.loads(d).get("request_amount") for d in data_of_completed_requests)
|
||||
return request_amounts
|
||||
if self.payment_request_type == "Inward" and self.payment_gateway:
|
||||
return v1_gateway_before_submit(self, self.payment_gateway)
|
||||
|
||||
def on_cancel(self):
|
||||
self.check_if_payment_entry_exists()
|
||||
@@ -219,51 +173,6 @@ class PaymentRequest(Document):
|
||||
si = si.insert(ignore_permissions=True)
|
||||
si.submit()
|
||||
|
||||
def payment_gateway_validation(self):
|
||||
try:
|
||||
controller = _get_payment_gateway_controller(self.payment_gateway)
|
||||
if hasattr(controller, "on_payment_request_submission"):
|
||||
return controller.on_payment_request_submission(self)
|
||||
else:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def set_payment_request_url(self):
|
||||
if self.payment_account and self.payment_gateway and self.payment_gateway_validation():
|
||||
self.payment_url = self.get_payment_url()
|
||||
|
||||
def get_payment_url(self):
|
||||
if self.reference_doctype != "Fees":
|
||||
data = frappe.db.get_value(
|
||||
self.reference_doctype, self.reference_name, ["company", "customer_name"], as_dict=1
|
||||
)
|
||||
else:
|
||||
data = frappe.db.get_value(
|
||||
self.reference_doctype, self.reference_name, ["student_name"], as_dict=1
|
||||
)
|
||||
data.update({"company": frappe.defaults.get_defaults().company})
|
||||
|
||||
controller = _get_payment_gateway_controller(self.payment_gateway)
|
||||
controller.validate_transaction_currency(self.currency)
|
||||
|
||||
if hasattr(controller, "validate_minimum_transaction_amount"):
|
||||
controller.validate_minimum_transaction_amount(self.currency, self.grand_total)
|
||||
|
||||
return controller.get_payment_url(
|
||||
**{
|
||||
"amount": flt(self.grand_total, self.precision("grand_total")),
|
||||
"title": data.company.encode("utf-8"),
|
||||
"description": self.subject.encode("utf-8"),
|
||||
"reference_doctype": "Payment Request",
|
||||
"reference_docname": self.name,
|
||||
"payer_email": self.email_to or frappe.session.user,
|
||||
"payer_name": frappe.safe_encode(data.customer_name),
|
||||
"order_id": self.name,
|
||||
"currency": self.currency,
|
||||
}
|
||||
)
|
||||
|
||||
def set_as_paid(self):
|
||||
if self.payment_channel == "Phone":
|
||||
self.db_set("status", "Paid")
|
||||
@@ -331,6 +240,17 @@ class PaymentRequest(Document):
|
||||
payment_entry.received_amount = amount
|
||||
payment_entry.get("references")[0].allocated_amount = amount
|
||||
|
||||
# Update 'Paid Amount' on Forex transactions
|
||||
if self.currency != ref_doc.company_currency:
|
||||
if (
|
||||
self.payment_request_type == "Outward"
|
||||
and payment_entry.paid_from_account_currency == ref_doc.company_currency
|
||||
and payment_entry.paid_from_account_currency != payment_entry.paid_to_account_currency
|
||||
):
|
||||
payment_entry.paid_amount = payment_entry.base_paid_amount = (
|
||||
payment_entry.target_exchange_rate * payment_entry.received_amount
|
||||
)
|
||||
|
||||
for dimension in get_accounting_dimensions():
|
||||
payment_entry.update({dimension: self.get(dimension)})
|
||||
|
||||
@@ -407,12 +327,8 @@ class PaymentRequest(Document):
|
||||
)
|
||||
comm.insert(ignore_permissions=True)
|
||||
|
||||
def create_subscription(self, payment_provider, gateway_controller, data):
|
||||
if payment_provider == "stripe":
|
||||
with payment_app_import_guard():
|
||||
from payments.payment_gateways.stripe_integration import create_stripe_subscription
|
||||
|
||||
return create_stripe_subscription(gateway_controller, data)
|
||||
def create_subscription(self, *args, **kwargs):
|
||||
return v1_create_subscription(*args, **kwargs)
|
||||
|
||||
|
||||
@frappe.whitelist(allow_guest=True)
|
||||
@@ -420,7 +336,9 @@ def make_payment_request(**args):
|
||||
"""Make payment request"""
|
||||
|
||||
args = frappe._dict(args)
|
||||
if args.dt not in [
|
||||
ref_doc = args.ref_doc or frappe.get_doc(args.dt, args.dn)
|
||||
|
||||
if ref_doc.doctype not in [
|
||||
"Sales Order",
|
||||
"Purchase Order",
|
||||
"Sales Invoice",
|
||||
@@ -428,22 +346,23 @@ def make_payment_request(**args):
|
||||
"POS Invoice",
|
||||
"Fees",
|
||||
]:
|
||||
frappe.throw(_("Payment Requests cannot be created against: {0}").format(frappe.bold(args.dt)))
|
||||
frappe.throw(
|
||||
_("Payment Requests cannot be created against: {0}").format(frappe.bold(ref_doc.doctype))
|
||||
)
|
||||
|
||||
ref_doc = frappe.get_doc(args.dt, args.dn)
|
||||
gateway_account = get_gateway_details(args) or frappe._dict()
|
||||
|
||||
grand_total = get_amount(ref_doc, gateway_account.get("payment_account"))
|
||||
if not grand_total:
|
||||
frappe.throw(_("Payment Entry is already created"))
|
||||
if args.loyalty_points and args.dt == "Sales Order":
|
||||
|
||||
if args.loyalty_points and ref_doc.doctype == "Sales Order":
|
||||
from erpnext.accounts.doctype.loyalty_program.loyalty_program import validate_loyalty_points
|
||||
|
||||
loyalty_amount = validate_loyalty_points(ref_doc, int(args.loyalty_points))
|
||||
frappe.db.set_value(
|
||||
"Sales Order", args.dn, "loyalty_points", int(args.loyalty_points), update_modified=False
|
||||
)
|
||||
frappe.db.set_value("Sales Order", args.dn, "loyalty_amount", loyalty_amount, update_modified=False)
|
||||
loyalty_amount: Document = validate_loyalty_points(
|
||||
ref_doc, int(args.loyalty_points)
|
||||
) # sets fields on ref_doc
|
||||
loyalty_amount.db_update()
|
||||
grand_total = grand_total - loyalty_amount
|
||||
|
||||
bank_account = (
|
||||
@@ -452,10 +371,10 @@ def make_payment_request(**args):
|
||||
|
||||
draft_payment_request = frappe.db.get_value(
|
||||
"Payment Request",
|
||||
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0},
|
||||
{"reference_doctype": ref_doc.doctype, "reference_name": ref_doc.name, "docstatus": 0},
|
||||
)
|
||||
|
||||
existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
|
||||
existing_payment_request_amount = get_existing_payment_request_amount(ref_doc.doctype, ref_doc.name)
|
||||
|
||||
if existing_payment_request_amount:
|
||||
grand_total -= existing_payment_request_amount
|
||||
@@ -486,8 +405,9 @@ def make_payment_request(**args):
|
||||
"email_to": args.recipient_id or ref_doc.owner,
|
||||
"subject": _("Payment Request for {0}").format(args.dn),
|
||||
"message": gateway_account.get("message") or get_dummy_message(ref_doc),
|
||||
"reference_doctype": args.dt,
|
||||
"reference_name": args.dn,
|
||||
"reference_doctype": ref_doc.doctype,
|
||||
"reference_name": ref_doc.name,
|
||||
"company": ref_doc.get("company"),
|
||||
"party_type": args.get("party_type") or "Customer",
|
||||
"party": args.get("party") or ref_doc.get("customer"),
|
||||
"bank_account": bank_account,
|
||||
@@ -522,7 +442,7 @@ def make_payment_request(**args):
|
||||
if args.order_type == "Shopping Cart":
|
||||
frappe.db.commit()
|
||||
frappe.local.response["type"] = "redirect"
|
||||
frappe.local.response["location"] = pr.get_payment_url()
|
||||
frappe.local.response["location"] = pr.payment_url
|
||||
|
||||
if args.return_doc:
|
||||
return pr
|
||||
@@ -555,7 +475,10 @@ def get_amount(ref_doc, payment_account=None):
|
||||
elif dt == "Fees":
|
||||
grand_total = ref_doc.outstanding_amount
|
||||
|
||||
return grand_total
|
||||
if grand_total > 0:
|
||||
return flt(grand_total, get_currency_precision())
|
||||
else:
|
||||
frappe.throw(_("Payment Entry is already created"))
|
||||
|
||||
|
||||
def get_existing_payment_request_amount(ref_dt, ref_dn):
|
||||
|
||||
@@ -5,10 +5,12 @@ import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
|
||||
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
@@ -53,7 +55,7 @@ payment_method = [
|
||||
]
|
||||
|
||||
|
||||
class TestPaymentRequest(unittest.TestCase):
|
||||
class TestPaymentRequest(FrappeTestCase):
|
||||
def setUp(self):
|
||||
for payment_gateway in payment_gateways:
|
||||
if not frappe.db.get_value("Payment Gateway", payment_gateway["gateway"], "name"):
|
||||
@@ -86,6 +88,9 @@ class TestPaymentRequest(unittest.TestCase):
|
||||
self._get_payment_gateway_controller = _get_payment_gateway_controller.start()
|
||||
self.addCleanup(_get_payment_gateway_controller.stop)
|
||||
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
def test_payment_request_linkings(self):
|
||||
so_inr = make_sales_order(currency="INR", do_not_save=True)
|
||||
so_inr.disable_rounded_total = 1
|
||||
@@ -394,3 +399,19 @@ class TestPaymentRequest(unittest.TestCase):
|
||||
# Try to make Payment Request more than SO amount, should give validation
|
||||
pr2.grand_total = 900
|
||||
self.assertRaises(frappe.ValidationError, pr2.save)
|
||||
|
||||
def test_conversion_on_foreign_currency_accounts(self):
|
||||
po_doc = create_purchase_order(supplier="_Test Supplier USD", currency="USD", do_not_submit=1)
|
||||
po_doc.conversion_rate = 80
|
||||
po_doc.items[0].qty = 1
|
||||
po_doc.items[0].rate = 10
|
||||
po_doc.save().submit()
|
||||
|
||||
pr = make_payment_request(dt=po_doc.doctype, dn=po_doc.name, recipient_id="nabin@erpnext.com")
|
||||
pr = frappe.get_doc(pr).save().submit()
|
||||
|
||||
pe = pr.create_payment_entry()
|
||||
self.assertEqual(pe.base_paid_amount, 800)
|
||||
self.assertEqual(pe.paid_amount, 800)
|
||||
self.assertEqual(pe.base_received_amount, 800)
|
||||
self.assertEqual(pe.received_amount, 10)
|
||||
|
||||
@@ -87,19 +87,15 @@ class POSClosingEntry(StatusUpdater):
|
||||
as_dict=1,
|
||||
)[0]
|
||||
if pos_invoice.consolidated_invoice:
|
||||
invalid_row.setdefault("msg", []).append(
|
||||
_("POS Invoice is {}").format(frappe.bold("already consolidated"))
|
||||
)
|
||||
invalid_row.setdefault("msg", []).append(_("POS Invoice is already consolidated"))
|
||||
invalid_rows.append(invalid_row)
|
||||
continue
|
||||
if pos_invoice.pos_profile != self.pos_profile:
|
||||
invalid_row.setdefault("msg", []).append(
|
||||
_("POS Profile doesn't matches {}").format(frappe.bold(self.pos_profile))
|
||||
_("POS Profile doesn't match {}").format(frappe.bold(self.pos_profile))
|
||||
)
|
||||
if pos_invoice.docstatus != 1:
|
||||
invalid_row.setdefault("msg", []).append(
|
||||
_("POS Invoice is not {}").format(frappe.bold("submitted"))
|
||||
)
|
||||
invalid_row.setdefault("msg", []).append(_("POS Invoice is not submitted"))
|
||||
if pos_invoice.owner != self.user:
|
||||
invalid_row.setdefault("msg", []).append(
|
||||
_("POS Invoice isn't created by user {}").format(frappe.bold(self.owner))
|
||||
|
||||
@@ -188,7 +188,7 @@ class POSInvoice(SalesInvoice):
|
||||
def validate(self):
|
||||
if not cint(self.is_pos):
|
||||
frappe.throw(
|
||||
_("POS Invoice should have {} field checked.").format(frappe.bold("Include Payment"))
|
||||
_("POS Invoice should have the field {0} checked.").format(frappe.bold(_("Include Payment")))
|
||||
)
|
||||
|
||||
# run on validate method of selling controller
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"column_break_19",
|
||||
"discount_percentage",
|
||||
"discount_amount",
|
||||
"distributed_discount_amount",
|
||||
"base_rate_with_margin",
|
||||
"section_break1",
|
||||
"rate",
|
||||
@@ -847,6 +848,12 @@
|
||||
{
|
||||
"fieldname": "column_break_ciit",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "distributed_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Distributed Discount Amount",
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
|
||||
@@ -39,6 +39,7 @@ class POSInvoiceItem(SalesInvoiceItem):
|
||||
description: DF.TextEditor
|
||||
discount_amount: DF.Currency
|
||||
discount_percentage: DF.Percent
|
||||
distributed_discount_amount: DF.Currency
|
||||
dn_detail: DF.Data | None
|
||||
enable_deferred_revenue: DF.Check
|
||||
expense_account: DF.Link | None
|
||||
|
||||
@@ -97,16 +97,15 @@ class POSInvoiceMergeLog(Document):
|
||||
return_against_status = frappe.db.get_value("POS Invoice", return_against, "status")
|
||||
if return_against_status != "Consolidated":
|
||||
# if return entry is not getting merged in the current pos closing and if it is not consolidated
|
||||
bold_unconsolidated = frappe.bold("not Consolidated")
|
||||
msg = _("Row #{}: Original Invoice {} of return invoice {} is {}.").format(
|
||||
d.idx, bold_return_against, bold_pos_invoice, bold_unconsolidated
|
||||
)
|
||||
msg = _(
|
||||
"Row #{}: The original Invoice {} of return invoice {} is not consolidated."
|
||||
).format(d.idx, bold_return_against, bold_pos_invoice)
|
||||
msg += " "
|
||||
msg += _(
|
||||
"Original invoice should be consolidated before or along with the return invoice."
|
||||
"The original invoice should be consolidated before or along with the return invoice."
|
||||
)
|
||||
msg += "<br><br>"
|
||||
msg += _("You can add original invoice {} manually to proceed.").format(
|
||||
msg += _("You can add the original invoice {} manually to proceed.").format(
|
||||
bold_return_against
|
||||
)
|
||||
frappe.throw(msg)
|
||||
|
||||
@@ -186,7 +186,8 @@ class PricingRule(Document):
|
||||
if not self.priority:
|
||||
throw(
|
||||
_("As the field {0} is enabled, the field {1} is mandatory.").format(
|
||||
frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority")
|
||||
frappe.bold(_("Apply Discount on Discounted Rate")),
|
||||
frappe.bold(_("Priority")),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -194,7 +195,7 @@ class PricingRule(Document):
|
||||
throw(
|
||||
_(
|
||||
"As the field {0} is enabled, the value of the field {1} should be more than 1."
|
||||
).format(frappe.bold("Apply Discount on Discounted Rate"), frappe.bold("Priority"))
|
||||
).format(frappe.bold(_("Apply Discount on Discounted Rate")), frappe.bold(_("Priority")))
|
||||
)
|
||||
|
||||
def validate_applicable_for_selling_or_buying(self):
|
||||
|
||||
@@ -486,7 +486,7 @@ def get_qty_and_rate_for_other_item(doc, pr_doc, pricing_rules, row_item):
|
||||
continue
|
||||
|
||||
stock_qty = row.get("qty") * (row.get("conversion_factor") or 1.0)
|
||||
amount = stock_qty * (row.get("price_list_rate") or row.get("rate"))
|
||||
amount = stock_qty * (flt(row.get("price_list_rate")) or flt(row.get("rate")))
|
||||
pricing_rules = filter_pricing_rules_for_qty_amount(stock_qty, amount, pricing_rules, row)
|
||||
|
||||
if pricing_rules and pricing_rules[0]:
|
||||
@@ -731,7 +731,7 @@ def validate_coupon_code(coupon_name):
|
||||
elif coupon.valid_upto:
|
||||
if coupon.valid_upto < getdate(today()):
|
||||
frappe.throw(_("Sorry, this coupon code's validity has expired"))
|
||||
elif coupon.used >= coupon.maximum_use:
|
||||
elif coupon.maximum_use and coupon.used >= coupon.maximum_use:
|
||||
frappe.throw(_("Sorry, this coupon code is no longer valid"))
|
||||
|
||||
|
||||
@@ -739,7 +739,10 @@ def update_coupon_code_count(coupon_name, transaction_type):
|
||||
coupon = frappe.get_doc("Coupon Code", coupon_name)
|
||||
if coupon:
|
||||
if transaction_type == "used":
|
||||
if coupon.used < coupon.maximum_use:
|
||||
if not coupon.maximum_use:
|
||||
coupon.used = coupon.used + 1
|
||||
coupon.save(ignore_permissions=True)
|
||||
elif coupon.used < coupon.maximum_use:
|
||||
coupon.used = coupon.used + 1
|
||||
coupon.save(ignore_permissions=True)
|
||||
else:
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
|
||||
<h2 class="text-center">{{ _("STATEMENT OF ACCOUNTS") }}</h2>
|
||||
<div>
|
||||
{% if filters.party[0] == filters.party_name[0] %}
|
||||
<h5 style="float: left;">{{ _("Customer: ") }} <b>{{ filters.party_name[0] }}</b></h5>
|
||||
@@ -22,11 +22,11 @@
|
||||
<h5 style="float: left; margin-left:15px">{{ _("Customer Name: ") }} <b>{{filters.party_name[0] }}</b></h5>
|
||||
{% endif %}
|
||||
<h5 style="float: right;">
|
||||
{{ _("Date: ") }}
|
||||
<b>{{ frappe.format(filters.from_date, 'Date')}}
|
||||
{{ _("to") }}
|
||||
{{ frappe.format(filters.to_date, 'Date')}}</b>
|
||||
</h5>
|
||||
{{ _("Date: {0} to {1}").format(
|
||||
frappe.format(filters.from_date, "Date"),
|
||||
frappe.format(filters.to_date, 'Date')
|
||||
) }}
|
||||
</h5>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<br>
|
||||
{% endif %}
|
||||
|
||||
<br>{{ _("Remarks") }}: {{ row.remarks }}
|
||||
<br>{{ _("Remarks:") }} {{ row.remarks }}
|
||||
{% if row.bill_no %}
|
||||
<br>{{ _("Supplier Invoice No") }}: {{ row.bill_no }}
|
||||
{% endif %}
|
||||
@@ -83,17 +83,20 @@
|
||||
</table>
|
||||
<br>
|
||||
{% if ageing %}
|
||||
<h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
|
||||
{{ _("up to " ) }} {{ frappe.format(filters.to_date, 'Date')}}
|
||||
<h4 class="text-center">
|
||||
{{ _("Ageing Report based on {0} up to {1}").format(
|
||||
ageing.ageing_based_on,
|
||||
frappe.format(filters.to_date, "Date")
|
||||
) }}
|
||||
</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 20%">0 - 30 Days</th>
|
||||
<th style="width: 20%">30 - 60 Days</th>
|
||||
<th style="width: 20%">60 - 90 Days</th>
|
||||
<th style="width: 20%">90 - 120 Days</th>
|
||||
<th style="width: 20%">Above 120 Days</th>
|
||||
<th style="width: 20%">{{ _("0 - 30 Days") }}</th>
|
||||
<th style="width: 20%">{{ _("30 - 60 Days") }}</th>
|
||||
<th style="width: 20%">{{ _("60 - 90 Days") }}</th>
|
||||
<th style="width: 20%">{{ _("90 - 120 Days") }}</th>
|
||||
<th style="width: 20%">{{ _("Above 120 Days") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"cost_center",
|
||||
"territory",
|
||||
"ignore_exchange_rate_revaluation_journals",
|
||||
"ignore_cr_dr_notes",
|
||||
"column_break_14",
|
||||
"to_date",
|
||||
"finance_book",
|
||||
@@ -383,10 +384,16 @@
|
||||
"fieldname": "ignore_exchange_rate_revaluation_journals",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Exchange Rate Revaluation Journals"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "ignore_cr_dr_notes",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore System Generated Credit / Debit Notes"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:10:19.075913",
|
||||
"modified": "2024-08-13 10:41:18.381165",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Process Statement Of Accounts",
|
||||
|
||||
@@ -54,6 +54,7 @@ class ProcessStatementOfAccounts(Document):
|
||||
frequency: DF.Literal["Weekly", "Monthly", "Quarterly"]
|
||||
from_date: DF.Date | None
|
||||
group_by: DF.Literal["", "Group by Voucher", "Group by Voucher (Consolidated)"]
|
||||
ignore_cr_dr_notes: DF.Check
|
||||
ignore_exchange_rate_revaluation_journals: DF.Check
|
||||
include_ageing: DF.Check
|
||||
include_break: DF.Check
|
||||
@@ -133,6 +134,9 @@ def get_statement_dict(doc, get_statement_dict=False):
|
||||
if doc.ignore_exchange_rate_revaluation_journals:
|
||||
filters.update({"ignore_err": True})
|
||||
|
||||
if doc.ignore_cr_dr_notes:
|
||||
filters.update({"ignore_cr_dr_notes": True})
|
||||
|
||||
if doc.report == "General Ledger":
|
||||
filters.update(get_gl_filters(doc, entry, tax_id, presentation_currency))
|
||||
col, res = get_soa(filters)
|
||||
|
||||
@@ -6,228 +6,302 @@
|
||||
.print-format td {
|
||||
vertical-align:middle !important;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
<div id="header-html" class="hidden-pdf">
|
||||
{% if letter_head.content %}
|
||||
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
||||
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="footer-html" class="visible-pdf">
|
||||
{% if letter_head.footer %}
|
||||
<div class="letter-head-footer">
|
||||
<hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
|
||||
{{ letter_head.footer }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
||||
<h4 class="text-center">
|
||||
{{ filters.customer_name }}
|
||||
</h4>
|
||||
<h6 class="text-center">
|
||||
{% if (filters.tax_id) %}
|
||||
{{ _("Tax Id: ") }}{{ filters.tax_id }}
|
||||
{% endif %}
|
||||
</h6>
|
||||
<h5 class="text-center">
|
||||
{{ _(filters.ageing_based_on) }}
|
||||
{{ _("Until") }}
|
||||
{{ frappe.format(filters.report_date, 'Date') }}
|
||||
</h5>
|
||||
|
||||
<div class="clearfix">
|
||||
<div class="pull-left">
|
||||
{% if(filters.payment_terms) %}
|
||||
<strong>{{ _("Payment Terms") }}:</strong> {{ filters.payment_terms }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if(filters.credit_limit) %}
|
||||
<strong>{{ _("Credit Limit") }}:</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% set balance_row = data.slice(-1).pop() %}
|
||||
{% for i in report.columns %}
|
||||
{% if i.fieldname == 'age' %}
|
||||
{% set elem = i %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% set start = report.columns.findIndex(elem) %}
|
||||
{% set range1 = report.columns[start].label %}
|
||||
{% set range2 = report.columns[start+1].label %}
|
||||
{% set range3 = report.columns[start+2].label %}
|
||||
{% set range4 = report.columns[start+3].label %}
|
||||
{% set range5 = report.columns[start+4].label %}
|
||||
{% set range6 = report.columns[start+5].label %}
|
||||
|
||||
{% if(balance_row) %}
|
||||
<table class="table table-bordered table-condensed">
|
||||
<caption class="text-right">(Amount in {{ data[0]["currency"] ~ "" }})</caption>
|
||||
<colgroup>
|
||||
<col style="width: 30mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
</colgroup>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ _(" ") }}</th>
|
||||
<th>{{ _(range1) }}</th>
|
||||
<th>{{ _(range2) }}</th>
|
||||
<th>{{ _(range3) }}</th>
|
||||
<th>{{ _(range4) }}</th>
|
||||
<th>{{ _(range5) }}</th>
|
||||
<th>{{ _(range6) }}</th>
|
||||
<th>{{ _("Total") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ _("Total Outstanding") }}</td>
|
||||
<td class="text-right">
|
||||
{{ format_number(balance_row["age"], null, 2) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
</tr>
|
||||
<td>{{ _("Future Payments") }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<tr class="cvs-footer">
|
||||
<th class="text-left">{{ _("Cheques Required") }}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) }}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
{% endif %}
|
||||
<div id="header-html" class="hidden-pdf">
|
||||
{% if letter_head.content %}
|
||||
<div class="letter-head text-center">{{ letter_head.content }}</div>
|
||||
<hr style="height:2px;border-width:0;color:black;background-color:black;">
|
||||
{% endif %}
|
||||
<table class="table table-bordered">
|
||||
</div>
|
||||
<div id="footer-html" class="visible-pdf">
|
||||
{% if letter_head.footer %}
|
||||
<div class="letter-head-footer">
|
||||
<hr style="border-width:0;color:black;background-color:black;padding-bottom:2px;">
|
||||
{{ letter_head.footer }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<h2 class="text-center" style="margin-top:0">{{ _(report.report_name) }}</h2>
|
||||
<h4 class="text-center">
|
||||
{{ filters.customer_name }}
|
||||
</h4>
|
||||
<h6 class="text-center">
|
||||
{% if (filters.tax_id) %}
|
||||
{{ _("Tax Id: {0}").format(filters.tax_id) }}
|
||||
{% endif %}
|
||||
</h6>
|
||||
<h5 class="text-center">
|
||||
{{ _("{0} until {1}").format(
|
||||
_(filters.ageing_based_on),
|
||||
frappe.format(filters.report_date, 'Date')
|
||||
) }}
|
||||
</h5>
|
||||
|
||||
<div class="clearfix">
|
||||
<div class="pull-left">
|
||||
{% if(filters.payment_terms) %}
|
||||
<strong>{{ _("Payment Terms:") }}</strong> {{ filters.payment_terms }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if(filters.credit_limit) %}
|
||||
<strong>{{ _("Credit Limit:") }}</strong> {{ frappe.utils.fmt_money(filters.credit_limit) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% set balance_row = data.slice(-1).pop() %}
|
||||
{% for i in report.columns %}
|
||||
{% if i.fieldname == 'age' %}
|
||||
{% set elem = i %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% set start = report.columns.findIndex(elem) %}
|
||||
{% set range1 = report.columns[start].label %}
|
||||
{% set range2 = report.columns[start+1].label %}
|
||||
{% set range3 = report.columns[start+2].label %}
|
||||
{% set range4 = report.columns[start+3].label %}
|
||||
{% set range5 = report.columns[start+4].label %}
|
||||
{% set range6 = report.columns[start+5].label %}
|
||||
|
||||
{% if(balance_row) %}
|
||||
<table class="table table-bordered table-condensed">
|
||||
<caption class="text-right">{{ _("Amount in {0}").format(data[0]["currency"] ~ "") }}</caption>
|
||||
<colgroup>
|
||||
<col style="width: 30mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
<col style="width: 18mm;">
|
||||
</colgroup>
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||
<th style="width: 10%">{{ _("Date") }}</th>
|
||||
<th style="width: 4%">{{ _("Age (Days)") }}</th>
|
||||
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<th style="width: 14%">{{ _("Reference") }}</th>
|
||||
<th style="width: 10%">{{ _("Sales Person") }}</th>
|
||||
{% else %}
|
||||
<th style="width: 24%">{{ _("Reference") }}</th>
|
||||
{% endif %}
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<th style="width: 20%">
|
||||
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||
{{ _("Remarks") }}
|
||||
{% else %}
|
||||
{{ _("Party") }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
|
||||
<th style="width: 10%; text-align: right">
|
||||
{% if report.report_name == "Accounts Receivable" %}
|
||||
{{ _('Credit Note') }}
|
||||
{% else %}
|
||||
{{ _('Debit Note') }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Outstanding Amount") }}</th>
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<th style="width: 12%">{{ _("Customer LPO No.") }}</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
|
||||
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
|
||||
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<th style="width: 40%">
|
||||
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||
{{ _("Remarks")}}
|
||||
{% else %}
|
||||
{{ _("Party") }}
|
||||
{% endif %}
|
||||
</th>
|
||||
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
|
||||
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
|
||||
<th style="width: 15%">
|
||||
{% if report.report_name == "Accounts Receivable Summary" %}
|
||||
{{ _('Credit Note Amount') }}
|
||||
{% else %}
|
||||
{{ _('Debit Note Amount') }}
|
||||
{% endif %}
|
||||
</th>
|
||||
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
|
||||
{% endif %}
|
||||
<th>{{ _(" ") }}</th>
|
||||
<th>{{ _(range1) }}</th>
|
||||
<th>{{ _(range2) }}</th>
|
||||
<th>{{ _(range3) }}</th>
|
||||
<th>{{ _(range4) }}</th>
|
||||
<th>{{ _(range5) }}</th>
|
||||
<th>{{ _(range6) }}</th>
|
||||
<th>{{ _("Total") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i in range(data|length) %}
|
||||
<tr>
|
||||
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||
{% if(data[i]["party"]) %}
|
||||
<td>{{ (data[i]["posting_date"]) }}</td>
|
||||
<td style="text-align: right">{{ data[i]["age"] }}</td>
|
||||
<td>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
{{ data[i]["voucher_type"] }}
|
||||
<br>
|
||||
{% endif %}
|
||||
{{ data[i]["voucher_no"] }}
|
||||
</td>
|
||||
<tr>
|
||||
<td>{{ _("Total Outstanding") }}</td>
|
||||
<td class="text-right">
|
||||
{{ format_number(balance_row["age"], null, 2) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range1"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range2"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range3"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range4"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(balance_row["range5"], data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"]), data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
</tr>
|
||||
<td>{{ _("Future Payments") }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row[("future_amount")]), data[data.length-1]["currency"]) }}
|
||||
</td>
|
||||
<tr class="cvs-footer">
|
||||
<th class="text-left">{{ _("Cheques Required") }}</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th class="text-right">
|
||||
{{ frappe.utils.fmt_money(flt(balance_row["outstanding"] - balance_row[("future_amount")]), data[data.length-1]["currency"]) }}</th>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<td>{{ data[i]["sales_person"] }}</td>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||
<th style="width: 10%">{{ _("Date") }}</th>
|
||||
<th style="width: 4%">{{ _("Age (Days)") }}</th>
|
||||
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<th style="width: 14%">{{ _("Reference") }}</th>
|
||||
<th style="width: 10%">{{ _("Sales Person") }}</th>
|
||||
{% else %}
|
||||
<th style="width: 24%">{{ _("Reference") }}</th>
|
||||
{% endif %}
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<th style="width: 20%">
|
||||
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||
{{ _("Remarks") }}
|
||||
{% else %}
|
||||
{{ _("Party") }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Invoiced Amount") }}</th>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Paid Amount") }}</th>
|
||||
<th style="width: 10%; text-align: right">
|
||||
{% if report.report_name == "Accounts Receivable" %}
|
||||
{{ _("Credit Note") }}
|
||||
{% else %}
|
||||
{{ _("Debit Note") }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%; text-align: right">{{ _("Outstanding Amount") }}</th>
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<th style="width: 12%">{{ _("Customer LPO No.") }}</th>
|
||||
{% endif %}
|
||||
<th style="width: 10%">{{ _("Future Payment Ref") }}</th>
|
||||
<th style="width: 10%">{{ _("Future Payment Amount") }}</th>
|
||||
<th style="width: 10%">{{ _("Remaining Balance") }}</th>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<th style="width: 40%">
|
||||
{% if (filters.customer or filters.supplier or filters.customer_name) %}
|
||||
{{ _("Remarks")}}
|
||||
{% else %}
|
||||
{{ _("Party") }}
|
||||
{% endif %}
|
||||
</th>
|
||||
<th style="width: 15%">{{ _("Total Invoiced Amount") }}</th>
|
||||
<th style="width: 15%">{{ _("Total Paid Amount") }}</th>
|
||||
<th style="width: 15%">
|
||||
{% if report.report_name == "Accounts Receivable Summary" %}
|
||||
{{ _("Credit Note Amount") }}
|
||||
{% else %}
|
||||
{{ _("Debit Note Amount") }}
|
||||
{% endif %}
|
||||
</th>
|
||||
<th style="width: 15%">{{ _("Total Outstanding Amount") }}</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for i in range(data|length) %}
|
||||
<tr>
|
||||
{% if(report.report_name == "Accounts Receivable" or report.report_name == "Accounts Payable") %}
|
||||
{% if(data[i]["party"]) %}
|
||||
<td>{{ (data[i]["posting_date"]) }}</td>
|
||||
<td style="text-align: right">{{ data[i]["age"] }}</td>
|
||||
<td>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
{{ data[i]["voucher_type"] }}
|
||||
<br>
|
||||
{% endif %}
|
||||
{{ data[i]["voucher_no"] }}
|
||||
</td>
|
||||
|
||||
{% if not (filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<td>{{ data[i]["sales_person"] }}</td>
|
||||
{% endif %}
|
||||
|
||||
{% if not (filters.show_future_payments) %}
|
||||
<td>
|
||||
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
|
||||
{{ data[i]["party"] }}
|
||||
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["customer_name"] }}
|
||||
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["supplier_name"] }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div>
|
||||
{% if data[i]["remarks"] %}
|
||||
{{ _("Remarks") }}:
|
||||
{{ data[i]["remarks"] }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<td style="text-align: right">
|
||||
{{ data[i]["po_no"] }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
<td></td>
|
||||
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
|
||||
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<td style="text-align: right">
|
||||
{{ data[i]["po_no"] }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if(data[i]["party"] or " ") %}
|
||||
{% if not(data[i]["is_total_row"]) %}
|
||||
<td>
|
||||
{% if(not(filters.customer or filters.supplier or filters.customer_name)) %}
|
||||
{% if(not(filters.customer | filters.supplier)) %}
|
||||
{{ data[i]["party"] }}
|
||||
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["customer_name"] }}
|
||||
@@ -235,132 +309,62 @@
|
||||
<br> {{ data[i]["supplier_name"] }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div>
|
||||
{% if data[i]["remarks"] %}
|
||||
{{ _("Remarks") }}:
|
||||
{{ data[i]["remarks"] }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<br>{{ _("Remarks") }}:
|
||||
{{ data[i]["remarks"] }}
|
||||
</td>
|
||||
{% endif %}
|
||||
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<td style="text-align: right">
|
||||
{{ data[i]["po_no"] }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
{% if(report.report_name == "Accounts Receivable" and filters.show_sales_person) %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
<td></td>
|
||||
<td style="text-align: right"><b>{{ _("Total") }}</b></td>
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["invoiced"], data[i]["currency"]) }}</td>
|
||||
|
||||
{% if not(filters.show_future_payments) %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }} </td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">
|
||||
{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
|
||||
{% if(filters.show_future_payments) %}
|
||||
{% if(report.report_name == "Accounts Receivable") %}
|
||||
<td style="text-align: right">
|
||||
{{ data[i]["po_no"] }}</td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ data[i]["future_ref"] }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["future_amount"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["remaining_balance"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if(data[i]["party"] or " ") %}
|
||||
{% if not(data[i]["is_total_row"]) %}
|
||||
<td>
|
||||
{% if(not(filters.customer | filters.supplier)) %}
|
||||
{{ data[i]["party"] }}
|
||||
{% if(data[i]["customer_name"] and data[i]["customer_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["customer_name"] }}
|
||||
{% elif(data[i]["supplier_name"] != data[i]["party"]) %}
|
||||
<br> {{ data[i]["supplier_name"] }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<br>{{ _("Remarks") }}:
|
||||
{{ data[i]["remarks"] }}
|
||||
</td>
|
||||
{% else %}
|
||||
<td><b>{{ _("Total") }}</b></td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
<td><b>{{ _("Total") }}</b></td>
|
||||
{% endif %}
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["invoiced"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["paid"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["credit_note"], currency=data[i]["currency"]) }}</td>
|
||||
<td style="text-align: right">{{ frappe.utils.fmt_money(data[i]["outstanding"], currency=data[i]["currency"]) }}</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
{% if ageing %}
|
||||
<h4 class="text-center">{{ _("Ageing Report based on ") }} {{ ageing.ageing_based_on }}
|
||||
{{ _("up to " ) }} {{ frappe.format(filters.report_date, 'Date')}}
|
||||
</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25%">0 - 30 Days</th>
|
||||
<th style="width: 25%">30 - 60 Days</th>
|
||||
<th style="width: 25%">60 - 90 Days</th>
|
||||
<th style="width: 25%">90 - 120 Days</th>
|
||||
<th style="width: 20%">Above 120 Days</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if terms_and_conditions %}
|
||||
<div>
|
||||
{{ terms_and_conditions }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-right text-muted">{{ _("Printed On ") }}{{ frappe.utils.now() }}</p>
|
||||
{% endfor %}
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="invoiced"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="paid"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="credit_note"), currency=data[0]["currency"]) }}</b></td>
|
||||
<td style="text-align: right"><b>{{ frappe.utils.fmt_money(data|sum(attribute="outstanding"), currency=data[0]["currency"]) }}</b></td>
|
||||
</tbody>
|
||||
</table>
|
||||
<br>
|
||||
{% if ageing %}
|
||||
<h4 class="text-center">
|
||||
{{ _("Ageing Report based on {0} up to {1}").format(
|
||||
ageing.ageing_based_on,
|
||||
frappe.format(filters.report_date, "Date")
|
||||
) }}
|
||||
</h4>
|
||||
<table class="table table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 25%">{{ _("0 - 30 Days") }}</th>
|
||||
<th style="width: 25%">{{ _("30 - 60 Days") }}</th>
|
||||
<th style="width: 25%">{{ _("60 - 90 Days") }}</th>
|
||||
<th style="width: 25%">{{ _("90 - 120 Days") }}</th>
|
||||
<th style="width: 20%">{{ _("Above 120 Days") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range1, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range2, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range3, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range4, currency=data[0]["currency"]) }}</td>
|
||||
<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% if terms_and_conditions %}
|
||||
<div>
|
||||
{{ terms_and_conditions }}
|
||||
</div>
|
||||
{% endif %}
|
||||
<p class="text-right text-muted">{{ _("Printed on {0}").format(frappe.utils.now()) }}</p>
|
||||
|
||||
@@ -378,16 +378,16 @@ class PurchaseInvoice(BuyingController):
|
||||
if account.report_type != "Balance Sheet":
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please ensure {} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account."
|
||||
).format(frappe.bold("Credit To")),
|
||||
"Please ensure that the {0} account is a Balance Sheet account. You can change the parent account to a Balance Sheet account or select a different account."
|
||||
).format(frappe.bold(_("Credit To"))),
|
||||
title=_("Invalid Account"),
|
||||
)
|
||||
|
||||
if self.supplier and account.account_type != "Payable":
|
||||
frappe.throw(
|
||||
_(
|
||||
"Please ensure {} account {} is a Payable account. Change the account type to Payable or select a different account."
|
||||
).format(frappe.bold("Credit To"), frappe.bold(self.credit_to)),
|
||||
"Please ensure that the {0} account {1} is a Payable account. You can change the account type to Payable or select a different account."
|
||||
).format(frappe.bold(_("Credit To")), frappe.bold(self.credit_to)),
|
||||
title=_("Invalid Account"),
|
||||
)
|
||||
|
||||
@@ -635,7 +635,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"To submit the invoice without purchase order please set {0} as {1} in {2}"
|
||||
).format(
|
||||
frappe.bold(_("Purchase Order Required")),
|
||||
frappe.bold("No"),
|
||||
frappe.bold(_("No")),
|
||||
get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"),
|
||||
)
|
||||
throw(msg, title=_("Mandatory Purchase Order"))
|
||||
@@ -656,7 +656,7 @@ class PurchaseInvoice(BuyingController):
|
||||
"To submit the invoice without purchase receipt please set {0} as {1} in {2}"
|
||||
).format(
|
||||
frappe.bold(_("Purchase Receipt Required")),
|
||||
frappe.bold("No"),
|
||||
frappe.bold(_("No")),
|
||||
get_link_to_form("Buying Settings", "Buying Settings", "Buying Settings"),
|
||||
)
|
||||
throw(msg, title=_("Mandatory Purchase Receipt"))
|
||||
@@ -1267,7 +1267,11 @@ class PurchaseInvoice(BuyingController):
|
||||
def update_gross_purchase_amount_for_linked_assets(self, item):
|
||||
assets = frappe.db.get_all(
|
||||
"Asset",
|
||||
filters={"purchase_invoice": self.name, "item_code": item.item_code},
|
||||
filters={
|
||||
"purchase_invoice": self.name,
|
||||
"item_code": item.item_code,
|
||||
"purchase_invoice_item": ("in", [item.name, ""]),
|
||||
},
|
||||
fields=["name", "asset_quantity"],
|
||||
)
|
||||
for asset in assets:
|
||||
|
||||
@@ -2246,6 +2246,62 @@ class TestPurchaseInvoice(FrappeTestCase, StockTestMixin):
|
||||
self.assertEqual(pi_expected_values[i][1], gle.debit)
|
||||
self.assertEqual(pi_expected_values[i][2], gle.credit)
|
||||
|
||||
def test_adjust_incoming_rate_from_pi_with_multi_currency(self):
|
||||
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
|
||||
make_landed_cost_voucher,
|
||||
)
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1)
|
||||
|
||||
# Increase the cost of the item
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
qty=10, rate=1, currency="USD", do_not_save=1, supplier="_Test Supplier USD"
|
||||
)
|
||||
pr.conversion_rate = 6300
|
||||
pr.plc_conversion_rate = 1
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
self.assertEqual(pr.conversion_rate, 6300)
|
||||
self.assertEqual(pr.plc_conversion_rate, 1)
|
||||
self.assertEqual(pr.base_grand_total, 6300 * 10)
|
||||
|
||||
stock_value_difference = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||
"stock_value_difference",
|
||||
)
|
||||
self.assertEqual(stock_value_difference, 6300 * 10)
|
||||
|
||||
make_landed_cost_voucher(
|
||||
company=pr.company,
|
||||
receipt_document_type="Purchase Receipt",
|
||||
receipt_document=pr.name,
|
||||
charges=3000,
|
||||
distribute_charges_based_on="Qty",
|
||||
)
|
||||
|
||||
pi = create_purchase_invoice_from_receipt(pr.name)
|
||||
for row in pi.items:
|
||||
row.rate = 1.1
|
||||
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
stock_value_difference = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
|
||||
"stock_value_difference",
|
||||
)
|
||||
self.assertEqual(stock_value_difference, 7230 * 10)
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0)
|
||||
|
||||
frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"column_break_30",
|
||||
"discount_percentage",
|
||||
"discount_amount",
|
||||
"distributed_discount_amount",
|
||||
"base_rate_with_margin",
|
||||
"sec_break2",
|
||||
"rate",
|
||||
@@ -838,7 +839,7 @@
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
|
||||
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount || doc.distributed_discount_amount",
|
||||
"fieldname": "section_break_26",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount and Margin"
|
||||
@@ -969,6 +970,12 @@
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency",
|
||||
"print_hide": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "distributed_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Distributed Discount Amount",
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
|
||||
@@ -34,6 +34,7 @@ class PurchaseInvoiceItem(Document):
|
||||
description: DF.TextEditor | None
|
||||
discount_amount: DF.Currency
|
||||
discount_percentage: DF.Percent
|
||||
distributed_discount_amount: DF.Currency
|
||||
enable_deferred_expense: DF.Check
|
||||
expense_account: DF.Link | None
|
||||
from_warehouse: DF.Link | None
|
||||
|
||||
@@ -315,17 +315,6 @@ class SalesInvoice(SellingController):
|
||||
if not self.is_opening:
|
||||
self.is_opening = "No"
|
||||
|
||||
if self.redeem_loyalty_points:
|
||||
lp = frappe.get_doc("Loyalty Program", self.loyalty_program)
|
||||
self.loyalty_redemption_account = (
|
||||
lp.expense_account if not self.loyalty_redemption_account else self.loyalty_redemption_account
|
||||
)
|
||||
self.loyalty_redemption_cost_center = (
|
||||
lp.cost_center
|
||||
if not self.loyalty_redemption_cost_center
|
||||
else self.loyalty_redemption_cost_center
|
||||
)
|
||||
|
||||
self.set_against_income_account()
|
||||
self.validate_time_sheets_are_submitted()
|
||||
self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
|
||||
@@ -344,12 +333,7 @@ class SalesInvoice(SellingController):
|
||||
if self.is_pos and self.is_return:
|
||||
self.verify_payment_amount_is_negative()
|
||||
|
||||
if (
|
||||
self.redeem_loyalty_points
|
||||
and self.loyalty_program
|
||||
and self.loyalty_points
|
||||
and not self.is_consolidated
|
||||
):
|
||||
if self.redeem_loyalty_points and self.loyalty_points and not self.is_consolidated:
|
||||
validate_loyalty_points(self, self.loyalty_points)
|
||||
|
||||
self.reset_default_field_value("set_warehouse", "items", "warehouse")
|
||||
@@ -527,7 +511,7 @@ class SalesInvoice(SellingController):
|
||||
)
|
||||
if pos_closing_entry and pos_closing_entry[0]:
|
||||
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
|
||||
frappe.bold("Consolidated Sales Invoice"),
|
||||
frappe.bold(_("Consolidated Sales Invoice")),
|
||||
get_link_to_form("POS Closing Entry", pos_closing_entry[0]),
|
||||
)
|
||||
frappe.throw(msg, title=_("Not Allowed"))
|
||||
@@ -874,7 +858,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if account.report_type != "Balance Sheet":
|
||||
msg = (
|
||||
_("Please ensure {} account is a Balance Sheet account.").format(frappe.bold("Debit To"))
|
||||
_("Please ensure {} account is a Balance Sheet account.").format(frappe.bold(_("Debit To")))
|
||||
+ " "
|
||||
)
|
||||
msg += _(
|
||||
@@ -885,7 +869,7 @@ class SalesInvoice(SellingController):
|
||||
if self.customer and account.account_type != "Receivable":
|
||||
msg = (
|
||||
_("Please ensure {} account {} is a Receivable account.").format(
|
||||
frappe.bold("Debit To"), frappe.bold(self.debit_to)
|
||||
frappe.bold(_("Debit To")), frappe.bold(self.debit_to)
|
||||
)
|
||||
+ " "
|
||||
)
|
||||
@@ -1444,7 +1428,7 @@ class SalesInvoice(SellingController):
|
||||
asset.set_status("Sold" if self.docstatus == 1 else None)
|
||||
|
||||
def make_loyalty_point_redemption_gle(self, gl_entries):
|
||||
if cint(self.redeem_loyalty_points):
|
||||
if cint(self.redeem_loyalty_points and self.loyalty_points and not self.is_consolidated):
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
@@ -1485,6 +1469,10 @@ class SalesInvoice(SellingController):
|
||||
if skip_change_gl_entries and payment_mode.account == self.account_for_change_amount:
|
||||
payment_mode.base_amount -= flt(self.change_amount)
|
||||
|
||||
against_voucher = self.name
|
||||
if self.is_return and self.return_against and not self.update_outstanding_for_self:
|
||||
against_voucher = self.return_against
|
||||
|
||||
if payment_mode.base_amount:
|
||||
# POS, make payment entries
|
||||
gl_entries.append(
|
||||
@@ -1498,7 +1486,7 @@ class SalesInvoice(SellingController):
|
||||
"credit_in_account_currency": payment_mode.base_amount
|
||||
if self.party_account_currency == self.company_currency
|
||||
else payment_mode.amount,
|
||||
"against_voucher": self.name,
|
||||
"against_voucher": against_voucher,
|
||||
"against_voucher_type": self.doctype,
|
||||
"cost_center": self.cost_center,
|
||||
},
|
||||
@@ -1772,7 +1760,8 @@ class SalesInvoice(SellingController):
|
||||
loyalty_program=self.loyalty_program,
|
||||
include_expired_entry=True,
|
||||
)
|
||||
frappe.db.set_value("Customer", self.customer, "loyalty_program_tier", lp_details.tier_name)
|
||||
customer = frappe.get_doc("Customer", self.customer)
|
||||
customer.db_set("loyalty_program_tier", lp_details.tier_name)
|
||||
|
||||
def get_returned_amount(self):
|
||||
from frappe.query_builder.functions import Sum
|
||||
@@ -2077,7 +2066,7 @@ def make_delivery_note(source_name, target_doc=None):
|
||||
"postprocess": update_item,
|
||||
"condition": lambda doc: doc.delivered_by_supplier != 1,
|
||||
},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
|
||||
"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "reset_value": True},
|
||||
"Sales Team": {
|
||||
"doctype": "Sales Team",
|
||||
"field_map": {"incentives": "incentives"},
|
||||
|
||||
@@ -5,6 +5,7 @@ import copy
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.model.dynamic_links import get_dynamic_link_map
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import add_days, flt, getdate, nowdate, today
|
||||
@@ -3723,6 +3724,65 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
check_gl_entries(self, pe.name, expected_gle, nowdate(), voucher_type="Payment Entry")
|
||||
set_advance_flag(company="_Test Company", flag=0, default_account="")
|
||||
|
||||
def test_loyalty_points_redemption_with_shopping_cart(self):
|
||||
from erpnext.accounts.doctype.loyalty_program.test_loyalty_program import (
|
||||
create_records,
|
||||
create_sales_invoice_record,
|
||||
)
|
||||
from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
|
||||
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
|
||||
|
||||
# Set up loyalty program
|
||||
create_records()
|
||||
frappe.db.set_value("Customer", "Test Loyalty Customer", "loyalty_program", "Test Single Loyalty")
|
||||
create_sales_invoice_record(10).insert().submit()
|
||||
|
||||
# Create a sales order
|
||||
so = make_sales_order(qty=10, do_not_save=True, customer="Test Loyalty Customer")
|
||||
so.name = "_T-Sales Order LP-0001"
|
||||
so.order_type = "Shopping Cart"
|
||||
so.loyalty_points = 50
|
||||
so.loyalty_amount = 50
|
||||
so.insert()
|
||||
so.submit()
|
||||
|
||||
# Create sales invoice from the sales order
|
||||
si = make_sales_invoice(so.name)
|
||||
from frappe.model.trace import traced_field_context
|
||||
|
||||
with traced_field_context(si.__class__, "loyalty_program", forbidden_values=[None]):
|
||||
si.insert()
|
||||
si.submit()
|
||||
|
||||
# Check if loyalty points are applied correctly
|
||||
self.assertEqual(si.loyalty_program, "Test Single Loyalty")
|
||||
self.assertEqual(si.loyalty_points, 50)
|
||||
self.assertEqual(si.loyalty_amount, 50)
|
||||
|
||||
# Check GL entries for loyalty points redemption
|
||||
gl_entries = frappe.get_all(
|
||||
"GL Entry",
|
||||
filters={"voucher_type": "Sales Invoice", "voucher_no": si.name},
|
||||
fields=["account", "debit", "credit"],
|
||||
)
|
||||
|
||||
loyalty_account = frappe.db.get_value("Loyalty Program", "Test Single Loyalty", "expense_account")
|
||||
expected_gl_entries = [
|
||||
{"account": si.debit_to, "debit": si.grand_total, "credit": 0},
|
||||
{"account": si.items[0].income_account, "debit": 0, "credit": si.net_total},
|
||||
{"account": loyalty_account, "debit": 50, "credit": 0},
|
||||
]
|
||||
|
||||
for entry in expected_gl_entries:
|
||||
self.assertTrue(
|
||||
any(
|
||||
gl_entry.account == entry["account"]
|
||||
and gl_entry.debit == entry["debit"]
|
||||
and gl_entry.credit == entry["credit"]
|
||||
for gl_entry in gl_entries
|
||||
)
|
||||
)
|
||||
|
||||
def test_pulling_advance_based_on_debit_to(self):
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||
|
||||
@@ -3849,6 +3909,40 @@ class TestSalesInvoice(FrappeTestCase):
|
||||
]
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_pos_returns_without_update_outstanding_for_self(self):
|
||||
from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
|
||||
|
||||
pos_profile = make_pos_profile()
|
||||
pos_profile.payments = []
|
||||
pos_profile.append("payments", {"default": 1, "mode_of_payment": "Cash"})
|
||||
pos_profile.save()
|
||||
|
||||
pos = create_sales_invoice(qty=10, do_not_save=True)
|
||||
pos.is_pos = 1
|
||||
pos.pos_profile = pos_profile.name
|
||||
pos.append(
|
||||
"payments", {"mode_of_payment": "Bank Draft", "account": "_Test Bank - _TC", "amount": 500}
|
||||
)
|
||||
pos.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 500})
|
||||
pos.save().submit()
|
||||
|
||||
pos_return = make_sales_return(pos.name)
|
||||
pos_return.update_outstanding_for_self = False
|
||||
pos_return.save().submit()
|
||||
|
||||
gle = qb.DocType("GL Entry")
|
||||
res = (
|
||||
qb.from_(gle)
|
||||
.select(gle.against_voucher)
|
||||
.distinct()
|
||||
.where(
|
||||
gle.is_cancelled.eq(0) & gle.voucher_no.eq(pos_return.name) & gle.against_voucher.notnull()
|
||||
)
|
||||
.run(as_list=1)
|
||||
)
|
||||
self.assertEqual(len(res), 1)
|
||||
self.assertEqual(res[0][0], pos_return.return_against)
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"column_break_19",
|
||||
"discount_percentage",
|
||||
"discount_amount",
|
||||
"distributed_discount_amount",
|
||||
"base_rate_with_margin",
|
||||
"section_break1",
|
||||
"rate",
|
||||
@@ -253,7 +254,7 @@
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount",
|
||||
"collapsible_depends_on": "eval: doc.margin_type || doc.discount_amount || doc.distributed_discount_amount",
|
||||
"fieldname": "discount_and_margin",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Discount and Margin"
|
||||
@@ -922,12 +923,18 @@
|
||||
{
|
||||
"fieldname": "column_break_ytgd",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "distributed_discount_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Distributed Discount Amount",
|
||||
"options": "currency"
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-05-23 16:36:18.970862",
|
||||
"modified": "2024-06-02 06:14:40.009020",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -45,6 +45,7 @@ class SalesInvoiceItem(Document):
|
||||
discount_account: DF.Link | None
|
||||
discount_amount: DF.Currency
|
||||
discount_percentage: DF.Percent
|
||||
distributed_discount_amount: DF.Currency
|
||||
dn_detail: DF.Data | None
|
||||
enable_deferred_revenue: DF.Check
|
||||
expense_account: DF.Link | None
|
||||
|
||||
@@ -38,6 +38,12 @@ frappe.ui.form.on("Subscription", {
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Force-Fetch Subscription Updates"),
|
||||
() => frm.trigger("force_fetch_subscription_updates"),
|
||||
__("Actions")
|
||||
);
|
||||
|
||||
frm.add_custom_button(
|
||||
__("Cancel Subscription"),
|
||||
() => frm.trigger("cancel_this_subscription"),
|
||||
@@ -82,4 +88,11 @@ frappe.ui.form.on("Subscription", {
|
||||
}
|
||||
});
|
||||
},
|
||||
force_fetch_subscription_updates: function (frm) {
|
||||
frm.call("force_fetch_subscription_updates").then((r) => {
|
||||
if (!r.exec) {
|
||||
frm.reload_doc();
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -717,6 +717,28 @@ class Subscription(Document):
|
||||
self.update_subscription_period(posting_date or nowdate())
|
||||
self.save()
|
||||
|
||||
@frappe.whitelist()
|
||||
def force_fetch_subscription_updates(self):
|
||||
"""
|
||||
Process Subscription and create Invoices even if current date doesn't lie between current_invoice_start and currenct_invoice_end
|
||||
It makes use of 'Proces Subscription' to force processing in a specific 'posting_date'
|
||||
"""
|
||||
|
||||
# Don't process future subscriptions
|
||||
if nowdate() < self.current_invoice_start:
|
||||
frappe.msgprint(_("Subscription for Future dates cannot be processed."))
|
||||
return
|
||||
|
||||
processing_date = None
|
||||
if self.generate_invoice_at == "Beginning of the current subscription period":
|
||||
processing_date = self.current_invoice_start
|
||||
elif self.generate_invoice_at == "End of the current subscription period":
|
||||
processing_date = self.current_invoice_end
|
||||
elif self.generate_invoice_at == "Days before the current subscription period":
|
||||
processing_date = add_days(self.current_invoice_start, -self.number_of_days)
|
||||
|
||||
self.process(posting_date=processing_date)
|
||||
|
||||
|
||||
def is_prorate() -> int:
|
||||
return cint(frappe.db.get_single_value("Subscription Settings", "prorate"))
|
||||
|
||||
@@ -521,6 +521,18 @@ class TestSubscription(FrappeTestCase):
|
||||
subscription.process(posting_date="2023-01-22")
|
||||
self.assertEqual(len(subscription.invoices), 2)
|
||||
|
||||
def test_future_subscription(self):
|
||||
"""Force-Fetch should not process future subscriptions"""
|
||||
subscription = create_subscription(
|
||||
start_date=add_months(nowdate(), 1),
|
||||
submit_invoice=0,
|
||||
generate_new_invoices_past_due_date=1,
|
||||
party="_Test Subscription Customer John Doe",
|
||||
)
|
||||
subscription.force_fetch_subscription_updates()
|
||||
subscription.reload()
|
||||
self.assertEqual(len(subscription.invoices), 0)
|
||||
|
||||
|
||||
def make_plans():
|
||||
create_plan(plan_name="_Test Plan Name", cost=900, currency="INR")
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
{
|
||||
"creation": "2021-06-29 17:00:18.273054",
|
||||
"docstatus": 0,
|
||||
"doctype": "Form Tour",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2024-01-24 02:20:26.145996",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
"owner": "Administrator",
|
||||
"reference_doctype": "Accounts Settings",
|
||||
"save_on_complete": 0,
|
||||
"steps": [
|
||||
{
|
||||
"description": "The percentage by which you can overbill transactions. For example, if the order value is $100 for an Item and percentage here is set as 10% then you are allowed to bill for $110.",
|
||||
"field": "",
|
||||
"fieldname": "over_billing_allowance",
|
||||
"fieldtype": "Currency",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Over Billing Allowance (%)",
|
||||
"parent_field": "",
|
||||
"position": "Right",
|
||||
"title": "Over Billing Allowance Percentage"
|
||||
},
|
||||
{
|
||||
"description": "Select the role that is allowed to overbill a transactions.",
|
||||
"field": "",
|
||||
"fieldname": "role_allowed_to_over_bill",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Role Allowed to Over Bill ",
|
||||
"parent_field": "",
|
||||
"position": "Right",
|
||||
"title": "Role Allowed to Over Bill"
|
||||
},
|
||||
{
|
||||
"description": "If checked, system will unlink the payment against the respective invoice.",
|
||||
"field": "",
|
||||
"fieldname": "unlink_payment_on_cancellation_of_invoice",
|
||||
"fieldtype": "Check",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Unlink Payment on Cancellation of Invoice",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Unlink Payment on Cancellation of Invoice"
|
||||
},
|
||||
{
|
||||
"description": "Similar to the previous option, this unlinks any advance payments made against Purchase/Sales Orders.",
|
||||
"field": "",
|
||||
"fieldname": "unlink_advance_payment_on_cancelation_of_order",
|
||||
"fieldtype": "Check",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Unlink Advance Payment on Cancellation of Order",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Unlink Advance Payment on Cancellation of Order"
|
||||
},
|
||||
{
|
||||
"description": "Tax category can be set on Addresses. An address can be Shipping or Billing address. Set which addres to select when applying Tax Category.",
|
||||
"field": "",
|
||||
"fieldname": "determine_address_tax_category_from",
|
||||
"fieldtype": "Select",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Determine Address Tax Category From",
|
||||
"parent_field": "",
|
||||
"position": "Right",
|
||||
"title": "Determine Address Tax Category From"
|
||||
},
|
||||
{
|
||||
"description": "Freeze accounting transactions up to specified date, nobody can make/modify entry except the specified Role.",
|
||||
"field": "",
|
||||
"fieldname": "acc_frozen_upto",
|
||||
"fieldtype": "Date",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Accounts Frozen Till Date",
|
||||
"parent_field": "",
|
||||
"position": "Right",
|
||||
"title": "Accounts Frozen Up To"
|
||||
},
|
||||
{
|
||||
"description": "Users with this Role are allowed to set frozen accounts and create/modify accounting entries against frozen accounts.",
|
||||
"field": "",
|
||||
"fieldname": "frozen_accounts_modifier",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Role Allowed to Set Frozen Accounts and Edit Frozen Entries",
|
||||
"parent_field": "",
|
||||
"position": "Right",
|
||||
"title": "Role Allowed to Set Frozen Accounts & Edit Frozen Entries"
|
||||
},
|
||||
{
|
||||
"description": "Select the role that is allowed to submit transactions that exceed credit limits set. The credit limit can be set in the Customer form.",
|
||||
"field": "",
|
||||
"fieldname": "credit_controller",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Credit Controller",
|
||||
"parent_field": "",
|
||||
"position": "Left",
|
||||
"title": "Credit Controller"
|
||||
}
|
||||
],
|
||||
"title": "Accounts Settings"
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
{
|
||||
"creation": "2021-06-29 16:31:48.558826",
|
||||
"docstatus": 0,
|
||||
"doctype": "Form Tour",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2021-06-29 16:31:48.558826",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice",
|
||||
"owner": "Administrator",
|
||||
"reference_doctype": "Purchase Invoice",
|
||||
"save_on_complete": 1,
|
||||
"steps": [
|
||||
{
|
||||
"description": "Select Supplier",
|
||||
"field": "",
|
||||
"fieldname": "supplier",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 1,
|
||||
"is_table_field": 0,
|
||||
"label": "Supplier",
|
||||
"next_step_condition": "supplier",
|
||||
"parent_field": "",
|
||||
"position": "Right",
|
||||
"title": "Select Supplier"
|
||||
},
|
||||
{
|
||||
"description": "Add items in the table",
|
||||
"field": "",
|
||||
"fieldname": "items",
|
||||
"fieldtype": "Table",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Items",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "List of Items"
|
||||
},
|
||||
{
|
||||
"child_doctype": "Purchase Invoice Item",
|
||||
"description": "Select an item",
|
||||
"field": "",
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 1,
|
||||
"label": "Item",
|
||||
"parent_field": "",
|
||||
"parent_fieldname": "items",
|
||||
"position": "Right",
|
||||
"title": "Select Item"
|
||||
},
|
||||
{
|
||||
"child_doctype": "Purchase Invoice Item",
|
||||
"description": "Enter the quantity",
|
||||
"field": "",
|
||||
"fieldname": "qty",
|
||||
"fieldtype": "Float",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 1,
|
||||
"label": "Accepted Qty",
|
||||
"parent_field": "",
|
||||
"parent_fieldname": "items",
|
||||
"position": "Right",
|
||||
"title": "Enter Quantity"
|
||||
},
|
||||
{
|
||||
"child_doctype": "Purchase Invoice Item",
|
||||
"description": "Enter rate of the item",
|
||||
"field": "",
|
||||
"fieldname": "rate",
|
||||
"fieldtype": "Currency",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 1,
|
||||
"label": "Rate",
|
||||
"parent_field": "",
|
||||
"parent_fieldname": "items",
|
||||
"position": "Right",
|
||||
"title": "Enter Rate"
|
||||
},
|
||||
{
|
||||
"description": "You can add taxes here",
|
||||
"field": "",
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Purchase Taxes and Charges",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Select taxes"
|
||||
}
|
||||
],
|
||||
"title": "Purchase Invoice"
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"creation": "2023-05-23 09:58:17.235916",
|
||||
"docstatus": 0,
|
||||
"doctype": "Form Tour",
|
||||
"first_document": 0,
|
||||
"idx": 0,
|
||||
"include_name_field": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2023-05-23 13:10:56.227127",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice",
|
||||
"owner": "Administrator",
|
||||
"reference_doctype": "Sales Invoice",
|
||||
"save_on_complete": 1,
|
||||
"steps": [
|
||||
{
|
||||
"description": "Select a customer for whom this invoice is being prepared.",
|
||||
"fieldname": "customer",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 1,
|
||||
"is_table_field": 0,
|
||||
"label": "Customer",
|
||||
"next_step_condition": "eval: doc.customer",
|
||||
"position": "Right",
|
||||
"title": "Select Customer"
|
||||
},
|
||||
{
|
||||
"child_doctype": "Sales Invoice Item",
|
||||
"description": "Select item that you have sold along with quantity and rate.",
|
||||
"fieldname": "items",
|
||||
"fieldtype": "Table",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"parent_fieldname": "items",
|
||||
"position": "Top",
|
||||
"title": "Select Item"
|
||||
}
|
||||
],
|
||||
"title": "Sales Invoice"
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"creation": "2021-08-24 12:28:18.044902",
|
||||
"docstatus": 0,
|
||||
"doctype": "Form Tour",
|
||||
"first_document": 0,
|
||||
"idx": 0,
|
||||
"include_name_field": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2022-01-18 18:32:17.102330",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Taxes and Charges Template",
|
||||
"owner": "Administrator",
|
||||
"reference_doctype": "Sales Taxes and Charges Template",
|
||||
"save_on_complete": 1,
|
||||
"steps": [
|
||||
{
|
||||
"description": "A name by which you will identify this template. You can change this later.",
|
||||
"field": "",
|
||||
"fieldname": "title",
|
||||
"fieldtype": "Data",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Title",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Title"
|
||||
},
|
||||
{
|
||||
"description": "Company for which this tax template will be applicable",
|
||||
"field": "",
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Company",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Company"
|
||||
},
|
||||
{
|
||||
"description": "Set this template as the default for all sales transactions",
|
||||
"field": "",
|
||||
"fieldname": "is_default",
|
||||
"fieldtype": "Check",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Default",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Is this Default Tax Template?"
|
||||
},
|
||||
{
|
||||
"description": "You can add a row for a tax rule here. These rules can be applied on the net total, or can be a flat amount.",
|
||||
"field": "",
|
||||
"fieldname": "taxes",
|
||||
"fieldtype": "Table",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Sales Taxes and Charges",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Taxes Table"
|
||||
}
|
||||
],
|
||||
"title": "Sales Taxes and Charges Template"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"allow_roles": [
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
}
|
||||
],
|
||||
"creation": "2020-05-13 19:03:32.564049",
|
||||
"docstatus": 0,
|
||||
"doctype": "Module Onboarding",
|
||||
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/accounts",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"modified": "2022-06-14 17:38:24.967834",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts",
|
||||
"owner": "Administrator",
|
||||
"steps": [
|
||||
{
|
||||
"step": "Chart of Accounts"
|
||||
},
|
||||
{
|
||||
"step": "Setup Taxes"
|
||||
},
|
||||
{
|
||||
"step": "Accounts Settings"
|
||||
},
|
||||
{
|
||||
"step": "Cost Centers for Report and Budgeting"
|
||||
},
|
||||
{
|
||||
"step": "Create Your First Purchase Invoice"
|
||||
},
|
||||
{
|
||||
"step": "Updating Opening Balances"
|
||||
},
|
||||
{
|
||||
"step": "Financial Statements"
|
||||
}
|
||||
],
|
||||
"subtitle": "Accounts, Invoices, Taxation, and more.",
|
||||
"success_message": "The Accounts Module is all set up!",
|
||||
"title": "Let's Set Up Your Accounts and Taxes."
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"action": "Show Form Tour",
|
||||
"action_label": "Take a quick walk-through of Accounts Settings",
|
||||
"creation": "2021-06-29 16:42:03.400731",
|
||||
"description": "# Account Settings\n\nIn ERPNext, Accounting features are configurable as per your business needs. Accounts Settings is the place to define some of your accounting preferences like:\n\n - Credit Limit and over billing settings\n - Taxation preferences\n - Deferred accounting preferences\n",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 1,
|
||||
"is_skipped": 0,
|
||||
"modified": "2021-08-13 11:50:06.227835",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Accounts Settings",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Accounts Settings",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Accounts Settings",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"action": "Go to Page",
|
||||
"action_label": "Learn more about Chart of Accounts",
|
||||
"callback_message": "You can continue with the onboarding after exploring this page",
|
||||
"callback_title": "Explore Chart of Accounts",
|
||||
"creation": "2020-05-13 19:58:20.928127",
|
||||
"description": "# Chart Of Accounts\n\nERPNext sets up a simple chart of accounts for each Company you create, but you can modify it according to business and legal requirements.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"intro_video_url": "https://www.youtube.com/embed/AcfMCT7wLLo",
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2022-06-07 14:21:26.264769",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Chart of Accounts",
|
||||
"owner": "Administrator",
|
||||
"path": "Tree/Account",
|
||||
"reference_document": "Account",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Review Chart of Accounts",
|
||||
"validate_action": 0,
|
||||
"video_url": "https://www.youtube.com/embed/AcfMCT7wLLo"
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"action": "Show Form Tour",
|
||||
"creation": "2020-05-14 17:53:00.876946",
|
||||
"description": "# Account Settings\n\nThis is a crucial piece of configuration. There are various account settings in ERPNext to restrict and configure actions in the Accounting module.\n\nThe following settings are avaialble for you to configure\n\n1. Account Freezing \n2. Credit and Overbilling\n3. Invoicing and Tax Automations\n4. Balance Sheet configurations\n\nThere's much more, you can check it all out in this step",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 1,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-10-19 14:40:55.584484",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Configure Account Settings",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Accounts Settings",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Configure Account Settings",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"action": "Go to Page",
|
||||
"action_label": "View Cost Center Tree",
|
||||
"creation": "2021-07-12 12:02:05.726608",
|
||||
"description": "# Cost Centers for Budgeting and Analysis\n\nWhile your Books of Accounts are framed to fulfill statutory requirements, you can set up Cost Center and Accounting Dimensions to address your companies reporting and budgeting requirements.\n\nClick here to learn more about how <b>[Cost Center](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/cost-center)</b> and <b> [Dimensions](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/accounting-dimensions)</b> allow you to get advanced financial analytics reports from ERPNext.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2021-08-13 11:55:08.510366",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Cost Centers for Report and Budgeting",
|
||||
"owner": "Administrator",
|
||||
"path": "cost-center/view/tree",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Cost Centers for Budgeting and Analysis",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"creation": "2020-05-14 17:46:41.831517",
|
||||
"description": "## Who is a Customer?\n\nA customer, who is sometimes known as a client, buyer, or purchaser is the one who receives goods, services, products, or ideas, from a seller for a monetary consideration.\n\nEvery customer needs to be assigned a unique id. Customer name itself can be the id or you can set a naming series for ids to be generated in Selling Settings.\n\nJust like the supplier, let's quickly create a customer.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-10-30 15:28:46.659660",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create a Customer",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Customer",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create a Customer",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"creation": "2020-05-12 18:16:06.624554",
|
||||
"description": "## Products and Services\n\nDepending on the nature of your business, you might be selling products or services to your clients or even both. \nERPNext is optimized for itemized management of your sales and purchase.\n\nThe **Item Master** is where you can add all your sales items. If you are in services, you can create an Item for each service that you offer. If you run a manufacturing business, the same master is used for keeping a record of raw materials, sub-assemblies etc.\n\nCompleting the Item Master is very essential for the successful implementation of ERPNext. We have a brief video introducing the item master for you, you can watch it in the next step.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"intro_video_url": "https://www.youtube.com/watch?v=Sl5UFA5H5EQ",
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-10-30 15:20:30.133495",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create a Product",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Item",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create a Sales Item",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"creation": "2020-05-14 22:09:10.043554",
|
||||
"description": "## Who is a Supplier?\n\nSuppliers are companies or individuals who provide you with products or services. ERPNext has comprehensive features for purchase cycles. \n\nLet's quickly create a supplier with the minimal details required. You need the name of the supplier, assign the supplier to a group, and select the type of the supplier, viz. Company or Individual.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-10-30 15:26:48.315772",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create a Supplier",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Supplier",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Create a Supplier",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"action": "Show Form Tour",
|
||||
"action_label": "Let\u2019s create your first Purchase Invoice",
|
||||
"creation": "2020-05-14 22:10:07.049704",
|
||||
"description": "# Create your first Purchase Invoice\n\nA Purchase Invoice is a bill received from a Supplier for a product(s) or service(s) delivery to your company. You can track payables through Purchase Invoice and process Payment Entries against it.\n\nPurchase Invoices can also be created against a Purchase Order or Purchase Receipt.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2021-08-13 11:56:11.677253",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Your First Purchase Invoice",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Purchase Invoice",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Your First Purchase Invoice ",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"creation": "2020-05-14 17:48:21.019019",
|
||||
"description": "# All about sales invoice\n\nA Sales Invoice is a bill that you send to your Customers against which the Customer makes the payment. Sales Invoice is an accounting transaction. On submission of Sales Invoice, the system updates the receivable and books income against a Customer Account.\n\nHere's the flow of how a sales invoice is generally created\n\n\n",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-10-16 12:59:16.987507",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create Your First Sales Invoice",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Sales Invoice",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 1,
|
||||
"title": "Create Your First Sales Invoice ",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"action": "View Report",
|
||||
"creation": "2021-07-12 12:08:47.026115",
|
||||
"description": "# Financial Statements\n\nIn ERPNext, you can get crucial financial reports like [Balance Sheet] and [Profit and Loss] statements with a click of a button. You can run in the report for a different period and plot analytics charts premised on statement data. For more reports, check sections like Financial Statements, General Ledger, and Profitability reports.\n\n<b>[Check Accounting reports](https://docs.erpnext.com/docs/v13/user/manual/en/accounts/accounting-reports)</b>",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2021-08-13 11:59:18.767407",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Financial Statements",
|
||||
"owner": "Administrator",
|
||||
"reference_report": "General Ledger",
|
||||
"report_description": "General Ledger",
|
||||
"report_reference_doctype": "GL Entry",
|
||||
"report_type": "Script Report",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Financial Statements",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Manage Sales Tax Templates",
|
||||
"creation": "2020-05-13 19:29:43.844463",
|
||||
"description": "# Setting up Taxes\n\nERPNext lets you configure your taxes so that they are automatically applied in your buying and selling transactions. You can configure them globally or even on Items. ERPNext taxes are pre-configured for most regions.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2022-06-14 17:37:56.694261",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Setup Taxes",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Sales Taxes and Charges Template",
|
||||
"show_form_tour": 1,
|
||||
"show_full_form": 1,
|
||||
"title": "Setting up Taxes",
|
||||
"validate_action": 0
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"action": "Watch Video",
|
||||
"action_label": "Learn how to update opening balances",
|
||||
"creation": "2021-07-12 11:53:50.525030",
|
||||
"description": "# Updating Opening Balances\n\nOnce you close the financial statement in previous accounting software, you can update the same as opening in your ERPNext's Balance Sheet accounts. This will allow you to get complete financial statements from ERPNext in the coming years, and discontinue the parallel accounting system right away.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"intro_video_url": "https://www.youtube.com/embed/U5wPIvEn-0c",
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2021-08-13 11:56:45.483418",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Updating Opening Balances",
|
||||
"owner": "Administrator",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Updating Opening Balances",
|
||||
"validate_action": 1,
|
||||
"video_url": "https://www.youtube.com/embed/U5wPIvEn-0c"
|
||||
}
|
||||
@@ -162,6 +162,11 @@ frappe.query_reports["Accounts Payable"] = {
|
||||
label: __("Group by Voucher"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
{
|
||||
fieldname: "handle_employee_advances",
|
||||
label: __("Handle Employee Advances"),
|
||||
fieldtype: "Check",
|
||||
},
|
||||
],
|
||||
|
||||
formatter: function (value, row, column, data, default_formatter) {
|
||||
|
||||
@@ -112,6 +112,26 @@ class ReceivablePayableReport:
|
||||
|
||||
self.build_data()
|
||||
|
||||
def build_voucher_dict(self, ple):
|
||||
return frappe._dict(
|
||||
voucher_type=ple.voucher_type,
|
||||
voucher_no=ple.voucher_no,
|
||||
party=ple.party,
|
||||
party_account=ple.account,
|
||||
posting_date=ple.posting_date,
|
||||
account_currency=ple.account_currency,
|
||||
remarks=ple.remarks,
|
||||
invoiced=0.0,
|
||||
paid=0.0,
|
||||
credit_note=0.0,
|
||||
outstanding=0.0,
|
||||
invoiced_in_account_currency=0.0,
|
||||
paid_in_account_currency=0.0,
|
||||
credit_note_in_account_currency=0.0,
|
||||
outstanding_in_account_currency=0.0,
|
||||
cost_center=ple.cost_center,
|
||||
)
|
||||
|
||||
def init_voucher_balance(self):
|
||||
# build all keys, since we want to exclude vouchers beyond the report date
|
||||
for ple in self.ple_entries:
|
||||
@@ -123,24 +143,8 @@ class ReceivablePayableReport:
|
||||
key = (ple.account, ple.voucher_type, ple.voucher_no, ple.party)
|
||||
|
||||
if key not in self.voucher_balance:
|
||||
self.voucher_balance[key] = frappe._dict(
|
||||
voucher_type=ple.voucher_type,
|
||||
voucher_no=ple.voucher_no,
|
||||
party=ple.party,
|
||||
party_account=ple.account,
|
||||
posting_date=ple.posting_date,
|
||||
account_currency=ple.account_currency,
|
||||
remarks=ple.remarks,
|
||||
invoiced=0.0,
|
||||
paid=0.0,
|
||||
credit_note=0.0,
|
||||
outstanding=0.0,
|
||||
invoiced_in_account_currency=0.0,
|
||||
paid_in_account_currency=0.0,
|
||||
credit_note_in_account_currency=0.0,
|
||||
outstanding_in_account_currency=0.0,
|
||||
cost_center=ple.cost_center,
|
||||
)
|
||||
self.voucher_balance[key] = self.build_voucher_dict(ple)
|
||||
|
||||
self.get_invoices(ple)
|
||||
|
||||
if self.filters.get("group_by_party"):
|
||||
@@ -208,6 +212,18 @@ class ReceivablePayableReport:
|
||||
|
||||
row = self.voucher_balance.get(key)
|
||||
|
||||
# Build and use a separate row for Employee Advances.
|
||||
# This allows Payments or Journals made against Emp Advance to be processed.
|
||||
if (
|
||||
not row
|
||||
and ple.against_voucher_type == "Employee Advance"
|
||||
and self.filters.handle_employee_advances
|
||||
):
|
||||
_d = self.build_voucher_dict(ple)
|
||||
_d.voucher_type = ple.against_voucher_type
|
||||
_d.voucher_no = ple.against_voucher_no
|
||||
row = self.voucher_balance[key] = _d
|
||||
|
||||
if not row:
|
||||
# no invoice, this is an invoice / stand-alone payment / credit note
|
||||
if self.filters.get("ignore_accounts"):
|
||||
|
||||
@@ -46,5 +46,11 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
||||
options: "Asset",
|
||||
depends_on: "eval: doc.group_by == 'Asset'",
|
||||
},
|
||||
{
|
||||
fieldname: "finance_book",
|
||||
label: __("Finance Book"),
|
||||
fieldtype: "Link",
|
||||
options: "Finance Book",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -69,6 +69,9 @@ def get_asset_categories_for_grouped_by_category(filters):
|
||||
condition = ""
|
||||
if filters.get("asset_category"):
|
||||
condition += " and asset_category = %(asset_category)s"
|
||||
if filters.get("finance_book"):
|
||||
condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = a.name and ads.finance_book = %(finance_book)s)"
|
||||
|
||||
# nosemgrep
|
||||
return frappe.db.sql(
|
||||
f"""
|
||||
@@ -119,6 +122,7 @@ def get_asset_categories_for_grouped_by_category(filters):
|
||||
"from_date": filters.from_date,
|
||||
"company": filters.company,
|
||||
"asset_category": filters.get("asset_category"),
|
||||
"finance_book": filters.get("finance_book"),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
@@ -128,6 +132,10 @@ def get_asset_details_for_grouped_by_category(filters):
|
||||
condition = ""
|
||||
if filters.get("asset"):
|
||||
condition += " and name = %(asset)s"
|
||||
if filters.get("finance_book"):
|
||||
condition += " and exists (select 1 from `tabAsset Depreciation Schedule` ads where ads.asset = `tabAsset`.name and ads.finance_book = %(finance_book)s)"
|
||||
|
||||
# nosemgrep
|
||||
return frappe.db.sql(
|
||||
f"""
|
||||
SELECT name,
|
||||
@@ -176,6 +184,7 @@ def get_asset_details_for_grouped_by_category(filters):
|
||||
"from_date": filters.from_date,
|
||||
"company": filters.company,
|
||||
"asset": filters.get("asset"),
|
||||
"finance_book": filters.get("finance_book"),
|
||||
},
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
@@ -46,4 +46,20 @@ frappe.query_reports["Bank Reconciliation Statement"] = {
|
||||
fieldtype: "Check",
|
||||
},
|
||||
],
|
||||
formatter: function (value, row, column, data, default_formatter, filter) {
|
||||
if (column.fieldname == "payment_entry" && value == "Cheques and Deposits incorrectly cleared") {
|
||||
column.link_onclick =
|
||||
"frappe.query_reports['Bank Reconciliation Statement'].open_utility_report()";
|
||||
}
|
||||
return default_formatter(value, row, column, data);
|
||||
},
|
||||
open_utility_report: function () {
|
||||
frappe.route_options = {
|
||||
company: frappe.query_report.get_filter_value("company"),
|
||||
account: frappe.query_report.get_filter_value("account"),
|
||||
report_date: frappe.query_report.get_filter_value("report_date"),
|
||||
};
|
||||
frappe.open_in_new_tab = true;
|
||||
frappe.set_route("query-report", "Cheques and Deposits Incorrectly cleared");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -154,8 +154,8 @@ def get_payment_entries(filters):
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no, reference_date as ref_date,
|
||||
if(paid_to=%(account)s, received_amount, 0) as debit,
|
||||
if(paid_from=%(account)s, paid_amount, 0) as credit,
|
||||
if(paid_to=%(account)s, received_amount_after_tax, 0) as debit,
|
||||
if(paid_from=%(account)s, paid_amount_after_tax, 0) as credit,
|
||||
posting_date, ifnull(party,if(paid_from=%(account)s,paid_to,paid_from)) as against_account, clearance_date,
|
||||
if(paid_to=%(account)s, paid_to_account_currency, paid_from_account_currency) as account_currency
|
||||
from `tabPayment Entry`
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Cash Flow"] = $.extend({}, erpnext.financial_statements);
|
||||
frappe.query_reports["Cash Flow"] = $.extend(erpnext.financial_statements, {
|
||||
name_field: "section",
|
||||
parent_field: "parent_section",
|
||||
});
|
||||
|
||||
erpnext.utils.add_dimensions("Cash Flow", 10);
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ def execute(filters=None):
|
||||
company=filters.company,
|
||||
)
|
||||
|
||||
cash_flow_accounts = get_cash_flow_accounts()
|
||||
cash_flow_sections = get_cash_flow_accounts()
|
||||
|
||||
# compute net profit / loss
|
||||
income = get_data(
|
||||
@@ -60,14 +60,14 @@ def execute(filters=None):
|
||||
summary_data = {}
|
||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||
|
||||
for cash_flow_account in cash_flow_accounts:
|
||||
for cash_flow_section in cash_flow_sections:
|
||||
section_data = []
|
||||
data.append(
|
||||
{
|
||||
"account_name": cash_flow_account["section_header"],
|
||||
"parent_account": None,
|
||||
"section_name": "'" + cash_flow_section["section_header"] + "'",
|
||||
"parent_section": None,
|
||||
"indent": 0.0,
|
||||
"account": cash_flow_account["section_header"],
|
||||
"section": cash_flow_section["section_header"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -75,31 +75,40 @@ def execute(filters=None):
|
||||
# add first net income in operations section
|
||||
if net_profit_loss:
|
||||
net_profit_loss.update(
|
||||
{"indent": 1, "parent_account": cash_flow_accounts[0]["section_header"]}
|
||||
{"indent": 1, "parent_section": cash_flow_sections[0]["section_header"]}
|
||||
)
|
||||
data.append(net_profit_loss)
|
||||
section_data.append(net_profit_loss)
|
||||
|
||||
for account in cash_flow_account["account_types"]:
|
||||
account_data = get_account_type_based_data(
|
||||
filters.company, account["account_type"], period_list, filters.accumulated_values, filters
|
||||
for row in cash_flow_section["account_types"]:
|
||||
row_data = get_account_type_based_data(
|
||||
filters.company, row["account_type"], period_list, filters.accumulated_values, filters
|
||||
)
|
||||
account_data.update(
|
||||
accounts = frappe.get_all(
|
||||
"Account",
|
||||
filters={
|
||||
"account_type": row["account_type"],
|
||||
"is_group": 0,
|
||||
},
|
||||
pluck="name",
|
||||
)
|
||||
row_data.update(
|
||||
{
|
||||
"account_name": account["label"],
|
||||
"account": account["label"],
|
||||
"section_name": row["label"],
|
||||
"section": row["label"],
|
||||
"indent": 1,
|
||||
"parent_account": cash_flow_account["section_header"],
|
||||
"accounts": accounts,
|
||||
"parent_section": cash_flow_section["section_header"],
|
||||
"currency": company_currency,
|
||||
}
|
||||
)
|
||||
data.append(account_data)
|
||||
section_data.append(account_data)
|
||||
data.append(row_data)
|
||||
section_data.append(row_data)
|
||||
|
||||
add_total_row_account(
|
||||
data,
|
||||
section_data,
|
||||
cash_flow_account["section_footer"],
|
||||
cash_flow_section["section_footer"],
|
||||
period_list,
|
||||
company_currency,
|
||||
summary_data,
|
||||
@@ -109,7 +118,7 @@ def execute(filters=None):
|
||||
add_total_row_account(
|
||||
data, data, _("Net Change in Cash"), period_list, company_currency, summary_data, filters
|
||||
)
|
||||
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company)
|
||||
columns = get_columns(filters.periodicity, period_list, filters.accumulated_values, filters.company, True)
|
||||
|
||||
chart = get_chart_data(columns, data)
|
||||
|
||||
@@ -217,8 +226,8 @@ def get_start_date(period, accumulated_values, company):
|
||||
|
||||
def add_total_row_account(out, data, label, period_list, currency, summary_data, filters, consolidated=False):
|
||||
total_row = {
|
||||
"account_name": "'" + _("{0}").format(label) + "'",
|
||||
"account": "'" + _("{0}").format(label) + "'",
|
||||
"section_name": "'" + _("{0}").format(label) + "'",
|
||||
"section": "'" + _("{0}").format(label) + "'",
|
||||
"currency": currency,
|
||||
}
|
||||
|
||||
@@ -229,7 +238,7 @@ def add_total_row_account(out, data, label, period_list, currency, summary_data,
|
||||
period_list = get_filtered_list_for_consolidated_report(filters, period_list)
|
||||
|
||||
for row in data:
|
||||
if row.get("parent_account"):
|
||||
if row.get("parent_section"):
|
||||
for period in period_list:
|
||||
key = period if consolidated else period["key"]
|
||||
total_row.setdefault(key, 0.0)
|
||||
@@ -254,13 +263,14 @@ def get_report_summary(summary_data, currency):
|
||||
|
||||
def get_chart_data(columns, data):
|
||||
labels = [d.get("label") for d in columns[2:]]
|
||||
print(data)
|
||||
datasets = [
|
||||
{
|
||||
"name": account.get("account").replace("'", ""),
|
||||
"values": [account.get(d.get("fieldname")) for d in columns[2:]],
|
||||
"name": section.get("section").replace("'", ""),
|
||||
"values": [section.get(d.get("fieldname")) for d in columns[2:]],
|
||||
}
|
||||
for account in data
|
||||
if account.get("parent_account") is None and account.get("currency")
|
||||
for section in data
|
||||
if section.get("parent_section") is None and section.get("currency")
|
||||
]
|
||||
datasets = datasets[:-1]
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.query_reports["Cheques and Deposits Incorrectly cleared"] = {
|
||||
filters: [
|
||||
{
|
||||
fieldname: "company",
|
||||
label: __("Company"),
|
||||
fieldtype: "Link",
|
||||
options: "Company",
|
||||
reqd: 1,
|
||||
default: frappe.defaults.get_user_default("Company"),
|
||||
},
|
||||
{
|
||||
fieldname: "account",
|
||||
label: __("Bank Account"),
|
||||
fieldtype: "Link",
|
||||
options: "Account",
|
||||
default: frappe.defaults.get_user_default("Company")
|
||||
? locals[":Company"][frappe.defaults.get_user_default("Company")]["default_bank_account"]
|
||||
: "",
|
||||
reqd: 1,
|
||||
get_query: function () {
|
||||
var company = frappe.query_report.get_filter_value("company");
|
||||
return {
|
||||
query: "erpnext.controllers.queries.get_account_list",
|
||||
filters: [
|
||||
["Account", "account_type", "in", "Bank, Cash"],
|
||||
["Account", "is_group", "=", 0],
|
||||
["Account", "disabled", "=", 0],
|
||||
["Account", "company", "=", company],
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldname: "report_date",
|
||||
label: __("Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.datetime.get_today(),
|
||||
reqd: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2024-07-30 17:20:07.570971",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2024-07-30 17:20:07.570971",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Cheques and Deposits Incorrectly cleared",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Payment Entry",
|
||||
"report_name": "Cheques and Deposits Incorrectly cleared",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Accounts Manager"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb
|
||||
from frappe.query_builder import CustomFunction
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
columns = get_columns()
|
||||
data = build_data(filters)
|
||||
return columns, data
|
||||
|
||||
|
||||
def build_payment_entry_dict(row: dict) -> dict:
|
||||
row_dict = frappe._dict()
|
||||
row_dict.update(
|
||||
{
|
||||
"payment_document": row.get("doctype"),
|
||||
"payment_entry": row.get("name"),
|
||||
"posting_date": row.get("posting_date"),
|
||||
"clearance_date": row.get("clearance_date"),
|
||||
}
|
||||
)
|
||||
if row.get("payment_type") == "Receive" and row.get("party_type") in ["Customer", "Supplier"]:
|
||||
row_dict.update(
|
||||
{
|
||||
"debit": row.get("amount"),
|
||||
"credit": 0,
|
||||
}
|
||||
)
|
||||
else:
|
||||
row_dict.update(
|
||||
{
|
||||
"debit": 0,
|
||||
"credit": row.get("amount"),
|
||||
}
|
||||
)
|
||||
return row_dict
|
||||
|
||||
|
||||
def build_journal_entry_dict(row: dict) -> dict:
|
||||
row_dict = frappe._dict()
|
||||
row_dict.update(
|
||||
{
|
||||
"payment_document": row.get("doctype"),
|
||||
"payment_entry": row.get("name"),
|
||||
"posting_date": row.get("posting_date"),
|
||||
"clearance_date": row.get("clearance_date"),
|
||||
"debit": row.get("debit_in_account_currency"),
|
||||
"credit": row.get("credit_in_account_currency"),
|
||||
}
|
||||
)
|
||||
return row_dict
|
||||
|
||||
|
||||
def build_data(filters):
|
||||
vouchers = get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters)
|
||||
data = []
|
||||
for x in vouchers:
|
||||
if x.doctype == "Payment Entry":
|
||||
data.append(build_payment_entry_dict(x))
|
||||
elif x.doctype == "Journal Entry":
|
||||
data.append(build_journal_entry_dict(x))
|
||||
return data
|
||||
|
||||
|
||||
def get_amounts_not_reflected_in_system_for_bank_reconciliation_statement(filters):
|
||||
je = qb.DocType("Journal Entry")
|
||||
jea = qb.DocType("Journal Entry Account")
|
||||
doctype_name = ConstantColumn("Journal Entry")
|
||||
|
||||
journals = (
|
||||
qb.from_(je)
|
||||
.inner_join(jea)
|
||||
.on(je.name == jea.parent)
|
||||
.select(
|
||||
doctype_name.as_("doctype"),
|
||||
je.name,
|
||||
jea.debit_in_account_currency,
|
||||
jea.credit_in_account_currency,
|
||||
je.posting_date,
|
||||
je.clearance_date,
|
||||
)
|
||||
.where(
|
||||
je.docstatus.eq(1)
|
||||
& jea.account.eq(filters.account)
|
||||
& je.posting_date.gt(filters.report_date)
|
||||
& je.clearance_date.lte(filters.report_date)
|
||||
& (je.is_opening.isnull() | je.is_opening.eq("No"))
|
||||
)
|
||||
.run(as_dict=1)
|
||||
)
|
||||
|
||||
ifelse = CustomFunction("IF", ["condition", "then", "else"])
|
||||
pe = qb.DocType("Payment Entry")
|
||||
doctype_name = ConstantColumn("Payment Entry")
|
||||
payments = (
|
||||
qb.from_(pe)
|
||||
.select(
|
||||
doctype_name.as_("doctype"),
|
||||
pe.name,
|
||||
ifelse(pe.paid_from.eq(filters.account), pe.paid_amount, pe.received_amount).as_("amount"),
|
||||
pe.payment_type,
|
||||
pe.party_type,
|
||||
pe.posting_date,
|
||||
pe.clearance_date,
|
||||
)
|
||||
.where(
|
||||
pe.docstatus.eq(1)
|
||||
& (pe.paid_from.eq(filters.account) | pe.paid_to.eq(filters.account))
|
||||
& pe.posting_date.gt(filters.report_date)
|
||||
& pe.clearance_date.lte(filters.report_date)
|
||||
)
|
||||
.run(as_dict=1)
|
||||
)
|
||||
|
||||
return journals + payments
|
||||
|
||||
|
||||
def get_columns():
|
||||
return [
|
||||
{
|
||||
"fieldname": "payment_document",
|
||||
"label": _("Payment Document Type"),
|
||||
"fieldtype": "Data",
|
||||
"width": 220,
|
||||
},
|
||||
{
|
||||
"fieldname": "payment_entry",
|
||||
"label": _("Payment Document"),
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "payment_document",
|
||||
"width": 220,
|
||||
},
|
||||
{
|
||||
"fieldname": "debit",
|
||||
"label": _("Debit"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "account_currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"fieldname": "credit",
|
||||
"label": _("Credit"),
|
||||
"fieldtype": "Currency",
|
||||
"options": "account_currency",
|
||||
"width": 120,
|
||||
},
|
||||
{"fieldname": "posting_date", "label": _("Posting Date"), "fieldtype": "Date", "width": 110},
|
||||
{"fieldname": "clearance_date", "label": _("Clearance Date"), "fieldtype": "Date", "width": 110},
|
||||
]
|
||||
@@ -333,8 +333,8 @@ def filter_out_zero_value_rows(data, parent_children_map, show_zero_values=False
|
||||
|
||||
def add_total_row(out, root_type, balance_must_be, period_list, company_currency):
|
||||
total_row = {
|
||||
"account_name": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
|
||||
"account": _("Total {0} ({1})").format(_(root_type), _(balance_must_be)),
|
||||
"account_name": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
|
||||
"account": "'" + _("Total {0} ({1})").format(_(root_type), _(balance_must_be)) + "'",
|
||||
"currency": company_currency,
|
||||
"opening_balance": 0.0,
|
||||
}
|
||||
@@ -613,11 +613,11 @@ def get_cost_centers_with_children(cost_centers):
|
||||
return list(set(all_cost_centers))
|
||||
|
||||
|
||||
def get_columns(periodicity, period_list, accumulated_values=1, company=None):
|
||||
def get_columns(periodicity, period_list, accumulated_values=1, company=None, cash_flow=False):
|
||||
columns = [
|
||||
{
|
||||
"fieldname": "account",
|
||||
"label": _("Account"),
|
||||
"fieldname": "stub",
|
||||
"label": _("Account") if not cash_flow else _("Section"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 300,
|
||||
|
||||
@@ -248,7 +248,10 @@ def get_conditions(filters):
|
||||
as_list=True,
|
||||
)
|
||||
if system_generated_cr_dr_journals:
|
||||
filters.update({"voucher_no_not_in": [x[0] for x in system_generated_cr_dr_journals]})
|
||||
vouchers_to_ignore = (filters.get("voucher_no_not_in") or []) + [
|
||||
x[0] for x in system_generated_cr_dr_journals
|
||||
]
|
||||
filters.update({"voucher_no_not_in": vouchers_to_ignore})
|
||||
|
||||
if filters.get("voucher_no_not_in"):
|
||||
conditions.append("voucher_no not in %(voucher_no_not_in)s")
|
||||
|
||||
@@ -130,6 +130,7 @@ class PaymentLedger:
|
||||
)
|
||||
|
||||
def get_columns(self):
|
||||
company_currency = frappe.get_cached_value("Company", self.filters.get("company"), "default_currency")
|
||||
options = None
|
||||
self.columns.append(
|
||||
dict(
|
||||
@@ -194,7 +195,7 @@ class PaymentLedger:
|
||||
label=_("Amount"),
|
||||
fieldname="amount",
|
||||
fieldtype="Currency",
|
||||
options="Company:company:default_currency",
|
||||
options=company_currency,
|
||||
width="100",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -336,7 +336,7 @@ def get_tds_docs(filters):
|
||||
def get_tds_docs_query(filters, bank_accounts, tds_accounts):
|
||||
if not tds_accounts:
|
||||
frappe.throw(
|
||||
_("No {0} Accounts found for this company.").format(frappe.bold("Tax Withholding")),
|
||||
_("No {0} Accounts found for this company.").format(frappe.bold(_("Tax Withholding"))),
|
||||
title=_("Accounts Missing Error"),
|
||||
)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
|
||||
@@ -53,18 +53,28 @@ GL_REPOSTING_CHUNK = 100
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_fiscal_year(
|
||||
date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
|
||||
date=None,
|
||||
fiscal_year=None,
|
||||
label="Date",
|
||||
verbose=1,
|
||||
company=None,
|
||||
as_dict=False,
|
||||
boolean=None,
|
||||
raise_on_missing=True,
|
||||
):
|
||||
if isinstance(raise_on_missing, str):
|
||||
raise_on_missing = loads(raise_on_missing)
|
||||
|
||||
# backwards compat
|
||||
if isinstance(boolean, str):
|
||||
boolean = loads(boolean)
|
||||
if boolean is not None:
|
||||
raise_on_missing = not boolean
|
||||
|
||||
fiscal_years = get_fiscal_years(
|
||||
date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
|
||||
date, fiscal_year, label, verbose, company, as_dict=as_dict, raise_on_missing=raise_on_missing
|
||||
)
|
||||
if boolean:
|
||||
return fiscal_years
|
||||
else:
|
||||
return fiscal_years[0]
|
||||
return False if not fiscal_years else fiscal_years[0]
|
||||
|
||||
|
||||
def get_fiscal_years(
|
||||
@@ -74,8 +84,48 @@ def get_fiscal_years(
|
||||
verbose=1,
|
||||
company=None,
|
||||
as_dict=False,
|
||||
boolean=False,
|
||||
boolean=None,
|
||||
raise_on_missing=True,
|
||||
):
|
||||
if transaction_date:
|
||||
transaction_date = getdate(transaction_date)
|
||||
# backwards compat
|
||||
if boolean is not None:
|
||||
raise_on_missing = not boolean
|
||||
|
||||
all_fiscal_years = _get_fiscal_years(company=company)
|
||||
|
||||
# No restricting selectors
|
||||
if not transaction_date and not fiscal_year:
|
||||
return all_fiscal_years
|
||||
|
||||
for fy in all_fiscal_years:
|
||||
if (fiscal_year and fy.name == fiscal_year) or (
|
||||
transaction_date
|
||||
and getdate(fy.year_start_date) <= transaction_date
|
||||
and getdate(fy.year_end_date) >= transaction_date
|
||||
):
|
||||
if as_dict:
|
||||
return (fy,)
|
||||
else:
|
||||
return ((fy.name, fy.year_start_date, fy.year_end_date),)
|
||||
|
||||
# No match for restricting selectors
|
||||
if raise_on_missing:
|
||||
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(
|
||||
label, formatdate(transaction_date)
|
||||
)
|
||||
if company:
|
||||
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
|
||||
|
||||
if verbose == 1:
|
||||
frappe.msgprint(error_msg)
|
||||
|
||||
raise FiscalYearError(error_msg)
|
||||
return []
|
||||
|
||||
|
||||
def _get_fiscal_years(company=None):
|
||||
fiscal_years = frappe.cache().hget("fiscal_years", company) or []
|
||||
|
||||
if not fiscal_years:
|
||||
@@ -86,9 +136,6 @@ def get_fiscal_years(
|
||||
frappe.qb.from_(FY).select(FY.name, FY.year_start_date, FY.year_end_date).where(FY.disabled == 0)
|
||||
)
|
||||
|
||||
if fiscal_year:
|
||||
query = query.where(FY.name == fiscal_year)
|
||||
|
||||
if company:
|
||||
FYC = DocType("Fiscal Year Company")
|
||||
query = query.where(
|
||||
@@ -105,42 +152,7 @@ def get_fiscal_years(
|
||||
fiscal_years = query.run(as_dict=True)
|
||||
|
||||
frappe.cache().hset("fiscal_years", company, fiscal_years)
|
||||
|
||||
if not transaction_date and not fiscal_year:
|
||||
return fiscal_years
|
||||
|
||||
if transaction_date:
|
||||
transaction_date = getdate(transaction_date)
|
||||
|
||||
for fy in fiscal_years:
|
||||
matched = False
|
||||
if fiscal_year and fy.name == fiscal_year:
|
||||
matched = True
|
||||
|
||||
if (
|
||||
transaction_date
|
||||
and getdate(fy.year_start_date) <= transaction_date
|
||||
and getdate(fy.year_end_date) >= transaction_date
|
||||
):
|
||||
matched = True
|
||||
|
||||
if matched:
|
||||
if as_dict:
|
||||
return (fy,)
|
||||
else:
|
||||
return ((fy.name, fy.year_start_date, fy.year_end_date),)
|
||||
|
||||
error_msg = _("""{0} {1} is not in any active Fiscal Year""").format(label, formatdate(transaction_date))
|
||||
if company:
|
||||
error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
|
||||
|
||||
if boolean:
|
||||
return False
|
||||
|
||||
if verbose == 1:
|
||||
frappe.msgprint(error_msg)
|
||||
|
||||
raise FiscalYearError(error_msg)
|
||||
return fiscal_years
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -670,6 +670,11 @@ frappe.ui.form.on("Asset", {
|
||||
if (item.asset_location) {
|
||||
frm.set_value("location", item.asset_location);
|
||||
}
|
||||
if (doctype === "Purchase Receipt") {
|
||||
frm.set_value("purchase_receipt_item", item.name);
|
||||
} else if (doctype === "Purchase Invoice") {
|
||||
frm.set_value("purchase_invoice_item", item.name);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -33,14 +33,16 @@
|
||||
"dimension_col_break",
|
||||
"purchase_details_section",
|
||||
"purchase_receipt",
|
||||
"purchase_receipt_item",
|
||||
"purchase_invoice",
|
||||
"purchase_invoice_item",
|
||||
"purchase_date",
|
||||
"available_for_use_date",
|
||||
"total_asset_cost",
|
||||
"additional_asset_cost",
|
||||
"column_break_23",
|
||||
"gross_purchase_amount",
|
||||
"asset_quantity",
|
||||
"purchase_date",
|
||||
"additional_asset_cost",
|
||||
"total_asset_cost",
|
||||
"section_break_23",
|
||||
"calculate_depreciation",
|
||||
"column_break_33",
|
||||
@@ -536,6 +538,20 @@
|
||||
"fieldname": "opening_number_of_booked_depreciations",
|
||||
"fieldtype": "Int",
|
||||
"label": "Opening Number of Booked Depreciations"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_receipt_item",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Purchase Receipt Item",
|
||||
"options": "Purchase Receipt Item"
|
||||
},
|
||||
{
|
||||
"fieldname": "purchase_invoice_item",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 1,
|
||||
"label": "Purchase Invoice Item",
|
||||
"options": "Purchase Invoice Item"
|
||||
}
|
||||
],
|
||||
"idx": 72,
|
||||
@@ -579,7 +595,7 @@
|
||||
"link_fieldname": "target_asset"
|
||||
}
|
||||
],
|
||||
"modified": "2024-08-01 16:39:09.340973",
|
||||
"modified": "2024-08-26 23:28:29.095139",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
|
||||
@@ -94,7 +94,9 @@ class Asset(AccountsController):
|
||||
purchase_amount: DF.Currency
|
||||
purchase_date: DF.Date | None
|
||||
purchase_invoice: DF.Link | None
|
||||
purchase_invoice_item: DF.Link | None
|
||||
purchase_receipt: DF.Link | None
|
||||
purchase_receipt_item: DF.Link | None
|
||||
split_from: DF.Link | None
|
||||
status: DF.Literal[
|
||||
"Draft",
|
||||
@@ -670,7 +672,7 @@ class Asset(AccountsController):
|
||||
if not fixed_asset_account:
|
||||
frappe.throw(
|
||||
_("Set {0} in asset category {1} for company {2}").format(
|
||||
frappe.bold("Fixed Asset Account"),
|
||||
frappe.bold(_("Fixed Asset Account")),
|
||||
frappe.bold(self.asset_category),
|
||||
frappe.bold(self.company),
|
||||
),
|
||||
@@ -692,12 +694,17 @@ class Asset(AccountsController):
|
||||
return cwip_account
|
||||
|
||||
def make_gl_entries(self):
|
||||
if self.check_asset_capitalization_gl_entries():
|
||||
return
|
||||
|
||||
gl_entries = []
|
||||
|
||||
purchase_document = self.get_purchase_document()
|
||||
fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
|
||||
|
||||
if purchase_document and self.purchase_amount and getdate(self.available_for_use_date) <= getdate():
|
||||
if (self.is_composite_asset or (purchase_document and self.purchase_amount)) and getdate(
|
||||
self.available_for_use_date
|
||||
) <= getdate():
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
@@ -734,6 +741,24 @@ class Asset(AccountsController):
|
||||
make_gl_entries(gl_entries)
|
||||
self.db_set("booked_fixed_asset", 1)
|
||||
|
||||
def check_asset_capitalization_gl_entries(self):
|
||||
if self.is_composite_asset:
|
||||
result = frappe.db.get_value(
|
||||
"Asset Capitalization",
|
||||
{"target_asset": self.name, "docstatus": 1},
|
||||
["name", "target_fixed_asset_account"],
|
||||
)
|
||||
|
||||
if result:
|
||||
asset_capitalization, target_fixed_asset_account = result
|
||||
# Check GL entries for the retrieved Asset Capitalization and target fixed asset account
|
||||
return has_gl_entries(
|
||||
"Asset Capitalization", asset_capitalization, target_fixed_asset_account
|
||||
)
|
||||
# return if there are no submitted capitalization for given asset
|
||||
return True
|
||||
return False
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_depreciation_rate(self, args, on_validate=False):
|
||||
if isinstance(args, str):
|
||||
@@ -780,6 +805,22 @@ class Asset(AccountsController):
|
||||
return flt((100 * (1 - depreciation_rate)), float_precision)
|
||||
|
||||
|
||||
def has_gl_entries(doctype, docname, target_account):
|
||||
gl_entry = frappe.qb.DocType("GL Entry")
|
||||
gl_entries = (
|
||||
frappe.qb.from_(gl_entry)
|
||||
.select(gl_entry.account)
|
||||
.where(
|
||||
(gl_entry.voucher_type == doctype)
|
||||
& (gl_entry.voucher_no == docname)
|
||||
& (gl_entry.debit != 0)
|
||||
& (gl_entry.account == target_account)
|
||||
)
|
||||
.run(as_dict=True)
|
||||
)
|
||||
return len(gl_entries) > 0
|
||||
|
||||
|
||||
def update_maintenance_status():
|
||||
assets = frappe.get_all(
|
||||
"Asset", filters={"docstatus": 1, "maintenance_required": 1, "disposal_date": ("is", "not set")}
|
||||
@@ -905,7 +946,7 @@ def transfer_asset(args):
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_item_details(item_code, asset_category, gross_purchase_amount):
|
||||
asset_category_doc = frappe.get_doc("Asset Category", asset_category)
|
||||
asset_category_doc = frappe.get_cached_doc("Asset Category", asset_category)
|
||||
books = []
|
||||
for d in asset_category_doc.finance_books:
|
||||
books.append(
|
||||
|
||||
@@ -234,7 +234,7 @@ class TestAsset(AssetSetup):
|
||||
pro_rata_amount, _, _ = _get_pro_rata_amt(
|
||||
asset.finance_books[0],
|
||||
9000,
|
||||
get_last_day(add_months(purchase_date, 1)),
|
||||
add_days(get_last_day(add_months(purchase_date, 1)), 1),
|
||||
date,
|
||||
original_schedule_date=get_last_day(nowdate()),
|
||||
)
|
||||
@@ -320,7 +320,7 @@ class TestAsset(AssetSetup):
|
||||
pro_rata_amount, _, _ = _get_pro_rata_amt(
|
||||
asset.finance_books[0],
|
||||
9000,
|
||||
get_last_day(add_months(purchase_date, 1)),
|
||||
add_days(get_last_day(add_months(purchase_date, 1)), 1),
|
||||
date,
|
||||
original_schedule_date=get_last_day(nowdate()),
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.s
|
||||
|
||||
onload() {
|
||||
this.setup_queries();
|
||||
erpnext.accounts.dimensions.setup_dimension_filters(this.frm, this.frm.doctype);
|
||||
}
|
||||
|
||||
refresh() {
|
||||
|
||||
@@ -317,7 +317,16 @@ class AssetCapitalization(StockController):
|
||||
if not self.target_is_fixed_asset and not self.get("asset_items"):
|
||||
frappe.throw(_("Consumed Asset Items is mandatory for Decapitalization"))
|
||||
|
||||
if not (self.get("stock_items") or self.get("asset_items") or self.get("service_items")):
|
||||
if self.capitalization_method == "Create a new composite asset" and not (
|
||||
self.get("stock_items") or self.get("asset_items")
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Consumed Stock Items or Consumed Asset Items are mandatory for creating new composite asset"
|
||||
)
|
||||
)
|
||||
|
||||
elif not (self.get("stock_items") or self.get("asset_items") or self.get("service_items")):
|
||||
frappe.throw(
|
||||
_(
|
||||
"Consumed Stock Items, Consumed Asset Items or Consumed Service Items is mandatory for Capitalization"
|
||||
@@ -460,13 +469,24 @@ class AssetCapitalization(StockController):
|
||||
self.get_gl_entries_for_consumed_asset_items(gl_entries, target_account, target_against, precision)
|
||||
self.get_gl_entries_for_consumed_service_items(gl_entries, target_account, target_against, precision)
|
||||
|
||||
self.get_gl_entries_for_target_item(gl_entries, target_against, precision)
|
||||
self.get_gl_entries_for_target_item(gl_entries, target_account, target_against, precision)
|
||||
|
||||
return gl_entries
|
||||
|
||||
def get_target_account(self):
|
||||
if self.target_is_fixed_asset:
|
||||
return self.target_fixed_asset_account
|
||||
from erpnext.assets.doctype.asset.asset import is_cwip_accounting_enabled
|
||||
|
||||
asset_category = frappe.get_cached_value("Asset", self.target_asset, "asset_category")
|
||||
if is_cwip_accounting_enabled(asset_category):
|
||||
target_account = get_asset_category_account(
|
||||
"capital_work_in_progress_account",
|
||||
asset_category=asset_category,
|
||||
company=self.company,
|
||||
)
|
||||
return target_account if target_account else self.target_fixed_asset_account
|
||||
else:
|
||||
return self.target_fixed_asset_account
|
||||
else:
|
||||
return self.warehouse_account[self.target_warehouse]["account"]
|
||||
|
||||
@@ -554,13 +574,13 @@ class AssetCapitalization(StockController):
|
||||
)
|
||||
)
|
||||
|
||||
def get_gl_entries_for_target_item(self, gl_entries, target_against, precision):
|
||||
def get_gl_entries_for_target_item(self, gl_entries, target_account, target_against, precision):
|
||||
if self.target_is_fixed_asset:
|
||||
# Capitalization
|
||||
gl_entries.append(
|
||||
self.get_gl_dict(
|
||||
{
|
||||
"account": self.target_fixed_asset_account,
|
||||
"account": target_account,
|
||||
"against": ", ".join(target_against),
|
||||
"remarks": self.get("remarks") or _("Accounting Entry for Asset"),
|
||||
"debit": flt(self.total_value, precision),
|
||||
|
||||
@@ -31,6 +31,12 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
def test_capitalization_with_perpetual_inventory(self):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
set_depreciation_settings_in_company(company=company)
|
||||
name = frappe.db.get_value(
|
||||
"Asset Category Account",
|
||||
filters={"parent": "Computers", "company_name": company},
|
||||
fieldname=["name"],
|
||||
)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||
|
||||
# Variables
|
||||
consumed_asset_value = 100000
|
||||
@@ -187,9 +193,10 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
# Test General Ledger Entries
|
||||
default_expense_account = frappe.db.get_value("Company", company, "default_expense_account")
|
||||
expected_gle = {
|
||||
"_Test Fixed Asset - _TC": 3000,
|
||||
"Expenses Included In Asset Valuation - _TC": -1000,
|
||||
default_expense_account: -2000,
|
||||
"_Test Fixed Asset - _TC": -100000.0,
|
||||
default_expense_account: -2000.0,
|
||||
"CWIP Account - _TC": 103000.0,
|
||||
"Expenses Included In Asset Valuation - _TC": -1000.0,
|
||||
}
|
||||
actual_gle = get_actual_gle_dict(asset_capitalization.name)
|
||||
|
||||
@@ -214,6 +221,12 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
def test_capitalization_with_wip_composite_asset(self):
|
||||
company = "_Test Company with perpetual inventory"
|
||||
set_depreciation_settings_in_company(company=company)
|
||||
name = frappe.db.get_value(
|
||||
"Asset Category Account",
|
||||
filters={"parent": "Computers", "company_name": company},
|
||||
fieldname=["name"],
|
||||
)
|
||||
frappe.db.set_value("Asset Category Account", name, "capital_work_in_progress_account", "")
|
||||
|
||||
stock_rate = 1000
|
||||
stock_qty = 2
|
||||
@@ -424,7 +437,7 @@ class TestAssetCapitalization(unittest.TestCase):
|
||||
self.assertEqual(target_asset.purchase_amount, total_amount)
|
||||
|
||||
expected_gle = {
|
||||
"_Test Fixed Asset - _TC": 1000.0,
|
||||
"CWIP Account - _TC": 1000.0,
|
||||
"Expenses Included In Asset Valuation - _TC": -1000.0,
|
||||
}
|
||||
|
||||
|
||||
@@ -291,7 +291,9 @@ class AssetDepreciationSchedule(Document):
|
||||
if skip_row:
|
||||
continue
|
||||
|
||||
schedule_date = add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
|
||||
schedule_date = get_last_day(
|
||||
add_months(row.depreciation_start_date, n * cint(row.frequency_of_depreciation))
|
||||
)
|
||||
if not current_fiscal_year_end_date:
|
||||
current_fiscal_year_end_date = get_fiscal_year(row.depreciation_start_date)[2]
|
||||
elif getdate(schedule_date) > getdate(current_fiscal_year_end_date):
|
||||
@@ -330,8 +332,10 @@ class AssetDepreciationSchedule(Document):
|
||||
getdate(asset_doc.available_for_use_date),
|
||||
(asset_doc.opening_number_of_booked_depreciations * row.frequency_of_depreciation),
|
||||
)
|
||||
if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
|
||||
from_date = get_last_day(from_date)
|
||||
if self.depreciation_schedule:
|
||||
from_date = self.depreciation_schedule[-1].schedule_date
|
||||
from_date = add_days(self.depreciation_schedule[-1].schedule_date, 1)
|
||||
|
||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||
row,
|
||||
@@ -353,9 +357,8 @@ class AssetDepreciationSchedule(Document):
|
||||
and not self.opening_accumulated_depreciation
|
||||
and not self.flags.wdv_it_act_applied
|
||||
):
|
||||
from_date = add_days(
|
||||
asset_doc.available_for_use_date, -1
|
||||
) # needed to calc depr amount for available_for_use_date too
|
||||
from_date = asset_doc.available_for_use_date
|
||||
# needed to calc depr amount for available_for_use_date too
|
||||
depreciation_amount, days, months = _get_pro_rata_amt(
|
||||
row,
|
||||
depreciation_amount,
|
||||
@@ -406,6 +409,8 @@ class AssetDepreciationSchedule(Document):
|
||||
(n + self.opening_number_of_booked_depreciations)
|
||||
* cint(row.frequency_of_depreciation),
|
||||
)
|
||||
if is_last_day_of_the_month(getdate(asset_doc.available_for_use_date)):
|
||||
asset_doc.to_date = get_last_day(asset_doc.to_date)
|
||||
|
||||
depreciation_amount_without_pro_rata = depreciation_amount
|
||||
|
||||
@@ -421,7 +426,7 @@ class AssetDepreciationSchedule(Document):
|
||||
depreciation_amount_without_pro_rata, depreciation_amount
|
||||
)
|
||||
|
||||
schedule_date = add_days(schedule_date, days)
|
||||
schedule_date = add_days(schedule_date, days - 1)
|
||||
|
||||
if not depreciation_amount:
|
||||
continue
|
||||
@@ -504,7 +509,10 @@ class AssetDepreciationSchedule(Document):
|
||||
continue
|
||||
|
||||
if not accumulated_depreciation:
|
||||
if i > 0 and asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||
if i > 0 and (
|
||||
asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment
|
||||
or asset_doc.flags.increase_in_asset_value_due_to_repair
|
||||
):
|
||||
accumulated_depreciation = self.get("depreciation_schedule")[
|
||||
i - 1
|
||||
].accumulated_depreciation_amount
|
||||
@@ -553,9 +561,11 @@ def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
|
||||
# otherwise, if opening_number_of_booked_depreciations = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
|
||||
# from_date = 01/01/2022
|
||||
if row.depreciation_method in ("Straight Line", "Manual"):
|
||||
prev_depreciation_start_date = add_months(
|
||||
row.depreciation_start_date,
|
||||
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
|
||||
prev_depreciation_start_date = get_last_day(
|
||||
add_months(
|
||||
row.depreciation_start_date,
|
||||
(row.frequency_of_depreciation * -1) * asset_doc.opening_number_of_booked_depreciations,
|
||||
)
|
||||
)
|
||||
from_date = asset_doc.available_for_use_date
|
||||
days = date_diff(prev_depreciation_start_date, from_date) + 1
|
||||
@@ -610,7 +620,7 @@ def _get_pro_rata_amt(
|
||||
has_wdv_or_dd_non_yearly_pro_rata=False,
|
||||
original_schedule_date=None,
|
||||
):
|
||||
days = date_diff(to_date, from_date)
|
||||
days = date_diff(to_date, from_date) + 1
|
||||
months = month_diff(to_date, from_date)
|
||||
if has_wdv_or_dd_non_yearly_pro_rata:
|
||||
total_days = get_total_days(original_schedule_date or to_date, 12)
|
||||
@@ -670,7 +680,7 @@ def get_straight_line_or_manual_depr_amount(
|
||||
# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
|
||||
elif asset.flags.increase_in_asset_value_due_to_repair:
|
||||
return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
|
||||
row.total_number_of_depreciations
|
||||
number_of_pending_depreciations
|
||||
)
|
||||
# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
|
||||
elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
|
||||
@@ -757,8 +767,12 @@ def get_daily_depr_amount(asset, row, schedule_idx, amount):
|
||||
|
||||
every_year_depr = amount / total_years
|
||||
|
||||
depr_period_start_date = add_days(
|
||||
get_last_day(add_months(row.depreciation_start_date, row.frequency_of_depreciation * -1)), 1
|
||||
)
|
||||
|
||||
year_start_date = add_years(
|
||||
row.depreciation_start_date, (row.frequency_of_depreciation * schedule_idx) // 12
|
||||
depr_period_start_date, ((row.frequency_of_depreciation * schedule_idx) // 12)
|
||||
)
|
||||
year_end_date = add_days(add_years(year_start_date, 1), -1)
|
||||
|
||||
@@ -1034,6 +1048,7 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
date_of_return=None,
|
||||
value_after_depreciation=None,
|
||||
ignore_booked_entry=False,
|
||||
difference_amount=None,
|
||||
):
|
||||
for row in asset_doc.get("finance_books"):
|
||||
current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
|
||||
@@ -1048,6 +1063,8 @@ def make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
)
|
||||
|
||||
new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
|
||||
if asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment and not value_after_depreciation:
|
||||
value_after_depreciation = row.value_after_depreciation + difference_amount
|
||||
|
||||
if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in (
|
||||
"Written Down Value",
|
||||
|
||||
@@ -29,6 +29,15 @@ frappe.ui.form.on("Asset Repair", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("purchase_invoice", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
docstatus: 1,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("warehouse", "stock_items", function () {
|
||||
return {
|
||||
filters: {
|
||||
|
||||
@@ -117,7 +117,9 @@ class AssetRepair(AccountsController):
|
||||
get_link_to_form(self.doctype, self.name),
|
||||
)
|
||||
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
self.asset_doc, notes, ignore_booked_entry=True
|
||||
)
|
||||
self.asset_doc.save()
|
||||
|
||||
add_asset_activity(
|
||||
@@ -154,7 +156,9 @@ class AssetRepair(AccountsController):
|
||||
get_link_to_form(self.doctype, self.name),
|
||||
)
|
||||
self.asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
self.asset_doc, notes, ignore_booked_entry=True
|
||||
)
|
||||
self.asset_doc.save()
|
||||
|
||||
add_asset_activity(
|
||||
|
||||
@@ -5,7 +5,6 @@ frappe.provide("erpnext.accounts.dimensions");
|
||||
|
||||
frappe.ui.form.on("Asset Value Adjustment", {
|
||||
setup: function (frm) {
|
||||
frm.add_fetch("company", "cost_center", "cost_center");
|
||||
frm.set_query("cost_center", function () {
|
||||
return {
|
||||
filters: {
|
||||
@@ -22,6 +21,14 @@ frappe.ui.form.on("Asset Value Adjustment", {
|
||||
},
|
||||
};
|
||||
});
|
||||
frm.set_query("difference_account", function () {
|
||||
return {
|
||||
filters: {
|
||||
company: frm.doc.company,
|
||||
is_group: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
onload: function (frm) {
|
||||
@@ -37,7 +44,7 @@ frappe.ui.form.on("Asset Value Adjustment", {
|
||||
},
|
||||
|
||||
asset: function (frm) {
|
||||
frm.trigger("set_current_asset_value");
|
||||
frm.trigger("set_acc_dimension");
|
||||
},
|
||||
|
||||
finance_book: function (frm) {
|
||||
@@ -60,4 +67,15 @@ frappe.ui.form.on("Asset Value Adjustment", {
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
set_acc_dimension: function (frm) {
|
||||
if (frm.doc.asset) {
|
||||
frm.call({
|
||||
method: "erpnext.assets.doctype.asset_value_adjustment.asset_value_adjustment.get_value_of_accounting_dimensions",
|
||||
args: {
|
||||
asset_name: frm.doc.asset,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
"new_asset_value",
|
||||
"column_break_11",
|
||||
"difference_amount",
|
||||
"difference_account",
|
||||
"journal_entry",
|
||||
"accounting_dimensions_section",
|
||||
"cost_center",
|
||||
@@ -54,6 +55,7 @@
|
||||
"fieldtype": "Link",
|
||||
"label": "Journal Entry",
|
||||
"options": "Journal Entry",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -79,6 +81,7 @@
|
||||
"fieldtype": "Currency",
|
||||
"in_list_view": 1,
|
||||
"label": "New Asset Value",
|
||||
"no_copy": 1,
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
@@ -120,12 +123,20 @@
|
||||
{
|
||||
"fieldname": "column_break_11",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "difference_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Difference Account",
|
||||
"no_copy": 1,
|
||||
"options": "Account",
|
||||
"reqd": 1
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2024-03-27 13:06:36.004049",
|
||||
"modified": "2024-08-13 16:21:18.639208",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Value Adjustment",
|
||||
|
||||
@@ -34,6 +34,7 @@ class AssetValueAdjustment(Document):
|
||||
cost_center: DF.Link | None
|
||||
current_asset_value: DF.Currency
|
||||
date: DF.Date
|
||||
difference_account: DF.Link
|
||||
difference_amount: DF.Currency
|
||||
finance_book: DF.Link | None
|
||||
journal_entry: DF.Link | None
|
||||
@@ -47,6 +48,7 @@ class AssetValueAdjustment(Document):
|
||||
|
||||
def on_submit(self):
|
||||
self.make_depreciation_entry()
|
||||
self.set_value_after_depreciation()
|
||||
self.update_asset(self.new_asset_value)
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
@@ -56,7 +58,8 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
self.update_asset(self.current_asset_value)
|
||||
frappe.get_doc("Journal Entry", self.journal_entry).cancel()
|
||||
self.update_asset()
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
_("Asset's value adjusted after cancellation of Asset Value Adjustment {0}").format(
|
||||
@@ -75,7 +78,10 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
|
||||
def set_difference_amount(self):
|
||||
self.difference_amount = flt(self.current_asset_value - self.new_asset_value)
|
||||
self.difference_amount = flt(self.new_asset_value - self.current_asset_value)
|
||||
|
||||
def set_value_after_depreciation(self):
|
||||
frappe.db.set_value("Asset", self.asset, "value_after_depreciation", self.new_asset_value)
|
||||
|
||||
def set_current_asset_value(self):
|
||||
if not self.current_asset_value and self.asset:
|
||||
@@ -84,7 +90,7 @@ class AssetValueAdjustment(Document):
|
||||
def make_depreciation_entry(self):
|
||||
asset = frappe.get_doc("Asset", self.asset)
|
||||
(
|
||||
_,
|
||||
fixed_asset_account,
|
||||
accumulated_depreciation_account,
|
||||
depreciation_expense_account,
|
||||
) = get_depreciation_accounts(asset.asset_category, asset.company)
|
||||
@@ -94,28 +100,41 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
|
||||
je = frappe.new_doc("Journal Entry")
|
||||
je.voucher_type = "Depreciation Entry"
|
||||
je.voucher_type = "Journal Entry"
|
||||
je.naming_series = depreciation_series
|
||||
je.posting_date = self.date
|
||||
je.company = self.company
|
||||
je.remark = f"Depreciation Entry against {self.asset} worth {self.difference_amount}"
|
||||
je.remark = f"Revaluation Entry against {self.asset} worth {self.difference_amount}"
|
||||
je.finance_book = self.finance_book
|
||||
|
||||
credit_entry = {
|
||||
"account": accumulated_depreciation_account,
|
||||
"credit_in_account_currency": self.difference_amount,
|
||||
"cost_center": depreciation_cost_center or self.cost_center,
|
||||
entry_template = {
|
||||
"cost_center": self.cost_center or depreciation_cost_center,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": self.asset,
|
||||
"reference_name": asset.name,
|
||||
}
|
||||
|
||||
debit_entry = {
|
||||
"account": depreciation_expense_account,
|
||||
"debit_in_account_currency": self.difference_amount,
|
||||
"cost_center": depreciation_cost_center or self.cost_center,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": self.asset,
|
||||
}
|
||||
if self.difference_amount < 0:
|
||||
credit_entry = {
|
||||
"account": fixed_asset_account,
|
||||
"credit_in_account_currency": -self.difference_amount,
|
||||
**entry_template,
|
||||
}
|
||||
debit_entry = {
|
||||
"account": self.difference_account,
|
||||
"debit_in_account_currency": -self.difference_amount,
|
||||
**entry_template,
|
||||
}
|
||||
elif self.difference_amount > 0:
|
||||
credit_entry = {
|
||||
"account": self.difference_account,
|
||||
"credit_in_account_currency": self.difference_amount,
|
||||
**entry_template,
|
||||
}
|
||||
debit_entry = {
|
||||
"account": fixed_asset_account,
|
||||
"debit_in_account_currency": self.difference_amount,
|
||||
**entry_template,
|
||||
}
|
||||
|
||||
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
|
||||
|
||||
@@ -144,7 +163,7 @@ class AssetValueAdjustment(Document):
|
||||
|
||||
self.db_set("journal_entry", je.name)
|
||||
|
||||
def update_asset(self, asset_value):
|
||||
def update_asset(self, asset_value=None):
|
||||
asset = frappe.get_doc("Asset", self.asset)
|
||||
|
||||
if not asset.calculate_depreciation:
|
||||
@@ -170,7 +189,17 @@ class AssetValueAdjustment(Document):
|
||||
)
|
||||
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(
|
||||
asset, notes, value_after_depreciation=asset_value, ignore_booked_entry=True
|
||||
asset,
|
||||
notes,
|
||||
value_after_depreciation=asset_value,
|
||||
ignore_booked_entry=True,
|
||||
difference_amount=self.difference_amount,
|
||||
)
|
||||
asset.flags.ignore_validate_update_after_submit = True
|
||||
asset.save()
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_value_of_accounting_dimensions(asset_name):
|
||||
dimension_fields = [*frappe.get_list("Accounting Dimension", pluck="fieldname"), "cost_center"]
|
||||
return frappe.db.get_value("Asset", asset_name, fieldname=dimension_fields, as_dict=True)
|
||||
|
||||
@@ -12,6 +12,7 @@ from erpnext.assets.doctype.asset.test_asset import create_asset_data
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
get_asset_depr_schedule_doc,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_repair.test_asset_repair import create_asset_repair
|
||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||
|
||||
|
||||
@@ -92,8 +93,8 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
self.assertEqual(first_asset_depr_schedule.status, "Cancelled")
|
||||
|
||||
expected_gle = (
|
||||
("_Test Accumulated Depreciations - _TC", 0.0, 4625.29),
|
||||
("_Test Depreciations - _TC", 4625.29, 0.0),
|
||||
("_Test Difference Account - _TC", 4625.29, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 4625.29),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
@@ -128,6 +129,169 @@ class TestAssetValueAdjustment(unittest.TestCase):
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_depreciation_after_cancelling_asset_repair(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location")
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
|
||||
asset_doc = frappe.get_doc("Asset", asset_name)
|
||||
asset_doc.calculate_depreciation = 1
|
||||
asset_doc.available_for_use_date = "2023-01-15"
|
||||
asset_doc.purchase_date = "2023-01-15"
|
||||
|
||||
asset_doc.append(
|
||||
"finance_books",
|
||||
{
|
||||
"expected_value_after_useful_life": 200,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 12,
|
||||
"frequency_of_depreciation": 1,
|
||||
"depreciation_start_date": "2023-01-31",
|
||||
},
|
||||
)
|
||||
asset_doc.submit()
|
||||
|
||||
post_depreciation_entries(getdate("2023-08-21"))
|
||||
|
||||
# create asset repair
|
||||
asset_repair = create_asset_repair(asset=asset_doc, capitalize_repair_cost=1, submit=1)
|
||||
|
||||
first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
||||
self.assertEqual(first_asset_depr_schedule.status, "Active")
|
||||
|
||||
# create asset value adjustment
|
||||
current_value = get_asset_value_after_depreciation(asset_doc.name)
|
||||
|
||||
adj_doc = make_asset_value_adjustment(
|
||||
asset=asset_doc.name,
|
||||
current_asset_value=current_value,
|
||||
new_asset_value=50000.0,
|
||||
date="2023-08-21",
|
||||
)
|
||||
adj_doc.submit()
|
||||
|
||||
first_asset_depr_schedule.load_from_db()
|
||||
|
||||
second_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
||||
self.assertEqual(second_asset_depr_schedule.status, "Active")
|
||||
self.assertEqual(first_asset_depr_schedule.status, "Cancelled")
|
||||
|
||||
# Test gl entry creted from asset value adjustemnet
|
||||
expected_gle = (
|
||||
("_Test Difference Account - _TC", 5625.29, 0.0),
|
||||
("_Test Fixed Asset - _TC", 0.0, 5625.29),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Journal Entry' and voucher_no = %s
|
||||
order by account""",
|
||||
adj_doc.journal_entry,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
# test depreciation schedule after asset repair and asset value adjustemnet
|
||||
expected_schedules = [
|
||||
["2023-01-31", 5474.73, 5474.73],
|
||||
["2023-02-28", 9983.33, 15458.06],
|
||||
["2023-03-31", 9983.33, 25441.39],
|
||||
["2023-04-30", 9983.33, 35424.72],
|
||||
["2023-05-31", 9983.33, 45408.05],
|
||||
["2023-06-30", 9983.33, 55391.38],
|
||||
["2023-07-31", 9983.33, 65374.71],
|
||||
["2023-08-31", 2766.67, 68141.38],
|
||||
["2023-09-30", 2766.67, 70908.05],
|
||||
["2023-10-31", 2766.67, 73674.72],
|
||||
["2023-11-30", 2766.67, 76441.39],
|
||||
["2023-12-31", 2766.67, 79208.06],
|
||||
["2024-01-31", 2766.67, 81974.73],
|
||||
["2024-02-29", 2766.67, 84741.4],
|
||||
["2024-03-31", 2766.67, 87508.07],
|
||||
["2024-04-30", 2766.67, 90274.74],
|
||||
["2024-05-31", 2766.67, 93041.41],
|
||||
["2024-06-30", 2766.67, 95808.08],
|
||||
["2024-07-31", 2766.67, 98574.75],
|
||||
["2024-08-31", 2766.67, 101341.42],
|
||||
["2024-09-30", 2766.67, 104108.09],
|
||||
["2024-10-31", 2766.67, 106874.76],
|
||||
["2024-11-30", 2766.67, 109641.43],
|
||||
["2024-12-31", 2766.67, 112408.1],
|
||||
["2025-01-15", 2766.61, 115174.71],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
for d in second_asset_depr_schedule.get("depreciation_schedule")
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
# Cancel asset repair
|
||||
asset_repair.cancel()
|
||||
asset_repair.load_from_db()
|
||||
second_asset_depr_schedule.load_from_db()
|
||||
|
||||
third_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
|
||||
self.assertEqual(third_asset_depr_schedule.status, "Active")
|
||||
self.assertEqual(second_asset_depr_schedule.status, "Cancelled")
|
||||
|
||||
# After cancelling asset repair asset life will be decreased and new depreciation schedule should be calculated
|
||||
expected_schedules = [
|
||||
["2023-01-31", 5474.73, 5474.73],
|
||||
["2023-02-28", 9983.33, 15458.06],
|
||||
["2023-03-31", 9983.33, 25441.39],
|
||||
["2023-04-30", 9983.33, 35424.72],
|
||||
["2023-05-31", 9983.33, 45408.05],
|
||||
["2023-06-30", 9983.33, 55391.38],
|
||||
["2023-07-31", 9983.33, 65374.71],
|
||||
["2023-08-31", 8133.33, 73508.04],
|
||||
["2023-09-30", 8133.33, 81641.37],
|
||||
["2023-10-31", 8133.33, 89774.7],
|
||||
["2023-11-30", 8133.33, 97908.03],
|
||||
["2023-12-31", 8133.33, 106041.36],
|
||||
["2024-01-15", 8133.35, 114174.71],
|
||||
]
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
for d in third_asset_depr_schedule.get("depreciation_schedule")
|
||||
]
|
||||
|
||||
self.assertEqual(schedules, expected_schedules)
|
||||
|
||||
def test_difference_amount(self):
|
||||
pr = make_purchase_receipt(item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location")
|
||||
|
||||
asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
|
||||
asset_doc = frappe.get_doc("Asset", asset_name)
|
||||
asset_doc.calculate_depreciation = 1
|
||||
asset_doc.available_for_use_date = "2023-01-15"
|
||||
asset_doc.purchase_date = "2023-01-15"
|
||||
|
||||
asset_doc.append(
|
||||
"finance_books",
|
||||
{
|
||||
"expected_value_after_useful_life": 200,
|
||||
"depreciation_method": "Straight Line",
|
||||
"total_number_of_depreciations": 12,
|
||||
"frequency_of_depreciation": 1,
|
||||
"depreciation_start_date": "2023-01-31",
|
||||
},
|
||||
)
|
||||
asset_doc.submit()
|
||||
|
||||
adj_doc = make_asset_value_adjustment(
|
||||
asset=asset_doc.name,
|
||||
current_asset_value=54000,
|
||||
new_asset_value=50000.0,
|
||||
date="2023-08-21",
|
||||
)
|
||||
adj_doc.submit()
|
||||
difference_amount = adj_doc.new_asset_value - adj_doc.current_asset_value
|
||||
self.assertEqual(difference_amount, -4000)
|
||||
asset_doc.load_from_db()
|
||||
self.assertEqual(asset_doc.value_after_depreciation, 50000.0)
|
||||
|
||||
|
||||
def make_asset_value_adjustment(**args):
|
||||
args = frappe._dict(args)
|
||||
@@ -141,7 +305,22 @@ def make_asset_value_adjustment(**args):
|
||||
"new_asset_value": args.new_asset_value,
|
||||
"current_asset_value": args.current_asset_value,
|
||||
"cost_center": args.cost_center or "Main - _TC",
|
||||
"difference_account": make_difference_account(),
|
||||
}
|
||||
).insert()
|
||||
|
||||
return doc
|
||||
|
||||
|
||||
def make_difference_account(**args):
|
||||
account = "_Test Difference Account - _TC"
|
||||
if not frappe.db.exists("Account", account):
|
||||
acc = frappe.new_doc("Account")
|
||||
acc.account_name = "_Test Difference Account"
|
||||
acc.parent_account = "Direct Income - _TC"
|
||||
acc.company = "_Test Company"
|
||||
acc.is_group = 0
|
||||
acc.insert()
|
||||
return acc.name
|
||||
else:
|
||||
return account
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
{
|
||||
"creation": "2021-08-24 16:55:10.923434",
|
||||
"docstatus": 0,
|
||||
"doctype": "Form Tour",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2021-08-24 16:55:10.923434",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
"owner": "Administrator",
|
||||
"reference_doctype": "Asset",
|
||||
"save_on_complete": 0,
|
||||
"steps": [
|
||||
{
|
||||
"description": "Select Naming Series based on which Asset ID will be generated",
|
||||
"field": "",
|
||||
"fieldname": "naming_series",
|
||||
"fieldtype": "Select",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Naming Series",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Naming Series"
|
||||
},
|
||||
{
|
||||
"description": "Select an Asset Item",
|
||||
"field": "",
|
||||
"fieldname": "item_code",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Item Code",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Item Code"
|
||||
},
|
||||
{
|
||||
"description": "Select a Location",
|
||||
"field": "",
|
||||
"fieldname": "location",
|
||||
"fieldtype": "Link",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Location",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Location"
|
||||
},
|
||||
{
|
||||
"description": "Check Is Existing Asset",
|
||||
"field": "",
|
||||
"fieldname": "is_existing_asset",
|
||||
"fieldtype": "Check",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Is Existing Asset",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Is Existing Asset?"
|
||||
},
|
||||
{
|
||||
"description": "Set Available for use date",
|
||||
"field": "",
|
||||
"fieldname": "available_for_use_date",
|
||||
"fieldtype": "Date",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Available-for-use Date",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Available For Use Date"
|
||||
},
|
||||
{
|
||||
"description": "Set Gross purchase amount",
|
||||
"field": "",
|
||||
"fieldname": "gross_purchase_amount",
|
||||
"fieldtype": "Currency",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Gross Purchase Amount",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Gross Purchase Amount"
|
||||
},
|
||||
{
|
||||
"description": "Set Purchase Date",
|
||||
"field": "",
|
||||
"fieldname": "purchase_date",
|
||||
"fieldtype": "Date",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Purchase Date",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Purchase Date"
|
||||
},
|
||||
{
|
||||
"description": "Check Calculate Depreciation",
|
||||
"field": "",
|
||||
"fieldname": "calculate_depreciation",
|
||||
"fieldtype": "Check",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Calculate Depreciation",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Calculate Depreciation"
|
||||
},
|
||||
{
|
||||
"description": "Enter depreciation which has already been booked for this asset",
|
||||
"field": "",
|
||||
"fieldname": "opening_accumulated_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Opening Accumulated Depreciation",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Accumulated Depreciation"
|
||||
}
|
||||
],
|
||||
"title": "Asset"
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"creation": "2021-08-24 12:48:20.763173",
|
||||
"docstatus": 0,
|
||||
"doctype": "Form Tour",
|
||||
"idx": 0,
|
||||
"is_standard": 1,
|
||||
"modified": "2021-08-24 12:48:20.763173",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Category",
|
||||
"owner": "Administrator",
|
||||
"reference_doctype": "Asset Category",
|
||||
"save_on_complete": 0,
|
||||
"steps": [
|
||||
{
|
||||
"description": "Name Asset category. You can create categories based on Asset Types like Furniture, Property, Electronics etc.",
|
||||
"field": "",
|
||||
"fieldname": "asset_category_name",
|
||||
"fieldtype": "Data",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Asset Category Name",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Asset Category Name"
|
||||
},
|
||||
{
|
||||
"description": "Check to enable Capital Work in Progress accounting",
|
||||
"field": "",
|
||||
"fieldname": "enable_cwip_accounting",
|
||||
"fieldtype": "Check",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Enable Capital Work in Progress Accounting",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Enable CWIP Accounting"
|
||||
},
|
||||
{
|
||||
"description": "Add a row to define Depreciation Method and other details. Note that you can leave Finance Book blank to have it's accounting done in the primary books of accounts.",
|
||||
"field": "",
|
||||
"fieldname": "finance_books",
|
||||
"fieldtype": "Table",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Finance Books",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Finance Book Detail"
|
||||
},
|
||||
{
|
||||
"description": "Select the Fixed Asset and Depreciation accounts applicable for this Asset Category type",
|
||||
"field": "",
|
||||
"fieldname": "accounts",
|
||||
"fieldtype": "Table",
|
||||
"has_next_condition": 0,
|
||||
"is_table_field": 0,
|
||||
"label": "Accounts",
|
||||
"parent_field": "",
|
||||
"position": "Bottom",
|
||||
"title": "Accounts"
|
||||
}
|
||||
],
|
||||
"title": "Asset Category"
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
"allow_roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Maintenance User"
|
||||
}
|
||||
],
|
||||
"creation": "2020-05-08 15:10:45.571457",
|
||||
"docstatus": 0,
|
||||
"doctype": "Module Onboarding",
|
||||
"documentation_url": "https://docs.erpnext.com/docs/user/manual/en/asset",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"modified": "2021-12-02 11:24:37.963746",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Assets",
|
||||
"owner": "Administrator",
|
||||
"steps": [
|
||||
{
|
||||
"step": "Fixed Asset Accounts"
|
||||
},
|
||||
{
|
||||
"step": "Asset Category"
|
||||
},
|
||||
{
|
||||
"step": "Asset Item"
|
||||
},
|
||||
{
|
||||
"step": "Asset Purchase"
|
||||
},
|
||||
{
|
||||
"step": "Existing Asset"
|
||||
}
|
||||
],
|
||||
"subtitle": "Assets, Depreciations, Repairs, and more.",
|
||||
"success_message": "The Assets Module is all set up!",
|
||||
"title": "Let's Set Up the Assets Module."
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"action": "Show Form Tour",
|
||||
"action_label": "Let's review existing Asset Category",
|
||||
"creation": "2021-08-13 14:26:18.656303",
|
||||
"description": "# Asset Category\n\nAn Asset Category classifies different assets of a Company.\n\nYou can create an Asset Category based on the type of assets. For example, all your desktops and laptops can be part of an Asset Category named \"Electronic Equipment\". Create a separate category for furniture. Also, you can update default properties for each category, like:\n - Depreciation type and duration\n - Fixed asset account\n - Depreciation account\n",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2024-01-24 02:20:26.145996",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Asset Category",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Asset Category",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Define Asset Category",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"action_label": "Let's create a new Asset item",
|
||||
"creation": "2021-08-13 14:27:07.277167",
|
||||
"description": "# Asset Item\n\nAsset items are created based on Asset Category. You can create one or multiple items against once Asset Category. The sales and purchase transaction for Asset is done via Asset Item. ",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"form_tour": "Item",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2021-12-02 11:23:48.158504",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Asset Item",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Item",
|
||||
"show_form_tour": 1,
|
||||
"show_full_form": 1,
|
||||
"title": "Create an Asset Item",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"action": "Show Form Tour",
|
||||
"action_label": "Let's create a Purchase Receipt",
|
||||
"creation": "2021-08-13 14:27:53.678621",
|
||||
"description": "# Purchase an Asset\n\nAssets purchases process if done following the standard Purchase cycle. If capital work in progress is enabled in Asset Category, Asset will be created as soon as Purchase Receipt is created for it. You can quickly create a Purchase Receipt for Asset and see its impact on books of accounts.",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2021-11-23 10:02:03.235498",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Asset Purchase",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Purchase Receipt",
|
||||
"show_form_tour": 0,
|
||||
"show_full_form": 0,
|
||||
"title": "Purchase an Asset",
|
||||
"validate_action": 1
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"creation": "2020-05-08 13:20:00.259985",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_mandatory": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-05-08 13:20:00.259985",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create a Fixed Asset Item",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Item",
|
||||
"show_full_form": 0,
|
||||
"title": "Create a Fixed Asset Item",
|
||||
"validate_action": 0
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"action": "Create Entry",
|
||||
"creation": "2020-05-08 13:21:53.332538",
|
||||
"docstatus": 0,
|
||||
"doctype": "Onboarding Step",
|
||||
"idx": 0,
|
||||
"is_complete": 0,
|
||||
"is_mandatory": 0,
|
||||
"is_single": 0,
|
||||
"is_skipped": 0,
|
||||
"modified": "2020-05-08 13:21:53.332538",
|
||||
"modified_by": "Administrator",
|
||||
"name": "Create an Asset",
|
||||
"owner": "Administrator",
|
||||
"reference_document": "Asset",
|
||||
"show_full_form": 0,
|
||||
"title": "Create an Asset",
|
||||
"validate_action": 0
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user