mirror of
https://github.com/frappe/erpnext.git
synced 2026-07-24 13:15:03 +00:00
Compare commits
189 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77d019cc3b | ||
|
|
fd84119273 | ||
|
|
9b9d839835 | ||
|
|
0ef0ff470f | ||
|
|
10c9640cbd | ||
|
|
da1218f324 | ||
|
|
407e5b5fa3 | ||
|
|
b9833db7bd | ||
|
|
d4e680c109 | ||
|
|
b63fbe4286 | ||
|
|
0602ddcfc8 | ||
|
|
18c3a668d9 | ||
|
|
26489121f3 | ||
|
|
41902c3676 | ||
|
|
0bcd0476a2 | ||
|
|
41344593c9 | ||
|
|
bcfd7708f2 | ||
|
|
4dd088cba4 | ||
|
|
12b62571b8 | ||
|
|
3785fe6927 | ||
|
|
de529f0adf | ||
|
|
2e2c319f20 | ||
|
|
8939e95c62 | ||
|
|
6c4dff38da | ||
|
|
000ebe4479 | ||
|
|
0fe95bf77e | ||
|
|
60a170d1a4 | ||
|
|
51dd0ec876 | ||
|
|
aa8446d794 | ||
|
|
7239e839a0 | ||
|
|
04990d51db | ||
|
|
0ec74b059e | ||
|
|
2a6e80214c | ||
|
|
af10d8080b | ||
|
|
e899c30428 | ||
|
|
a53832e16e | ||
|
|
c6885e6789 | ||
|
|
f22969d266 | ||
|
|
75b3423ab1 | ||
|
|
3d0add81fa | ||
|
|
46d0b7d317 | ||
|
|
0a8b7148a5 | ||
|
|
6400a574b6 | ||
|
|
eaa1589331 | ||
|
|
d49a8ad74f | ||
|
|
a98a13b683 | ||
|
|
e37b6bbbf1 | ||
|
|
bcc8a45c4e | ||
|
|
571c977e8e | ||
|
|
ac9f1fefe6 | ||
|
|
513da54b6d | ||
|
|
f8a8cf3046 | ||
|
|
1685305b53 | ||
|
|
b95d459812 | ||
|
|
0e11317303 | ||
|
|
9a659254e3 | ||
|
|
e75ca14a88 | ||
|
|
c2bf8e3502 | ||
|
|
4d4f218175 | ||
|
|
4c2c037a86 | ||
|
|
4f79214ae6 | ||
|
|
5b37abd2d6 | ||
|
|
a24d488817 | ||
|
|
986a90efe0 | ||
|
|
4a35ff0e57 | ||
|
|
8e3636ff53 | ||
|
|
1b69b37229 | ||
|
|
97f4af8d97 | ||
|
|
9d5b500060 | ||
|
|
3831c7920d | ||
|
|
f182fc1f8e | ||
|
|
1897d6f214 | ||
|
|
1415f40dfb | ||
|
|
09cf050b0d | ||
|
|
55448017d7 | ||
|
|
202513ae6a | ||
|
|
e3a8a8d195 | ||
|
|
169af8f9f8 | ||
|
|
f0580b0e4d | ||
|
|
b5b34c14b2 | ||
|
|
839a1f0454 | ||
|
|
63fba9db39 | ||
|
|
00fd08c7bc | ||
|
|
d8dd22adaf | ||
|
|
6f43829c32 | ||
|
|
3e95d56240 | ||
|
|
44cb62824d | ||
|
|
022893391b | ||
|
|
139a193f1d | ||
|
|
4f5ee6876d | ||
|
|
270eb1db4d | ||
|
|
20d3381010 | ||
|
|
fd04bd0f72 | ||
|
|
166ec0e58c | ||
|
|
1e1dddfe6c | ||
|
|
0a42e6ff0f | ||
|
|
97f9c0d53f | ||
|
|
137898d55d | ||
|
|
f65be40037 | ||
|
|
75f4a616f1 | ||
|
|
8d97f8b0b7 | ||
|
|
f63b866de3 | ||
|
|
d6427cfe53 | ||
|
|
774092343a | ||
|
|
2aa7729243 | ||
|
|
66ba74f3fc | ||
|
|
dc04b24234 | ||
|
|
eb243c2470 | ||
|
|
f7ed4ecd56 | ||
|
|
6bc8749eaf | ||
|
|
2747df78ac | ||
|
|
d316955d18 | ||
|
|
2e3f8e8846 | ||
|
|
9af4e117d4 | ||
|
|
6191cfee4c | ||
|
|
f0c9d89aab | ||
|
|
06deecbd92 | ||
|
|
6c170abdf9 | ||
|
|
7506132861 | ||
|
|
387f8b9e1a | ||
|
|
c2ae8eaec0 | ||
|
|
f5f88bb62c | ||
|
|
188cfc2e3c | ||
|
|
c7c2bad6ab | ||
|
|
e6a9252f79 | ||
|
|
4a9ad09c7f | ||
|
|
e37b9030fb | ||
|
|
77f548c814 | ||
|
|
7626d51db1 | ||
|
|
48e5846ed5 | ||
|
|
ac26e4ba2a | ||
|
|
8b9f8c6ab7 | ||
|
|
a1d717053a | ||
|
|
8b3d6ee7b0 | ||
|
|
1380f7a7ec | ||
|
|
2825253339 | ||
|
|
40cfd5215c | ||
|
|
af8142cf85 | ||
|
|
e2af66c7be | ||
|
|
ef2d4febdd | ||
|
|
cb0d567d7b | ||
|
|
9a376039aa | ||
|
|
2f74026513 | ||
|
|
740313ff09 | ||
|
|
db6d0e03f5 | ||
|
|
778ba6956c | ||
|
|
b19b0a4a98 | ||
|
|
b31d8eec05 | ||
|
|
078161cf6b | ||
|
|
635559d905 | ||
|
|
6bdf143084 | ||
|
|
198a64d574 | ||
|
|
7d6e2f979f | ||
|
|
6992e727cf | ||
|
|
a852dc1f11 | ||
|
|
c5261cde9c | ||
|
|
d3c769c183 | ||
|
|
563e5c0b69 | ||
|
|
5746ddce84 | ||
|
|
54388e8d92 | ||
|
|
784ea7cf48 | ||
|
|
fc42e026ab | ||
|
|
cef7126a35 | ||
|
|
297facc1cb | ||
|
|
31bda37970 | ||
|
|
1d6917f340 | ||
|
|
b85d8946f7 | ||
|
|
5f28b1d330 | ||
|
|
61a3121172 | ||
|
|
d5a80b5615 | ||
|
|
189b020d22 | ||
|
|
be2095ad03 | ||
|
|
83afaf48df | ||
|
|
9087ac0829 | ||
|
|
7759d1e390 | ||
|
|
cfa1a2b050 | ||
|
|
c1187bed26 | ||
|
|
9957981039 | ||
|
|
82b46f2bfe | ||
|
|
91b5a33564 | ||
|
|
d215a85747 | ||
|
|
813b4d4de2 | ||
|
|
983140acd8 | ||
|
|
102ac9f74d | ||
|
|
ebf8deb933 | ||
|
|
5680045f2b | ||
|
|
d010b048dc | ||
|
|
046bf64fa3 | ||
|
|
b73422e4ee |
@@ -4,7 +4,7 @@ import frappe
|
||||
|
||||
from erpnext.hooks import regional_overrides
|
||||
|
||||
__version__ = "13.49.13"
|
||||
__version__ = "13.52.0"
|
||||
|
||||
|
||||
def get_default_company(user=None):
|
||||
|
||||
@@ -297,7 +297,7 @@ def _make_test_records(verbose=None):
|
||||
# fixed asset depreciation
|
||||
["_Test Fixed Asset", "Current Assets", 0, "Fixed Asset", None],
|
||||
["_Test Accumulated Depreciations", "Current Assets", 0, "Accumulated Depreciation", None],
|
||||
["_Test Depreciations", "Expenses", 0, None, None],
|
||||
["_Test Depreciations", "Expenses", 0, "Depreciation", None],
|
||||
["_Test Gain/Loss on Asset Disposal", "Expenses", 0, None, None],
|
||||
# Receivable / Payable Account
|
||||
["_Test Receivable", "Current Assets", 0, "Receivable", None],
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
"submit_journal_entries",
|
||||
"print_settings",
|
||||
"show_inclusive_tax_in_print",
|
||||
"show_taxes_as_table_in_print",
|
||||
"column_break_12",
|
||||
"show_payment_schedule_in_print",
|
||||
"currency_exchange_section",
|
||||
@@ -175,6 +176,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Payment Terms from orders will be fetched into the invoices as is",
|
||||
"fieldname": "automatically_fetch_payment_terms",
|
||||
"fieldtype": "Check",
|
||||
"label": "Automatically Fetch Payment Terms from Order"
|
||||
@@ -292,6 +294,12 @@
|
||||
"fieldname": "book_tax_discount_loss",
|
||||
"fieldtype": "Check",
|
||||
"label": "Book Tax Loss on Early Payment Discount"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "show_taxes_as_table_in_print",
|
||||
"fieldtype": "Check",
|
||||
"label": "Show Taxes as Table in Print"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -299,7 +307,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-28 09:50:20.375233",
|
||||
"modified": "2023-06-13 18:47:46.430291",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -56,7 +56,7 @@ class BankClearance(Document):
|
||||
select
|
||||
"Payment Entry" as payment_document, name as payment_entry,
|
||||
reference_no as cheque_number, reference_date as cheque_date,
|
||||
if(paid_from=%(account)s, paid_amount, 0) as credit,
|
||||
if(paid_from=%(account)s, paid_amount + total_taxes_and_charges, 0) as credit,
|
||||
if(paid_from=%(account)s, 0, received_amount) as debit,
|
||||
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
|
||||
|
||||
@@ -8,7 +8,7 @@ frappe.provide("erpnext.journal_entry");
|
||||
frappe.ui.form.on("Journal Entry", {
|
||||
setup: function(frm) {
|
||||
frm.add_fetch("bank_account", "account", "account");
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice'];
|
||||
frm.ignore_doctypes_on_cancel_all = ['Sales Invoice', 'Purchase Invoice', 'Asset', 'Asset Movement'];
|
||||
},
|
||||
|
||||
refresh: function(frm) {
|
||||
|
||||
@@ -75,6 +75,7 @@ class JournalEntry(AccountsController):
|
||||
self.validate_empty_accounts_table()
|
||||
self.set_account_and_party_balance()
|
||||
self.validate_inter_company_accounts()
|
||||
self.validate_depr_entry_voucher_type()
|
||||
|
||||
if self.docstatus == 0:
|
||||
self.apply_tax_withholding()
|
||||
@@ -134,6 +135,13 @@ class JournalEntry(AccountsController):
|
||||
if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
|
||||
frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
|
||||
|
||||
def validate_depr_entry_voucher_type(self):
|
||||
if (
|
||||
any(d.account_type == "Depreciation" for d in self.get("accounts"))
|
||||
and self.voucher_type != "Depreciation Entry"
|
||||
):
|
||||
frappe.throw(_("Journal Entry type should be set as Depreciation Entry for asset depreciation"))
|
||||
|
||||
def validate_stock_accounts(self):
|
||||
stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
|
||||
for account in stock_accounts:
|
||||
@@ -237,25 +245,30 @@ class JournalEntry(AccountsController):
|
||||
self.remove(d)
|
||||
|
||||
def update_asset_value(self):
|
||||
if self.voucher_type != "Depreciation Entry":
|
||||
if self.flags.planned_depr_entry or self.voucher_type != "Depreciation Entry":
|
||||
return
|
||||
|
||||
processed_assets = []
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if (
|
||||
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
|
||||
d.reference_type == "Asset"
|
||||
and d.reference_name
|
||||
and d.account_type == "Depreciation"
|
||||
and d.debit
|
||||
):
|
||||
processed_assets.append(d.reference_name)
|
||||
|
||||
asset = frappe.get_doc("Asset", d.reference_name)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
continue
|
||||
|
||||
depr_value = d.debit or d.credit
|
||||
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - depr_value)
|
||||
fb_idx = 1
|
||||
if self.finance_book:
|
||||
for fb_row in asset.get("finance_books"):
|
||||
if fb_row.finance_book == self.finance_book:
|
||||
fb_idx = fb_row.idx
|
||||
break
|
||||
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||
fb_row.value_after_depreciation -= d.debit
|
||||
fb_row.db_update()
|
||||
else:
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation - d.debit)
|
||||
|
||||
asset.set_status()
|
||||
|
||||
@@ -317,38 +330,45 @@ class JournalEntry(AccountsController):
|
||||
d.db_update()
|
||||
|
||||
def unlink_asset_reference(self):
|
||||
if self.voucher_type != "Depreciation Entry":
|
||||
return
|
||||
|
||||
processed_assets = []
|
||||
|
||||
for d in self.get("accounts"):
|
||||
if (
|
||||
d.reference_type == "Asset" and d.reference_name and d.reference_name not in processed_assets
|
||||
self.voucher_type == "Depreciation Entry"
|
||||
and d.reference_type == "Asset"
|
||||
and d.reference_name
|
||||
and d.account_type == "Depreciation"
|
||||
and d.debit
|
||||
):
|
||||
processed_assets.append(d.reference_name)
|
||||
|
||||
asset = frappe.get_doc("Asset", d.reference_name)
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
fb_idx = None
|
||||
for s in asset.get("schedules"):
|
||||
if s.journal_entry == self.name:
|
||||
s.db_set("journal_entry", None)
|
||||
|
||||
idx = cint(s.finance_book_id) or 1
|
||||
finance_books = asset.get("finance_books")[idx - 1]
|
||||
finance_books.value_after_depreciation += s.depreciation_amount
|
||||
finance_books.db_update()
|
||||
|
||||
asset.set_status()
|
||||
|
||||
fb_idx = cint(s.finance_book_id) or 1
|
||||
break
|
||||
if not fb_idx:
|
||||
fb_idx = 1
|
||||
if self.finance_book:
|
||||
for fb_row in asset.get("finance_books"):
|
||||
if fb_row.finance_book == self.finance_book:
|
||||
fb_idx = fb_row.idx
|
||||
break
|
||||
fb_row = asset.get("finance_books")[fb_idx - 1]
|
||||
fb_row.value_after_depreciation += d.debit
|
||||
fb_row.db_update()
|
||||
else:
|
||||
depr_value = d.debit or d.credit
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation + d.debit)
|
||||
asset.set_status()
|
||||
elif self.voucher_type == "Journal Entry" and d.reference_type == "Asset" and d.reference_name:
|
||||
journal_entry_for_scrap = frappe.db.get_value(
|
||||
"Asset", d.reference_name, "journal_entry_for_scrap"
|
||||
)
|
||||
|
||||
asset.db_set("value_after_depreciation", asset.value_after_depreciation + depr_value)
|
||||
|
||||
asset.set_status()
|
||||
if journal_entry_for_scrap == self.name:
|
||||
frappe.throw(
|
||||
_("Journal Entry for Asset scrapping cannot be cancelled. Please restore the Asset.")
|
||||
)
|
||||
|
||||
def unlink_inter_company_jv(self):
|
||||
if (
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
// For license information, please see license.txt
|
||||
|
||||
frappe.ui.form.on("Journal Entry Template", {
|
||||
onload: function(frm) {
|
||||
if(frm.is_new()) {
|
||||
frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series",
|
||||
callback: function(r){
|
||||
if(r.message) {
|
||||
frm.set_df_property("naming_series", "options", r.message.split("\n"));
|
||||
frm.set_value("naming_series", r.message.split("\n")[0]);
|
||||
frm.refresh_field("naming_series");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
refresh: function(frm) {
|
||||
frappe.model.set_default_values(frm.doc);
|
||||
|
||||
@@ -19,18 +34,6 @@ frappe.ui.form.on("Journal Entry Template", {
|
||||
|
||||
return { filters: filters };
|
||||
});
|
||||
|
||||
frappe.call({
|
||||
type: "GET",
|
||||
method: "erpnext.accounts.doctype.journal_entry_template.journal_entry_template.get_naming_series",
|
||||
callback: function(r){
|
||||
if(r.message){
|
||||
frm.set_df_property("naming_series", "options", r.message.split("\n"));
|
||||
frm.set_value("naming_series", r.message.split("\n")[0]);
|
||||
frm.refresh_field("naming_series");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
voucher_type: function(frm) {
|
||||
var add_accounts = function(doc, r) {
|
||||
|
||||
@@ -623,7 +623,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
frm.events.set_unallocated_amount(frm);
|
||||
},
|
||||
|
||||
get_outstanding_invoice: function(frm) {
|
||||
get_outstanding_invoices_or_orders: function(frm, get_outstanding_invoices, get_orders_to_be_billed) {
|
||||
const today = frappe.datetime.get_today();
|
||||
const fields = [
|
||||
{fieldtype:"Section Break", label: __("Posting Date")},
|
||||
@@ -653,12 +653,29 @@ frappe.ui.form.on('Payment Entry', {
|
||||
{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
|
||||
];
|
||||
|
||||
let btn_text = "";
|
||||
|
||||
if (get_outstanding_invoices) {
|
||||
btn_text = "Get Outstanding Invoices";
|
||||
}
|
||||
else if (get_orders_to_be_billed) {
|
||||
btn_text = "Get Outstanding Orders";
|
||||
}
|
||||
|
||||
frappe.prompt(fields, function(filters){
|
||||
frappe.flags.allocate_payment_amount = true;
|
||||
frm.events.validate_filters_data(frm, filters);
|
||||
frm.doc.cost_center = filters.cost_center;
|
||||
frm.events.get_outstanding_documents(frm, filters);
|
||||
}, __("Filters"), __("Get Outstanding Documents"));
|
||||
frm.events.get_outstanding_documents(frm, filters, get_outstanding_invoices, get_orders_to_be_billed);
|
||||
}, __("Filters"), __(btn_text));
|
||||
},
|
||||
|
||||
get_outstanding_invoices: function(frm) {
|
||||
frm.events.get_outstanding_invoices_or_orders(frm, true, false);
|
||||
},
|
||||
|
||||
get_outstanding_orders: function(frm) {
|
||||
frm.events.get_outstanding_invoices_or_orders(frm, false, true);
|
||||
},
|
||||
|
||||
validate_filters_data: function(frm, filters) {
|
||||
@@ -684,7 +701,7 @@ frappe.ui.form.on('Payment Entry', {
|
||||
}
|
||||
},
|
||||
|
||||
get_outstanding_documents: function(frm, filters) {
|
||||
get_outstanding_documents: function(frm, filters, get_outstanding_invoices, get_orders_to_be_billed) {
|
||||
frm.clear_table("references");
|
||||
|
||||
if(!frm.doc.party) {
|
||||
@@ -708,6 +725,13 @@ frappe.ui.form.on('Payment Entry', {
|
||||
args[key] = filters[key];
|
||||
}
|
||||
|
||||
if (get_outstanding_invoices) {
|
||||
args["get_outstanding_invoices"] = true;
|
||||
}
|
||||
else if (get_orders_to_be_billed) {
|
||||
args["get_orders_to_be_billed"] = true;
|
||||
}
|
||||
|
||||
frappe.flags.allocate_payment_amount = filters['allocate_payment_amount'];
|
||||
|
||||
return frappe.call({
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
"base_received_amount",
|
||||
"base_received_amount_after_tax",
|
||||
"section_break_14",
|
||||
"get_outstanding_invoice",
|
||||
"get_outstanding_invoices",
|
||||
"get_outstanding_orders",
|
||||
"references",
|
||||
"section_break_34",
|
||||
"total_allocated_amount",
|
||||
@@ -353,12 +354,6 @@
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Reference"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus==0",
|
||||
"fieldname": "get_outstanding_invoice",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Outstanding Invoice"
|
||||
},
|
||||
{
|
||||
"fieldname": "references",
|
||||
"fieldtype": "Table",
|
||||
@@ -726,12 +721,24 @@
|
||||
"fieldname": "section_break_60",
|
||||
"fieldtype": "Section Break",
|
||||
"hide_border": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus==0",
|
||||
"fieldname": "get_outstanding_invoices",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Outstanding Invoices"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:doc.docstatus==0",
|
||||
"fieldname": "get_outstanding_orders",
|
||||
"fieldtype": "Button",
|
||||
"label": "Get Outstanding Orders"
|
||||
}
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-02-23 20:08:39.559814",
|
||||
"modified": "2023-06-19 11:38:04.387219",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Payment Entry",
|
||||
|
||||
@@ -150,19 +150,68 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
|
||||
def validate_allocated_amount(self):
|
||||
if self.payment_type == "Internal Transfer":
|
||||
return
|
||||
|
||||
if self.party_type in ("Customer", "Supplier"):
|
||||
self.validate_allocated_amount_with_latest_data()
|
||||
else:
|
||||
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
||||
for d in self.get("references"):
|
||||
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
# Check for negative outstanding invoices as well
|
||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
def validate_allocated_amount_with_latest_data(self):
|
||||
latest_references = get_outstanding_reference_documents(
|
||||
{
|
||||
"posting_date": self.posting_date,
|
||||
"company": self.company,
|
||||
"party_type": self.party_type,
|
||||
"payment_type": self.payment_type,
|
||||
"party": self.party,
|
||||
"party_account": self.paid_from if self.payment_type == "Receive" else self.paid_to,
|
||||
"get_outstanding_invoices": True,
|
||||
"get_orders_to_be_billed": True,
|
||||
}
|
||||
)
|
||||
|
||||
# Group latest_references by (voucher_type, voucher_no)
|
||||
latest_lookup = {}
|
||||
for d in latest_references:
|
||||
d = frappe._dict(d)
|
||||
latest_lookup.update({(d.voucher_type, d.voucher_no): d})
|
||||
|
||||
for d in self.get("references"):
|
||||
if (flt(d.allocated_amount)) > 0:
|
||||
if flt(d.allocated_amount) > flt(d.outstanding_amount):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
|
||||
)
|
||||
latest = latest_lookup.get((d.reference_doctype, d.reference_name))
|
||||
|
||||
# The reference has already been fully paid
|
||||
if not latest:
|
||||
frappe.throw(
|
||||
_("{0} {1} has already been fully paid.").format(d.reference_doctype, d.reference_name)
|
||||
)
|
||||
# The reference has already been partly paid
|
||||
elif (
|
||||
latest.outstanding_amount < latest.invoice_amount
|
||||
and d.outstanding_amount != latest.outstanding_amount
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
"{0} {1} has already been partly paid. Please use the 'Get Outstanding Invoice' or the 'Get Outstanding Orders' button to get the latest outstanding amounts."
|
||||
).format(d.reference_doctype, d.reference_name)
|
||||
)
|
||||
|
||||
fail_message = _("Row #{0}: Allocated Amount cannot be greater than outstanding amount.")
|
||||
|
||||
if (flt(d.allocated_amount)) > 0 and flt(d.allocated_amount) > flt(latest.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
# Check for negative outstanding invoices as well
|
||||
if flt(d.allocated_amount) < 0:
|
||||
if flt(d.allocated_amount) < flt(d.outstanding_amount):
|
||||
frappe.throw(
|
||||
_("Row #{0}: Allocated Amount cannot be greater than outstanding amount.").format(d.idx)
|
||||
)
|
||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
|
||||
def delink_advance_entry_references(self):
|
||||
for reference in self.references:
|
||||
@@ -391,7 +440,7 @@ class PaymentEntry(AccountsController):
|
||||
for k, v in no_oustanding_refs.items():
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"{} - {} now have {} as they had no outstanding amount left before submitting the Payment Entry."
|
||||
"{} - {} now has {} as it had no outstanding amount left before submitting the Payment Entry."
|
||||
).format(
|
||||
_(k),
|
||||
frappe.bold(", ".join(d.reference_name for d in v)),
|
||||
@@ -1318,32 +1367,48 @@ def get_outstanding_reference_documents(args):
|
||||
if args.get("company"):
|
||||
condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
|
||||
|
||||
outstanding_invoices = get_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("party_account"),
|
||||
args.get("company"),
|
||||
filters=args,
|
||||
condition=condition,
|
||||
)
|
||||
outstanding_invoices = []
|
||||
negative_outstanding_invoices = []
|
||||
|
||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
||||
if args.get("get_outstanding_invoices"):
|
||||
outstanding_invoices = get_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("party_account"),
|
||||
args.get("company"),
|
||||
filters=args,
|
||||
condition=condition,
|
||||
)
|
||||
|
||||
for d in outstanding_invoices:
|
||||
d["exchange_rate"] = 1
|
||||
if party_account_currency != company_currency:
|
||||
if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
|
||||
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
|
||||
elif d.voucher_type == "Journal Entry":
|
||||
d["exchange_rate"] = get_exchange_rate(
|
||||
party_account_currency, company_currency, d.posting_date
|
||||
)
|
||||
if d.voucher_type in ("Purchase Invoice"):
|
||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||
outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
|
||||
|
||||
for d in outstanding_invoices:
|
||||
d["exchange_rate"] = 1
|
||||
if party_account_currency != company_currency:
|
||||
if d.voucher_type in ("Sales Invoice", "Purchase Invoice", "Expense Claim"):
|
||||
d["exchange_rate"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "conversion_rate")
|
||||
elif d.voucher_type == "Journal Entry":
|
||||
d["exchange_rate"] = get_exchange_rate(
|
||||
party_account_currency, company_currency, d.posting_date
|
||||
)
|
||||
if d.voucher_type in ("Purchase Invoice"):
|
||||
d["bill_no"] = frappe.db.get_value(d.voucher_type, d.voucher_no, "bill_no")
|
||||
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
negative_outstanding_invoices = []
|
||||
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("party_account"),
|
||||
party_account_currency,
|
||||
company_currency,
|
||||
condition=condition,
|
||||
)
|
||||
|
||||
# Get all SO / PO which are not fully billed or against which full advance not paid
|
||||
orders_to_be_billed = []
|
||||
if args.get("party_type") != "Student":
|
||||
if args.get("get_orders_to_be_billed") and args.get("party_type") != "Student":
|
||||
orders_to_be_billed = get_orders_to_be_billed(
|
||||
args.get("posting_date"),
|
||||
args.get("party_type"),
|
||||
@@ -1354,25 +1419,22 @@ def get_outstanding_reference_documents(args):
|
||||
filters=args,
|
||||
)
|
||||
|
||||
# Get negative outstanding sales /purchase invoices
|
||||
negative_outstanding_invoices = []
|
||||
if args.get("party_type") not in ["Student", "Employee"] and not args.get("voucher_no"):
|
||||
negative_outstanding_invoices = get_negative_outstanding_invoices(
|
||||
args.get("party_type"),
|
||||
args.get("party"),
|
||||
args.get("party_account"),
|
||||
party_account_currency,
|
||||
company_currency,
|
||||
condition=condition,
|
||||
)
|
||||
|
||||
data = negative_outstanding_invoices + outstanding_invoices + orders_to_be_billed
|
||||
|
||||
if not data:
|
||||
if args.get("get_outstanding_invoices") and args.get("get_orders_to_be_billed"):
|
||||
ref_document_type = "invoices or orders"
|
||||
elif args.get("get_outstanding_invoices"):
|
||||
ref_document_type = "invoices"
|
||||
elif args.get("get_orders_to_be_billed"):
|
||||
ref_document_type = "orders"
|
||||
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"No outstanding invoices found for the {0} {1} which qualify the filters you have specified."
|
||||
).format(_(args.get("party_type")).lower(), frappe.bold(args.get("party")))
|
||||
"No outstanding {0} found for the {1} {2} which qualify the filters you have specified."
|
||||
).format(
|
||||
ref_document_type, _(args.get("party_type")).lower(), frappe.bold(args.get("party"))
|
||||
)
|
||||
)
|
||||
|
||||
return data
|
||||
@@ -1457,7 +1519,7 @@ def get_orders_to_be_billed(
|
||||
if voucher_type:
|
||||
doc = frappe.get_doc({"doctype": voucher_type})
|
||||
condition = ""
|
||||
if doc and hasattr(doc, "cost_center"):
|
||||
if doc and hasattr(doc, "cost_center") and doc.cost_center:
|
||||
condition = " and cost_center='%s'" % cost_center
|
||||
|
||||
orders = []
|
||||
@@ -1503,9 +1565,15 @@ def get_orders_to_be_billed(
|
||||
|
||||
order_list = []
|
||||
for d in orders:
|
||||
if not (
|
||||
flt(d.outstanding_amount) >= flt(filters.get("outstanding_amt_greater_than"))
|
||||
and flt(d.outstanding_amount) <= flt(filters.get("outstanding_amt_less_than"))
|
||||
if (
|
||||
filters
|
||||
and filters.get("outstanding_amt_greater_than")
|
||||
and filters.get("outstanding_amt_less_than")
|
||||
and not (
|
||||
flt(filters.get("outstanding_amt_greater_than"))
|
||||
<= flt(d.outstanding_amount)
|
||||
<= flt(filters.get("outstanding_amt_less_than"))
|
||||
)
|
||||
):
|
||||
continue
|
||||
|
||||
@@ -1855,7 +1923,10 @@ def get_payment_entry(
|
||||
):
|
||||
reference_doc = None
|
||||
doc = frappe.get_doc(dt, dn)
|
||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) > 0:
|
||||
over_billing_allowance = frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
|
||||
if dt in ("Sales Order", "Purchase Order") and flt(doc.per_billed, 2) >= (
|
||||
100.0 + over_billing_allowance
|
||||
):
|
||||
frappe.throw(_("Can only make payment against unbilled {0}").format(dt))
|
||||
|
||||
party_type = set_party_type(dt)
|
||||
@@ -1917,7 +1988,12 @@ def get_payment_entry(
|
||||
if doc.doctype == "Purchase Invoice" and doc.invoice_is_blocked():
|
||||
frappe.msgprint(_("{0} is on hold till {1}").format(doc.name, doc.release_date))
|
||||
else:
|
||||
if doc.doctype in ("Sales Invoice", "Purchase Invoice") and frappe.get_value(
|
||||
if doc.doctype in (
|
||||
"Sales Invoice",
|
||||
"Purchase Invoice",
|
||||
"Purchase Order",
|
||||
"Sales Order",
|
||||
) and frappe.get_value(
|
||||
"Payment Terms Template",
|
||||
{"name": doc.payment_terms_template},
|
||||
"allocate_payment_based_on_payment_terms",
|
||||
|
||||
@@ -999,6 +999,30 @@ class TestPaymentEntry(unittest.TestCase):
|
||||
|
||||
self.assertTrue("is on hold" in str(err.exception).lower())
|
||||
|
||||
def test_duplicate_payment_entry_allocate_amount(self):
|
||||
si = create_sales_invoice()
|
||||
|
||||
pe_draft = get_payment_entry("Sales Invoice", si.name)
|
||||
pe_draft.insert()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name)
|
||||
pe.submit()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||
|
||||
def test_duplicate_payment_entry_partial_allocate_amount(self):
|
||||
si = create_sales_invoice()
|
||||
|
||||
pe_draft = get_payment_entry("Sales Invoice", si.name)
|
||||
pe_draft.insert()
|
||||
|
||||
pe = get_payment_entry("Sales Invoice", si.name)
|
||||
pe.received_amount = si.total / 2
|
||||
pe.references[0].allocated_amount = si.total / 2
|
||||
pe.submit()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pe_draft.submit)
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
|
||||
@@ -42,7 +42,7 @@ frappe.ui.form.on("Payment Request", "refresh", function(frm) {
|
||||
});
|
||||
}
|
||||
|
||||
if(!frm.doc.payment_gateway_account && frm.doc.status == "Initiated") {
|
||||
if((!frm.doc.payment_gateway_account || frm.doc.payment_request_type == "Outward") && frm.doc.status == "Initiated") {
|
||||
frm.add_custom_button(__('Create Payment Entry'), function(){
|
||||
frappe.call({
|
||||
method: "erpnext.accounts.doctype.payment_request.payment_request.make_payment_entry",
|
||||
|
||||
@@ -254,6 +254,7 @@ class PaymentRequest(Document):
|
||||
|
||||
payment_entry.update(
|
||||
{
|
||||
"mode_of_payment": self.mode_of_payment,
|
||||
"reference_no": self.name,
|
||||
"reference_date": nowdate(),
|
||||
"remarks": "Payment Entry against {0} {1} via Payment Request {2}".format(
|
||||
@@ -403,25 +404,22 @@ def make_payment_request(**args):
|
||||
else ""
|
||||
)
|
||||
|
||||
existing_payment_request = None
|
||||
if args.order_type == "Shopping Cart":
|
||||
existing_payment_request = frappe.db.get_value(
|
||||
"Payment Request",
|
||||
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": ("!=", 2)},
|
||||
)
|
||||
draft_payment_request = frappe.db.get_value(
|
||||
"Payment Request",
|
||||
{"reference_doctype": args.dt, "reference_name": args.dn, "docstatus": 0},
|
||||
)
|
||||
|
||||
if existing_payment_request:
|
||||
existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
|
||||
|
||||
if existing_payment_request_amount:
|
||||
grand_total -= existing_payment_request_amount
|
||||
|
||||
if draft_payment_request:
|
||||
frappe.db.set_value(
|
||||
"Payment Request", existing_payment_request, "grand_total", grand_total, update_modified=False
|
||||
"Payment Request", draft_payment_request, "grand_total", grand_total, update_modified=False
|
||||
)
|
||||
pr = frappe.get_doc("Payment Request", existing_payment_request)
|
||||
pr = frappe.get_doc("Payment Request", draft_payment_request)
|
||||
else:
|
||||
if args.order_type != "Shopping Cart":
|
||||
existing_payment_request_amount = get_existing_payment_request_amount(args.dt, args.dn)
|
||||
|
||||
if existing_payment_request_amount:
|
||||
grand_total -= existing_payment_request_amount
|
||||
|
||||
pr = frappe.new_doc("Payment Request")
|
||||
pr.update(
|
||||
{
|
||||
|
||||
@@ -169,21 +169,18 @@ class PeriodClosingVoucher(AccountsController):
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
t2.account_currency,
|
||||
t1.account_currency,
|
||||
{dimension_fields},
|
||||
sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
|
||||
sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
|
||||
from `tabGL Entry` t1, `tabAccount` t2
|
||||
from `tabGL Entry` t1
|
||||
where
|
||||
t1.is_cancelled = 0
|
||||
and t1.account = t2.name
|
||||
and t2.report_type = 'Profit and Loss'
|
||||
and t2.docstatus < 2
|
||||
and t2.company = %s
|
||||
and t1.account in (select name from `tabAccount` where report_type = 'Profit and Loss' and docstatus < 2 and company = %s)
|
||||
and t1.posting_date between %s and %s
|
||||
group by {dimension_fields}
|
||||
""".format(
|
||||
dimension_fields=", ".join(dimension_fields)
|
||||
dimension_fields=", ".join(dimension_fields),
|
||||
),
|
||||
(self.company, self.get("year_start_date"), self.posting_date),
|
||||
as_dict=1,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import IfNull, Sum
|
||||
from frappe.utils import cint, flt, get_link_to_form, getdate, nowdate
|
||||
from six import iteritems
|
||||
|
||||
@@ -675,18 +676,22 @@ def get_bin_qty(item_code, warehouse):
|
||||
|
||||
|
||||
def get_pos_reserved_qty(item_code, warehouse):
|
||||
reserved_qty = frappe.db.sql(
|
||||
"""select sum(p_item.qty) as qty
|
||||
from `tabPOS Invoice` p, `tabPOS Invoice Item` p_item
|
||||
where p.name = p_item.parent
|
||||
and ifnull(p.consolidated_invoice, '') = ''
|
||||
and p_item.docstatus = 1
|
||||
and p_item.item_code = %s
|
||||
and p_item.warehouse = %s
|
||||
""",
|
||||
(item_code, warehouse),
|
||||
as_dict=1,
|
||||
)
|
||||
p_inv = frappe.qb.DocType("POS Invoice")
|
||||
p_item = frappe.qb.DocType("POS Invoice Item")
|
||||
|
||||
reserved_qty = (
|
||||
frappe.qb.from_(p_inv)
|
||||
.from_(p_item)
|
||||
.select(Sum(p_item.qty).as_("qty"))
|
||||
.where(
|
||||
(p_inv.name == p_item.parent)
|
||||
& (IfNull(p_inv.consolidated_invoice, "") == "")
|
||||
& (p_inv.is_return == 0)
|
||||
& (p_item.docstatus == 1)
|
||||
& (p_item.item_code == item_code)
|
||||
& (p_item.warehouse == warehouse)
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
return reserved_qty[0].qty or 0 if reserved_qty else 0
|
||||
|
||||
|
||||
@@ -303,7 +303,7 @@ erpnext.accounts.PurchaseInvoice = erpnext.buying.BuyingController.extend({
|
||||
|
||||
apply_tds: function(frm) {
|
||||
var me = this;
|
||||
|
||||
me.frm.set_value("tax_withheld_vouchers", []);
|
||||
if (!me.frm.doc.apply_tds) {
|
||||
me.frm.set_value("tax_withholding_category", '');
|
||||
me.frm.set_df_property("tax_withholding_category", "hidden", 1);
|
||||
|
||||
@@ -176,6 +176,7 @@
|
||||
"fieldname": "received_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Qty",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -872,7 +873,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-12 03:37:29.032732",
|
||||
"modified": "2023-07-02 18:39:41.495723",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -316,6 +316,7 @@ erpnext.accounts.SalesInvoiceController = erpnext.selling.SellingController.exte
|
||||
},
|
||||
|
||||
make_inter_company_invoice: function() {
|
||||
let me = this;
|
||||
frappe.model.open_mapped_doc({
|
||||
method: "erpnext.accounts.doctype.sales_invoice.sales_invoice.make_inter_company_purchase_invoice",
|
||||
frm: me.frm
|
||||
@@ -652,19 +653,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
}
|
||||
}
|
||||
|
||||
// expense account
|
||||
frm.fields_dict['items'].grid.get_field('expense_account').get_query = function(doc) {
|
||||
if (erpnext.is_perpetual_inventory_enabled(doc.company)) {
|
||||
return {
|
||||
filters: {
|
||||
'report_type': 'Profit and Loss',
|
||||
'company': doc.company,
|
||||
"is_group": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// discount account
|
||||
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
|
||||
return {
|
||||
|
||||
@@ -1107,7 +1107,7 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if self.is_return:
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_regain(
|
||||
asset, item.base_net_amount, item.finance_book
|
||||
asset, item.base_net_amount, item.finance_book, self.posting_date
|
||||
)
|
||||
asset.db_set("disposal_date", None)
|
||||
|
||||
@@ -1122,7 +1122,7 @@ class SalesInvoice(SellingController):
|
||||
asset.reload()
|
||||
|
||||
fixed_asset_gl_entries = get_gl_entries_on_asset_disposal(
|
||||
asset, item.base_net_amount, item.finance_book
|
||||
asset, item.base_net_amount, item.finance_book, self.posting_date
|
||||
)
|
||||
asset.db_set("disposal_date", self.posting_date)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, getdate
|
||||
from frappe.utils import cint, flt, getdate
|
||||
|
||||
|
||||
class TaxWithholdingCategory(Document):
|
||||
@@ -274,7 +274,7 @@ def get_invoice_vouchers(parties, tax_details, company, party_type="Supplier"):
|
||||
"docstatus": 1,
|
||||
}
|
||||
|
||||
if not tax_details.get("consider_party_ledger_amount") and doctype != "Sales Invoice":
|
||||
if doctype != "Sales Invoice":
|
||||
filters.update(
|
||||
{"apply_tds": 1, "tax_withholding_category": tax_details.get("tax_withholding_category")}
|
||||
)
|
||||
@@ -518,10 +518,19 @@ def get_invoice_total_without_tcs(inv, tax_details):
|
||||
|
||||
def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net_total):
|
||||
tds_amount = 0
|
||||
limit_consumed = frappe.db.get_value(
|
||||
"Purchase Invoice",
|
||||
{"supplier": ("in", parties), "apply_tds": 1, "docstatus": 1},
|
||||
"sum(net_total)",
|
||||
|
||||
limit_consumed = flt(
|
||||
frappe.db.get_all(
|
||||
"Purchase Invoice",
|
||||
filters={
|
||||
"supplier": ("in", parties),
|
||||
"apply_tds": 1,
|
||||
"docstatus": 1,
|
||||
"tax_withholding_category": ldc.tax_withholding_category,
|
||||
"posting_date": ("between", (ldc.valid_from, ldc.valid_upto)),
|
||||
},
|
||||
fields=["sum(base_net_total) as limit_consumed"],
|
||||
)[0].get("limit_consumed")
|
||||
)
|
||||
|
||||
if is_valid_certificate(
|
||||
@@ -535,10 +544,10 @@ def get_tds_amount_from_ldc(ldc, parties, pan_no, tax_details, posting_date, net
|
||||
|
||||
|
||||
def get_ltds_amount(current_amount, deducted_amount, certificate_limit, rate, tax_details):
|
||||
if current_amount < (certificate_limit - deducted_amount):
|
||||
if certificate_limit - flt(deducted_amount) - flt(current_amount) >= 0:
|
||||
return current_amount * rate / 100
|
||||
else:
|
||||
ltds_amount = certificate_limit - deducted_amount
|
||||
ltds_amount = certificate_limit - flt(deducted_amount)
|
||||
tds_amount = current_amount - ltds_amount
|
||||
|
||||
return ltds_amount * rate / 100 + tds_amount * tax_details.rate / 100
|
||||
@@ -549,9 +558,9 @@ def is_valid_certificate(
|
||||
):
|
||||
valid = False
|
||||
|
||||
if (
|
||||
getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)
|
||||
) and certificate_limit > deducted_amount:
|
||||
available_amount = flt(certificate_limit) - flt(deducted_amount)
|
||||
|
||||
if (getdate(valid_from) <= getdate(posting_date) <= getdate(valid_upto)) and available_amount > 0:
|
||||
valid = True
|
||||
|
||||
return valid
|
||||
|
||||
@@ -110,9 +110,9 @@ class TestTaxWithholdingCategory(unittest.TestCase):
|
||||
invoices.append(pi1)
|
||||
|
||||
# Cumulative threshold is 30000
|
||||
# Threshold calculation should be on both the invoices
|
||||
# TDS should be applied only on 1000
|
||||
self.assertEqual(pi1.taxes[0].tax_amount, 1000)
|
||||
# Threshold calculation should be only on the Second invoice
|
||||
# Second didn't breach, no TDS should be applied
|
||||
self.assertEqual(pi1.taxes, [])
|
||||
|
||||
for d in reversed(invoices):
|
||||
d.cancel()
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2016-04-08 14:49:58.133098",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:08:26.084484",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Asset Depreciation Ledger",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Asset",
|
||||
"report_name": "Asset Depreciation Ledger",
|
||||
"report_type": "Script Report",
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2016-04-08 14:49:58.133098",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2023-06-06 09:00:07.435151",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Asset Depreciation Ledger",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Asset",
|
||||
"report_name": "Asset Depreciation Ledger",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"apply_user_permissions": 1,
|
||||
"creation": "2016-04-08 14:56:37.235981",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2017-02-24 20:08:18.660476",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Asset Depreciations and Balances",
|
||||
"owner": "Administrator",
|
||||
"ref_doctype": "Asset",
|
||||
"report_name": "Asset Depreciations and Balances",
|
||||
"report_type": "Script Report",
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2016-04-08 14:56:37.235981",
|
||||
"disable_prepared_report": 0,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2023-06-06 11:33:29.611277",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Asset Depreciations and Balances",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Asset",
|
||||
"report_name": "Asset Depreciations and Balances",
|
||||
"report_type": "Script Report",
|
||||
"roles": [
|
||||
{
|
||||
"role": "Accounts User"
|
||||
|
||||
@@ -114,28 +114,6 @@ def get_assets(filters):
|
||||
sum(results.depreciation_eliminated_during_the_period) as depreciation_eliminated_during_the_period,
|
||||
sum(results.depreciation_amount_during_the_period) as depreciation_amount_during_the_period
|
||||
from (SELECT a.asset_category,
|
||||
ifnull(sum(case when ds.schedule_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
||||
ds.depreciation_amount
|
||||
else
|
||||
0
|
||||
end), 0) as accumulated_depreciation_as_on_from_date,
|
||||
ifnull(sum(case when ifnull(a.disposal_date, 0) != 0 and a.disposal_date >= %(from_date)s
|
||||
and a.disposal_date <= %(to_date)s and ds.schedule_date <= a.disposal_date then
|
||||
ds.depreciation_amount
|
||||
else
|
||||
0
|
||||
end), 0) as depreciation_eliminated_during_the_period,
|
||||
ifnull(sum(case when ds.schedule_date >= %(from_date)s and ds.schedule_date <= %(to_date)s
|
||||
and (ifnull(a.disposal_date, 0) = 0 or ds.schedule_date <= a.disposal_date) then
|
||||
ds.depreciation_amount
|
||||
else
|
||||
0
|
||||
end), 0) as depreciation_amount_during_the_period
|
||||
from `tabAsset` a, `tabDepreciation Schedule` ds
|
||||
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and a.name = ds.parent and ifnull(ds.journal_entry, '') != ''
|
||||
group by a.asset_category
|
||||
union
|
||||
SELECT a.asset_category,
|
||||
ifnull(sum(case when gle.posting_date < %(from_date)s and (ifnull(a.disposal_date, 0) = 0 or a.disposal_date >= %(from_date)s) then
|
||||
gle.debit
|
||||
else
|
||||
@@ -160,7 +138,7 @@ def get_assets(filters):
|
||||
aca.parent = a.asset_category and aca.company_name = %(company)s
|
||||
join `tabCompany` company on
|
||||
company.name = %(company)s
|
||||
where a.docstatus=1 and a.company=%(company)s and a.calculate_depreciation=0 and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||
where a.docstatus=1 and a.company=%(company)s and a.purchase_date <= %(to_date)s and gle.debit != 0 and gle.is_cancelled = 0 and gle.account = ifnull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||
group by a.asset_category
|
||||
union
|
||||
SELECT a.asset_category,
|
||||
|
||||
@@ -79,7 +79,7 @@ def get_entries(filters):
|
||||
payment_entries = frappe.db.sql(
|
||||
"""SELECT
|
||||
"Payment Entry", name, posting_date, reference_no, clearance_date, party,
|
||||
if(paid_from=%(account)s, paid_amount * -1, received_amount)
|
||||
if(paid_from=%(account)s, ((paid_amount * -1) - total_taxes_and_charges) , received_amount)
|
||||
FROM
|
||||
`tabPayment Entry`
|
||||
WHERE
|
||||
|
||||
@@ -524,11 +524,26 @@ def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||
additional_conditions.append("cost_center in %(cost_center)s")
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
additional_conditions.append(
|
||||
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
if filters.get("finance_book"):
|
||||
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
|
||||
filters.get("company_fb")
|
||||
):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
additional_conditions.append(
|
||||
"(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
else:
|
||||
additional_conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
if filters.get("finance_book"):
|
||||
additional_conditions.append(
|
||||
"(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
else:
|
||||
additional_conditions.append("(finance_book in ('') OR finance_book IS NULL)")
|
||||
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
|
||||
@@ -176,7 +176,8 @@ frappe.query_reports["General Ledger"] = {
|
||||
{
|
||||
"fieldname": "include_default_book_entries",
|
||||
"label": __("Include Default Book Entries"),
|
||||
"fieldtype": "Check"
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "show_cancelled_entries",
|
||||
|
||||
@@ -287,13 +287,23 @@ def get_conditions(filters):
|
||||
if filters.get("project"):
|
||||
conditions.append("project in %(project)s")
|
||||
|
||||
if filters.get("finance_book"):
|
||||
if filters.get("include_default_book_entries"):
|
||||
conditions.append(
|
||||
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
if filters.get("include_default_book_entries"):
|
||||
if filters.get("finance_book"):
|
||||
if filters.get("company_fb") and cstr(filters.get("finance_book")) != cstr(
|
||||
filters.get("company_fb")
|
||||
):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
conditions.append("finance_book in (%(finance_book)s)")
|
||||
conditions.append("(finance_book in (%(company_fb)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
if filters.get("finance_book"):
|
||||
conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
else:
|
||||
conditions.append("(finance_book in ('') OR finance_book IS NULL)")
|
||||
|
||||
if not filters.get("show_cancelled_entries"):
|
||||
conditions.append("is_cancelled = 0")
|
||||
|
||||
@@ -125,12 +125,14 @@ def get_revenue(data, period_list, include_in_gross=1):
|
||||
|
||||
data_to_be_removed = True
|
||||
while data_to_be_removed:
|
||||
revenue, data_to_be_removed = remove_parent_with_no_child(revenue, period_list)
|
||||
revenue = adjust_account(revenue, period_list)
|
||||
revenue, data_to_be_removed = remove_parent_with_no_child(revenue)
|
||||
|
||||
adjust_account_totals(revenue, period_list)
|
||||
|
||||
return copy.deepcopy(revenue)
|
||||
|
||||
|
||||
def remove_parent_with_no_child(data, period_list):
|
||||
def remove_parent_with_no_child(data):
|
||||
data_to_be_removed = False
|
||||
for parent in data:
|
||||
if "is_group" in parent and parent.get("is_group") == 1:
|
||||
@@ -147,16 +149,19 @@ def remove_parent_with_no_child(data, period_list):
|
||||
return data, data_to_be_removed
|
||||
|
||||
|
||||
def adjust_account(data, period_list, consolidated=False):
|
||||
leaf_nodes = [item for item in data if item["is_group"] == 0]
|
||||
def adjust_account_totals(data, period_list):
|
||||
totals = {}
|
||||
for node in leaf_nodes:
|
||||
set_total(node, node["total"], data, totals)
|
||||
for d in data:
|
||||
for period in period_list:
|
||||
key = period if consolidated else period.key
|
||||
d["total"] = totals[d["account"]]
|
||||
return data
|
||||
for d in reversed(data):
|
||||
if d.get("is_group"):
|
||||
for period in period_list:
|
||||
# reset totals for group accounts as totals set by get_data doesn't consider include_in_gross check
|
||||
d[period.key] = sum(
|
||||
item[period.key] for item in data if item.get("parent_account") == d.get("account")
|
||||
)
|
||||
else:
|
||||
set_total(d, d["total"], data, totals)
|
||||
|
||||
d["total"] = totals[d["account"]]
|
||||
|
||||
|
||||
def set_total(node, value, complete_list, totals):
|
||||
@@ -191,6 +196,9 @@ def get_profit(
|
||||
|
||||
if profit_loss[key]:
|
||||
has_value = True
|
||||
if not profit_loss.get("total"):
|
||||
profit_loss["total"] = 0
|
||||
profit_loss["total"] += profit_loss[key]
|
||||
|
||||
if has_value:
|
||||
return profit_loss
|
||||
@@ -229,6 +237,9 @@ def get_net_profit(
|
||||
|
||||
if profit_loss[key]:
|
||||
has_value = True
|
||||
if not profit_loss.get("total"):
|
||||
profit_loss["total"] = 0
|
||||
profit_loss["total"] += profit_loss[key]
|
||||
|
||||
if has_value:
|
||||
return profit_loss
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
import frappe
|
||||
from frappe import _, qb, scrub
|
||||
@@ -666,7 +667,7 @@ class GrossProfitGenerator(object):
|
||||
def load_invoice_items(self):
|
||||
conditions = ""
|
||||
if self.filters.company:
|
||||
conditions += " and company = %(company)s"
|
||||
conditions += " and `tabSales Invoice`.company = %(company)s"
|
||||
if self.filters.from_date:
|
||||
conditions += " and posting_date >= %(from_date)s"
|
||||
if self.filters.to_date:
|
||||
@@ -760,30 +761,30 @@ class GrossProfitGenerator(object):
|
||||
Turns list of Sales Invoice Items to a tree of Sales Invoices with their Items as children.
|
||||
"""
|
||||
|
||||
parents = []
|
||||
grouped = OrderedDict()
|
||||
|
||||
for row in self.si_list:
|
||||
if row.parent not in parents:
|
||||
parents.append(row.parent)
|
||||
# initialize list with a header row for each new parent
|
||||
grouped.setdefault(row.parent, [self.get_invoice_row(row)]).append(
|
||||
row.update(
|
||||
{"indent": 1.0, "parent_invoice": row.parent, "invoice_or_item": row.item_code}
|
||||
) # descendant rows will have indent: 1.0 or greater
|
||||
)
|
||||
|
||||
parents_index = 0
|
||||
for index, row in enumerate(self.si_list):
|
||||
if parents_index < len(parents) and row.parent == parents[parents_index]:
|
||||
invoice = self.get_invoice_row(row)
|
||||
self.si_list.insert(index, invoice)
|
||||
parents_index += 1
|
||||
# if item is a bundle, add it's components as seperate rows
|
||||
if frappe.db.exists("Product Bundle", row.item_code):
|
||||
bundled_items = self.get_bundle_items(row)
|
||||
for x in bundled_items:
|
||||
bundle_item = self.get_bundle_item_row(row, x)
|
||||
grouped.get(row.parent).append(bundle_item)
|
||||
|
||||
else:
|
||||
# skipping the bundle items rows
|
||||
if not row.indent:
|
||||
row.indent = 1.0
|
||||
row.parent_invoice = row.parent
|
||||
row.invoice_or_item = row.item_code
|
||||
self.si_list.clear()
|
||||
|
||||
if frappe.db.exists("Product Bundle", row.item_code):
|
||||
self.add_bundle_items(row, index)
|
||||
for items in grouped.values():
|
||||
self.si_list.extend(items)
|
||||
|
||||
def get_invoice_row(self, row):
|
||||
# header row format
|
||||
return frappe._dict(
|
||||
{
|
||||
"parent_invoice": "",
|
||||
@@ -812,13 +813,6 @@ class GrossProfitGenerator(object):
|
||||
}
|
||||
)
|
||||
|
||||
def add_bundle_items(self, product_bundle, index):
|
||||
bundle_items = self.get_bundle_items(product_bundle)
|
||||
|
||||
for i, item in enumerate(bundle_items):
|
||||
bundle_item = self.get_bundle_item_row(product_bundle, item)
|
||||
self.si_list.insert((index + i + 1), bundle_item)
|
||||
|
||||
def get_bundle_items(self, product_bundle):
|
||||
return frappe.get_all(
|
||||
"Product Bundle Item", filters={"parent": product_bundle.item_code}, fields=["item_code", "qty"]
|
||||
|
||||
@@ -15,7 +15,6 @@ from erpnext.accounts.report.item_wise_sales_register.item_wise_sales_register i
|
||||
get_group_by_conditions,
|
||||
get_tax_accounts,
|
||||
)
|
||||
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import get_item_details
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -40,6 +39,16 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
tax_doctype="Purchase Taxes and Charges",
|
||||
)
|
||||
|
||||
scrubbed_tax_fields = {}
|
||||
|
||||
for tax in tax_columns:
|
||||
scrubbed_tax_fields.update(
|
||||
{
|
||||
tax + " Rate": frappe.scrub(tax + " Rate"),
|
||||
tax + " Amount": frappe.scrub(tax + " Amount"),
|
||||
}
|
||||
)
|
||||
|
||||
po_pr_map = get_purchase_receipts_against_purchase_order(item_list)
|
||||
|
||||
data = []
|
||||
@@ -50,11 +59,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
if filters.get("group_by"):
|
||||
grand_total = get_grand_total(filters, "Purchase Invoice")
|
||||
|
||||
item_details = get_item_details()
|
||||
|
||||
for d in item_list:
|
||||
item_record = item_details.get(d.item_code)
|
||||
|
||||
purchase_receipt = None
|
||||
if d.purchase_receipt:
|
||||
purchase_receipt = d.purchase_receipt
|
||||
@@ -67,8 +72,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
|
||||
row = {
|
||||
"item_code": d.item_code,
|
||||
"item_name": item_record.item_name if item_record else d.item_name,
|
||||
"item_group": item_record.item_group if item_record else d.item_group,
|
||||
"item_name": d.pi_item_name if d.pi_item_name else d.i_item_name,
|
||||
"item_group": d.pi_item_group if d.pi_item_group else d.i_item_group,
|
||||
"description": d.description,
|
||||
"invoice": d.parent,
|
||||
"posting_date": d.posting_date,
|
||||
@@ -87,7 +92,7 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
"project": d.project,
|
||||
"company": d.company,
|
||||
"purchase_order": d.purchase_order,
|
||||
"purchase_receipt": d.purchase_receipt,
|
||||
"purchase_receipt": purchase_receipt,
|
||||
"expense_account": expense_account,
|
||||
"stock_qty": d.stock_qty,
|
||||
"stock_uom": d.stock_uom,
|
||||
@@ -101,8 +106,8 @@ def _execute(filters=None, additional_table_columns=None, additional_query_colum
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
row.update(
|
||||
{
|
||||
frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
|
||||
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
|
||||
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
|
||||
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
|
||||
}
|
||||
)
|
||||
total_tax += flt(item_tax.get("tax_amount"))
|
||||
@@ -241,7 +246,7 @@ def get_columns(additional_table_columns, filters):
|
||||
},
|
||||
{
|
||||
"label": _("Purchase Receipt"),
|
||||
"fieldname": "Purchase Receipt",
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Receipt",
|
||||
"width": 100,
|
||||
@@ -325,15 +330,17 @@ def get_items(filters, additional_query_columns):
|
||||
`tabPurchase Invoice`.supplier, `tabPurchase Invoice`.remarks, `tabPurchase Invoice`.base_net_total,
|
||||
`tabPurchase Invoice`.unrealized_profit_loss_account,
|
||||
`tabPurchase Invoice Item`.`item_code`, `tabPurchase Invoice Item`.description,
|
||||
`tabPurchase Invoice Item`.`item_name`, `tabPurchase Invoice Item`.`item_group`,
|
||||
`tabPurchase Invoice Item`.`item_name` as pi_item_name, `tabPurchase Invoice Item`.`item_group` as pi_item_group,
|
||||
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
|
||||
`tabPurchase Invoice Item`.`project`, `tabPurchase Invoice Item`.`purchase_order`,
|
||||
`tabPurchase Invoice Item`.`purchase_receipt`, `tabPurchase Invoice Item`.`po_detail`,
|
||||
`tabPurchase Invoice Item`.`expense_account`, `tabPurchase Invoice Item`.`stock_qty`,
|
||||
`tabPurchase Invoice Item`.`stock_uom`, `tabPurchase Invoice Item`.`base_net_amount`,
|
||||
`tabPurchase Invoice`.`supplier_name`, `tabPurchase Invoice`.`mode_of_payment` {0}
|
||||
from `tabPurchase Invoice`, `tabPurchase Invoice Item`
|
||||
from `tabPurchase Invoice`, `tabPurchase Invoice Item`, `tabItem`
|
||||
where `tabPurchase Invoice`.name = `tabPurchase Invoice Item`.`parent` and
|
||||
`tabPurchase Invoice`.docstatus = 1 %s
|
||||
`tabItem`.name = `tabPurchase Invoice Item`.`item_code` and
|
||||
`tabPurchase Invoice`.docstatus = 1 %s
|
||||
""".format(
|
||||
additional_query_columns
|
||||
)
|
||||
|
||||
@@ -11,7 +11,6 @@ from frappe.utils.xlsxutils import handle_html
|
||||
from erpnext.accounts.report.sales_register.sales_register import get_mode_of_payments
|
||||
from erpnext.selling.report.item_wise_sales_history.item_wise_sales_history import (
|
||||
get_customer_details,
|
||||
get_item_details,
|
||||
)
|
||||
|
||||
|
||||
@@ -35,6 +34,16 @@ def _execute(
|
||||
if item_list:
|
||||
itemised_tax, tax_columns = get_tax_accounts(item_list, columns, company_currency)
|
||||
|
||||
scrubbed_tax_fields = {}
|
||||
|
||||
for tax in tax_columns:
|
||||
scrubbed_tax_fields.update(
|
||||
{
|
||||
tax + " Rate": frappe.scrub(tax + " Rate"),
|
||||
tax + " Amount": frappe.scrub(tax + " Amount"),
|
||||
}
|
||||
)
|
||||
|
||||
mode_of_payments = get_mode_of_payments(set(d.parent for d in item_list))
|
||||
so_dn_map = get_delivery_notes_against_sales_order(item_list)
|
||||
|
||||
@@ -47,11 +56,9 @@ def _execute(
|
||||
grand_total = get_grand_total(filters, "Sales Invoice")
|
||||
|
||||
customer_details = get_customer_details()
|
||||
item_details = get_item_details()
|
||||
|
||||
for d in item_list:
|
||||
customer_record = customer_details.get(d.customer)
|
||||
item_record = item_details.get(d.item_code)
|
||||
|
||||
delivery_note = None
|
||||
if d.delivery_note:
|
||||
@@ -64,8 +71,8 @@ def _execute(
|
||||
|
||||
row = {
|
||||
"item_code": d.item_code,
|
||||
"item_name": item_record.item_name if item_record else d.item_name,
|
||||
"item_group": item_record.item_group if item_record else d.item_group,
|
||||
"item_name": d.si_item_name if d.si_item_name else d.i_item_name,
|
||||
"item_group": d.si_item_group if d.si_item_group else d.i_item_group,
|
||||
"description": d.description,
|
||||
"invoice": d.parent,
|
||||
"posting_date": d.posting_date,
|
||||
@@ -107,8 +114,8 @@ def _execute(
|
||||
item_tax = itemised_tax.get(d.name, {}).get(tax, {})
|
||||
row.update(
|
||||
{
|
||||
frappe.scrub(tax + " Rate"): item_tax.get("tax_rate", 0),
|
||||
frappe.scrub(tax + " Amount"): item_tax.get("tax_amount", 0),
|
||||
scrubbed_tax_fields[tax + " Rate"]: item_tax.get("tax_rate", 0),
|
||||
scrubbed_tax_fields[tax + " Amount"]: item_tax.get("tax_amount", 0),
|
||||
}
|
||||
)
|
||||
if item_tax.get("is_other_charges"):
|
||||
@@ -399,19 +406,23 @@ def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
`tabSales Invoice`.posting_date, `tabSales Invoice`.debit_to,
|
||||
`tabSales Invoice`.unrealized_profit_loss_account,
|
||||
`tabSales Invoice`.is_internal_customer,
|
||||
`tabSales Invoice`.project, `tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||
`tabSales Invoice`.customer, `tabSales Invoice`.remarks,
|
||||
`tabSales Invoice`.territory, `tabSales Invoice`.company, `tabSales Invoice`.base_net_total,
|
||||
`tabSales Invoice Item`.project,
|
||||
`tabSales Invoice Item`.item_code, `tabSales Invoice Item`.description,
|
||||
`tabSales Invoice Item`.`item_name`, `tabSales Invoice Item`.`item_group`,
|
||||
`tabSales Invoice Item`.`item_name` as si_item_name, `tabSales Invoice Item`.`item_group` as si_item_group,
|
||||
`tabItem`.`item_name` as i_item_name, `tabItem`.`item_group` as i_item_group,
|
||||
`tabSales Invoice Item`.sales_order, `tabSales Invoice Item`.delivery_note,
|
||||
`tabSales Invoice Item`.income_account, `tabSales Invoice Item`.cost_center,
|
||||
`tabSales Invoice Item`.stock_qty, `tabSales Invoice Item`.stock_uom,
|
||||
`tabSales Invoice Item`.base_net_rate, `tabSales Invoice Item`.base_net_amount,
|
||||
`tabSales Invoice`.customer_name, `tabSales Invoice`.customer_group, `tabSales Invoice Item`.so_detail,
|
||||
`tabSales Invoice`.update_stock, `tabSales Invoice Item`.uom, `tabSales Invoice Item`.qty {0}
|
||||
from `tabSales Invoice`, `tabSales Invoice Item`
|
||||
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent
|
||||
and `tabSales Invoice`.docstatus = 1 {1}
|
||||
from `tabSales Invoice`, `tabSales Invoice Item`, `tabItem`
|
||||
where `tabSales Invoice`.name = `tabSales Invoice Item`.parent and
|
||||
`tabItem`.name = `tabSales Invoice Item`.`item_code` and
|
||||
`tabSales Invoice`.docstatus = 1 {1}
|
||||
""".format(
|
||||
additional_query_columns or "", conditions
|
||||
),
|
||||
|
||||
@@ -157,12 +157,25 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
if filters.project:
|
||||
additional_conditions += " and project = %(project)s"
|
||||
|
||||
company_fb = frappe.db.get_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
additional_conditions += (
|
||||
" AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
if filters.get("finance_book"):
|
||||
if company_fb and cstr(filters.get("finance_book")) != cstr(company_fb):
|
||||
frappe.throw(
|
||||
_("To use a different finance book, please uncheck 'Include Default Book Entries'")
|
||||
)
|
||||
else:
|
||||
additional_conditions += (
|
||||
" AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
else:
|
||||
additional_conditions += " AND (finance_book in (%(company_fb)s, '') OR finance_book IS NULL)"
|
||||
else:
|
||||
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
if filters.get("finance_book"):
|
||||
additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
|
||||
else:
|
||||
additional_conditions += " AND (finance_book in ('') OR finance_book IS NULL)"
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
@@ -174,7 +187,7 @@ def get_rootwise_opening_balances(filters, report_type):
|
||||
"year_start_date": filters.year_start_date,
|
||||
"project": filters.project,
|
||||
"finance_book": filters.finance_book,
|
||||
"company_fb": frappe.db.get_value("Company", filters.company, "default_finance_book"),
|
||||
"company_fb": company_fb,
|
||||
}
|
||||
|
||||
if accounting_dimensions:
|
||||
|
||||
@@ -27,6 +27,7 @@ from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
get_depreciation_accounts,
|
||||
get_disposal_account_and_cost_center,
|
||||
is_first_day_of_the_month,
|
||||
is_last_day_of_the_month,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
@@ -339,13 +340,9 @@ class Asset(AccountsController):
|
||||
if should_get_last_day:
|
||||
schedule_date = get_last_day(schedule_date)
|
||||
|
||||
# schedule date will be a year later from start date
|
||||
# so monthly schedule date is calculated by removing 11 months from it
|
||||
monthly_schedule_date = add_months(schedule_date, -finance_book.frequency_of_depreciation + 1)
|
||||
|
||||
# if asset is being sold
|
||||
if date_of_disposal:
|
||||
from_date = self.get_from_date(finance_book.finance_book)
|
||||
from_date = self.get_from_date_for_disposal(finance_book)
|
||||
depreciation_amount, days, months = self.get_pro_rata_amt(
|
||||
finance_book,
|
||||
depreciation_amount,
|
||||
@@ -369,9 +366,9 @@ class Asset(AccountsController):
|
||||
|
||||
# For first row
|
||||
if (
|
||||
(has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||
n == 0
|
||||
and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
|
||||
and not self.opening_accumulated_depreciation
|
||||
and n == 0
|
||||
):
|
||||
from_date = add_days(
|
||||
self.available_for_use_date, -1
|
||||
@@ -383,10 +380,26 @@ class Asset(AccountsController):
|
||||
finance_book.depreciation_start_date,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
# For first depr schedule date will be the start date
|
||||
# so monthly schedule date is calculated by removing month difference between use date and start date
|
||||
monthly_schedule_date = add_months(finance_book.depreciation_start_date, -months + 1)
|
||||
elif n == 0 and has_wdv_or_dd_non_yearly_pro_rata and self.opening_accumulated_depreciation:
|
||||
if not is_first_day_of_the_month(getdate(self.available_for_use_date)):
|
||||
from_date = get_last_day(
|
||||
add_months(
|
||||
getdate(self.available_for_use_date),
|
||||
((self.number_of_depreciations_booked - 1) * finance_book.frequency_of_depreciation),
|
||||
)
|
||||
)
|
||||
else:
|
||||
from_date = add_months(
|
||||
getdate(add_days(self.available_for_use_date, -1)),
|
||||
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
|
||||
)
|
||||
depreciation_amount, days, months = self.get_pro_rata_amt(
|
||||
finance_book,
|
||||
depreciation_amount,
|
||||
from_date,
|
||||
finance_book.depreciation_start_date,
|
||||
has_wdv_or_dd_non_yearly_pro_rata,
|
||||
)
|
||||
|
||||
# For last row
|
||||
elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
|
||||
@@ -411,9 +424,7 @@ class Asset(AccountsController):
|
||||
depreciation_amount_without_pro_rata, depreciation_amount, finance_book.finance_book
|
||||
)
|
||||
|
||||
monthly_schedule_date = add_months(schedule_date, 1)
|
||||
schedule_date = add_days(schedule_date, days)
|
||||
last_schedule_date = schedule_date
|
||||
|
||||
if not depreciation_amount:
|
||||
continue
|
||||
@@ -432,7 +443,7 @@ class Asset(AccountsController):
|
||||
)
|
||||
skip_row = True
|
||||
|
||||
if depreciation_amount > 0:
|
||||
if flt(depreciation_amount, self.precision("gross_purchase_amount")) > 0:
|
||||
self.append(
|
||||
"schedules",
|
||||
{
|
||||
@@ -490,16 +501,19 @@ class Asset(AccountsController):
|
||||
for idx, s in enumerate(self.schedules, 1):
|
||||
s.idx = idx
|
||||
|
||||
def get_from_date(self, finance_book):
|
||||
def get_from_date_for_disposal(self, finance_book):
|
||||
if not self.get("schedules"):
|
||||
return self.available_for_use_date
|
||||
return add_months(
|
||||
getdate(self.available_for_use_date),
|
||||
(self.number_of_depreciations_booked * finance_book.frequency_of_depreciation),
|
||||
)
|
||||
|
||||
if len(self.finance_books) == 1:
|
||||
return self.schedules[-1].schedule_date
|
||||
|
||||
from_date = ""
|
||||
for schedule in self.get("schedules"):
|
||||
if schedule.finance_book == finance_book:
|
||||
if schedule.finance_book == finance_book.finance_book:
|
||||
from_date = schedule.schedule_date
|
||||
|
||||
if from_date:
|
||||
@@ -1281,9 +1295,11 @@ def get_straight_line_or_manual_depr_amount(asset, row):
|
||||
)
|
||||
# if the Depreciation Schedule is being prepared for the first time
|
||||
else:
|
||||
return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
|
||||
row.total_number_of_depreciations
|
||||
)
|
||||
return (
|
||||
flt(asset.gross_purchase_amount)
|
||||
- flt(asset.opening_accumulated_depreciation)
|
||||
- flt(row.expected_value_after_useful_life)
|
||||
) / flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
|
||||
|
||||
|
||||
def get_wdv_or_dd_depr_amount(
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe.utils import (
|
||||
add_months,
|
||||
cint,
|
||||
flt,
|
||||
get_first_day,
|
||||
get_last_day,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
@@ -133,16 +134,17 @@ def make_depreciation_entry(asset_name, date=None):
|
||||
je.append("accounts", debit_entry)
|
||||
|
||||
je.flags.ignore_permissions = True
|
||||
je.flags.planned_depr_entry = True
|
||||
je.save()
|
||||
if not je.meta.get_workflow():
|
||||
je.submit()
|
||||
|
||||
d.db_set("journal_entry", je.name)
|
||||
|
||||
idx = cint(d.finance_book_id)
|
||||
finance_books = asset.get("finance_books")[idx - 1]
|
||||
finance_books.value_after_depreciation -= d.depreciation_amount
|
||||
finance_books.db_update()
|
||||
if not je.meta.get_workflow():
|
||||
je.submit()
|
||||
idx = cint(d.finance_book_id)
|
||||
finance_books = asset.get("finance_books")[idx - 1]
|
||||
finance_books.value_after_depreciation -= d.depreciation_amount
|
||||
finance_books.db_update()
|
||||
|
||||
asset.db_set("depr_entry_posting_status", "Successful")
|
||||
|
||||
@@ -278,7 +280,7 @@ def scrap_asset(asset_name):
|
||||
je.company = asset.company
|
||||
je.remark = "Scrap Entry for asset {0}".format(asset_name)
|
||||
|
||||
for entry in get_gl_entries_on_asset_disposal(asset):
|
||||
for entry in get_gl_entries_on_asset_disposal(asset, date):
|
||||
entry.update({"reference_type": "Asset", "reference_name": asset_name})
|
||||
je.append("accounts", entry)
|
||||
|
||||
@@ -342,6 +344,9 @@ def modify_depreciation_schedule_for_asset_repairs(asset):
|
||||
def reverse_depreciation_entry_made_after_disposal(asset, date):
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||
|
||||
if not asset.calculate_depreciation:
|
||||
return
|
||||
|
||||
row = -1
|
||||
finance_book = asset.get("schedules")[0].get("finance_book")
|
||||
for schedule in asset.get("schedules"):
|
||||
@@ -402,7 +407,10 @@ def disposal_happens_in_the_future(posting_date_of_disposal):
|
||||
return False
|
||||
|
||||
|
||||
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
|
||||
def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None, date=None):
|
||||
if not date:
|
||||
date = getdate()
|
||||
|
||||
(
|
||||
fixed_asset_account,
|
||||
asset,
|
||||
@@ -419,23 +427,30 @@ def get_gl_entries_on_asset_regain(asset, selling_amount=0, finance_book=None):
|
||||
"debit_in_account_currency": asset.gross_purchase_amount,
|
||||
"debit": asset.gross_purchase_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": date,
|
||||
},
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"credit_in_account_currency": accumulated_depr_amount,
|
||||
"credit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": date,
|
||||
},
|
||||
]
|
||||
|
||||
profit_amount = abs(flt(value_after_depreciation)) - abs(flt(selling_amount))
|
||||
if profit_amount:
|
||||
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
||||
get_profit_gl_entries(
|
||||
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
|
||||
)
|
||||
|
||||
return gl_entries
|
||||
|
||||
|
||||
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None):
|
||||
def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None, date=None):
|
||||
if not date:
|
||||
date = getdate()
|
||||
|
||||
(
|
||||
fixed_asset_account,
|
||||
asset,
|
||||
@@ -452,18 +467,26 @@ def get_gl_entries_on_asset_disposal(asset, selling_amount=0, finance_book=None)
|
||||
"credit_in_account_currency": asset.gross_purchase_amount,
|
||||
"credit": asset.gross_purchase_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
},
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"debit_in_account_currency": accumulated_depr_amount,
|
||||
"debit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": date,
|
||||
},
|
||||
]
|
||||
|
||||
if accumulated_depr_amount:
|
||||
gl_entries.append(
|
||||
{
|
||||
"account": accumulated_depr_account,
|
||||
"debit_in_account_currency": accumulated_depr_amount,
|
||||
"debit": accumulated_depr_amount,
|
||||
"cost_center": depreciation_cost_center,
|
||||
"posting_date": date,
|
||||
},
|
||||
)
|
||||
|
||||
profit_amount = flt(selling_amount) - flt(value_after_depreciation)
|
||||
if profit_amount:
|
||||
get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center)
|
||||
get_profit_gl_entries(
|
||||
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date
|
||||
)
|
||||
|
||||
return gl_entries
|
||||
|
||||
@@ -490,7 +513,12 @@ def get_asset_details(asset, finance_book=None):
|
||||
)
|
||||
|
||||
|
||||
def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciation_cost_center):
|
||||
def get_profit_gl_entries(
|
||||
profit_amount, gl_entries, disposal_account, depreciation_cost_center, date=None
|
||||
):
|
||||
if not date:
|
||||
date = getdate()
|
||||
|
||||
debit_or_credit = "debit" if profit_amount < 0 else "credit"
|
||||
gl_entries.append(
|
||||
{
|
||||
@@ -498,6 +526,7 @@ def get_profit_gl_entries(profit_amount, gl_entries, disposal_account, depreciat
|
||||
"cost_center": depreciation_cost_center,
|
||||
debit_or_credit: abs(profit_amount),
|
||||
debit_or_credit + "_in_account_currency": abs(profit_amount),
|
||||
"posting_date": date,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -522,3 +551,9 @@ def is_last_day_of_the_month(date):
|
||||
last_day_of_the_month = get_last_day(date)
|
||||
|
||||
return getdate(last_day_of_the_month) == getdate(date)
|
||||
|
||||
|
||||
def is_first_day_of_the_month(date):
|
||||
first_day_of_the_month = get_first_day(date)
|
||||
|
||||
return getdate(first_day_of_the_month) == getdate(date)
|
||||
|
||||
@@ -298,6 +298,79 @@ class TestAsset(AssetSetup):
|
||||
si.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Partially Depreciated")
|
||||
|
||||
def test_gle_made_by_asset_sale_for_existing_asset(self):
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
available_for_use_date="2020-04-01",
|
||||
purchase_date="2020-04-01",
|
||||
expected_value_after_useful_life=0,
|
||||
total_number_of_depreciations=5,
|
||||
number_of_depreciations_booked=2,
|
||||
frequency_of_depreciation=12,
|
||||
depreciation_start_date="2023-03-31",
|
||||
opening_accumulated_depreciation=24000,
|
||||
gross_purchase_amount=60000,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
expected_depr_values = [
|
||||
["2023-03-31", 12000, 36000],
|
||||
["2024-03-31", 12000, 48000],
|
||||
["2025-03-31", 12000, 60000],
|
||||
]
|
||||
|
||||
for i, schedule in enumerate(asset.schedules):
|
||||
self.assertEqual(getdate(expected_depr_values[i][0]), schedule.schedule_date)
|
||||
self.assertEqual(expected_depr_values[i][1], schedule.depreciation_amount)
|
||||
self.assertEqual(expected_depr_values[i][2], schedule.accumulated_depreciation_amount)
|
||||
|
||||
post_depreciation_entries(date="2023-03-31")
|
||||
|
||||
si = create_sales_invoice(
|
||||
item_code="Macbook Pro", asset=asset.name, qty=1, rate=40000, posting_date=getdate("2023-05-23")
|
||||
)
|
||||
asset.load_from_db()
|
||||
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "status"), "Sold")
|
||||
|
||||
expected_values = [["2023-03-31", 12000, 36000], ["2023-05-23", 1742.47, 37742.47]]
|
||||
|
||||
for i, schedule in enumerate(asset.schedules):
|
||||
self.assertEqual(getdate(expected_values[i][0]), schedule.schedule_date)
|
||||
self.assertEqual(expected_values[i][1], schedule.depreciation_amount)
|
||||
self.assertEqual(expected_values[i][2], schedule.accumulated_depreciation_amount)
|
||||
self.assertTrue(schedule.journal_entry)
|
||||
|
||||
expected_gle = (
|
||||
(
|
||||
"_Test Accumulated Depreciations - _TC",
|
||||
37742.47,
|
||||
0.0,
|
||||
),
|
||||
(
|
||||
"_Test Fixed Asset - _TC",
|
||||
0.0,
|
||||
60000.0,
|
||||
),
|
||||
(
|
||||
"_Test Gain/Loss on Asset Disposal - _TC",
|
||||
0.0,
|
||||
17742.47,
|
||||
),
|
||||
("Debtors - _TC", 40000.0, 0.0),
|
||||
)
|
||||
|
||||
gle = frappe.db.sql(
|
||||
"""select account, debit, credit from `tabGL Entry`
|
||||
where voucher_type='Sales Invoice' and voucher_no = %s
|
||||
order by account""",
|
||||
si.name,
|
||||
)
|
||||
|
||||
self.assertSequenceEqual(gle, expected_gle)
|
||||
|
||||
def test_asset_with_maintenance_required_status_after_sale(self):
|
||||
asset = create_asset(
|
||||
calculate_depreciation=1,
|
||||
@@ -569,7 +642,7 @@ class TestDepreciationMethods(AssetSetup):
|
||||
)
|
||||
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
expected_schedules = [["2032-12-31", 30000.0, 77095.89], ["2033-06-06", 12904.11, 90000.0]]
|
||||
expected_schedules = [["2032-12-31", 42904.11, 90000.0]]
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), flt(d.depreciation_amount, 2), d.accumulated_depreciation_amount]
|
||||
for d in asset.get("schedules")
|
||||
@@ -613,14 +686,14 @@ class TestDepreciationMethods(AssetSetup):
|
||||
number_of_depreciations_booked=1,
|
||||
opening_accumulated_depreciation=50000,
|
||||
expected_value_after_useful_life=10000,
|
||||
depreciation_start_date="2030-12-31",
|
||||
depreciation_start_date="2031-12-31",
|
||||
total_number_of_depreciations=3,
|
||||
frequency_of_depreciation=12,
|
||||
)
|
||||
|
||||
self.assertEqual(asset.status, "Draft")
|
||||
|
||||
expected_schedules = [["2030-12-31", 33333.50, 83333.50], ["2031-12-31", 6666.50, 90000.0]]
|
||||
expected_schedules = [["2031-12-31", 33333.50, 83333.50], ["2032-12-31", 6666.50, 90000.0]]
|
||||
|
||||
schedules = [
|
||||
[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
|
||||
@@ -1421,7 +1494,7 @@ class TestDepreciationBasics(AssetSetup):
|
||||
)
|
||||
|
||||
self.assertEqual(asset.status, "Submitted")
|
||||
self.assertEqual(asset.get("value_after_depreciation"), 100000)
|
||||
self.assertEqual(asset.get_value_after_depreciation(), 100000)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
|
||||
@@ -1434,12 +1507,68 @@ class TestDepreciationBasics(AssetSetup):
|
||||
jv.submit()
|
||||
|
||||
asset.reload()
|
||||
self.assertEqual(asset.get("value_after_depreciation"), 99900)
|
||||
self.assertEqual(asset.get_value_after_depreciation(), 99900)
|
||||
|
||||
jv.cancel()
|
||||
|
||||
asset.reload()
|
||||
self.assertEqual(asset.get("value_after_depreciation"), 100000)
|
||||
self.assertEqual(asset.get_value_after_depreciation(), 100000)
|
||||
|
||||
def test_manual_depreciation_for_depreciable_asset(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro",
|
||||
calculate_depreciation=1,
|
||||
purchase_date="2020-01-30",
|
||||
available_for_use_date="2020-01-30",
|
||||
expected_value_after_useful_life=10000,
|
||||
total_number_of_depreciations=10,
|
||||
frequency_of_depreciation=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
self.assertEqual(asset.status, "Submitted")
|
||||
self.assertEqual(asset.get_value_after_depreciation(), 100000)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
|
||||
)
|
||||
for d in jv.accounts:
|
||||
d.reference_type = "Asset"
|
||||
d.reference_name = asset.name
|
||||
jv.voucher_type = "Depreciation Entry"
|
||||
jv.insert()
|
||||
jv.submit()
|
||||
|
||||
asset.reload()
|
||||
self.assertEqual(asset.get_value_after_depreciation(), 99900)
|
||||
|
||||
jv.cancel()
|
||||
|
||||
asset.reload()
|
||||
self.assertEqual(asset.get_value_after_depreciation(), 100000)
|
||||
|
||||
def test_manual_depreciation_with_incorrect_jv_voucher_type(self):
|
||||
asset = create_asset(
|
||||
item_code="Macbook Pro",
|
||||
calculate_depreciation=1,
|
||||
purchase_date="2020-01-30",
|
||||
available_for_use_date="2020-01-30",
|
||||
expected_value_after_useful_life=10000,
|
||||
total_number_of_depreciations=10,
|
||||
frequency_of_depreciation=1,
|
||||
submit=1,
|
||||
)
|
||||
|
||||
jv = make_journal_entry(
|
||||
"_Test Depreciations - _TC", "_Test Accumulated Depreciations - _TC", 100, save=False
|
||||
)
|
||||
for d in jv.accounts:
|
||||
d.reference_type = "Asset"
|
||||
d.reference_name = asset.name
|
||||
d.account_type = "Depreciation"
|
||||
jv.voucher_type = "Journal Entry"
|
||||
|
||||
self.assertRaises(frappe.ValidationError, jv.insert)
|
||||
|
||||
|
||||
def create_asset_data():
|
||||
|
||||
@@ -96,7 +96,6 @@ class AssetCategory(Document):
|
||||
frappe.throw(msg, title=_("Missing Account"))
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_asset_category_account(
|
||||
fieldname, item=None, asset=None, account=None, asset_category=None, company=None
|
||||
):
|
||||
|
||||
@@ -70,19 +70,21 @@ frappe.ui.form.on('Asset Movement', {
|
||||
else if (frm.doc.purpose === 'Issue') {
|
||||
fieldnames_to_be_altered = {
|
||||
target_location: { read_only: 1, reqd: 0 },
|
||||
source_location: { read_only: 1, reqd: 1 },
|
||||
source_location: { read_only: 1, reqd: 0 },
|
||||
from_employee: { read_only: 1, reqd: 0 },
|
||||
to_employee: { read_only: 0, reqd: 1 }
|
||||
};
|
||||
}
|
||||
Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
|
||||
let property_to_be_altered = fieldnames_to_be_altered[fieldname];
|
||||
Object.keys(property_to_be_altered).forEach(property => {
|
||||
let value = property_to_be_altered[property];
|
||||
frm.set_df_property(fieldname, property, value, cdn, 'assets');
|
||||
if (fieldnames_to_be_altered) {
|
||||
Object.keys(fieldnames_to_be_altered).forEach(fieldname => {
|
||||
let property_to_be_altered = fieldnames_to_be_altered[fieldname];
|
||||
Object.keys(property_to_be_altered).forEach(property => {
|
||||
let value = property_to_be_altered[property];
|
||||
frm.fields_dict['assets'].grid.update_docfield_property(fieldname, property, value);
|
||||
});
|
||||
});
|
||||
});
|
||||
frm.refresh_field('assets');
|
||||
frm.refresh_field('assets');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "Now",
|
||||
"fieldname": "transaction_date",
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
@@ -95,10 +96,11 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-01-22 12:30:55.295670",
|
||||
"modified": "2023-06-28 16:54:26.571083",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Movement",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
@@ -148,5 +150,6 @@
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC"
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
@@ -28,26 +28,20 @@ class AssetMovement(Document):
|
||||
def validate_location(self):
|
||||
for d in self.assets:
|
||||
if self.purpose in ["Transfer", "Issue"]:
|
||||
if not d.source_location:
|
||||
d.source_location = frappe.db.get_value("Asset", d.asset, "location")
|
||||
|
||||
if not d.source_location:
|
||||
frappe.throw(_("Source Location is required for the Asset {0}").format(d.asset))
|
||||
|
||||
current_location = frappe.db.get_value("Asset", d.asset, "location")
|
||||
if d.source_location:
|
||||
current_location = frappe.db.get_value("Asset", d.asset, "location")
|
||||
|
||||
if current_location != d.source_location:
|
||||
frappe.throw(
|
||||
_("Asset {0} does not belongs to the location {1}").format(d.asset, d.source_location)
|
||||
)
|
||||
else:
|
||||
d.source_location = current_location
|
||||
|
||||
if self.purpose == "Issue":
|
||||
if d.target_location:
|
||||
frappe.throw(
|
||||
_(
|
||||
"Issuing cannot be done to a location. \
|
||||
Please enter employee who has issued Asset {0}"
|
||||
"Issuing cannot be done to a location. Please enter employee to issue the Asset {0} to"
|
||||
).format(d.asset),
|
||||
title="Incorrect Movement Purpose",
|
||||
)
|
||||
@@ -110,12 +104,12 @@ class AssetMovement(Document):
|
||||
)
|
||||
|
||||
def on_submit(self):
|
||||
self.set_latest_location_in_asset()
|
||||
self.set_latest_location_and_custodian_in_asset()
|
||||
|
||||
def on_cancel(self):
|
||||
self.set_latest_location_in_asset()
|
||||
self.set_latest_location_and_custodian_in_asset()
|
||||
|
||||
def set_latest_location_in_asset(self):
|
||||
def set_latest_location_and_custodian_in_asset(self):
|
||||
current_location, current_employee = "", ""
|
||||
cond = "1=1"
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class TestAssetMovement(unittest.TestCase):
|
||||
if not frappe.db.exists("Location", "Test Location 2"):
|
||||
frappe.get_doc({"doctype": "Location", "location_name": "Test Location 2"}).insert()
|
||||
|
||||
movement1 = create_asset_movement(
|
||||
create_asset_movement(
|
||||
purpose="Transfer",
|
||||
company=asset.company,
|
||||
assets=[
|
||||
@@ -58,7 +58,7 @@ class TestAssetMovement(unittest.TestCase):
|
||||
)
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
|
||||
|
||||
create_asset_movement(
|
||||
movement1 = create_asset_movement(
|
||||
purpose="Transfer",
|
||||
company=asset.company,
|
||||
assets=[
|
||||
@@ -70,21 +70,32 @@ class TestAssetMovement(unittest.TestCase):
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
|
||||
|
||||
movement1.cancel()
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location 2")
|
||||
|
||||
employee = make_employee("testassetmovemp@example.com", company="_Test Company")
|
||||
create_asset_movement(
|
||||
purpose="Issue",
|
||||
company=asset.company,
|
||||
assets=[{"asset": asset.name, "source_location": "Test Location", "to_employee": employee}],
|
||||
assets=[{"asset": asset.name, "source_location": "Test Location 2", "to_employee": employee}],
|
||||
reference_doctype="Purchase Receipt",
|
||||
reference_name=pr.name,
|
||||
)
|
||||
|
||||
# after issuing asset should belong to an employee not at a location
|
||||
# after issuing, asset should belong to an employee not at a location
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), None)
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "custodian"), employee)
|
||||
|
||||
create_asset_movement(
|
||||
purpose="Receipt",
|
||||
company=asset.company,
|
||||
assets=[{"asset": asset.name, "from_employee": employee, "target_location": "Test Location"}],
|
||||
reference_doctype="Purchase Receipt",
|
||||
reference_name=pr.name,
|
||||
)
|
||||
|
||||
# after receiving, asset should belong to a location not at an employee
|
||||
self.assertEqual(frappe.db.get_value("Asset", asset.name, "location"), "Test Location")
|
||||
|
||||
def test_last_movement_cancellation(self):
|
||||
pr = make_purchase_receipt(
|
||||
item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
|
||||
|
||||
@@ -119,7 +119,9 @@ class AssetValueAdjustment(Document):
|
||||
if d.depreciation_method in ("Straight Line", "Manual"):
|
||||
end_date = max(s.schedule_date for s in asset.schedules if cint(s.finance_book_id) == d.idx)
|
||||
total_days = date_diff(end_date, self.date)
|
||||
rate_per_day = flt(d.value_after_depreciation) / flt(total_days)
|
||||
rate_per_day = flt(d.value_after_depreciation - d.expected_value_after_useful_life) / flt(
|
||||
total_days
|
||||
)
|
||||
from_date = self.date
|
||||
else:
|
||||
no_of_depreciations = len(
|
||||
|
||||
@@ -19,56 +19,6 @@ frappe.query_reports["Fixed Asset Register"] = {
|
||||
options: "\nIn Location\nDisposed",
|
||||
default: 'In Location'
|
||||
},
|
||||
{
|
||||
"fieldname":"filter_based_on",
|
||||
"label": __("Period Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Fiscal Year", "Date Range"],
|
||||
"default": "Fiscal Year",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("Start Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
|
||||
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("End Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.nowdate(),
|
||||
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"from_fiscal_year",
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_fiscal_year",
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"date_based_on",
|
||||
"label": __("Date Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Purchase Date", "Available For Use Date"],
|
||||
"default": "Purchase Date",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
fieldname:"asset_category",
|
||||
label: __("Asset Category"),
|
||||
@@ -89,22 +39,67 @@ frappe.query_reports["Fixed Asset Register"] = {
|
||||
default: "--Select a group--",
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname:"finance_book",
|
||||
label: __("Finance Book"),
|
||||
fieldtype: "Link",
|
||||
options: "Finance Book",
|
||||
depends_on: "eval: doc.only_depreciable_assets == 1",
|
||||
},
|
||||
{
|
||||
fieldname:"only_depreciable_assets",
|
||||
label: __("Only depreciable assets"),
|
||||
fieldtype: "Check"
|
||||
},
|
||||
{
|
||||
fieldname:"only_existing_assets",
|
||||
label: __("Only existing assets"),
|
||||
fieldtype: "Check"
|
||||
},
|
||||
{
|
||||
fieldname:"finance_book",
|
||||
label: __("Finance Book"),
|
||||
fieldtype: "Link",
|
||||
options: "Finance Book",
|
||||
},
|
||||
{
|
||||
"fieldname": "include_default_book_assets",
|
||||
"label": __("Include Default Book Assets"),
|
||||
"fieldtype": "Check",
|
||||
"default": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"filter_based_on",
|
||||
"label": __("Period Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["--Select a period--", "Fiscal Year", "Date Range"],
|
||||
"default": "--Select a period--",
|
||||
},
|
||||
{
|
||||
"fieldname":"from_date",
|
||||
"label": __("Start Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.add_months(frappe.datetime.nowdate(), -12),
|
||||
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("End Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.datetime.nowdate(),
|
||||
"depends_on": "eval: doc.filter_based_on == 'Date Range'",
|
||||
},
|
||||
{
|
||||
"fieldname":"from_fiscal_year",
|
||||
"label": __("Start Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
||||
},
|
||||
{
|
||||
"fieldname":"to_fiscal_year",
|
||||
"label": __("End Year"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Fiscal Year",
|
||||
"default": frappe.defaults.get_user_default("fiscal_year"),
|
||||
"depends_on": "eval: doc.filter_based_on == 'Fiscal Year'",
|
||||
},
|
||||
{
|
||||
"fieldname":"date_based_on",
|
||||
"label": __("Date Based On"),
|
||||
"fieldtype": "Select",
|
||||
"options": ["Purchase Date", "Available For Use Date"],
|
||||
"default": "Purchase Date",
|
||||
"depends_on": "eval: doc.filter_based_on == 'Date Range' || doc.filter_based_on == 'Fiscal Year'",
|
||||
},
|
||||
]
|
||||
};
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
from itertools import chain
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.query_builder.functions import IfNull, Sum
|
||||
from frappe.utils import cstr, flt, formatdate, getdate
|
||||
|
||||
from erpnext.accounts.report.financial_statements import (
|
||||
@@ -13,7 +15,6 @@ from erpnext.accounts.report.financial_statements import (
|
||||
validate_fiscal_year,
|
||||
)
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
|
||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -45,8 +46,6 @@ def get_conditions(filters):
|
||||
filters.year_end_date = getdate(fiscal_year.year_end_date)
|
||||
|
||||
conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
|
||||
if filters.get("only_depreciable_assets"):
|
||||
conditions["calculate_depreciation"] = filters.get("only_depreciable_assets")
|
||||
if filters.get("only_existing_assets"):
|
||||
conditions["is_existing_asset"] = filters.get("only_existing_assets")
|
||||
if filters.get("asset_category"):
|
||||
@@ -66,11 +65,9 @@ def get_conditions(filters):
|
||||
|
||||
|
||||
def get_data(filters):
|
||||
|
||||
data = []
|
||||
|
||||
conditions = get_conditions(filters)
|
||||
depreciation_amount_map = get_finance_book_value_map(filters)
|
||||
pr_supplier_map = get_purchase_receipt_supplier_map()
|
||||
pi_supplier_map = get_purchase_invoice_supplier_map()
|
||||
|
||||
@@ -104,20 +101,31 @@ def get_data(filters):
|
||||
]
|
||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||
|
||||
assets_linked_to_fb = None
|
||||
assets_linked_to_fb = get_assets_linked_to_fb(filters)
|
||||
|
||||
if filters.only_depreciable_assets:
|
||||
assets_linked_to_fb = frappe.db.get_all(
|
||||
doctype="Asset Finance Book",
|
||||
filters={"finance_book": filters.finance_book or ("is", "not set")},
|
||||
pluck="parent",
|
||||
)
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
if filters.include_default_book_assets and company_fb:
|
||||
finance_book = company_fb
|
||||
elif filters.finance_book:
|
||||
finance_book = filters.finance_book
|
||||
else:
|
||||
finance_book = None
|
||||
|
||||
depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book)
|
||||
|
||||
for asset in assets_record:
|
||||
if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb:
|
||||
if (
|
||||
assets_linked_to_fb
|
||||
and asset.calculate_depreciation
|
||||
and asset.asset_id not in assets_linked_to_fb
|
||||
):
|
||||
continue
|
||||
|
||||
asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
|
||||
asset_value = get_asset_value_after_depreciation(
|
||||
asset.asset_id, finance_book
|
||||
) or get_asset_value_after_depreciation(asset.asset_id)
|
||||
|
||||
row = {
|
||||
"asset_id": asset.asset_id,
|
||||
"asset_name": asset.asset_name,
|
||||
@@ -128,7 +136,7 @@ def get_data(filters):
|
||||
or pi_supplier_map.get(asset.purchase_invoice),
|
||||
"gross_purchase_amount": asset.gross_purchase_amount,
|
||||
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
||||
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters),
|
||||
"depreciated_amount": get_depreciation_amount_of_asset(asset, depreciation_amount_map),
|
||||
"available_for_use_date": asset.available_for_use_date,
|
||||
"location": asset.location,
|
||||
"asset_category": asset.asset_category,
|
||||
@@ -142,14 +150,23 @@ def get_data(filters):
|
||||
|
||||
def prepare_chart_data(data, filters):
|
||||
labels_values_map = {}
|
||||
date_field = frappe.scrub(filters.date_based_on)
|
||||
if filters.filter_based_on not in ("Date Range", "Fiscal Year"):
|
||||
filters_filter_based_on = "Date Range"
|
||||
date_field = "purchase_date"
|
||||
filters_from_date = min(data, key=lambda a: a.get(date_field)).get(date_field)
|
||||
filters_to_date = max(data, key=lambda a: a.get(date_field)).get(date_field)
|
||||
else:
|
||||
filters_filter_based_on = filters.filter_based_on
|
||||
date_field = frappe.scrub(filters.date_based_on)
|
||||
filters_from_date = filters.from_date
|
||||
filters_to_date = filters.to_date
|
||||
|
||||
period_list = get_period_list(
|
||||
filters.from_fiscal_year,
|
||||
filters.to_fiscal_year,
|
||||
filters.from_date,
|
||||
filters.to_date,
|
||||
filters.filter_based_on,
|
||||
filters_from_date,
|
||||
filters_to_date,
|
||||
filters_filter_based_on,
|
||||
"Monthly",
|
||||
company=filters.company,
|
||||
ignore_fiscal_year=True,
|
||||
@@ -186,57 +203,76 @@ def prepare_chart_data(data, filters):
|
||||
}
|
||||
|
||||
|
||||
def get_depreciation_amount_of_asset(asset, depreciation_amount_map, filters):
|
||||
if asset.calculate_depreciation:
|
||||
depr_amount = depreciation_amount_map.get(asset.asset_id) or 0.0
|
||||
else:
|
||||
depr_amount = get_manual_depreciation_amount_of_asset(asset, filters)
|
||||
def get_assets_linked_to_fb(filters):
|
||||
afb = frappe.qb.DocType("Asset Finance Book")
|
||||
|
||||
return flt(depr_amount, 2)
|
||||
|
||||
|
||||
def get_finance_book_value_map(filters):
|
||||
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
|
||||
|
||||
return frappe._dict(
|
||||
frappe.db.sql(
|
||||
""" Select
|
||||
parent, SUM(depreciation_amount)
|
||||
FROM `tabDepreciation Schedule`
|
||||
WHERE
|
||||
parentfield='schedules'
|
||||
AND schedule_date<=%s
|
||||
AND journal_entry IS NOT NULL
|
||||
AND ifnull(finance_book, '')=%s
|
||||
GROUP BY parent""",
|
||||
(date, cstr(filters.finance_book or "")),
|
||||
)
|
||||
query = frappe.qb.from_(afb).select(
|
||||
afb.parent,
|
||||
)
|
||||
|
||||
if filters.include_default_book_assets:
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
|
||||
def get_manual_depreciation_amount_of_asset(asset, filters):
|
||||
if filters.finance_book and company_fb and cstr(filters.finance_book) != cstr(company_fb):
|
||||
frappe.throw(_("To use a different finance book, please uncheck 'Include Default Book Assets'"))
|
||||
|
||||
query = query.where(
|
||||
(afb.finance_book.isin([cstr(filters.finance_book), cstr(company_fb), ""]))
|
||||
| (afb.finance_book.isnull())
|
||||
)
|
||||
else:
|
||||
query = query.where(
|
||||
(afb.finance_book.isin([cstr(filters.finance_book), ""])) | (afb.finance_book.isnull())
|
||||
)
|
||||
|
||||
assets_linked_to_fb = list(chain(*query.run(as_list=1)))
|
||||
|
||||
return assets_linked_to_fb
|
||||
|
||||
|
||||
def get_depreciation_amount_of_asset(asset, depreciation_amount_map):
|
||||
return depreciation_amount_map.get(asset.asset_id) or 0.0
|
||||
|
||||
|
||||
def get_asset_depreciation_amount_map(filters, finance_book):
|
||||
date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
|
||||
|
||||
(_, _, depreciation_expense_account) = get_depreciation_accounts(asset)
|
||||
|
||||
asset = frappe.qb.DocType("Asset")
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
aca = frappe.qb.DocType("Asset Category Account")
|
||||
company = frappe.qb.DocType("Company")
|
||||
|
||||
result = (
|
||||
query = (
|
||||
frappe.qb.from_(gle)
|
||||
.select(Sum(gle.debit))
|
||||
.where(gle.against_voucher == asset.asset_id)
|
||||
.where(gle.account == depreciation_expense_account)
|
||||
.join(asset)
|
||||
.on(gle.against_voucher == asset.name)
|
||||
.join(aca)
|
||||
.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
|
||||
.join(company)
|
||||
.on(company.name == asset.company)
|
||||
.select(asset.name.as_("asset"), Sum(gle.debit).as_("depreciation_amount"))
|
||||
.where(
|
||||
gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
|
||||
)
|
||||
.where(gle.debit != 0)
|
||||
.where(gle.is_cancelled == 0)
|
||||
.where(gle.posting_date <= date)
|
||||
).run()
|
||||
.where(asset.docstatus == 1)
|
||||
.groupby(asset.name)
|
||||
)
|
||||
|
||||
if result and result[0] and result[0][0]:
|
||||
depr_amount = result[0][0]
|
||||
if finance_book:
|
||||
query = query.where(
|
||||
(gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull())
|
||||
)
|
||||
else:
|
||||
depr_amount = 0
|
||||
query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull()))
|
||||
|
||||
return depr_amount
|
||||
if filters.filter_based_on in ("Date Range", "Fiscal Year"):
|
||||
query = query.where(gle.posting_date <= date)
|
||||
|
||||
asset_depr_amount_map = query.run()
|
||||
|
||||
return dict(asset_depr_amount_map)
|
||||
|
||||
|
||||
def get_purchase_receipt_supplier_map():
|
||||
|
||||
@@ -199,7 +199,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
);
|
||||
}
|
||||
|
||||
if(flt(doc.per_billed)==0) {
|
||||
if(flt(doc.per_billed) < 100) {
|
||||
this.frm.add_custom_button(__('Payment Request'),
|
||||
function() { me.make_payment_request() }, __('Create'));
|
||||
}
|
||||
@@ -242,7 +242,7 @@ erpnext.buying.PurchaseOrderController = erpnext.buying.BuyingController.extend(
|
||||
source_name: this.frm.doc.supplier,
|
||||
target: this.frm,
|
||||
setters: {
|
||||
company: me.frm.doc.company
|
||||
company: this.frm.doc.company
|
||||
},
|
||||
get_query_filters: {
|
||||
docstatus: ["!=", 2],
|
||||
|
||||
@@ -84,7 +84,7 @@ def get_data(conditions, filters):
|
||||
and po.docstatus = 1
|
||||
{0}
|
||||
GROUP BY poi.name
|
||||
ORDER BY po.transaction_date ASC
|
||||
ORDER BY po.transaction_date ASC, poi.item_code ASC
|
||||
""".format(
|
||||
conditions
|
||||
),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import json
|
||||
|
||||
import frappe
|
||||
from frappe import _, throw
|
||||
from frappe import _, bold, throw
|
||||
from frappe.model.workflow import get_workflow_name, is_transition_condition_satisfied
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import (
|
||||
@@ -256,8 +256,8 @@ class AccountsController(TransactionBase):
|
||||
self.validate_payment_schedule_dates()
|
||||
self.set_due_date()
|
||||
self.set_payment_schedule()
|
||||
self.validate_payment_schedule_amount()
|
||||
if not self.get("ignore_default_payment_terms_template"):
|
||||
self.validate_payment_schedule_amount()
|
||||
self.validate_due_date()
|
||||
self.validate_advance_entries()
|
||||
|
||||
@@ -388,6 +388,15 @@ class AccountsController(TransactionBase):
|
||||
msg += _("Please create purchase from internal sale or delivery document itself")
|
||||
frappe.throw(msg, title=_("Internal Sales Reference Missing"))
|
||||
|
||||
label = "Delivery Note Item" if self.doctype == "Purchase Receipt" else "Sales Invoice Item"
|
||||
|
||||
field = frappe.scrub(label)
|
||||
|
||||
for row in self.get("items"):
|
||||
if not row.get(field):
|
||||
msg = f"At Row {row.idx}: The field {bold(label)} is mandatory for internal transfer"
|
||||
frappe.throw(_(msg), title=_("Internal Transfer Reference Missing"))
|
||||
|
||||
def disable_pricing_rule_on_internal_transfer(self):
|
||||
if not self.get("ignore_pricing_rule") and self.is_internal_transfer():
|
||||
self.ignore_pricing_rule = 1
|
||||
@@ -882,6 +891,9 @@ class AccountsController(TransactionBase):
|
||||
|
||||
return is_inclusive
|
||||
|
||||
def should_show_taxes_as_table_in_print(self):
|
||||
return cint(frappe.db.get_single_value("Accounts Settings", "show_taxes_as_table_in_print"))
|
||||
|
||||
def validate_advance_entries(self):
|
||||
order_field = "sales_order" if self.doctype == "Sales Invoice" else "purchase_order"
|
||||
order_list = list(set(d.get(order_field) for d in self.get("items") if d.get(order_field)))
|
||||
@@ -1577,6 +1589,7 @@ class AccountsController(TransactionBase):
|
||||
|
||||
base_grand_total = self.get("base_rounded_total") or self.base_grand_total
|
||||
grand_total = self.get("rounded_total") or self.grand_total
|
||||
automatically_fetch_payment_terms = 0
|
||||
|
||||
if self.doctype in ("Sales Invoice", "Purchase Invoice"):
|
||||
base_grand_total = base_grand_total - flt(self.base_write_off_amount)
|
||||
@@ -1622,19 +1635,23 @@ class AccountsController(TransactionBase):
|
||||
)
|
||||
self.append("payment_schedule", data)
|
||||
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(
|
||||
grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount")
|
||||
)
|
||||
d.base_payment_amount = flt(
|
||||
base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount")
|
||||
)
|
||||
d.outstanding = d.payment_amount
|
||||
elif not d.invoice_portion:
|
||||
d.base_payment_amount = flt(
|
||||
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
|
||||
)
|
||||
if not (
|
||||
automatically_fetch_payment_terms
|
||||
and self.linked_order_has_payment_terms(po_or_so, fieldname, doctype)
|
||||
):
|
||||
for d in self.get("payment_schedule"):
|
||||
if d.invoice_portion:
|
||||
d.payment_amount = flt(
|
||||
grand_total * flt(d.invoice_portion / 100), d.precision("payment_amount")
|
||||
)
|
||||
d.base_payment_amount = flt(
|
||||
base_grand_total * flt(d.invoice_portion / 100), d.precision("base_payment_amount")
|
||||
)
|
||||
d.outstanding = d.payment_amount
|
||||
elif not d.invoice_portion:
|
||||
d.base_payment_amount = flt(
|
||||
d.payment_amount * self.get("conversion_rate"), d.precision("base_payment_amount")
|
||||
)
|
||||
|
||||
def get_order_details(self):
|
||||
if self.doctype == "Sales Invoice":
|
||||
@@ -1687,6 +1704,10 @@ class AccountsController(TransactionBase):
|
||||
"invoice_portion": schedule.invoice_portion,
|
||||
"mode_of_payment": schedule.mode_of_payment,
|
||||
"description": schedule.description,
|
||||
"payment_amount": schedule.payment_amount,
|
||||
"base_payment_amount": schedule.base_payment_amount,
|
||||
"outstanding": schedule.outstanding,
|
||||
"paid_amount": schedule.paid_amount,
|
||||
}
|
||||
|
||||
if schedule.discount_type == "Percentage":
|
||||
|
||||
@@ -30,10 +30,16 @@ def set_print_templates_for_taxes(doc, settings):
|
||||
doc.print_templates.update(
|
||||
{
|
||||
"total": "templates/print_formats/includes/total.html",
|
||||
"taxes": "templates/print_formats/includes/taxes.html",
|
||||
}
|
||||
)
|
||||
|
||||
if not doc.should_show_taxes_as_table_in_print():
|
||||
doc.print_templates.update(
|
||||
{
|
||||
"taxes": "templates/print_formats/includes/taxes.html",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def format_columns(display_columns, compact_fields):
|
||||
compact_fields = compact_fields + ["image", "item_code", "item_name"]
|
||||
|
||||
@@ -678,7 +678,7 @@ class StockController(AccountsController):
|
||||
message += _("Please adjust the qty or edit {0} to proceed.").format(rule_link)
|
||||
return message
|
||||
|
||||
def repost_future_sle_and_gle(self):
|
||||
def repost_future_sle_and_gle(self, force=False):
|
||||
args = frappe._dict(
|
||||
{
|
||||
"posting_date": self.posting_date,
|
||||
@@ -689,7 +689,10 @@ class StockController(AccountsController):
|
||||
}
|
||||
)
|
||||
|
||||
if future_sle_exists(args) or repost_required_for_queue(self):
|
||||
if self.docstatus == 2:
|
||||
force = True
|
||||
|
||||
if force or future_sle_exists(args) or repost_required_for_queue(self):
|
||||
item_based_reposting = cint(
|
||||
frappe.db.get_single_value("Stock Reposting Settings", "item_based_reposting")
|
||||
)
|
||||
|
||||
@@ -78,9 +78,10 @@ erpnext.ProductList = class {
|
||||
let title_html = `<div style="display: flex; margin-left: -15px;">`;
|
||||
title_html += `
|
||||
<div class="col-8" style="margin-right: -15px;">
|
||||
<a class="" href="/${ item.route || '#' }"
|
||||
style="color: var(--gray-800); font-weight: 500;">
|
||||
<a href="/${ item.route || '#' }">
|
||||
<div class="product-title">
|
||||
${ title }
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
@@ -201,4 +202,4 @@ erpnext.ProductList = class {
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
@@ -165,6 +165,7 @@
|
||||
"fieldname": "slide_3_content_align",
|
||||
"fieldtype": "Select",
|
||||
"label": "Content Align",
|
||||
"options": "Left\nCentre\nRight",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
@@ -214,6 +215,7 @@
|
||||
"fieldname": "slide_4_content_align",
|
||||
"fieldtype": "Select",
|
||||
"label": "Content Align",
|
||||
"options": "Left\nCentre\nRight",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
@@ -263,6 +265,7 @@
|
||||
"fieldname": "slide_5_content_align",
|
||||
"fieldtype": "Select",
|
||||
"label": "Content Align",
|
||||
"options": "Left\nCentre\nRight",
|
||||
"reqd": 0
|
||||
},
|
||||
{
|
||||
@@ -274,7 +277,7 @@
|
||||
}
|
||||
],
|
||||
"idx": 2,
|
||||
"modified": "2021-02-24 15:57:05.889709",
|
||||
"modified": "2023-05-12 15:03:57.604060",
|
||||
"modified_by": "Administrator",
|
||||
"module": "E-commerce",
|
||||
"name": "Hero Slider",
|
||||
|
||||
@@ -333,7 +333,6 @@ def get_leave_allocation_for_period(
|
||||
).run()[0][0] or 0.0
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_carry_forwarded_leaves(employee, leave_type, date, carry_forward=None):
|
||||
"""Returns carry forwarded leaves for the given employee"""
|
||||
unused_leaves = 0.0
|
||||
|
||||
@@ -8,7 +8,15 @@ from math import ceil
|
||||
import frappe
|
||||
from frappe import _, bold
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import date_diff, flt, formatdate, get_last_day, get_link_to_form, getdate
|
||||
from frappe.utils import (
|
||||
comma_and,
|
||||
date_diff,
|
||||
flt,
|
||||
formatdate,
|
||||
get_last_day,
|
||||
get_link_to_form,
|
||||
getdate,
|
||||
)
|
||||
from six import string_types
|
||||
|
||||
|
||||
@@ -66,7 +74,6 @@ class LeavePolicyAssignment(Document):
|
||||
).format(frappe.bold(get_link_to_form("Leave Type", leave_type.name)))
|
||||
frappe.msgprint(msg, indicator="orange", alert=True)
|
||||
|
||||
@frappe.whitelist()
|
||||
def grant_leave_alloc_for_employee(self):
|
||||
if self.leaves_allocated:
|
||||
frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment"))
|
||||
@@ -192,9 +199,9 @@ def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj
|
||||
date = getdate(frappe.flags.current_date) or getdate()
|
||||
|
||||
if based_on_doj:
|
||||
# if leave type allocation is based on DOJ, and the date of assignment creation is same as DOJ,
|
||||
# if leave type allocation is based on DOJ, and the date of assignment creation is after DOJ,
|
||||
# then the month should be considered
|
||||
if date.day == date_of_joining.day:
|
||||
if date.day >= date_of_joining.day:
|
||||
months_passed += 1
|
||||
else:
|
||||
last_day_of_month = get_last_day(date)
|
||||
@@ -207,7 +214,6 @@ def add_current_month_if_applicable(months_passed, date_of_joining, based_on_doj
|
||||
|
||||
@frappe.whitelist()
|
||||
def create_assignment_for_multiple_employees(employees, data):
|
||||
|
||||
if isinstance(employees, string_types):
|
||||
employees = json.loads(employees)
|
||||
|
||||
@@ -215,6 +221,8 @@ def create_assignment_for_multiple_employees(employees, data):
|
||||
data = frappe._dict(json.loads(data))
|
||||
|
||||
docs_name = []
|
||||
failed = []
|
||||
|
||||
for employee in employees:
|
||||
assignment = frappe.new_doc("Leave Policy Assignment")
|
||||
assignment.employee = employee
|
||||
@@ -225,18 +233,45 @@ def create_assignment_for_multiple_employees(employees, data):
|
||||
assignment.leave_period = data.leave_period or None
|
||||
assignment.carry_forward = data.carry_forward
|
||||
assignment.save()
|
||||
try:
|
||||
assignment.submit()
|
||||
except frappe.exceptions.ValidationError:
|
||||
continue
|
||||
|
||||
frappe.db.commit()
|
||||
savepoint = "before_assignment_submission"
|
||||
|
||||
try:
|
||||
frappe.db.savepoint(savepoint)
|
||||
assignment.submit()
|
||||
except Exception as e:
|
||||
frappe.db.rollback(save_point=savepoint)
|
||||
frappe.log_error(title=f"Leave Policy Assignment submission failed for {assignment.name}")
|
||||
failed.append(assignment.name)
|
||||
|
||||
docs_name.append(assignment.name)
|
||||
|
||||
if failed:
|
||||
show_assignment_submission_status(failed)
|
||||
|
||||
return docs_name
|
||||
|
||||
|
||||
def show_assignment_submission_status(failed):
|
||||
frappe.clear_messages()
|
||||
assignment_list = [get_link_to_form("Leave Policy Assignment", entry) for entry in failed]
|
||||
|
||||
msg = _("Failed to submit some leave policy assignments:")
|
||||
msg += " " + comma_and(assignment_list, False) + "<hr>"
|
||||
msg += (
|
||||
_("Check {0} for more details")
|
||||
.format("<a href='/app/List/Error Log?reference_doctype=Leave Policy Assignment'>{0}</a>")
|
||||
.format(_("Error Log"))
|
||||
)
|
||||
|
||||
frappe.msgprint(
|
||||
msg,
|
||||
indicator="red",
|
||||
title=_("Submission Failed"),
|
||||
is_minimizable=True,
|
||||
)
|
||||
|
||||
|
||||
def get_leave_type_details():
|
||||
leave_type_details = frappe._dict()
|
||||
leave_types = frappe.get_all(
|
||||
|
||||
@@ -52,7 +52,7 @@ frappe.listview_settings['Leave Policy Assignment'] = {
|
||||
get_query() {
|
||||
let filters = {"is_active": 1};
|
||||
if (cur_dialog.fields_dict.company.value)
|
||||
filters["company"] = cur_dialog.fields_dict.company.value;
|
||||
filters["company"] = cur_dialog?.fields_dict?.company?.value;
|
||||
|
||||
return {
|
||||
filters: filters
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import frappe
|
||||
from frappe.query_builder.functions import Count
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
@@ -15,31 +16,34 @@ def get_children(parent=None, company=None, exclude_node=None):
|
||||
if exclude_node:
|
||||
filters.append(["name", "!=", exclude_node])
|
||||
|
||||
employees = frappe.get_list(
|
||||
employees = frappe.get_all(
|
||||
"Employee",
|
||||
fields=["employee_name as name", "name as id", "reports_to", "image", "designation as title"],
|
||||
fields=[
|
||||
"employee_name as name",
|
||||
"name as id",
|
||||
"lft",
|
||||
"rgt",
|
||||
"reports_to",
|
||||
"image",
|
||||
"designation as title",
|
||||
],
|
||||
filters=filters,
|
||||
order_by="name",
|
||||
)
|
||||
|
||||
for employee in employees:
|
||||
is_expandable = frappe.db.count("Employee", filters={"reports_to": employee.get("id")})
|
||||
employee.connections = get_connections(employee.id)
|
||||
employee.expandable = 1 if is_expandable else 0
|
||||
employee.connections = get_connections(employee.id, employee.lft, employee.rgt)
|
||||
employee.expandable = bool(employee.connections)
|
||||
|
||||
return employees
|
||||
|
||||
|
||||
def get_connections(employee):
|
||||
num_connections = 0
|
||||
def get_connections(employee: str, lft: int, rgt: int) -> int:
|
||||
Employee = frappe.qb.DocType("Employee")
|
||||
query = (
|
||||
frappe.qb.from_(Employee)
|
||||
.select(Count(Employee.name))
|
||||
.where((Employee.lft > lft) & (Employee.rgt < rgt))
|
||||
).run()
|
||||
|
||||
nodes_to_expand = frappe.get_list("Employee", filters=[["reports_to", "=", employee]])
|
||||
num_connections += len(nodes_to_expand)
|
||||
|
||||
while nodes_to_expand:
|
||||
parent = nodes_to_expand.pop(0)
|
||||
descendants = frappe.get_list("Employee", filters=[["reports_to", "=", parent.name]])
|
||||
num_connections += len(descendants)
|
||||
nodes_to_expand.extend(descendants)
|
||||
|
||||
return num_connections
|
||||
return query[0][0]
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import unittest
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
from erpnext.hr.doctype.employee.test_employee import make_employee
|
||||
from erpnext.hr.page.organizational_chart.organizational_chart import get_children
|
||||
|
||||
|
||||
class TestOrganizationalChart(FrappeTestCase):
|
||||
def setUp(self):
|
||||
self.company = create_company("Test Org Chart").name
|
||||
frappe.db.delete("Employee", {"company": self.company})
|
||||
|
||||
def test_get_children(self):
|
||||
company = create_company("Test Org Chart").name
|
||||
emp1 = make_employee("testemp1@mail.com", company=self.company)
|
||||
emp2 = make_employee("testemp2@mail.com", company=self.company, reports_to=emp1)
|
||||
emp3 = make_employee("testemp3@mail.com", company=self.company, reports_to=emp1)
|
||||
make_employee("testemp4@mail.com", company=self.company, reports_to=emp2)
|
||||
|
||||
# root node
|
||||
children = get_children(company=self.company)
|
||||
self.assertEqual(len(children), 1)
|
||||
self.assertEqual(children[0].id, emp1)
|
||||
self.assertEqual(children[0].connections, 3)
|
||||
|
||||
# root's children
|
||||
children = get_children(parent=emp1, company=self.company)
|
||||
self.assertEqual(len(children), 2)
|
||||
self.assertEqual(children[0].id, emp2)
|
||||
self.assertEqual(children[0].connections, 1)
|
||||
self.assertEqual(children[1].id, emp3)
|
||||
self.assertEqual(children[1].connections, 0)
|
||||
|
||||
|
||||
def create_company(name):
|
||||
if frappe.db.exists("Company", name):
|
||||
return frappe.get_doc("Company", name)
|
||||
|
||||
company = frappe.new_doc("Company")
|
||||
company.update(
|
||||
{
|
||||
"company_name": name,
|
||||
"default_currency": "USD",
|
||||
"country": "United States",
|
||||
}
|
||||
)
|
||||
return company.insert()
|
||||
@@ -160,4 +160,3 @@ class TestLoanDisbursement(unittest.TestCase):
|
||||
interest = per_day_interest * 15
|
||||
|
||||
self.assertEqual(amounts["pending_principal_amount"], 1500000)
|
||||
self.assertEqual(amounts["interest_amount"], flt(interest + previous_interest, 2))
|
||||
|
||||
@@ -22,7 +22,7 @@ class LoanInterestAccrual(AccountsController):
|
||||
frappe.throw(_("Interest Amount or Principal Amount is mandatory"))
|
||||
|
||||
if not self.last_accrual_date:
|
||||
self.last_accrual_date = get_last_accrual_date(self.loan)
|
||||
self.last_accrual_date = get_last_accrual_date(self.loan, self.posting_date)
|
||||
|
||||
def on_submit(self):
|
||||
self.make_gl_entries()
|
||||
@@ -274,14 +274,14 @@ def make_loan_interest_accrual_entry(args):
|
||||
|
||||
|
||||
def get_no_of_days_for_interest_accural(loan, posting_date):
|
||||
last_interest_accrual_date = get_last_accrual_date(loan.name)
|
||||
last_interest_accrual_date = get_last_accrual_date(loan.name, posting_date)
|
||||
|
||||
no_of_days = date_diff(posting_date or nowdate(), last_interest_accrual_date) + 1
|
||||
|
||||
return no_of_days
|
||||
|
||||
|
||||
def get_last_accrual_date(loan):
|
||||
def get_last_accrual_date(loan, posting_date):
|
||||
last_posting_date = frappe.db.sql(
|
||||
""" SELECT MAX(posting_date) from `tabLoan Interest Accrual`
|
||||
WHERE loan = %s and docstatus = 1""",
|
||||
@@ -289,12 +289,30 @@ def get_last_accrual_date(loan):
|
||||
)
|
||||
|
||||
if last_posting_date[0][0]:
|
||||
last_interest_accrual_date = last_posting_date[0][0]
|
||||
# interest for last interest accrual date is already booked, so add 1 day
|
||||
return add_days(last_posting_date[0][0], 1)
|
||||
last_disbursement_date = get_last_disbursement_date(loan, posting_date)
|
||||
|
||||
if last_disbursement_date and getdate(last_disbursement_date) > add_days(
|
||||
getdate(last_interest_accrual_date), 1
|
||||
):
|
||||
last_interest_accrual_date = last_disbursement_date
|
||||
|
||||
return add_days(last_interest_accrual_date, 1)
|
||||
else:
|
||||
return frappe.db.get_value("Loan", loan, "disbursement_date")
|
||||
|
||||
|
||||
def get_last_disbursement_date(loan, posting_date):
|
||||
last_disbursement_date = frappe.db.get_value(
|
||||
"Loan Disbursement",
|
||||
{"docstatus": 1, "against_loan": loan, "posting_date": ("<", posting_date)},
|
||||
"MAX(posting_date)",
|
||||
)
|
||||
|
||||
return last_disbursement_date
|
||||
|
||||
|
||||
def days_in_year(year):
|
||||
days = 365
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ class LoanRepayment(AccountsController):
|
||||
if flt(self.total_interest_paid, precision) > flt(self.interest_payable, precision):
|
||||
if not self.is_term_loan:
|
||||
# get last loan interest accrual date
|
||||
last_accrual_date = get_last_accrual_date(self.against_loan)
|
||||
last_accrual_date = get_last_accrual_date(self.against_loan, self.posting_date)
|
||||
|
||||
# get posting date upto which interest has to be accrued
|
||||
per_day_interest = get_per_day_interest(
|
||||
@@ -724,7 +724,7 @@ def get_amounts(amounts, against_loan, posting_date):
|
||||
if due_date:
|
||||
pending_days = date_diff(posting_date, due_date) + 1
|
||||
else:
|
||||
last_accrual_date = get_last_accrual_date(against_loan_doc.name)
|
||||
last_accrual_date = get_last_accrual_date(against_loan_doc.name, posting_date)
|
||||
pending_days = date_diff(posting_date, last_accrual_date) + 1
|
||||
|
||||
if pending_days > 0:
|
||||
|
||||
@@ -48,7 +48,8 @@ frappe.ui.form.on("BOM", {
|
||||
return {
|
||||
query: "erpnext.manufacturing.doctype.bom.bom.item_query",
|
||||
filters: {
|
||||
"item_code": doc.item
|
||||
"include_item_in_manufacturing": 1,
|
||||
"is_fixed_asset": 0
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1346,8 +1346,9 @@ def item_query(doctype, txt, searchfield, start, page_len, filters):
|
||||
if not has_variants:
|
||||
query_filters["has_variants"] = 0
|
||||
|
||||
if filters and filters.get("is_stock_item"):
|
||||
query_filters["is_stock_item"] = 1
|
||||
if filters:
|
||||
for fieldname, value in filters.items():
|
||||
query_filters[fieldname] = value
|
||||
|
||||
return frappe.get_list(
|
||||
"Item",
|
||||
|
||||
@@ -605,6 +605,45 @@ class TestBOM(FrappeTestCase):
|
||||
bom.update_cost()
|
||||
self.assertFalse(bom.flags.cost_updated)
|
||||
|
||||
def test_do_not_include_manufacturing_and_fixed_items(self):
|
||||
from erpnext.manufacturing.doctype.bom.bom import item_query
|
||||
|
||||
if not frappe.db.exists("Asset Category", "Computers-Test"):
|
||||
doc = frappe.get_doc({"doctype": "Asset Category", "asset_category_name": "Computers-Test"})
|
||||
doc.flags.ignore_mandatory = True
|
||||
doc.insert()
|
||||
|
||||
for item_code, properties in {
|
||||
"_Test RM Item 1 Do Not Include In Manufacture": {
|
||||
"is_stock_item": 1,
|
||||
"include_item_in_manufacturing": 0,
|
||||
},
|
||||
"_Test RM Item 2 Fixed Asset Item": {
|
||||
"is_fixed_asset": 1,
|
||||
"is_stock_item": 0,
|
||||
"asset_category": "Computers-Test",
|
||||
},
|
||||
"_Test RM Item 3 Manufacture Item": {"is_stock_item": 1, "include_item_in_manufacturing": 1},
|
||||
}.items():
|
||||
make_item(item_code, properties)
|
||||
|
||||
data = item_query(
|
||||
"Item",
|
||||
txt="_Test RM Item",
|
||||
searchfield="name",
|
||||
start=0,
|
||||
page_len=20000,
|
||||
filters={"include_item_in_manufacturing": 1, "is_fixed_asset": 0},
|
||||
)
|
||||
|
||||
items = []
|
||||
for row in data:
|
||||
items.append(row[0])
|
||||
|
||||
self.assertTrue("_Test RM Item 1 Do Not Include In Manufacture" not in items)
|
||||
self.assertTrue("_Test RM Item 2 Fixed Asset Item" not in items)
|
||||
self.assertTrue("_Test RM Item 3 Manufacture Item" in items)
|
||||
|
||||
|
||||
def get_default_bom(item_code="_Test FG Item 2"):
|
||||
return frappe.db.get_value("BOM", {"item": item_code, "is_active": 1, "is_default": 1})
|
||||
|
||||
@@ -420,7 +420,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-11-12 10:15:06.572401",
|
||||
"modified": "2023-05-22 23:26:57.589331",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Job Card",
|
||||
|
||||
@@ -657,7 +657,7 @@ class JobCard(Document):
|
||||
self.status = {0: "Open", 1: "Submitted", 2: "Cancelled"}[self.docstatus or 0]
|
||||
|
||||
if self.docstatus < 2:
|
||||
if self.for_quantity <= self.transferred_qty:
|
||||
if flt(self.for_quantity) <= flt(self.transferred_qty):
|
||||
self.status = "Material Transferred"
|
||||
|
||||
if self.time_logs:
|
||||
|
||||
@@ -592,20 +592,18 @@ erpnext.work_order = {
|
||||
// all materials transferred for manufacturing, make this primary
|
||||
finish_btn.addClass('btn-primary');
|
||||
}
|
||||
} else {
|
||||
frappe.db.get_doc("Manufacturing Settings").then((doc) => {
|
||||
let allowance_percentage = doc.overproduction_percentage_for_work_order;
|
||||
} else if (frm.doc.__onload && frm.doc.__onload.overproduction_percentage) {
|
||||
let allowance_percentage = frm.doc.__onload.overproduction_percentage;
|
||||
|
||||
if (allowance_percentage > 0) {
|
||||
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
|
||||
if (allowance_percentage > 0) {
|
||||
let allowed_qty = frm.doc.qty + ((allowance_percentage / 100) * frm.doc.qty);
|
||||
|
||||
if ((flt(doc.produced_qty) < allowed_qty)) {
|
||||
frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
}
|
||||
if ((flt(doc.produced_qty) < allowed_qty)) {
|
||||
frm.add_custom_button(__('Finish'), function() {
|
||||
erpnext.work_order.make_se(frm, 'Manufacture');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -69,7 +69,7 @@ def get_columns(filters):
|
||||
"label": _("Id"),
|
||||
"fieldname": "name",
|
||||
"fieldtype": "Link",
|
||||
"options": "Work Order",
|
||||
"options": "Quality Inspection",
|
||||
"width": 100,
|
||||
},
|
||||
{"label": _("Report Date"), "fieldname": "report_date", "fieldtype": "Date", "width": 150},
|
||||
|
||||
@@ -7,4 +7,6 @@ def execute():
|
||||
frappe.reload_doc("manufacturing", "doctype", "work_order")
|
||||
frappe.reload_doc("manufacturing", "doctype", "work_order_item")
|
||||
|
||||
frappe.db.sql("""UPDATE `tabWork Order Item` SET amount = rate * required_qty""")
|
||||
frappe.db.sql(
|
||||
"""UPDATE `tabWork Order Item` SET amount = ifnull(rate, 0.0) * ifnull(required_qty, 0.0)"""
|
||||
)
|
||||
|
||||
@@ -145,7 +145,6 @@ class AdditionalSalary(Document):
|
||||
return amount_per_day * no_of_days
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_additional_salaries(employee, start_date, end_date, component_type):
|
||||
from frappe.query_builder import Criterion
|
||||
|
||||
|
||||
@@ -67,7 +67,6 @@
|
||||
"label": "Disabled"
|
||||
},
|
||||
{
|
||||
"depends_on": "allow_tax_exemption",
|
||||
"fieldname": "standard_tax_exemption_amount",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Standard Tax Exemption Amount",
|
||||
@@ -104,7 +103,7 @@
|
||||
],
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2021-03-31 22:42:08.139520",
|
||||
"modified": "2023-05-01 13:42:08.139520",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Payroll",
|
||||
"name": "Income Tax Slab",
|
||||
|
||||
@@ -80,22 +80,27 @@ frappe.ui.form.on("Salary Slip", {
|
||||
},
|
||||
|
||||
currency: function(frm) {
|
||||
frm.trigger("update_currency_changes");
|
||||
},
|
||||
|
||||
update_currency_changes: function(frm) {
|
||||
frm.trigger("set_exchange_rate");
|
||||
frm.trigger("set_dynamic_labels");
|
||||
},
|
||||
|
||||
set_dynamic_labels: function(frm) {
|
||||
var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency");
|
||||
if (frm.doc.employee && frm.doc.currency) {
|
||||
frappe.run_serially([
|
||||
() => frm.events.set_exchange_rate(frm, company_currency),
|
||||
() => frm.events.change_form_labels(frm, company_currency),
|
||||
() => frm.events.change_grid_labels(frm),
|
||||
() => frm.refresh_fields()
|
||||
() => frm.events.change_form_labels(frm),
|
||||
() => frm.events.change_grid_labels(frm),
|
||||
() => frm.refresh_fields()
|
||||
]);
|
||||
}
|
||||
},
|
||||
|
||||
set_exchange_rate: function(frm, company_currency) {
|
||||
set_exchange_rate: function(frm) {
|
||||
const company_currency = erpnext.get_currency(frm.doc.company);
|
||||
|
||||
if (frm.doc.docstatus === 0) {
|
||||
if (frm.doc.currency) {
|
||||
var from_currency = frm.doc.currency;
|
||||
@@ -133,9 +138,11 @@ frappe.ui.form.on("Salary Slip", {
|
||||
frm.set_df_property('section_break_43', 'hidden', 1);
|
||||
},
|
||||
|
||||
change_form_labels: function(frm, company_currency) {
|
||||
change_form_labels: function(frm) {
|
||||
const company_currency = erpnext.get_currency(frm.doc.company);
|
||||
|
||||
frm.set_currency_labels(["base_hour_rate", "base_gross_pay", "base_total_deduction",
|
||||
"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date", "gross_base_year_to_date"],
|
||||
"base_net_pay", "base_rounded_total", "base_total_in_words", "base_year_to_date", "base_month_to_date", "base_gross_year_to_date"],
|
||||
company_currency);
|
||||
|
||||
frm.set_currency_labels(["hour_rate", "gross_pay", "total_deduction", "net_pay", "rounded_total", "total_in_words", "year_to_date", "month_to_date", "gross_year_to_date"],
|
||||
@@ -207,6 +214,9 @@ frappe.ui.form.on("Salary Slip", {
|
||||
frm.fields_dict.absent_days.set_description(__("Unmarked Days is treated as {0}. You can can change this in {1}", [r.message, frappe.utils.get_form_link("Payroll Settings", "Payroll Settings", true)]));
|
||||
}
|
||||
frm.refresh();
|
||||
// triggering events explicitly because structure is set on the server-side
|
||||
// and currency is fetched from the structure
|
||||
frm.trigger("update_currency_changes");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# License: GNU General Public License v3. See license.txt
|
||||
|
||||
import datetime
|
||||
import math
|
||||
from datetime import date
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.naming import make_autoname
|
||||
from frappe.utils import (
|
||||
add_days,
|
||||
ceil,
|
||||
cint,
|
||||
cstr,
|
||||
date_diff,
|
||||
floor,
|
||||
flt,
|
||||
formatdate,
|
||||
get_first_day,
|
||||
@@ -57,8 +58,10 @@ class SalarySlip(TransactionBase):
|
||||
"float": float,
|
||||
"long": int,
|
||||
"round": round,
|
||||
"date": datetime.date,
|
||||
"date": date,
|
||||
"getdate": getdate,
|
||||
"ceil": ceil,
|
||||
"floor": floor,
|
||||
}
|
||||
|
||||
def autoname(self):
|
||||
@@ -653,15 +656,17 @@ class SalarySlip(TransactionBase):
|
||||
amount = self.eval_condition_and_formula(struct_row, data)
|
||||
|
||||
if struct_row.statistical_component:
|
||||
default_data[struct_row.abbr] = amount
|
||||
|
||||
# update statitical component amount in reference data based on payment days
|
||||
# since row for statistical component is not added to salary slip
|
||||
if struct_row.depends_on_payment_days:
|
||||
joining_date, relieving_date = self.get_joining_and_relieving_dates()
|
||||
default_data[struct_row.abbr] = amount
|
||||
data[struct_row.abbr] = flt(
|
||||
(flt(amount) * flt(self.payment_days) / cint(self.total_working_days)),
|
||||
struct_row.precision("amount"),
|
||||
payment_days_amount = (
|
||||
flt(amount) * flt(self.payment_days) / cint(self.total_working_days)
|
||||
if self.total_working_days
|
||||
else 0
|
||||
)
|
||||
data[struct_row.abbr] = payment_days_amount
|
||||
|
||||
elif amount or struct_row.amount_based_on_formula and amount is not None:
|
||||
default_amount = self.eval_condition_and_formula(struct_row, default_data)
|
||||
@@ -957,7 +962,7 @@ class SalarySlip(TransactionBase):
|
||||
tax_slab.allow_tax_exemption, payroll_period=payroll_period
|
||||
)
|
||||
future_structured_taxable_earnings = current_taxable_earnings.taxable_earnings * (
|
||||
math.ceil(remaining_sub_periods) - 1
|
||||
ceil(remaining_sub_periods) - 1
|
||||
)
|
||||
|
||||
# get taxable_earnings, addition_earnings for current actual payment days
|
||||
@@ -1331,6 +1336,7 @@ class SalarySlip(TransactionBase):
|
||||
if declaration:
|
||||
total_exemption_amount = declaration
|
||||
|
||||
if tax_slab.standard_tax_exemption_amount:
|
||||
total_exemption_amount += flt(tax_slab.standard_tax_exemption_amount)
|
||||
|
||||
return total_exemption_amount
|
||||
|
||||
@@ -8,222 +8,325 @@ from frappe.utils import flt
|
||||
|
||||
import erpnext
|
||||
|
||||
salary_slip = frappe.qb.DocType("Salary Slip")
|
||||
salary_detail = frappe.qb.DocType("Salary Detail")
|
||||
salary_component = frappe.qb.DocType("Salary Component")
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
currency = None
|
||||
if filters.get("currency"):
|
||||
currency = filters.get("currency")
|
||||
company_currency = erpnext.get_company_currency(filters.get("company"))
|
||||
|
||||
salary_slips = get_salary_slips(filters, company_currency)
|
||||
if not salary_slips:
|
||||
return [], []
|
||||
|
||||
columns, earning_types, ded_types = get_columns(salary_slips)
|
||||
ss_earning_map = get_ss_earning_map(salary_slips, currency, company_currency)
|
||||
ss_ded_map = get_ss_ded_map(salary_slips, currency, company_currency)
|
||||
earning_types, ded_types = get_earning_and_deduction_types(salary_slips)
|
||||
columns = get_columns(earning_types, ded_types)
|
||||
|
||||
ss_earning_map = get_salary_slip_details(salary_slips, currency, company_currency, "earnings")
|
||||
ss_ded_map = get_salary_slip_details(salary_slips, currency, company_currency, "deductions")
|
||||
|
||||
doj_map = get_employee_doj_map()
|
||||
|
||||
data = []
|
||||
for ss in salary_slips:
|
||||
row = [
|
||||
ss.name,
|
||||
ss.employee,
|
||||
ss.employee_name,
|
||||
doj_map.get(ss.employee),
|
||||
ss.branch,
|
||||
ss.department,
|
||||
ss.designation,
|
||||
ss.company,
|
||||
ss.start_date,
|
||||
ss.end_date,
|
||||
ss.leave_without_pay,
|
||||
ss.payment_days,
|
||||
]
|
||||
row = {
|
||||
"salary_slip_id": ss.name,
|
||||
"employee": ss.employee,
|
||||
"employee_name": ss.employee_name,
|
||||
"data_of_joining": doj_map.get(ss.employee),
|
||||
"branch": ss.branch,
|
||||
"department": ss.department,
|
||||
"designation": ss.designation,
|
||||
"company": ss.company,
|
||||
"start_date": ss.start_date,
|
||||
"end_date": ss.end_date,
|
||||
"leave_without_pay": ss.leave_without_pay,
|
||||
"payment_days": ss.payment_days,
|
||||
"currency": currency or company_currency,
|
||||
"total_loan_repayment": ss.total_loan_repayment,
|
||||
}
|
||||
|
||||
if ss.branch is not None:
|
||||
columns[3] = columns[3].replace("-1", "120")
|
||||
if ss.department is not None:
|
||||
columns[4] = columns[4].replace("-1", "120")
|
||||
if ss.designation is not None:
|
||||
columns[5] = columns[5].replace("-1", "120")
|
||||
if ss.leave_without_pay is not None:
|
||||
columns[9] = columns[9].replace("-1", "130")
|
||||
update_column_width(ss, columns)
|
||||
|
||||
for e in earning_types:
|
||||
row.append(ss_earning_map.get(ss.name, {}).get(e))
|
||||
|
||||
if currency == company_currency:
|
||||
row += [flt(ss.gross_pay) * flt(ss.exchange_rate)]
|
||||
else:
|
||||
row += [ss.gross_pay]
|
||||
row.update({frappe.scrub(e): ss_earning_map.get(ss.name, {}).get(e)})
|
||||
|
||||
for d in ded_types:
|
||||
row.append(ss_ded_map.get(ss.name, {}).get(d))
|
||||
|
||||
row.append(ss.total_loan_repayment)
|
||||
row.update({frappe.scrub(d): ss_ded_map.get(ss.name, {}).get(d)})
|
||||
|
||||
if currency == company_currency:
|
||||
row += [
|
||||
flt(ss.total_deduction) * flt(ss.exchange_rate),
|
||||
flt(ss.net_pay) * flt(ss.exchange_rate),
|
||||
]
|
||||
row.update(
|
||||
{
|
||||
"gross_pay": flt(ss.gross_pay) * flt(ss.exchange_rate),
|
||||
"total_deduction": flt(ss.total_deduction) * flt(ss.exchange_rate),
|
||||
"net_pay": flt(ss.net_pay) * flt(ss.exchange_rate),
|
||||
}
|
||||
)
|
||||
|
||||
else:
|
||||
row += [ss.total_deduction, ss.net_pay]
|
||||
row.append(currency or company_currency)
|
||||
row.update(
|
||||
{"gross_pay": ss.gross_pay, "total_deduction": ss.total_deduction, "net_pay": ss.net_pay}
|
||||
)
|
||||
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
|
||||
|
||||
def get_columns(salary_slips):
|
||||
"""
|
||||
def get_earning_and_deduction_types(salary_slips):
|
||||
salary_component_and_type = {_("Earning"): [], _("Deduction"): []}
|
||||
|
||||
for salary_compoent in get_salary_components(salary_slips):
|
||||
component_type = get_salary_component_type(salary_compoent[0])
|
||||
salary_component_and_type[_(component_type)].append(salary_compoent[0])
|
||||
|
||||
return sorted(salary_component_and_type[_("Earning")]), sorted(
|
||||
salary_component_and_type[_("Deduction")]
|
||||
)
|
||||
|
||||
|
||||
def update_column_width(ss, columns):
|
||||
if ss.branch is not None:
|
||||
columns[3].update({"width": 120})
|
||||
if ss.department is not None:
|
||||
columns[4].update({"width": 120})
|
||||
if ss.designation is not None:
|
||||
columns[5].update({"width": 120})
|
||||
if ss.leave_without_pay is not None:
|
||||
columns[9].update({"width": 120})
|
||||
|
||||
|
||||
def get_columns(earning_types, ded_types):
|
||||
columns = [
|
||||
_("Salary Slip ID") + ":Link/Salary Slip:150",
|
||||
_("Employee") + ":Link/Employee:120",
|
||||
_("Employee Name") + "::140",
|
||||
_("Date of Joining") + "::80",
|
||||
_("Branch") + ":Link/Branch:120",
|
||||
_("Department") + ":Link/Department:120",
|
||||
_("Designation") + ":Link/Designation:120",
|
||||
_("Company") + ":Link/Company:120",
|
||||
_("Start Date") + "::80",
|
||||
_("End Date") + "::80",
|
||||
_("Leave Without Pay") + ":Float:130",
|
||||
_("Payment Days") + ":Float:120",
|
||||
_("Currency") + ":Link/Currency:80"
|
||||
]
|
||||
"""
|
||||
columns = [
|
||||
_("Salary Slip ID") + ":Link/Salary Slip:150",
|
||||
_("Employee") + ":Link/Employee:120",
|
||||
_("Employee Name") + "::140",
|
||||
_("Date of Joining") + "::80",
|
||||
_("Branch") + ":Link/Branch:-1",
|
||||
_("Department") + ":Link/Department:-1",
|
||||
_("Designation") + ":Link/Designation:120",
|
||||
_("Company") + ":Link/Company:120",
|
||||
_("Start Date") + "::80",
|
||||
_("End Date") + "::80",
|
||||
_("Leave Without Pay") + ":Float:50",
|
||||
_("Payment Days") + ":Float:120",
|
||||
{
|
||||
"label": _("Salary Slip ID"),
|
||||
"fieldname": "salary_slip_id",
|
||||
"fieldtype": "Link",
|
||||
"options": "Salary Slip",
|
||||
"width": 150,
|
||||
},
|
||||
{
|
||||
"label": _("Employee"),
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"options": "Employee",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Employee Name"),
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Data",
|
||||
"width": 140,
|
||||
},
|
||||
{
|
||||
"label": _("Date of Joining"),
|
||||
"fieldname": "data_of_joining",
|
||||
"fieldtype": "Date",
|
||||
"width": 80,
|
||||
},
|
||||
{
|
||||
"label": _("Branch"),
|
||||
"fieldname": "branch",
|
||||
"fieldtype": "Link",
|
||||
"options": "Branch",
|
||||
"width": -1,
|
||||
},
|
||||
{
|
||||
"label": _("Department"),
|
||||
"fieldname": "department",
|
||||
"fieldtype": "Link",
|
||||
"options": "Department",
|
||||
"width": -1,
|
||||
},
|
||||
{
|
||||
"label": _("Designation"),
|
||||
"fieldname": "designation",
|
||||
"fieldtype": "Link",
|
||||
"options": "Designation",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Company"),
|
||||
"fieldname": "company",
|
||||
"fieldtype": "Link",
|
||||
"options": "Company",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Start Date"),
|
||||
"fieldname": "start_date",
|
||||
"fieldtype": "Data",
|
||||
"width": 80,
|
||||
},
|
||||
{
|
||||
"label": _("End Date"),
|
||||
"fieldname": "end_date",
|
||||
"fieldtype": "Data",
|
||||
"width": 80,
|
||||
},
|
||||
{
|
||||
"label": _("Leave Without Pay"),
|
||||
"fieldname": "leave_without_pay",
|
||||
"fieldtype": "Float",
|
||||
"width": 50,
|
||||
},
|
||||
{
|
||||
"label": _("Payment Days"),
|
||||
"fieldname": "payment_days",
|
||||
"fieldtype": "Float",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
|
||||
salary_components = {_("Earning"): [], _("Deduction"): []}
|
||||
for earning in earning_types:
|
||||
columns.append(
|
||||
{
|
||||
"label": earning,
|
||||
"fieldname": frappe.scrub(earning),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
for component in frappe.db.sql(
|
||||
"""select distinct sd.salary_component, sc.type
|
||||
from `tabSalary Detail` sd, `tabSalary Component` sc
|
||||
where sc.name=sd.salary_component and sd.amount != 0 and sd.parent in (%s)"""
|
||||
% (", ".join(["%s"] * len(salary_slips))),
|
||||
tuple([d.name for d in salary_slips]),
|
||||
as_dict=1,
|
||||
):
|
||||
salary_components[_(component.type)].append(component.salary_component)
|
||||
columns.append(
|
||||
{
|
||||
"label": _("Gross Pay"),
|
||||
"fieldname": "gross_pay",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ [(e + ":Currency:120") for e in salary_components[_("Earning")]]
|
||||
+ [_("Gross Pay") + ":Currency:120"]
|
||||
+ [(d + ":Currency:120") for d in salary_components[_("Deduction")]]
|
||||
+ [
|
||||
_("Loan Repayment") + ":Currency:120",
|
||||
_("Total Deduction") + ":Currency:120",
|
||||
_("Net Pay") + ":Currency:120",
|
||||
for deduction in ded_types:
|
||||
columns.append(
|
||||
{
|
||||
"label": deduction,
|
||||
"fieldname": frappe.scrub(deduction),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
columns.extend(
|
||||
[
|
||||
{
|
||||
"label": _("Loan Repayment"),
|
||||
"fieldname": "total_loan_repayment",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Total Deduction"),
|
||||
"fieldname": "total_deduction",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Net Pay"),
|
||||
"fieldname": "net_pay",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Currency"),
|
||||
"fieldname": "currency",
|
||||
"fieldtype": "Data",
|
||||
"options": "Currency",
|
||||
"hidden": 1,
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
return columns, salary_components[_("Earning")], salary_components[_("Deduction")]
|
||||
return columns
|
||||
|
||||
|
||||
def get_salary_components(salary_slips):
|
||||
return (
|
||||
frappe.qb.from_(salary_detail)
|
||||
.where((salary_detail.amount != 0) & (salary_detail.parent.isin([d.name for d in salary_slips])))
|
||||
.select(salary_detail.salary_component)
|
||||
.distinct()
|
||||
).run(as_list=True)
|
||||
|
||||
|
||||
def get_salary_component_type(salary_component):
|
||||
return frappe.db.get_value("Salary Component", salary_component, "type", cache=True)
|
||||
|
||||
|
||||
def get_salary_slips(filters, company_currency):
|
||||
filters.update({"from_date": filters.get("from_date"), "to_date": filters.get("to_date")})
|
||||
conditions, filters = get_conditions(filters, company_currency)
|
||||
salary_slips = frappe.db.sql(
|
||||
"""select * from `tabSalary Slip` where %s
|
||||
order by employee"""
|
||||
% conditions,
|
||||
filters,
|
||||
as_dict=1,
|
||||
)
|
||||
doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}
|
||||
|
||||
query = frappe.qb.from_(salary_slip).select(salary_slip.star)
|
||||
|
||||
if filters.get("docstatus"):
|
||||
query = query.where(salary_slip.docstatus == doc_status[filters.get("docstatus")])
|
||||
|
||||
if filters.get("from_date"):
|
||||
query = query.where(salary_slip.start_date >= filters.get("from_date"))
|
||||
|
||||
if filters.get("to_date"):
|
||||
query = query.where(salary_slip.end_date <= filters.get("to_date"))
|
||||
|
||||
if filters.get("company"):
|
||||
query = query.where(salary_slip.company == filters.get("company"))
|
||||
|
||||
if filters.get("employee"):
|
||||
query = query.where(salary_slip.employee == filters.get("employee"))
|
||||
|
||||
if filters.get("currency") and filters.get("currency") != company_currency:
|
||||
query = query.where(salary_slip.currency == filters.get("currency"))
|
||||
|
||||
salary_slips = query.run(as_dict=1)
|
||||
|
||||
return salary_slips or []
|
||||
|
||||
|
||||
def get_conditions(filters, company_currency):
|
||||
conditions = ""
|
||||
doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}
|
||||
|
||||
if filters.get("docstatus"):
|
||||
conditions += "docstatus = {0}".format(doc_status[filters.get("docstatus")])
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " and start_date >= %(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and end_date <= %(to_date)s"
|
||||
if filters.get("company"):
|
||||
conditions += " and company = %(company)s"
|
||||
if filters.get("employee"):
|
||||
conditions += " and employee = %(employee)s"
|
||||
if filters.get("currency") and filters.get("currency") != company_currency:
|
||||
conditions += " and currency = %(currency)s"
|
||||
|
||||
return conditions, filters
|
||||
|
||||
|
||||
def get_employee_doj_map():
|
||||
return frappe._dict(
|
||||
frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
employee,
|
||||
date_of_joining
|
||||
FROM `tabEmployee`
|
||||
"""
|
||||
employee = frappe.qb.DocType("Employee")
|
||||
|
||||
result = (frappe.qb.from_(employee).select(employee.name, employee.date_of_joining)).run()
|
||||
|
||||
return frappe._dict(result)
|
||||
|
||||
|
||||
def get_salary_slip_details(salary_slips, currency, company_currency, component_type):
|
||||
salary_slips = [ss.name for ss in salary_slips]
|
||||
|
||||
result = (
|
||||
frappe.qb.from_(salary_slip)
|
||||
.join(salary_detail)
|
||||
.on(salary_slip.name == salary_detail.parent)
|
||||
.where((salary_detail.parent.isin(salary_slips)) & (salary_detail.parentfield == component_type))
|
||||
.select(
|
||||
salary_detail.parent,
|
||||
salary_detail.salary_component,
|
||||
salary_detail.amount,
|
||||
salary_slip.exchange_rate,
|
||||
)
|
||||
)
|
||||
).run(as_dict=1)
|
||||
|
||||
ss_map = {}
|
||||
|
||||
def get_ss_earning_map(salary_slips, currency, company_currency):
|
||||
ss_earnings = frappe.db.sql(
|
||||
"""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
|
||||
from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)"""
|
||||
% (", ".join(["%s"] * len(salary_slips))),
|
||||
tuple([d.name for d in salary_slips]),
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
ss_earning_map = {}
|
||||
for d in ss_earnings:
|
||||
ss_earning_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0)
|
||||
for d in result:
|
||||
ss_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0)
|
||||
if currency == company_currency:
|
||||
ss_earning_map[d.parent][d.salary_component] += flt(d.amount) * flt(
|
||||
ss_map[d.parent][d.salary_component] += flt(d.amount) * flt(
|
||||
d.exchange_rate if d.exchange_rate else 1
|
||||
)
|
||||
else:
|
||||
ss_earning_map[d.parent][d.salary_component] += flt(d.amount)
|
||||
ss_map[d.parent][d.salary_component] += flt(d.amount)
|
||||
|
||||
return ss_earning_map
|
||||
|
||||
|
||||
def get_ss_ded_map(salary_slips, currency, company_currency):
|
||||
ss_deductions = frappe.db.sql(
|
||||
"""select sd.parent, sd.salary_component, sd.amount, ss.exchange_rate, ss.name
|
||||
from `tabSalary Detail` sd, `tabSalary Slip` ss where sd.parent=ss.name and sd.parent in (%s)"""
|
||||
% (", ".join(["%s"] * len(salary_slips))),
|
||||
tuple([d.name for d in salary_slips]),
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
ss_ded_map = {}
|
||||
for d in ss_deductions:
|
||||
ss_ded_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0)
|
||||
if currency == company_currency:
|
||||
ss_ded_map[d.parent][d.salary_component] += flt(d.amount) * flt(
|
||||
d.exchange_rate if d.exchange_rate else 1
|
||||
)
|
||||
else:
|
||||
ss_ded_map[d.parent][d.salary_component] += flt(d.amount)
|
||||
|
||||
return ss_ded_map
|
||||
return ss_map
|
||||
|
||||
@@ -25,20 +25,38 @@ frappe.listview_settings['Task'] = {
|
||||
}
|
||||
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
|
||||
},
|
||||
gantt_custom_popup_html: function(ganttobj, task) {
|
||||
var html = `<h5><a style="text-decoration:underline"\
|
||||
href="/app/task/${ganttobj.id}""> ${ganttobj.name} </a></h5>`;
|
||||
gantt_custom_popup_html: function (ganttobj, task) {
|
||||
let html = `
|
||||
<a class="text-white mb-2 inline-block cursor-pointer"
|
||||
href="/app/task/${ganttobj.id}"">
|
||||
${ganttobj.name}
|
||||
</a>
|
||||
`;
|
||||
|
||||
if(task.project) html += `<p>Project: ${task.project}</p>`;
|
||||
html += `<p>Progress: ${ganttobj.progress}</p>`;
|
||||
if (task.project) {
|
||||
html += `<p class="mb-1">${__("Project")}:
|
||||
<a class="text-white inline-block"
|
||||
href="/app/project/${task.project}"">
|
||||
${task.project}
|
||||
</a>
|
||||
</p>`;
|
||||
}
|
||||
html += `<p class="mb-1">
|
||||
${__("Progress")}:
|
||||
<span class="text-white">${ganttobj.progress}%</span>
|
||||
</p>`;
|
||||
|
||||
if(task._assign_list) {
|
||||
html += task._assign_list.reduce(
|
||||
(html, user) => html + frappe.avatar(user)
|
||||
, '');
|
||||
if (task._assign) {
|
||||
const assign_list = JSON.parse(task._assign);
|
||||
const assignment_wrapper = `
|
||||
<span>Assigned to:</span>
|
||||
<span class="text-white">
|
||||
${assign_list.map((user) => frappe.user_info(user).fullname).join(", ")}
|
||||
</span>
|
||||
`;
|
||||
html += assignment_wrapper;
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
return `<div class="p-3" style="min-width: 220px">${html}</div>`;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -764,11 +764,13 @@ erpnext.taxes_and_totals = erpnext.payments.extend({
|
||||
precision("base_grand_total")
|
||||
);
|
||||
}
|
||||
this.frm.doc.payments.find(pay => {
|
||||
if (pay.default) {
|
||||
pay.amount = total_amount_to_pay;
|
||||
}
|
||||
});
|
||||
if(!this.frm.doc.is_return){
|
||||
this.frm.doc.payments.find(payment => {
|
||||
if (payment.default) {
|
||||
payment.amount = total_amount_to_pay;
|
||||
}
|
||||
});
|
||||
}
|
||||
this.frm.refresh_fields();
|
||||
},
|
||||
|
||||
|
||||
@@ -173,7 +173,9 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
this.frm.set_query("expense_account", "items", function(doc) {
|
||||
return {
|
||||
filters: {
|
||||
"company": doc.company
|
||||
"company": doc.company,
|
||||
"report_type": "Profit and Loss",
|
||||
"is_group": 0
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -297,8 +299,8 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
});
|
||||
},
|
||||
|
||||
make_payment_request: function() {
|
||||
var me = this;
|
||||
make_payment_request() {
|
||||
let me = this;
|
||||
const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
|
||||
? "Inward" : "Outward";
|
||||
|
||||
@@ -314,7 +316,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
},
|
||||
callback: function(r) {
|
||||
if(!r.exc){
|
||||
var doc = frappe.model.sync(r.message);
|
||||
frappe.model.sync(r.message);
|
||||
frappe.set_route("Form", r.message.doctype, r.message.name);
|
||||
}
|
||||
}
|
||||
@@ -2011,7 +2013,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
},
|
||||
|
||||
prompt_user_for_reference_date(){
|
||||
var me = this;
|
||||
let me = this;
|
||||
frappe.prompt({
|
||||
label: __("Cheque/Reference Date"),
|
||||
fieldname: "reference_date",
|
||||
@@ -2038,7 +2040,7 @@ erpnext.TransactionController = erpnext.taxes_and_totals.extend({
|
||||
let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length;
|
||||
if(!is_eligible || !has_payment_schedule) return false;
|
||||
|
||||
let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date);
|
||||
let has_discount = this.frm.doc.payment_schedule.some(row => row.discount);
|
||||
return has_discount;
|
||||
},
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"section_break_4",
|
||||
"account_number_length",
|
||||
"column_break_6",
|
||||
"temporary_against_account_number"
|
||||
"temporary_against_account_number",
|
||||
"opening_against_account_number"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -70,14 +71,23 @@
|
||||
},
|
||||
{
|
||||
"allow_in_quick_entry": 1,
|
||||
"default": "9999",
|
||||
"description": "Will be used as against account for all normal ledger entries",
|
||||
"fieldname": "temporary_against_account_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Temporary Against Account Number",
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"default": "9000",
|
||||
"description": "Will be used as against account for opening ledger entries",
|
||||
"fieldname": "opening_against_account_number",
|
||||
"fieldtype": "Data",
|
||||
"label": "Opening Against Account Number"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2020-11-19 19:00:09.088816",
|
||||
"modified": "2023-06-30 00:56:59.556731",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Regional",
|
||||
"name": "DATEV Settings",
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
# Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
|
||||
# import frappe
|
||||
from frappe import _, throw
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class DATEVSettings(Document):
|
||||
pass
|
||||
def validate(self):
|
||||
if (
|
||||
self.temporary_against_account_number
|
||||
and len(self.temporary_against_account_number) != self.account_number_length
|
||||
):
|
||||
throw(
|
||||
_("Temporary Against Account Number must be {0} digits long").format(
|
||||
self.account_number_length
|
||||
)
|
||||
)
|
||||
|
||||
if (
|
||||
self.opening_against_account_number
|
||||
and len(self.opening_against_account_number) != self.account_number_length
|
||||
):
|
||||
throw(
|
||||
_("Opening Against Account Number must be {0} digits long").format(self.account_number_length)
|
||||
)
|
||||
|
||||
@@ -132,8 +132,12 @@ def execute(filters=None):
|
||||
"""Entry point for frappe."""
|
||||
data = []
|
||||
if filters and validate(filters):
|
||||
fn = "temporary_against_account_number"
|
||||
filters[fn] = frappe.get_value("DATEV Settings", filters.get("company"), fn)
|
||||
temp, opening = frappe.get_value(
|
||||
"DATEV Settings",
|
||||
filters.get("company"),
|
||||
["temporary_against_account_number", "opening_against_account_number"],
|
||||
)
|
||||
filters.update({"against_account": temp, "opening_account": opening or temp})
|
||||
data = get_transactions(filters, as_dict=0)
|
||||
|
||||
return COLUMNS, data
|
||||
@@ -315,7 +319,7 @@ def run_query(filters, extra_fields, extra_joins, extra_filters, as_dict=1):
|
||||
acc.account_number as 'Konto',
|
||||
|
||||
/* against number or, if empty, party against number */
|
||||
%(temporary_against_account_number)s as 'Gegenkonto (ohne BU-Schlüssel)',
|
||||
CASE gl.is_opening when 'Yes' then %(opening_account)s else %(against_account)s end as 'Gegenkonto (ohne BU-Schlüssel)',
|
||||
|
||||
'' as 'BU-Schlüssel',
|
||||
|
||||
@@ -530,18 +534,22 @@ def download_datev_csv(filters):
|
||||
filters = json.loads(filters)
|
||||
|
||||
validate(filters)
|
||||
|
||||
company = filters.get("company")
|
||||
|
||||
fiscal_year = get_fiscal_year(date=filters.get("from_date"), company=company)
|
||||
filters["fiscal_year_start"] = fiscal_year[1]
|
||||
|
||||
# set chart of accounts used
|
||||
coa = frappe.get_value("Company", company, "chart_of_accounts")
|
||||
filters["skr"] = "04" if "SKR04" in coa else ("03" if "SKR03" in coa else "")
|
||||
|
||||
datev_settings = frappe.get_doc("DATEV Settings", company)
|
||||
filters["account_number_length"] = datev_settings.account_number_length
|
||||
filters["temporary_against_account_number"] = datev_settings.temporary_against_account_number
|
||||
|
||||
filters.update(
|
||||
{
|
||||
"fiscal_year_start": fiscal_year[1],
|
||||
"skr": "04" if "SKR04" in coa else ("03" if "SKR03" in coa else ""),
|
||||
"account_number_length": datev_settings.account_number_length,
|
||||
"against_account": datev_settings.temporary_against_account_number,
|
||||
"opening_account": datev_settings.opening_against_account_number
|
||||
or datev_settings.temporary_against_account_number,
|
||||
}
|
||||
)
|
||||
|
||||
transactions = get_transactions(filters)
|
||||
account_names = get_account_names(filters)
|
||||
|
||||
@@ -139,7 +139,9 @@ def make_datev_settings(company):
|
||||
"client": company.name,
|
||||
"client_number": "12345",
|
||||
"consultant_number": "67890",
|
||||
"account_number_length": 4,
|
||||
"temporary_against_account_number": "9999",
|
||||
"opening_against_account_number": "9000",
|
||||
}
|
||||
).insert()
|
||||
|
||||
@@ -152,7 +154,8 @@ class TestDatev(TestCase):
|
||||
"company": self.company.name,
|
||||
"from_date": today(),
|
||||
"to_date": today(),
|
||||
"temporary_against_account_number": "9999",
|
||||
"against_account": "9999",
|
||||
"opening_account": "9000",
|
||||
}
|
||||
|
||||
make_datev_settings(self.company)
|
||||
|
||||
@@ -21,16 +21,14 @@ def get_columns(filters):
|
||||
columns = [
|
||||
{
|
||||
"label": _("Employee"),
|
||||
"options": "Employee",
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"options": "Employee",
|
||||
"width": 200,
|
||||
},
|
||||
{
|
||||
"label": _("Employee Name"),
|
||||
"options": "Employee",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Link",
|
||||
"width": 160,
|
||||
},
|
||||
{"label": _("Amount"), "fieldname": "amount", "fieldtype": "Currency", "width": 140},
|
||||
|
||||
@@ -18,16 +18,14 @@ def get_columns(filters):
|
||||
columns = [
|
||||
{
|
||||
"label": _("Employee"),
|
||||
"options": "Employee",
|
||||
"fieldname": "employee",
|
||||
"fieldtype": "Link",
|
||||
"options": "Employee",
|
||||
"width": 200,
|
||||
},
|
||||
{
|
||||
"label": _("Employee Name"),
|
||||
"options": "Employee",
|
||||
"fieldname": "employee_name",
|
||||
"fieldtype": "Link",
|
||||
"width": 160,
|
||||
},
|
||||
{"label": _("PF Account"), "fieldname": "pf_account", "fieldtype": "Data", "width": 140},
|
||||
|
||||
@@ -35,6 +35,7 @@ def add_print_formats():
|
||||
|
||||
|
||||
def add_permissions():
|
||||
frappe.reload_doc("regional", "doctype", "ksa_vat_setting", force=True)
|
||||
"""Add Permissions for KSA VAT Setting."""
|
||||
add_permission("KSA VAT Setting", "All", 0)
|
||||
for role in ("Accounts Manager", "Accounts User", "System Manager"):
|
||||
|
||||
@@ -656,11 +656,15 @@ def get_credit_limit(customer, company):
|
||||
|
||||
if not credit_limit:
|
||||
customer_group = frappe.get_cached_value("Customer", customer, "customer_group")
|
||||
credit_limit = frappe.db.get_value(
|
||||
|
||||
result = frappe.db.get_values(
|
||||
"Customer Credit Limit",
|
||||
{"parent": customer_group, "parenttype": "Customer Group", "company": company},
|
||||
"credit_limit",
|
||||
fieldname=["credit_limit", "bypass_credit_limit_check"],
|
||||
as_dict=True,
|
||||
)
|
||||
if result and not result[0].bypass_credit_limit_check:
|
||||
credit_limit = result[0].credit_limit
|
||||
|
||||
if not credit_limit:
|
||||
credit_limit = frappe.get_cached_value("Company", company, "credit_limit")
|
||||
|
||||
@@ -235,7 +235,7 @@ erpnext.selling.SalesOrderController = erpnext.selling.SellingController.extend(
|
||||
}
|
||||
}
|
||||
// payment request
|
||||
if(flt(doc.per_billed)<100) {
|
||||
if(flt(doc.per_billed, precision('per_billed', doc)) < 100 + frappe.boot.sysdefaults.over_billing_allowance) {
|
||||
this.frm.add_custom_button(__('Payment Request'), () => this.make_payment_request(), __('Create'));
|
||||
this.frm.add_custom_button(__('Payment'), () => this.make_payment_entry(), __('Create'));
|
||||
}
|
||||
|
||||
@@ -1389,8 +1389,9 @@ def get_work_order_items(sales_order, for_raw_material_request=0):
|
||||
.select(Sum(wo.qty))
|
||||
.where(
|
||||
(wo.production_item == i.item_code)
|
||||
& (wo.sales_order == so.name) * (wo.sales_order_item == i.name)
|
||||
& (wo.docstatus.lte(2))
|
||||
& (wo.sales_order == so.name)
|
||||
& (wo.sales_order_item == i.name)
|
||||
& (wo.docstatus.lt(2))
|
||||
)
|
||||
.run()[0][0]
|
||||
)
|
||||
|
||||
@@ -22,6 +22,10 @@ def boot_session(bootinfo):
|
||||
bootinfo.sysdefaults.allow_stale = cint(
|
||||
frappe.db.get_single_value("Accounts Settings", "allow_stale")
|
||||
)
|
||||
bootinfo.sysdefaults.over_billing_allowance = frappe.db.get_single_value(
|
||||
"Accounts Settings", "over_billing_allowance"
|
||||
)
|
||||
|
||||
bootinfo.sysdefaults.quotation_valid_till = cint(
|
||||
frappe.db.get_single_value("Selling Settings", "default_valid_till")
|
||||
)
|
||||
|
||||
@@ -292,7 +292,7 @@ def get_items_with_location_and_quantity(item_doc, item_location_map, docstatus)
|
||||
item_doc.qty if (docstatus == 1 and item_doc.stock_qty == 0) else item_doc.stock_qty
|
||||
)
|
||||
|
||||
while remaining_stock_qty > 0 and available_locations:
|
||||
while flt(remaining_stock_qty) > 0 and available_locations:
|
||||
item_location = available_locations.pop(0)
|
||||
item_location = frappe._dict(item_location)
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ class PurchaseReceipt(BuyingController):
|
||||
self.set_status()
|
||||
|
||||
self.po_required()
|
||||
self.validate_items_quality_inspection()
|
||||
self.validate_with_previous_doc()
|
||||
self.validate_uom_is_integer("uom", ["qty", "received_qty"])
|
||||
self.validate_uom_is_integer("stock_uom", "stock_qty")
|
||||
@@ -183,6 +184,26 @@ class PurchaseReceipt(BuyingController):
|
||||
if not d.purchase_order:
|
||||
frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
|
||||
|
||||
def validate_items_quality_inspection(self):
|
||||
for item in self.get("items"):
|
||||
if item.quality_inspection:
|
||||
qi = frappe.db.get_value(
|
||||
"Quality Inspection",
|
||||
item.quality_inspection,
|
||||
["reference_type", "reference_name", "item_code"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if qi.reference_type != self.doctype or qi.reference_name != self.name:
|
||||
msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Reference Type
|
||||
{frappe.bold(self.doctype)} and Reference Name {frappe.bold(self.name)}."""
|
||||
frappe.throw(_(msg))
|
||||
|
||||
if qi.item_code != item.item_code:
|
||||
msg = f"""Row #{item.idx}: Please select a valid Quality Inspection with Item Code
|
||||
{frappe.bold(item.item_code)}."""
|
||||
frappe.throw(_(msg))
|
||||
|
||||
def get_already_received_qty(self, po, po_detail):
|
||||
qty = frappe.db.sql(
|
||||
"""select sum(qty) from `tabPurchase Receipt Item`
|
||||
@@ -369,8 +390,20 @@ class PurchaseReceipt(BuyingController):
|
||||
)
|
||||
|
||||
outgoing_amount = d.base_net_amount
|
||||
if self.is_internal_supplier and d.valuation_rate:
|
||||
outgoing_amount = d.valuation_rate * d.stock_qty
|
||||
if self.is_internal_transfer() and d.valuation_rate:
|
||||
outgoing_amount = abs(
|
||||
frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": self.name,
|
||||
"voucher_detail_no": d.name,
|
||||
"warehouse": d.from_warehouse,
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
"stock_value_difference",
|
||||
)
|
||||
)
|
||||
credit_amount = outgoing_amount
|
||||
|
||||
if credit_amount:
|
||||
|
||||
@@ -1707,6 +1707,263 @@ class TestPurchaseReceipt(FrappeTestCase):
|
||||
|
||||
self.assertTrue(return_pi.docstatus == 1)
|
||||
|
||||
def test_internal_pr_gl_entries(self):
|
||||
from erpnext.stock import get_warehouse_account_map
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
|
||||
create_stock_reconciliation,
|
||||
)
|
||||
|
||||
prepare_data_for_internal_transfer()
|
||||
customer = "_Test Internal Customer 2"
|
||||
company = "_Test Company with perpetual inventory"
|
||||
from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
|
||||
target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
|
||||
to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
|
||||
|
||||
item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100})
|
||||
make_stock_entry(
|
||||
purpose="Material Receipt",
|
||||
item_code=item.name,
|
||||
qty=10,
|
||||
company=company,
|
||||
to_warehouse=from_warehouse,
|
||||
posting_date=add_days(today(), -3),
|
||||
)
|
||||
|
||||
# Step - 1: Create Delivery Note with Internal Customer
|
||||
dn = create_delivery_note(
|
||||
item_code=item.name,
|
||||
company=company,
|
||||
customer=customer,
|
||||
cost_center="Main - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
qty=10,
|
||||
rate=100,
|
||||
warehouse=from_warehouse,
|
||||
target_warehouse=target_warehouse,
|
||||
posting_date=add_days(today(), -2),
|
||||
)
|
||||
|
||||
# Step - 2: Create Internal Purchase Receipt
|
||||
pr = make_inter_company_purchase_receipt(dn.name)
|
||||
pr.items[0].qty = 10
|
||||
pr.items[0].from_warehouse = target_warehouse
|
||||
pr.items[0].warehouse = to_warehouse
|
||||
pr.items[0].rejected_warehouse = from_warehouse
|
||||
pr.save()
|
||||
pr.submit()
|
||||
|
||||
# Step - 3: Create back-date Stock Reconciliation [After DN and Before PR]
|
||||
create_stock_reconciliation(
|
||||
item_code=item.name,
|
||||
warehouse=target_warehouse,
|
||||
qty=10,
|
||||
rate=50,
|
||||
company=company,
|
||||
posting_date=add_days(today(), -1),
|
||||
expense_account="Stock Adjustment - TCP1",
|
||||
)
|
||||
|
||||
warehouse_account = get_warehouse_account_map(company)
|
||||
stock_account_value = frappe.db.get_value(
|
||||
"GL Entry",
|
||||
{
|
||||
"account": warehouse_account[target_warehouse]["account"],
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": pr.name,
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
fieldname=["credit"],
|
||||
)
|
||||
stock_diff = frappe.db.get_value(
|
||||
"Stock Ledger Entry",
|
||||
{
|
||||
"voucher_type": "Purchase Receipt",
|
||||
"voucher_no": pr.name,
|
||||
"is_cancelled": 0,
|
||||
},
|
||||
fieldname=["sum(stock_value_difference)"],
|
||||
)
|
||||
|
||||
# Value of Stock Account should be equal to the sum of Stock Value Difference
|
||||
self.assertEqual(stock_account_value, stock_diff)
|
||||
|
||||
def test_internal_pr_reference(self):
|
||||
item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100})
|
||||
customer = "_Test Internal Customer 2"
|
||||
company = "_Test Company with perpetual inventory"
|
||||
from_warehouse = create_warehouse("_Test Internal From Warehouse New 1", company=company)
|
||||
target_warehouse = create_warehouse("_Test Internal GIT Warehouse New 1", company=company)
|
||||
to_warehouse = create_warehouse("_Test Internal To Warehouse New 1", company=company)
|
||||
|
||||
# Step 2: Create Stock Entry (Material Receipt)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
make_stock_entry(
|
||||
purpose="Material Receipt",
|
||||
item_code=item.name,
|
||||
qty=15,
|
||||
company=company,
|
||||
to_warehouse=from_warehouse,
|
||||
)
|
||||
|
||||
# Step 3: Create Delivery Note with Internal Customer
|
||||
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||
|
||||
dn = create_delivery_note(
|
||||
item_code=item.name,
|
||||
company=company,
|
||||
customer=customer,
|
||||
cost_center="Main - TCP1",
|
||||
expense_account="Cost of Goods Sold - TCP1",
|
||||
qty=10,
|
||||
rate=100,
|
||||
warehouse=from_warehouse,
|
||||
target_warehouse=target_warehouse,
|
||||
)
|
||||
|
||||
# Step 4: Create Internal Purchase Receipt
|
||||
from erpnext.controllers.status_updater import OverAllowanceError
|
||||
from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
|
||||
|
||||
pr = make_inter_company_purchase_receipt(dn.name)
|
||||
pr.inter_company_reference = ""
|
||||
self.assertRaises(frappe.ValidationError, pr.save)
|
||||
|
||||
pr.inter_company_reference = dn.name
|
||||
pr.items[0].qty = 10
|
||||
pr.items[0].from_warehouse = target_warehouse
|
||||
pr.items[0].warehouse = to_warehouse
|
||||
pr.items[0].rejected_warehouse = from_warehouse
|
||||
pr.save()
|
||||
|
||||
delivery_note_item = pr.items[0].delivery_note_item
|
||||
pr.items[0].delivery_note_item = ""
|
||||
|
||||
self.assertRaises(frappe.ValidationError, pr.save)
|
||||
|
||||
pr.load_from_db()
|
||||
pr.items[0].delivery_note_item = delivery_note_item
|
||||
pr.save()
|
||||
|
||||
def test_purchase_receipt_with_backdated_landed_cost_voucher(self):
|
||||
from erpnext.controllers.sales_and_purchase_return import make_return_doc
|
||||
from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
|
||||
create_landed_cost_voucher,
|
||||
)
|
||||
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
|
||||
|
||||
item_code = "_Test Purchase Item With Landed Cost"
|
||||
create_item(item_code)
|
||||
|
||||
warehouse = create_warehouse("_Test Purchase Warehouse With Landed Cost")
|
||||
warehouse1 = create_warehouse("_Test Purchase Warehouse With Landed Cost 1")
|
||||
warehouse2 = create_warehouse("_Test Purchase Warehouse With Landed Cost 2")
|
||||
warehouse3 = create_warehouse("_Test Purchase Warehouse With Landed Cost 3")
|
||||
|
||||
pr = make_purchase_receipt(
|
||||
item_code=item_code,
|
||||
warehouse=warehouse,
|
||||
posting_date=add_days(today(), -10),
|
||||
posting_time="10:59:59",
|
||||
qty=100,
|
||||
rate=275.00,
|
||||
)
|
||||
|
||||
pr_return = make_return_doc("Purchase Receipt", pr.name)
|
||||
pr_return.posting_date = add_days(today(), -9)
|
||||
pr_return.items[0].qty = 2 * -1
|
||||
pr_return.items[0].received_qty = 2 * -1
|
||||
pr_return.submit()
|
||||
|
||||
ste1 = make_stock_entry(
|
||||
purpose="Material Transfer",
|
||||
posting_date=add_days(today(), -8),
|
||||
source=warehouse,
|
||||
target=warehouse1,
|
||||
item_code=item_code,
|
||||
qty=20,
|
||||
company=pr.company,
|
||||
)
|
||||
|
||||
ste1.reload()
|
||||
self.assertEqual(ste1.items[0].valuation_rate, 275.00)
|
||||
|
||||
ste2 = make_stock_entry(
|
||||
purpose="Material Transfer",
|
||||
posting_date=add_days(today(), -7),
|
||||
source=warehouse,
|
||||
target=warehouse2,
|
||||
item_code=item_code,
|
||||
qty=20,
|
||||
company=pr.company,
|
||||
)
|
||||
|
||||
ste2.reload()
|
||||
self.assertEqual(ste2.items[0].valuation_rate, 275.00)
|
||||
|
||||
ste3 = make_stock_entry(
|
||||
purpose="Material Transfer",
|
||||
posting_date=add_days(today(), -6),
|
||||
source=warehouse,
|
||||
target=warehouse3,
|
||||
item_code=item_code,
|
||||
qty=20,
|
||||
company=pr.company,
|
||||
)
|
||||
|
||||
ste3.reload()
|
||||
self.assertEqual(ste3.items[0].valuation_rate, 275.00)
|
||||
|
||||
ste4 = make_stock_entry(
|
||||
purpose="Material Transfer",
|
||||
posting_date=add_days(today(), -5),
|
||||
source=warehouse1,
|
||||
target=warehouse,
|
||||
item_code=item_code,
|
||||
qty=20,
|
||||
company=pr.company,
|
||||
)
|
||||
|
||||
ste4.reload()
|
||||
self.assertEqual(ste4.items[0].valuation_rate, 275.00)
|
||||
|
||||
ste5 = make_stock_entry(
|
||||
purpose="Material Transfer",
|
||||
posting_date=add_days(today(), -4),
|
||||
source=warehouse,
|
||||
target=warehouse1,
|
||||
item_code=item_code,
|
||||
qty=20,
|
||||
company=pr.company,
|
||||
)
|
||||
|
||||
ste5.reload()
|
||||
self.assertEqual(ste5.items[0].valuation_rate, 275.00)
|
||||
|
||||
create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=2500 * -1)
|
||||
|
||||
pr.reload()
|
||||
valuation_rate = pr.items[0].valuation_rate
|
||||
|
||||
ste1.reload()
|
||||
self.assertEqual(ste1.items[0].valuation_rate, valuation_rate)
|
||||
|
||||
ste2.reload()
|
||||
self.assertEqual(ste2.items[0].valuation_rate, valuation_rate)
|
||||
|
||||
ste3.reload()
|
||||
self.assertEqual(ste3.items[0].valuation_rate, valuation_rate)
|
||||
|
||||
ste4.reload()
|
||||
self.assertEqual(ste4.items[0].valuation_rate, valuation_rate)
|
||||
|
||||
ste5.reload()
|
||||
self.assertEqual(ste5.items[0].valuation_rate, valuation_rate)
|
||||
|
||||
|
||||
def prepare_data_for_internal_transfer():
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
|
||||
|
||||
@@ -204,6 +204,7 @@
|
||||
"fieldname": "received_qty",
|
||||
"fieldtype": "Float",
|
||||
"label": "Received Quantity",
|
||||
"no_copy": 1,
|
||||
"oldfieldname": "received_qty",
|
||||
"oldfieldtype": "Currency",
|
||||
"print_hide": 1,
|
||||
@@ -993,7 +994,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-10-12 03:37:59.516609",
|
||||
"modified": "2023-07-02 18:40:48.152637",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Purchase Receipt Item",
|
||||
@@ -1004,4 +1005,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import frappe
|
||||
from frappe import _
|
||||
from frappe.exceptions import QueryDeadlockError, QueryTimeoutError
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import cint, get_link_to_form, get_weekday, now, nowtime
|
||||
from frappe.query_builder.functions import Max
|
||||
from frappe.utils import cint, get_link_to_form, get_weekday, getdate, now, nowtime
|
||||
from frappe.utils.user import get_users_with_role
|
||||
from rq.timeouts import JobTimeoutException
|
||||
|
||||
@@ -22,9 +23,57 @@ RecoverableErrors = (JobTimeoutException, QueryDeadlockError, QueryTimeoutError)
|
||||
|
||||
class RepostItemValuation(Document):
|
||||
def validate(self):
|
||||
self.validate_period_closing_voucher()
|
||||
self.set_status(write=False)
|
||||
self.reset_field_values()
|
||||
self.set_company()
|
||||
self.validate_accounts_freeze()
|
||||
|
||||
def validate_period_closing_voucher(self):
|
||||
year_end_date = self.get_max_year_end_date(self.company)
|
||||
if year_end_date and getdate(self.posting_date) <= getdate(year_end_date):
|
||||
msg = f"Due to period closing, you cannot repost item valuation before {year_end_date}"
|
||||
frappe.throw(_(msg))
|
||||
|
||||
@staticmethod
|
||||
def get_max_year_end_date(company):
|
||||
data = frappe.get_all(
|
||||
"Period Closing Voucher", fields=["fiscal_year"], filters={"docstatus": 1, "company": company}
|
||||
)
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
fiscal_years = [d.fiscal_year for d in data]
|
||||
table = frappe.qb.DocType("Fiscal Year")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(table)
|
||||
.select(Max(table.year_end_date))
|
||||
.where((table.name.isin(fiscal_years)) & (table.disabled == 0))
|
||||
).run()
|
||||
|
||||
return query[0][0] if query else None
|
||||
|
||||
def validate_accounts_freeze(self):
|
||||
acc_settings = frappe.db.get_value(
|
||||
"Accounts Settings",
|
||||
"Accounts Settings",
|
||||
["acc_frozen_upto", "frozen_accounts_modifier"],
|
||||
as_dict=1,
|
||||
)
|
||||
if not acc_settings.acc_frozen_upto:
|
||||
return
|
||||
if getdate(self.posting_date) <= getdate(acc_settings.acc_frozen_upto):
|
||||
if (
|
||||
acc_settings.frozen_accounts_modifier
|
||||
and frappe.session.user in get_users_with_role(acc_settings.frozen_accounts_modifier)
|
||||
):
|
||||
frappe.msgprint(_("Caution: This might alter frozen accounts."))
|
||||
return
|
||||
frappe.throw(
|
||||
_("You cannot repost item valuation before {}").format(acc_settings.acc_frozen_upto)
|
||||
)
|
||||
|
||||
def reset_field_values(self):
|
||||
if self.based_on == "Transaction":
|
||||
@@ -235,7 +284,7 @@ def _get_directly_dependent_vouchers(doc):
|
||||
def notify_error_to_stock_managers(doc, traceback):
|
||||
recipients = get_users_with_role("Stock Manager")
|
||||
if not recipients:
|
||||
get_users_with_role("System Manager")
|
||||
recipients = get_users_with_role("System Manager")
|
||||
|
||||
subject = _("Error while reposting item valuation")
|
||||
message = (
|
||||
|
||||
@@ -272,3 +272,41 @@ class TestRepostItemValuation(FrappeTestCase, StockTestMixin):
|
||||
[{"credit": 50, "debit": 0}],
|
||||
gle_filters={"account": "Stock In Hand - TCP1"},
|
||||
)
|
||||
|
||||
def test_account_freeze_validation(self):
|
||||
today = nowdate()
|
||||
|
||||
riv = frappe.get_doc(
|
||||
doctype="Repost Item Valuation",
|
||||
item_code="_Test Item",
|
||||
warehouse="_Test Warehouse - _TC",
|
||||
based_on="Item and Warehouse",
|
||||
posting_date=today,
|
||||
posting_time="00:01:00",
|
||||
)
|
||||
riv.flags.dont_run_in_test = True # keep it queued
|
||||
|
||||
accounts_settings = frappe.get_doc("Accounts Settings")
|
||||
accounts_settings.acc_frozen_upto = today
|
||||
accounts_settings.frozen_accounts_modifier = ""
|
||||
accounts_settings.save()
|
||||
|
||||
self.assertRaises(frappe.ValidationError, riv.save)
|
||||
accounts_settings.acc_frozen_upto = ""
|
||||
accounts_settings.save()
|
||||
|
||||
def test_create_repost_entry_for_cancelled_document(self):
|
||||
pr = make_purchase_receipt(
|
||||
company="_Test Company with perpetual inventory",
|
||||
warehouse="Stores - TCP1",
|
||||
get_multiple_items=True,
|
||||
)
|
||||
|
||||
self.assertTrue(pr.docstatus == 1)
|
||||
self.assertFalse(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name}))
|
||||
|
||||
pr.load_from_db()
|
||||
|
||||
pr.cancel()
|
||||
self.assertTrue(pr.docstatus == 2)
|
||||
self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": pr.name}))
|
||||
|
||||
@@ -122,7 +122,8 @@
|
||||
"oldfieldname": "purpose",
|
||||
"oldfieldtype": "Select",
|
||||
"options": "Material Issue\nMaterial Receipt\nMaterial Transfer\nMaterial Transfer for Manufacture\nMaterial Consumption for Manufacture\nManufacture\nRepack\nSend to Subcontractor",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "company",
|
||||
@@ -619,7 +620,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2022-05-02 05:21:39.060501",
|
||||
"modified": "2023-06-19 18:23:40.748114",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Stock",
|
||||
"name": "Stock Entry",
|
||||
|
||||
@@ -2271,7 +2271,7 @@ def move_sample_to_retention_warehouse(company, items):
|
||||
"basic_rate": item.get("valuation_rate"),
|
||||
"uom": item.get("uom"),
|
||||
"stock_uom": item.get("stock_uom"),
|
||||
"conversion_factor": 1.0,
|
||||
"conversion_factor": item.get("conversion_factor") or 1.0,
|
||||
"serial_no": sample_serial_nos,
|
||||
"batch_no": item.get("batch_no"),
|
||||
},
|
||||
|
||||
@@ -529,7 +529,9 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin):
|
||||
# check if cancellation of stock reco is blocked
|
||||
self.assertRaises(NegativeStockError, sr.cancel)
|
||||
|
||||
repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name}))
|
||||
repost_exists = bool(
|
||||
frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name, "status": "Queued"})
|
||||
)
|
||||
self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation")
|
||||
|
||||
def test_intermediate_sr_bin_update(self):
|
||||
|
||||
@@ -13,10 +13,11 @@ frappe.ui.form.on("Warehouse", {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("parent_warehouse", function () {
|
||||
frm.set_query("parent_warehouse", function (doc) {
|
||||
return {
|
||||
filters: {
|
||||
is_group: 1,
|
||||
company: doc.company,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -96,14 +96,14 @@ def get_range_age(filters: Filters, fifo_queue: List, to_date: str, item_dict: D
|
||||
range1 = range2 = range3 = above_range3 = 0.0
|
||||
|
||||
for item in fifo_queue:
|
||||
age = date_diff(to_date, item[1])
|
||||
age = flt(date_diff(to_date, item[1]))
|
||||
qty = flt(item[0]) if not item_dict["has_serial_no"] else 1.0
|
||||
|
||||
if age <= filters.range1:
|
||||
if age <= flt(filters.range1):
|
||||
range1 = flt(range1 + qty, precision)
|
||||
elif age <= filters.range2:
|
||||
elif age <= flt(filters.range2):
|
||||
range2 = flt(range2 + qty, precision)
|
||||
elif age <= filters.range3:
|
||||
elif age <= flt(filters.range3):
|
||||
range3 = flt(range3 + qty, precision)
|
||||
else:
|
||||
above_range3 = flt(above_range3 + qty, precision)
|
||||
|
||||
@@ -502,6 +502,7 @@ class update_entries_after(object):
|
||||
def update_distinct_item_warehouses(self, dependant_sle):
|
||||
key = (dependant_sle.item_code, dependant_sle.warehouse)
|
||||
val = frappe._dict({"sle": dependant_sle})
|
||||
|
||||
if key not in self.distinct_item_warehouses:
|
||||
self.distinct_item_warehouses[key] = val
|
||||
self.new_items_found = True
|
||||
@@ -513,6 +514,9 @@ class update_entries_after(object):
|
||||
val.sle_changed = True
|
||||
self.distinct_item_warehouses[key] = val
|
||||
self.new_items_found = True
|
||||
elif self.distinct_item_warehouses[key].get("reposting_status"):
|
||||
self.distinct_item_warehouses[key] = val
|
||||
self.new_items_found = True
|
||||
|
||||
def process_sle(self, sle):
|
||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||
@@ -536,7 +540,7 @@ class update_entries_after(object):
|
||||
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
||||
and sle.voucher_detail_no
|
||||
and sle.actual_qty < 0
|
||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||
and is_internal_transfer(sle)
|
||||
):
|
||||
sle.outgoing_rate = get_incoming_rate_for_inter_company_transfer(sle)
|
||||
|
||||
@@ -648,7 +652,7 @@ class update_entries_after(object):
|
||||
elif (
|
||||
sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
|
||||
and sle.voucher_detail_no
|
||||
and frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_internal_supplier")
|
||||
and is_internal_transfer(sle)
|
||||
):
|
||||
rate = get_incoming_rate_for_inter_company_transfer(sle)
|
||||
else:
|
||||
@@ -1154,6 +1158,8 @@ def get_sle_by_voucher_detail_no(voucher_detail_no, excluded_sle=None):
|
||||
[
|
||||
"item_code",
|
||||
"warehouse",
|
||||
"actual_qty",
|
||||
"qty_after_transaction",
|
||||
"posting_date",
|
||||
"posting_time",
|
||||
"timestamp(posting_date, posting_time) as timestamp",
|
||||
@@ -1488,3 +1494,15 @@ def get_incoming_rate_for_inter_company_transfer(sle) -> float:
|
||||
)
|
||||
|
||||
return rate
|
||||
|
||||
|
||||
def is_internal_transfer(sle):
|
||||
data = frappe.get_cached_value(
|
||||
sle.voucher_type,
|
||||
sle.voucher_no,
|
||||
["is_internal_supplier", "represents_company", "company"],
|
||||
as_dict=True,
|
||||
)
|
||||
|
||||
if data.is_internal_supplier and data.represents_company == data.company:
|
||||
return True
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-07-27 11:16:45.596579",
|
||||
"modified": "2023-04-21 17:16:56.192560",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Support",
|
||||
"name": "Service Level Agreement",
|
||||
@@ -217,19 +217,12 @@
|
||||
"write": 1
|
||||
},
|
||||
{
|
||||
"create": 1,
|
||||
"delete": 1,
|
||||
"email": 1,
|
||||
"export": 1,
|
||||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "All",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
"role": "All"
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
|
||||
@@ -9896,3 +9896,11 @@ Total Equity,Eigenkapital,
|
||||
Warehouse wise Stock Value,Warenwert nach Lager,
|
||||
Discount Validity,Frist für den Rabatt,
|
||||
Discount Validity Based On,Frist für den Rabatt berechnet sich nach,
|
||||
Account Number Length,Kontonummer Länge,
|
||||
Temporary Against Account Number,Temporäre Gegenkontonummer,
|
||||
Change DATEV Settings,DATEV-Einstellungen ändern,
|
||||
Opening Against Account Number,Gegenkontonummer für Eröffnungsbuchungen,
|
||||
Will be used as against account for all normal ledger entries,Wird als Gegenkonto für alle normalen Buchungen verwendet,
|
||||
Will be used as against account for opening ledger entries,Wird als Gegenkonto für alle Eröffnungsbuchungen verwendet,
|
||||
Temporary Against Account Number must be {0} digits long,Temporäre Gegenkontonummer muss {0} Ziffern lang sein,
|
||||
Opening Against Account Number must be {0} digits long,Gegenkontonummer für Eröffnungsbuchungen muss {0} Ziffern lang sein,
|
||||
|
||||
|
Can't render this file because it is too large.
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user