mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-07 15:25:19 +00:00
@@ -341,7 +341,7 @@ def book_deferred_income_or_expense(doc, deferred_process, posting_date=None):
|
||||
"enable_deferred_revenue" if doc.doctype == "Sales Invoice" else "enable_deferred_expense"
|
||||
)
|
||||
|
||||
accounts_frozen_upto = frappe.get_cached_value("Accounts Settings", "None", "acc_frozen_upto")
|
||||
accounts_frozen_upto = frappe.db.get_single_value("Accounts Settings", "acc_frozen_upto")
|
||||
|
||||
def _book_deferred_revenue_or_expense(
|
||||
item,
|
||||
|
||||
@@ -79,8 +79,8 @@ frappe.ui.form.on('Account', {
|
||||
frm.add_custom_button(__('General Ledger'), function () {
|
||||
frappe.route_options = {
|
||||
"account": frm.doc.name,
|
||||
"from_date": frappe.sys_defaults.year_start_date,
|
||||
"to_date": frappe.sys_defaults.year_end_date,
|
||||
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
"company": frm.doc.company
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -194,8 +194,8 @@ frappe.treeview_settings["Account"] = {
|
||||
click: function(node, btn) {
|
||||
frappe.route_options = {
|
||||
"account": node.label,
|
||||
"from_date": frappe.sys_defaults.year_start_date,
|
||||
"to_date": frappe.sys_defaults.year_end_date,
|
||||
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
"company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
|
||||
};
|
||||
frappe.set_route("query-report", "General Ledger");
|
||||
|
||||
@@ -15,6 +15,17 @@ frappe.ui.form.on('Accounting Dimension', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.set_query("offsetting_account", "dimension_defaults", function(doc, cdt, cdn) {
|
||||
let d = locals[cdt][cdn];
|
||||
return {
|
||||
filters: {
|
||||
company: d.company,
|
||||
root_type: ["in", ["Asset", "Liability"]],
|
||||
is_group: 0
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!frm.is_new()) {
|
||||
frm.add_custom_button(__('Show {0}', [frm.doc.document_type]), function () {
|
||||
frappe.set_route("List", frm.doc.document_type);
|
||||
|
||||
@@ -39,6 +39,8 @@ class AccountingDimension(Document):
|
||||
if not self.is_new():
|
||||
self.validate_document_type_change()
|
||||
|
||||
self.validate_dimension_defaults()
|
||||
|
||||
def validate_document_type_change(self):
|
||||
doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
|
||||
if doctype_before_save != self.document_type:
|
||||
@@ -46,6 +48,14 @@ class AccountingDimension(Document):
|
||||
message += _("Please create a new Accounting Dimension if required.")
|
||||
frappe.throw(message)
|
||||
|
||||
def validate_dimension_defaults(self):
|
||||
companies = []
|
||||
for default in self.get("dimension_defaults"):
|
||||
if default.company not in companies:
|
||||
companies.append(default.company)
|
||||
else:
|
||||
frappe.throw(_("Company {0} is added more than once").format(frappe.bold(default.company)))
|
||||
|
||||
def after_insert(self):
|
||||
if frappe.flags.in_test:
|
||||
make_dimension_in_accounting_doctypes(doc=self)
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
"reference_document",
|
||||
"default_dimension",
|
||||
"mandatory_for_bs",
|
||||
"mandatory_for_pl"
|
||||
"mandatory_for_pl",
|
||||
"column_break_lqns",
|
||||
"automatically_post_balancing_accounting_entry",
|
||||
"offsetting_account"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
@@ -50,6 +53,23 @@
|
||||
"fieldtype": "Check",
|
||||
"in_list_view": 1,
|
||||
"label": "Mandatory For Profit and Loss Account"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"fieldname": "automatically_post_balancing_accounting_entry",
|
||||
"fieldtype": "Check",
|
||||
"label": "Automatically post balancing accounting entry"
|
||||
},
|
||||
{
|
||||
"fieldname": "offsetting_account",
|
||||
"fieldtype": "Link",
|
||||
"label": "Offsetting Account",
|
||||
"mandatory_depends_on": "eval: doc.automatically_post_balancing_accounting_entry",
|
||||
"options": "Account"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_lqns",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
"closing_settings_tab",
|
||||
"period_closing_settings_section",
|
||||
"acc_frozen_upto",
|
||||
"ignore_account_closing_balance",
|
||||
"column_break_25",
|
||||
"frozen_accounts_modifier",
|
||||
"tab_break_dpet",
|
||||
@@ -406,6 +407,13 @@
|
||||
"fieldname": "enable_fuzzy_matching",
|
||||
"fieldtype": "Check",
|
||||
"label": "Enable Fuzzy Matching"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "Financial reports will be generated using GL Entry doctypes (should be enabled if Period Closing Voucher is not posted for all years sequentially or missing) ",
|
||||
"fieldname": "ignore_account_closing_balance",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Account Closing Balance"
|
||||
}
|
||||
],
|
||||
"icon": "icon-cog",
|
||||
@@ -413,7 +421,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"issingle": 1,
|
||||
"links": [],
|
||||
"modified": "2023-06-15 16:35:45.123456",
|
||||
"modified": "2023-07-27 15:05:34.000264",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Accounts Settings",
|
||||
|
||||
@@ -14,21 +14,32 @@ from erpnext.stock.utils import check_pending_reposting
|
||||
|
||||
|
||||
class AccountsSettings(Document):
|
||||
def on_update(self):
|
||||
frappe.clear_cache()
|
||||
|
||||
def validate(self):
|
||||
frappe.db.set_default(
|
||||
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
|
||||
)
|
||||
old_doc = self.get_doc_before_save()
|
||||
clear_cache = False
|
||||
|
||||
frappe.db.set_default(
|
||||
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
|
||||
)
|
||||
if old_doc.add_taxes_from_item_tax_template != self.add_taxes_from_item_tax_template:
|
||||
frappe.db.set_default(
|
||||
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
|
||||
)
|
||||
clear_cache = True
|
||||
|
||||
if old_doc.enable_common_party_accounting != self.enable_common_party_accounting:
|
||||
frappe.db.set_default(
|
||||
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
|
||||
)
|
||||
clear_cache = True
|
||||
|
||||
self.validate_stale_days()
|
||||
self.enable_payment_schedule_in_print()
|
||||
self.validate_pending_reposts()
|
||||
|
||||
if old_doc.show_payment_schedule_in_print != self.show_payment_schedule_in_print:
|
||||
self.enable_payment_schedule_in_print()
|
||||
|
||||
if old_doc.acc_frozen_upto != self.acc_frozen_upto:
|
||||
self.validate_pending_reposts()
|
||||
|
||||
if clear_cache:
|
||||
frappe.clear_cache()
|
||||
|
||||
def validate_stale_days(self):
|
||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
||||
|
||||
@@ -141,7 +141,7 @@ def validate_loyalty_points(ref_doc, points_to_redeem):
|
||||
)
|
||||
|
||||
if points_to_redeem > loyalty_program_details.loyalty_points:
|
||||
frappe.throw(_("You don't have enought Loyalty Points to redeem"))
|
||||
frappe.throw(_("You don't have enough Loyalty Points to redeem"))
|
||||
|
||||
loyalty_amount = flt(points_to_redeem * loyalty_program_details.conversion_factor)
|
||||
|
||||
|
||||
@@ -903,12 +903,12 @@ frappe.ui.form.on('Payment Entry', {
|
||||
if(frm.doc.payment_type == "Receive"
|
||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
|
||||
&& frm.doc.total_allocated_amount < frm.doc.paid_amount + (total_deductions / frm.doc.source_exchange_rate)) {
|
||||
unallocated_amount = (frm.doc.base_received_amount + total_deductions + frm.doc.base_total_taxes_and_charges
|
||||
unallocated_amount = (frm.doc.base_received_amount + total_deductions + flt(frm.doc.base_total_taxes_and_charges)
|
||||
- frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
|
||||
} else if (frm.doc.payment_type == "Pay"
|
||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
|
||||
&& frm.doc.total_allocated_amount < frm.doc.received_amount + (total_deductions / frm.doc.target_exchange_rate)) {
|
||||
unallocated_amount = (frm.doc.base_paid_amount + frm.doc.base_total_taxes_and_charges - (total_deductions
|
||||
unallocated_amount = (frm.doc.base_paid_amount + flt(frm.doc.base_total_taxes_and_charges) - (total_deductions
|
||||
+ frm.doc.base_total_allocated_amount)) / frm.doc.target_exchange_rate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,12 +277,13 @@ class PaymentEntry(AccountsController):
|
||||
|
||||
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))
|
||||
|
||||
if d.payment_term and (
|
||||
(flt(d.allocated_amount)) > 0
|
||||
and flt(d.allocated_amount) > flt(latest.payment_term_outstanding)
|
||||
if (
|
||||
d.payment_term
|
||||
and (
|
||||
(flt(d.allocated_amount)) > 0
|
||||
and flt(d.allocated_amount) > flt(latest.payment_term_outstanding)
|
||||
)
|
||||
and self.term_based_allocation_enabled_for_reference(d.reference_doctype, d.reference_name)
|
||||
):
|
||||
frappe.throw(
|
||||
_(
|
||||
@@ -292,6 +293,9 @@ class PaymentEntry(AccountsController):
|
||||
)
|
||||
)
|
||||
|
||||
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 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
||||
frappe.throw(fail_message.format(d.idx))
|
||||
@@ -514,7 +518,7 @@ class PaymentEntry(AccountsController):
|
||||
_(
|
||||
"References {0} of type {1} had no outstanding amount left before submitting the Payment Entry. Now they have a negative outstanding amount."
|
||||
).format(
|
||||
frappe.bold(comma_and((d.reference_name for d in references))),
|
||||
frappe.bold(comma_and([d.reference_name for d in references])),
|
||||
_(reference_doctype),
|
||||
)
|
||||
+ "<br><br>"
|
||||
@@ -1738,7 +1742,7 @@ def get_orders_to_be_billed(
|
||||
{party_type} = %s
|
||||
and docstatus = 1
|
||||
and company = %s
|
||||
and ifnull(status, "") != "Closed"
|
||||
and status != "Closed"
|
||||
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
||||
and abs(100 - per_billed) > 0.01
|
||||
{condition}
|
||||
|
||||
@@ -1156,6 +1156,52 @@ class TestPaymentEntry(FrappeTestCase):
|
||||
si3.cancel()
|
||||
si3.delete()
|
||||
|
||||
@change_settings(
|
||||
"Accounts Settings",
|
||||
{
|
||||
"unlink_payment_on_cancellation_of_invoice": 1,
|
||||
"delete_linked_ledger_entries": 1,
|
||||
"allow_multi_currency_invoices_against_single_party_account": 1,
|
||||
},
|
||||
)
|
||||
def test_overallocation_validation_shouldnt_misfire(self):
|
||||
"""
|
||||
Overallocation validation shouldn't fire for Template without "Allocate Payment based on Payment Terms" enabled
|
||||
|
||||
"""
|
||||
customer = create_customer()
|
||||
create_payment_terms_template()
|
||||
|
||||
template = frappe.get_doc("Payment Terms Template", "Test Receivable Template")
|
||||
template.allocate_payment_based_on_payment_terms = 0
|
||||
template.save()
|
||||
|
||||
# Validate allocation on base/company currency
|
||||
si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
|
||||
si.payment_terms_template = "Test Receivable Template"
|
||||
si.save().submit()
|
||||
|
||||
si.reload()
|
||||
pe = get_payment_entry(si.doctype, si.name).save()
|
||||
# There will no term based allocation
|
||||
self.assertEqual(len(pe.references), 1)
|
||||
self.assertEqual(pe.references[0].payment_term, None)
|
||||
self.assertEqual(flt(pe.references[0].allocated_amount), flt(si.grand_total))
|
||||
pe.save()
|
||||
|
||||
# specify a term
|
||||
pe.references[0].payment_term = template.terms[0].payment_term
|
||||
# no validation error should be thrown
|
||||
pe.save()
|
||||
|
||||
pe.paid_amount = si.grand_total + 1
|
||||
pe.references[0].allocated_amount = si.grand_total + 1
|
||||
self.assertRaises(frappe.ValidationError, pe.save)
|
||||
|
||||
template = frappe.get_doc("Payment Terms Template", "Test Receivable Template")
|
||||
template.allocate_payment_based_on_payment_terms = 1
|
||||
template.save()
|
||||
|
||||
|
||||
def create_payment_entry(**args):
|
||||
payment_entry = frappe.new_doc("Payment Entry")
|
||||
|
||||
@@ -49,6 +49,24 @@ class TestPOSClosingEntry(unittest.TestCase):
|
||||
self.assertEqual(pcv_doc.total_quantity, 2)
|
||||
self.assertEqual(pcv_doc.net_total, 6700)
|
||||
|
||||
def test_pos_closing_without_item_code(self):
|
||||
"""
|
||||
Test if POS Closing Entry is created without item code
|
||||
"""
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
pos_inv = create_pos_invoice(
|
||||
rate=3500, do_not_submit=1, item_name="Test Item", without_item_code=1
|
||||
)
|
||||
pos_inv.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
|
||||
pos_inv.submit()
|
||||
|
||||
pcv_doc = make_closing_entry_from_opening(opening_entry)
|
||||
pcv_doc.submit()
|
||||
|
||||
self.assertTrue(pcv_doc.name)
|
||||
|
||||
def test_cancelling_of_pos_closing_entry(self):
|
||||
test_user, pos_profile = init_user_and_profile()
|
||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
||||
|
||||
@@ -986,19 +986,34 @@ def create_pos_invoice(**args):
|
||||
msg = f"Serial No {args.serial_no} not available for Item {args.item}"
|
||||
frappe.throw(_(msg))
|
||||
|
||||
pos_inv.append(
|
||||
"items",
|
||||
{
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty or 1,
|
||||
"rate": args.rate if args.get("rate") is not None else 100,
|
||||
"income_account": args.income_account or "Sales - _TC",
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
},
|
||||
)
|
||||
pos_invoice_item = {
|
||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||
"qty": args.qty or 1,
|
||||
"rate": args.rate if args.get("rate") is not None else 100,
|
||||
"income_account": args.income_account or "Sales - _TC",
|
||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||
"serial_and_batch_bundle": bundle_id,
|
||||
}
|
||||
# append in pos invoice items without item_code by checking flag without_item_code
|
||||
if args.without_item_code:
|
||||
pos_inv.append(
|
||||
"items",
|
||||
{
|
||||
**pos_invoice_item,
|
||||
"item_name": args.item_name or "_Test Item",
|
||||
"description": args.item_name or "_Test Item",
|
||||
},
|
||||
)
|
||||
|
||||
else:
|
||||
pos_inv.append(
|
||||
"items",
|
||||
{
|
||||
**pos_invoice_item,
|
||||
"item_code": args.item or args.item_code or "_Test Item",
|
||||
},
|
||||
)
|
||||
|
||||
if not args.do_not_save:
|
||||
pos_inv.insert()
|
||||
|
||||
@@ -101,12 +101,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
||||
cur_frm.add_custom_button(__('Return / Debit Note'),
|
||||
this.make_debit_note, __('Create'));
|
||||
}
|
||||
|
||||
if(!doc.auto_repeat) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __('Create'))
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
|
||||
|
||||
@@ -229,7 +229,7 @@ class PurchaseInvoice(BuyingController):
|
||||
)
|
||||
|
||||
if (
|
||||
cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate"))
|
||||
cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
|
||||
and not self.is_return
|
||||
and not self.is_internal_supplier
|
||||
):
|
||||
|
||||
@@ -1736,6 +1736,61 @@ class TestPurchaseInvoice(unittest.TestCase, StockTestMixin):
|
||||
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
|
||||
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
|
||||
|
||||
def test_offsetting_entries_for_accounting_dimensions(self):
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.report.trial_balance.test_trial_balance import (
|
||||
clear_dimension_defaults,
|
||||
create_accounting_dimension,
|
||||
disable_dimension,
|
||||
)
|
||||
|
||||
create_account(
|
||||
account_name="Offsetting",
|
||||
company="_Test Company",
|
||||
parent_account="Temporary Accounts - _TC",
|
||||
)
|
||||
|
||||
create_accounting_dimension(company="_Test Company", offsetting_account="Offsetting - _TC")
|
||||
|
||||
branch1 = frappe.new_doc("Branch")
|
||||
branch1.branch = "Location 1"
|
||||
branch1.insert(ignore_if_duplicate=True)
|
||||
branch2 = frappe.new_doc("Branch")
|
||||
branch2.branch = "Location 2"
|
||||
branch2.insert(ignore_if_duplicate=True)
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
company="_Test Company",
|
||||
customer="_Test Supplier",
|
||||
do_not_save=True,
|
||||
do_not_submit=True,
|
||||
rate=1000,
|
||||
price_list_rate=1000,
|
||||
qty=1,
|
||||
)
|
||||
pi.branch = branch1.branch
|
||||
pi.items[0].branch = branch2.branch
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
expected_gle = [
|
||||
["_Test Account Cost for Goods Sold - _TC", 1000, 0.0, nowdate(), branch2.branch],
|
||||
["Creditors - _TC", 0.0, 1000, nowdate(), branch1.branch],
|
||||
["Offsetting - _TC", 1000, 0.0, nowdate(), branch1.branch],
|
||||
["Offsetting - _TC", 0.0, 1000, nowdate(), branch2.branch],
|
||||
]
|
||||
|
||||
check_gl_entries(
|
||||
self,
|
||||
pi.name,
|
||||
expected_gle,
|
||||
nowdate(),
|
||||
voucher_type="Purchase Invoice",
|
||||
additional_columns=["branch"],
|
||||
)
|
||||
clear_dimension_defaults("Branch")
|
||||
disable_dimension()
|
||||
|
||||
|
||||
def set_advance_flag(company, flag, default_account):
|
||||
frappe.db.set_value(
|
||||
@@ -1748,9 +1803,16 @@ def set_advance_flag(company, flag, default_account):
|
||||
)
|
||||
|
||||
|
||||
def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="Purchase Invoice"):
|
||||
def check_gl_entries(
|
||||
doc,
|
||||
voucher_no,
|
||||
expected_gle,
|
||||
posting_date,
|
||||
voucher_type="Purchase Invoice",
|
||||
additional_columns=None,
|
||||
):
|
||||
gl = frappe.qb.DocType("GL Entry")
|
||||
q = (
|
||||
query = (
|
||||
frappe.qb.from_(gl)
|
||||
.select(gl.account, gl.debit, gl.credit, gl.posting_date)
|
||||
.where(
|
||||
@@ -1761,7 +1823,12 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="
|
||||
)
|
||||
.orderby(gl.posting_date, gl.account, gl.creation)
|
||||
)
|
||||
gl_entries = q.run(as_dict=True)
|
||||
|
||||
if additional_columns:
|
||||
for col in additional_columns:
|
||||
query = query.select(gl[col])
|
||||
|
||||
gl_entries = query.run(as_dict=True)
|
||||
|
||||
for i, gle in enumerate(gl_entries):
|
||||
doc.assertEqual(expected_gle[i][0], gle.account)
|
||||
@@ -1769,6 +1836,12 @@ def check_gl_entries(doc, voucher_no, expected_gle, posting_date, voucher_type="
|
||||
doc.assertEqual(expected_gle[i][2], gle.credit)
|
||||
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
||||
|
||||
if additional_columns:
|
||||
j = 4
|
||||
for col in additional_columns:
|
||||
doc.assertEqual(expected_gle[i][j], gle[col])
|
||||
j += 1
|
||||
|
||||
|
||||
def create_tax_witholding_category(category_name, company, account):
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
@@ -443,7 +443,8 @@
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_br_wh",
|
||||
@@ -890,7 +891,8 @@
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:parent.update_stock == 1",
|
||||
@@ -905,7 +907,7 @@
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-07-04 17:22:21.501152",
|
||||
"modified": "2023-07-26 12:54:53.178156",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Purchase Invoice Item",
|
||||
|
||||
@@ -163,12 +163,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
||||
cur_frm.cscript.make_maintenance_schedule();
|
||||
}, __('Create'));
|
||||
}
|
||||
|
||||
if(!doc.auto_repeat) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __('Create'))
|
||||
}
|
||||
}
|
||||
|
||||
// Show buttons only when pos view is active
|
||||
@@ -776,7 +770,6 @@ frappe.ui.form.on('Sales Invoice', {
|
||||
|
||||
update_stock: function(frm, dt, dn) {
|
||||
frm.events.hide_fields(frm);
|
||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock);
|
||||
frm.trigger('reset_posting_time');
|
||||
},
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ from erpnext.assets.doctype.asset.depreciation import (
|
||||
reset_depreciation_schedule,
|
||||
reverse_depreciation_entry_made_after_disposal,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.controllers.accounts_controller import validate_account_head
|
||||
from erpnext.controllers.selling_controller import SellingController
|
||||
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
||||
@@ -113,7 +114,6 @@ class SalesInvoice(SellingController):
|
||||
|
||||
if cint(self.update_stock):
|
||||
self.validate_dropship_item()
|
||||
self.validate_item_code()
|
||||
self.validate_warehouse()
|
||||
self.update_current_stock()
|
||||
self.validate_delivery_note()
|
||||
@@ -854,11 +854,6 @@ class SalesInvoice(SellingController):
|
||||
):
|
||||
frappe.throw(_("Paid amount + Write Off Amount can not be greater than Grand Total"))
|
||||
|
||||
def validate_item_code(self):
|
||||
for d in self.get("items"):
|
||||
if not d.item_code and self.is_opening == "No":
|
||||
msgprint(_("Item Code required at Row No {0}").format(d.idx), raise_exception=True)
|
||||
|
||||
def validate_warehouse(self):
|
||||
super(SalesInvoice, self).validate_warehouse()
|
||||
|
||||
@@ -1182,12 +1177,13 @@ class SalesInvoice(SellingController):
|
||||
self.get("posting_date"),
|
||||
)
|
||||
asset.db_set("disposal_date", None)
|
||||
add_asset_activity(asset.name, _("Asset returned"))
|
||||
|
||||
if asset.calculate_depreciation:
|
||||
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
||||
notes = _(
|
||||
"This schedule was created when Asset {0} was returned after being sold through Sales Invoice {1}."
|
||||
"This schedule was created when Asset {0} was returned through Sales Invoice {1}."
|
||||
).format(
|
||||
get_link_to_form(asset.doctype, asset.name),
|
||||
get_link_to_form(self.doctype, self.get("name")),
|
||||
@@ -1215,6 +1211,7 @@ class SalesInvoice(SellingController):
|
||||
self.get("posting_date"),
|
||||
)
|
||||
asset.db_set("disposal_date", self.posting_date)
|
||||
add_asset_activity(asset.name, _("Asset sold"))
|
||||
|
||||
for gle in fixed_asset_gl_entries:
|
||||
gle["against"] = self.customer
|
||||
@@ -1839,7 +1836,7 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc
|
||||
doc = frappe.get_doc(ref_doc, inter_company_reference)
|
||||
ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer
|
||||
if not frappe.db.get_value(partytype, {"represents_company": doc.company}, "name") == party:
|
||||
frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(partytype))
|
||||
frappe.throw(_("Invalid {0} for Inter Company Transaction.").format(_(partytype)))
|
||||
if not frappe.get_cached_value(ref_partytype, ref_party, "represents_company") == company:
|
||||
frappe.throw(_("Invalid Company for Inter Company Transaction."))
|
||||
|
||||
@@ -1853,7 +1850,7 @@ def validate_inter_company_party(doctype, party, company, inter_company_referenc
|
||||
if not company in companies:
|
||||
frappe.throw(
|
||||
_("{0} not allowed to transact with {1}. Please change the Company.").format(
|
||||
partytype, company
|
||||
_(partytype), company
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -604,7 +604,8 @@
|
||||
"hidden": 1,
|
||||
"label": "Batch No",
|
||||
"options": "Batch",
|
||||
"read_only": 1
|
||||
"read_only": 1,
|
||||
"search_index": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "col_break5",
|
||||
@@ -894,13 +895,14 @@
|
||||
"label": "Serial and Batch Bundle",
|
||||
"no_copy": 1,
|
||||
"options": "Serial and Batch Bundle",
|
||||
"print_hide": 1
|
||||
"print_hide": 1,
|
||||
"search_index": 1
|
||||
}
|
||||
],
|
||||
"idx": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-12 13:42:24.303113",
|
||||
"modified": "2023-07-26 12:53:22.404057",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Accounts",
|
||||
"name": "Sales Invoice Item",
|
||||
|
||||
@@ -28,6 +28,7 @@ def make_gl_entries(
|
||||
):
|
||||
if gl_map:
|
||||
if not cancel:
|
||||
make_acc_dimensions_offsetting_entry(gl_map)
|
||||
validate_accounting_period(gl_map)
|
||||
validate_disabled_accounts(gl_map)
|
||||
gl_map = process_gl_map(gl_map, merge_entries)
|
||||
@@ -51,6 +52,63 @@ def make_gl_entries(
|
||||
make_reverse_gl_entries(gl_map, adv_adj=adv_adj, update_outstanding=update_outstanding)
|
||||
|
||||
|
||||
def make_acc_dimensions_offsetting_entry(gl_map):
|
||||
accounting_dimensions_to_offset = get_accounting_dimensions_for_offsetting_entry(
|
||||
gl_map, gl_map[0].company
|
||||
)
|
||||
no_of_dimensions = len(accounting_dimensions_to_offset)
|
||||
if no_of_dimensions == 0:
|
||||
return
|
||||
|
||||
offsetting_entries = []
|
||||
|
||||
for gle in gl_map:
|
||||
for dimension in accounting_dimensions_to_offset:
|
||||
offsetting_entry = gle.copy()
|
||||
debit = flt(gle.credit) / no_of_dimensions if gle.credit != 0 else 0
|
||||
credit = flt(gle.debit) / no_of_dimensions if gle.debit != 0 else 0
|
||||
offsetting_entry.update(
|
||||
{
|
||||
"account": dimension.offsetting_account,
|
||||
"debit": debit,
|
||||
"credit": credit,
|
||||
"debit_in_account_currency": debit,
|
||||
"credit_in_account_currency": credit,
|
||||
"remarks": _("Offsetting for Accounting Dimension") + " - {0}".format(dimension.name),
|
||||
"against_voucher": None,
|
||||
}
|
||||
)
|
||||
offsetting_entry["against_voucher_type"] = None
|
||||
offsetting_entries.append(offsetting_entry)
|
||||
|
||||
gl_map += offsetting_entries
|
||||
|
||||
|
||||
def get_accounting_dimensions_for_offsetting_entry(gl_map, company):
|
||||
acc_dimension = frappe.qb.DocType("Accounting Dimension")
|
||||
dimension_detail = frappe.qb.DocType("Accounting Dimension Detail")
|
||||
|
||||
acc_dimensions = (
|
||||
frappe.qb.from_(acc_dimension)
|
||||
.inner_join(dimension_detail)
|
||||
.on(acc_dimension.name == dimension_detail.parent)
|
||||
.select(acc_dimension.fieldname, acc_dimension.name, dimension_detail.offsetting_account)
|
||||
.where(
|
||||
(acc_dimension.disabled == 0)
|
||||
& (dimension_detail.company == company)
|
||||
& (dimension_detail.automatically_post_balancing_accounting_entry == 1)
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
accounting_dimensions_to_offset = []
|
||||
for acc_dimension in acc_dimensions:
|
||||
values = set([entry.get(acc_dimension.fieldname) for entry in gl_map])
|
||||
if len(values) > 1:
|
||||
accounting_dimensions_to_offset.append(acc_dimension)
|
||||
|
||||
return accounting_dimensions_to_offset
|
||||
|
||||
|
||||
def validate_disabled_accounts(gl_map):
|
||||
accounts = [d.account for d in gl_map if d.account]
|
||||
|
||||
@@ -105,7 +163,8 @@ def process_gl_map(gl_map, merge_entries=True, precision=None):
|
||||
if not gl_map:
|
||||
return []
|
||||
|
||||
gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
|
||||
if gl_map[0].voucher_type != "Period Closing Voucher":
|
||||
gl_map = distribute_gl_based_on_cost_center_allocation(gl_map, precision)
|
||||
|
||||
if merge_entries:
|
||||
gl_map = merge_similar_entries(gl_map, precision)
|
||||
|
||||
@@ -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",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2023-07-26 21:05:33.554778",
|
||||
"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"
|
||||
|
||||
@@ -15,14 +15,14 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname":"to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 2,
|
||||
"is_standard": "Yes",
|
||||
"letterhead": null,
|
||||
"modified": "2023-07-26 21:04:54.751077",
|
||||
"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"
|
||||
|
||||
@@ -7,7 +7,7 @@ frappe.query_reports["Bank Clearance Summary"] = {
|
||||
"fieldname":"from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
"width": "80"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -8,7 +8,7 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
label: __("From Fiscal Year"),
|
||||
fieldtype: "Link",
|
||||
options: "Fiscal Year",
|
||||
default: frappe.sys_defaults.fiscal_year,
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
@@ -16,7 +16,7 @@ frappe.query_reports["Budget Variance Report"] = {
|
||||
label: __("To Fiscal Year"),
|
||||
fieldtype: "Link",
|
||||
options: "Fiscal Year",
|
||||
default: frappe.sys_defaults.fiscal_year,
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@ from collections import defaultdict
|
||||
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.query_builder import Criterion
|
||||
from frappe.utils import flt, getdate
|
||||
|
||||
import erpnext
|
||||
@@ -359,6 +360,7 @@ def get_data(
|
||||
accounts_by_name,
|
||||
accounts,
|
||||
ignore_closing_entries=False,
|
||||
root_type=root_type,
|
||||
)
|
||||
|
||||
calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year)
|
||||
@@ -603,6 +605,7 @@ def set_gl_entries_by_account(
|
||||
accounts_by_name,
|
||||
accounts,
|
||||
ignore_closing_entries=False,
|
||||
root_type=None,
|
||||
):
|
||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||
|
||||
@@ -610,7 +613,6 @@ def set_gl_entries_by_account(
|
||||
"Company", filters.get("company"), ["lft", "rgt"]
|
||||
)
|
||||
|
||||
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
|
||||
companies = frappe.db.sql(
|
||||
""" select name, default_currency from `tabCompany`
|
||||
where lft >= %(company_lft)s and rgt <= %(company_rgt)s""",
|
||||
@@ -626,27 +628,42 @@ def set_gl_entries_by_account(
|
||||
)
|
||||
|
||||
for d in companies:
|
||||
gl_entries = frappe.db.sql(
|
||||
"""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company,
|
||||
gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency,
|
||||
acc.account_name, acc.account_number
|
||||
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0
|
||||
{additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s
|
||||
order by gl.account, gl.posting_date""".format(
|
||||
additional_conditions=additional_conditions
|
||||
),
|
||||
{
|
||||
"from_date": from_date,
|
||||
"to_date": to_date,
|
||||
"lft": root_lft,
|
||||
"rgt": root_rgt,
|
||||
"company": d.name,
|
||||
"finance_book": filters.get("finance_book"),
|
||||
"company_fb": frappe.get_cached_value("Company", d.name, "default_finance_book"),
|
||||
},
|
||||
as_dict=True,
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
account = frappe.qb.DocType("Account")
|
||||
query = (
|
||||
frappe.qb.from_(gle)
|
||||
.inner_join(account)
|
||||
.on(account.name == gle.account)
|
||||
.select(
|
||||
gle.posting_date,
|
||||
gle.account,
|
||||
gle.debit,
|
||||
gle.credit,
|
||||
gle.is_opening,
|
||||
gle.company,
|
||||
gle.fiscal_year,
|
||||
gle.debit_in_account_currency,
|
||||
gle.credit_in_account_currency,
|
||||
gle.account_currency,
|
||||
account.account_name,
|
||||
account.account_number,
|
||||
)
|
||||
.where(
|
||||
(gle.company == d.name)
|
||||
& (gle.is_cancelled == 0)
|
||||
& (gle.posting_date <= to_date)
|
||||
& (account.lft >= root_lft)
|
||||
& (account.rgt <= root_rgt)
|
||||
& (account.root_type <= root_type)
|
||||
)
|
||||
.orderby(gle.account, gle.posting_date)
|
||||
)
|
||||
|
||||
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters, d)
|
||||
if additional_conditions:
|
||||
query = query.where(Criterion.all(additional_conditions))
|
||||
gl_entries = query.run(as_dict=True)
|
||||
|
||||
if filters and filters.get("presentation_currency") != d.default_currency:
|
||||
currency_info["company"] = d.name
|
||||
currency_info["company_currency"] = d.default_currency
|
||||
@@ -716,23 +733,25 @@ def validate_entries(key, entry, accounts_by_name, accounts):
|
||||
accounts.insert(idx + 1, args)
|
||||
|
||||
|
||||
def get_additional_conditions(from_date, ignore_closing_entries, filters):
|
||||
def get_additional_conditions(from_date, ignore_closing_entries, filters, d):
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
additional_conditions = []
|
||||
|
||||
if ignore_closing_entries:
|
||||
additional_conditions.append("ifnull(gl.voucher_type, '')!='Period Closing Voucher'")
|
||||
additional_conditions.append((gle.voucher_type != "Period Closing Voucher"))
|
||||
|
||||
if from_date:
|
||||
additional_conditions.append("gl.posting_date >= %(from_date)s")
|
||||
additional_conditions.append(gle.posting_date >= from_date)
|
||||
|
||||
finance_book = filters.get("finance_book")
|
||||
company_fb = frappe.get_cached_value("Company", d.name, "default_finance_book")
|
||||
|
||||
if filters.get("include_default_book_entries"):
|
||||
additional_conditions.append(
|
||||
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
||||
)
|
||||
additional_conditions.append((gle.finance_book.isin([finance_book, company_fb, "", None])))
|
||||
else:
|
||||
additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
|
||||
additional_conditions.append((gle.finance_book.isin([finance_book, "", None])))
|
||||
|
||||
return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
|
||||
return additional_conditions
|
||||
|
||||
|
||||
def add_total_row(out, root_type, balance_must_be, companies, company_currency):
|
||||
|
||||
@@ -2,6 +2,7 @@ import unittest
|
||||
|
||||
import frappe
|
||||
from frappe import qb
|
||||
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||
from frappe.utils import nowdate
|
||||
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
@@ -10,16 +11,15 @@ from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sal
|
||||
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
||||
Deferred_Revenue_and_Expense_Report,
|
||||
)
|
||||
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
|
||||
class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
clear_accounts_and_items()
|
||||
create_company()
|
||||
self.maxDiff = None
|
||||
|
||||
def clear_old_entries(self):
|
||||
@@ -51,55 +51,58 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
if deferred_invoices:
|
||||
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
||||
|
||||
def test_deferred_revenue(self):
|
||||
self.clear_old_entries()
|
||||
def setup_deferred_accounts_and_items(self):
|
||||
# created deferred expense accounts, if not found
|
||||
self.deferred_revenue_account = create_account(
|
||||
account_name="Deferred Revenue",
|
||||
parent_account="Current Liabilities - " + self.company_abbr,
|
||||
company=self.company,
|
||||
)
|
||||
|
||||
# created deferred expense accounts, if not found
|
||||
deferred_revenue_account = create_account(
|
||||
account_name="Deferred Revenue",
|
||||
parent_account="Current Liabilities - _CD",
|
||||
company="_Test Company DR",
|
||||
self.deferred_expense_account = create_account(
|
||||
account_name="Deferred Expense",
|
||||
parent_account="Current Assets - " + self.company_abbr,
|
||||
company=self.company,
|
||||
)
|
||||
|
||||
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||
acc_settings.book_deferred_entries_based_on = "Months"
|
||||
acc_settings.save()
|
||||
def setUp(self):
|
||||
self.create_company()
|
||||
self.create_customer("_Test Customer")
|
||||
self.create_supplier("_Test Furniture Supplier")
|
||||
self.setup_deferred_accounts_and_items()
|
||||
self.clear_old_entries()
|
||||
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = "_Test Customer DR"
|
||||
customer.type = "Individual"
|
||||
customer.insert()
|
||||
def tearDown(self):
|
||||
frappe.db.rollback()
|
||||
|
||||
item = create_item(
|
||||
"_Test Internet Subscription",
|
||||
is_stock_item=0,
|
||||
warehouse="All Warehouses - _CD",
|
||||
company="_Test Company DR",
|
||||
)
|
||||
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||
def test_deferred_revenue(self):
|
||||
self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company)
|
||||
item = frappe.get_doc("Item", self.item)
|
||||
item.enable_deferred_revenue = 1
|
||||
item.deferred_revenue_account = deferred_revenue_account
|
||||
item.deferred_revenue_account = self.deferred_revenue_account
|
||||
item.no_of_months = 3
|
||||
item.save()
|
||||
|
||||
si = create_sales_invoice(
|
||||
item=item.name,
|
||||
company="_Test Company DR",
|
||||
customer="_Test Customer DR",
|
||||
debit_to="Debtors - _CD",
|
||||
item=self.item,
|
||||
company=self.company,
|
||||
customer=self.customer,
|
||||
debit_to=self.debit_to,
|
||||
posting_date="2021-05-01",
|
||||
parent_cost_center="Main - _CD",
|
||||
cost_center="Main - _CD",
|
||||
parent_cost_center=self.cost_center,
|
||||
cost_center=self.cost_center,
|
||||
do_not_save=True,
|
||||
rate=300,
|
||||
price_list_rate=300,
|
||||
)
|
||||
|
||||
si.items[0].income_account = "Sales - _CD"
|
||||
si.items[0].income_account = self.income_account
|
||||
si.items[0].enable_deferred_revenue = 1
|
||||
si.items[0].service_start_date = "2021-05-01"
|
||||
si.items[0].service_end_date = "2021-08-01"
|
||||
si.items[0].deferred_revenue_account = deferred_revenue_account
|
||||
si.items[0].income_account = "Sales - _CD"
|
||||
si.items[0].deferred_revenue_account = self.deferred_revenue_account
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
@@ -110,7 +113,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
start_date="2021-05-01",
|
||||
end_date="2021-08-01",
|
||||
type="Income",
|
||||
company="_Test Company DR",
|
||||
company=self.company,
|
||||
)
|
||||
)
|
||||
pda.insert()
|
||||
@@ -120,7 +123,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||
self.filters = frappe._dict(
|
||||
{
|
||||
"company": frappe.defaults.get_user_default("Company"),
|
||||
"company": self.company,
|
||||
"filter_based_on": "Date Range",
|
||||
"period_start_date": "2021-05-01",
|
||||
"period_end_date": "2021-08-01",
|
||||
@@ -142,57 +145,36 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
]
|
||||
self.assertEqual(report.period_total, expected)
|
||||
|
||||
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||
def test_deferred_expense(self):
|
||||
self.clear_old_entries()
|
||||
|
||||
# created deferred expense accounts, if not found
|
||||
deferred_expense_account = create_account(
|
||||
account_name="Deferred Expense",
|
||||
parent_account="Current Assets - _CD",
|
||||
company="_Test Company DR",
|
||||
)
|
||||
|
||||
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||
acc_settings.book_deferred_entries_based_on = "Months"
|
||||
acc_settings.save()
|
||||
|
||||
supplier = create_supplier(
|
||||
supplier_name="_Test Furniture Supplier", supplier_group="Local", supplier_type="Company"
|
||||
)
|
||||
supplier.save()
|
||||
|
||||
item = create_item(
|
||||
"_Test Office Desk",
|
||||
is_stock_item=0,
|
||||
warehouse="All Warehouses - _CD",
|
||||
company="_Test Company DR",
|
||||
)
|
||||
self.create_item("_Test Office Desk", 0, self.warehouse, self.company)
|
||||
item = frappe.get_doc("Item", self.item)
|
||||
item.enable_deferred_expense = 1
|
||||
item.deferred_expense_account = deferred_expense_account
|
||||
item.deferred_expense_account = self.deferred_expense_account
|
||||
item.no_of_months_exp = 3
|
||||
item.save()
|
||||
|
||||
pi = make_purchase_invoice(
|
||||
item=item.name,
|
||||
company="_Test Company DR",
|
||||
supplier="_Test Furniture Supplier",
|
||||
item=self.item,
|
||||
company=self.company,
|
||||
supplier=self.supplier,
|
||||
is_return=False,
|
||||
update_stock=False,
|
||||
posting_date=frappe.utils.datetime.date(2021, 5, 1),
|
||||
parent_cost_center="Main - _CD",
|
||||
cost_center="Main - _CD",
|
||||
parent_cost_center=self.cost_center,
|
||||
cost_center=self.cost_center,
|
||||
do_not_save=True,
|
||||
rate=300,
|
||||
price_list_rate=300,
|
||||
warehouse="All Warehouses - _CD",
|
||||
warehouse=self.warehouse,
|
||||
qty=1,
|
||||
)
|
||||
pi.set_posting_time = True
|
||||
pi.items[0].enable_deferred_expense = 1
|
||||
pi.items[0].service_start_date = "2021-05-01"
|
||||
pi.items[0].service_end_date = "2021-08-01"
|
||||
pi.items[0].deferred_expense_account = deferred_expense_account
|
||||
pi.items[0].expense_account = "Office Maintenance Expenses - _CD"
|
||||
pi.items[0].deferred_expense_account = self.deferred_expense_account
|
||||
pi.items[0].expense_account = self.expense_account
|
||||
pi.save()
|
||||
pi.submit()
|
||||
|
||||
@@ -203,7 +185,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
start_date="2021-05-01",
|
||||
end_date="2021-08-01",
|
||||
type="Expense",
|
||||
company="_Test Company DR",
|
||||
company=self.company,
|
||||
)
|
||||
)
|
||||
pda.insert()
|
||||
@@ -213,7 +195,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||
self.filters = frappe._dict(
|
||||
{
|
||||
"company": frappe.defaults.get_user_default("Company"),
|
||||
"company": self.company,
|
||||
"filter_based_on": "Date Range",
|
||||
"period_start_date": "2021-05-01",
|
||||
"period_end_date": "2021-08-01",
|
||||
@@ -235,52 +217,31 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
]
|
||||
self.assertEqual(report.period_total, expected)
|
||||
|
||||
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||
def test_zero_months(self):
|
||||
self.clear_old_entries()
|
||||
# created deferred expense accounts, if not found
|
||||
deferred_revenue_account = create_account(
|
||||
account_name="Deferred Revenue",
|
||||
parent_account="Current Liabilities - _CD",
|
||||
company="_Test Company DR",
|
||||
)
|
||||
|
||||
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
||||
acc_settings.book_deferred_entries_based_on = "Months"
|
||||
acc_settings.save()
|
||||
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = "_Test Customer DR"
|
||||
customer.type = "Individual"
|
||||
customer.insert()
|
||||
|
||||
item = create_item(
|
||||
"_Test Internet Subscription",
|
||||
is_stock_item=0,
|
||||
warehouse="All Warehouses - _CD",
|
||||
company="_Test Company DR",
|
||||
)
|
||||
self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company)
|
||||
item = frappe.get_doc("Item", self.item)
|
||||
item.enable_deferred_revenue = 1
|
||||
item.deferred_revenue_account = deferred_revenue_account
|
||||
item.deferred_revenue_account = self.deferred_revenue_account
|
||||
item.no_of_months = 0
|
||||
item.save()
|
||||
|
||||
si = create_sales_invoice(
|
||||
item=item.name,
|
||||
company="_Test Company DR",
|
||||
customer="_Test Customer DR",
|
||||
debit_to="Debtors - _CD",
|
||||
company=self.company,
|
||||
customer=self.customer,
|
||||
debit_to=self.debit_to,
|
||||
posting_date="2021-05-01",
|
||||
parent_cost_center="Main - _CD",
|
||||
cost_center="Main - _CD",
|
||||
parent_cost_center=self.cost_center,
|
||||
cost_center=self.cost_center,
|
||||
do_not_save=True,
|
||||
rate=300,
|
||||
price_list_rate=300,
|
||||
)
|
||||
|
||||
si.items[0].enable_deferred_revenue = 1
|
||||
si.items[0].income_account = "Sales - _CD"
|
||||
si.items[0].deferred_revenue_account = deferred_revenue_account
|
||||
si.items[0].income_account = "Sales - _CD"
|
||||
si.items[0].income_account = self.income_account
|
||||
si.items[0].deferred_revenue_account = self.deferred_revenue_account
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
@@ -291,7 +252,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
start_date="2021-05-01",
|
||||
end_date="2021-08-01",
|
||||
type="Income",
|
||||
company="_Test Company DR",
|
||||
company=self.company,
|
||||
)
|
||||
)
|
||||
pda.insert()
|
||||
@@ -301,7 +262,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||
self.filters = frappe._dict(
|
||||
{
|
||||
"company": frappe.defaults.get_user_default("Company"),
|
||||
"company": self.company,
|
||||
"filter_based_on": "Date Range",
|
||||
"period_start_date": "2021-05-01",
|
||||
"period_end_date": "2021-08-01",
|
||||
@@ -322,30 +283,3 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
||||
{"key": "aug_2021", "total": 0, "actual": 0},
|
||||
]
|
||||
self.assertEqual(report.period_total, expected)
|
||||
|
||||
|
||||
def create_company():
|
||||
company = frappe.db.exists("Company", "_Test Company DR")
|
||||
if not company:
|
||||
company = frappe.new_doc("Company")
|
||||
company.company_name = "_Test Company DR"
|
||||
company.default_currency = "INR"
|
||||
company.chart_of_accounts = "Standard"
|
||||
company.insert()
|
||||
|
||||
|
||||
def clear_accounts_and_items():
|
||||
item = qb.DocType("Item")
|
||||
account = qb.DocType("Account")
|
||||
customer = qb.DocType("Customer")
|
||||
supplier = qb.DocType("Supplier")
|
||||
|
||||
qb.from_(account).delete().where(
|
||||
(account.account_name == "Deferred Revenue")
|
||||
| (account.account_name == "Deferred Expense") & (account.company == "_Test Company DR")
|
||||
).run()
|
||||
qb.from_(item).delete().where(
|
||||
(item.item_code == "_Test Internet Subscription") | (item.item_code == "_Test Office Rent")
|
||||
).run()
|
||||
qb.from_(customer).delete().where(customer.customer_name == "_Test Customer DR").run()
|
||||
qb.from_(supplier).delete().where(supplier.supplier_name == "_Test Furniture Supplier").run()
|
||||
|
||||
@@ -38,14 +38,14 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -188,6 +188,7 @@ def get_data(
|
||||
filters,
|
||||
gl_entries_by_account,
|
||||
ignore_closing_entries=ignore_closing_entries,
|
||||
root_type=root_type,
|
||||
)
|
||||
|
||||
calculate_values(
|
||||
@@ -417,23 +418,44 @@ def set_gl_entries_by_account(
|
||||
gl_entries_by_account,
|
||||
ignore_closing_entries=False,
|
||||
ignore_opening_entries=False,
|
||||
root_type=None,
|
||||
):
|
||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||
gl_entries = []
|
||||
|
||||
account_filters = {
|
||||
"company": company,
|
||||
"is_group": 0,
|
||||
"lft": (">=", root_lft),
|
||||
"rgt": ("<=", root_rgt),
|
||||
}
|
||||
|
||||
if root_type:
|
||||
account_filters.update(
|
||||
{
|
||||
"root_type": root_type,
|
||||
}
|
||||
)
|
||||
|
||||
accounts_list = frappe.db.get_all(
|
||||
"Account",
|
||||
filters={"company": company, "is_group": 0, "lft": (">=", root_lft), "rgt": ("<=", root_rgt)},
|
||||
filters=account_filters,
|
||||
pluck="name",
|
||||
)
|
||||
|
||||
if accounts_list:
|
||||
# For balance sheet
|
||||
if not from_date:
|
||||
from_date = filters["period_start_date"]
|
||||
ignore_closing_balances = frappe.db.get_single_value(
|
||||
"Accounts Settings", "ignore_account_closing_balance"
|
||||
)
|
||||
if not from_date and not ignore_closing_balances:
|
||||
last_period_closing_voucher = frappe.db.get_all(
|
||||
"Period Closing Voucher",
|
||||
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", from_date)},
|
||||
filters={
|
||||
"docstatus": 1,
|
||||
"company": filters.company,
|
||||
"posting_date": ("<", filters["period_start_date"]),
|
||||
},
|
||||
fields=["posting_date", "name"],
|
||||
order_by="posting_date desc",
|
||||
limit=1,
|
||||
|
||||
@@ -15,14 +15,14 @@ frappe.query_reports["Gross Profit"] = {
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
"reqd": 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -309,7 +309,8 @@ def get_conditions(filters):
|
||||
|
||||
def get_items(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
|
||||
@@ -381,7 +381,8 @@ def get_group_by_conditions(filters, doctype):
|
||||
|
||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||
conditions = get_conditions(filters, additional_conditions)
|
||||
|
||||
if additional_query_columns:
|
||||
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
|
||||
@@ -15,7 +15,7 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname:"to_date",
|
||||
|
||||
@@ -50,20 +50,20 @@ def get_pos_entries(filters, group_by_field):
|
||||
order_by = "p.posting_date"
|
||||
select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", ""
|
||||
if group_by_field == "mode_of_payment":
|
||||
select_mop_field = ", sip.mode_of_payment"
|
||||
select_mop_field = ", sip.mode_of_payment, sip.base_amount - IF(sip.type='Cash', p.change_amount, 0) as paid_amount"
|
||||
from_sales_invoice_payment = ", `tabSales Invoice Payment` sip"
|
||||
group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount, 0) != 0 AND"
|
||||
group_by_mop_condition = "sip.parent = p.name AND ifnull(sip.base_amount - IF(sip.type='Cash', p.change_amount, 0), 0) != 0 AND"
|
||||
order_by += ", sip.mode_of_payment"
|
||||
|
||||
elif group_by_field:
|
||||
order_by += ", p.{}".format(group_by_field)
|
||||
select_mop_field = ", p.base_paid_amount - p.change_amount as paid_amount "
|
||||
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
SELECT
|
||||
p.posting_date, p.name as pos_invoice, p.pos_profile,
|
||||
p.owner, p.base_grand_total as grand_total, p.base_paid_amount - p.change_amount as paid_amount,
|
||||
p.customer, p.is_return {select_mop_field}
|
||||
p.owner, p.customer, p.is_return, p.base_grand_total as grand_total {select_mop_field}
|
||||
FROM
|
||||
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||
WHERE
|
||||
|
||||
@@ -66,13 +66,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
"fieldname": "show_zero_values",
|
||||
|
||||
@@ -208,7 +208,7 @@ def set_gl_entries_by_account(
|
||||
additional_conditions = []
|
||||
|
||||
if ignore_closing_entries:
|
||||
additional_conditions.append("and ifnull(voucher_type, '')!='Period Closing Voucher'")
|
||||
additional_conditions.append("and voucher_type !='Period Closing Voucher'")
|
||||
|
||||
if from_date:
|
||||
additional_conditions.append("and posting_date >= %(from_date)s")
|
||||
|
||||
@@ -52,6 +52,12 @@ frappe.query_reports["Purchase Register"] = {
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_payments",
|
||||
"label": __("Show Ledger View"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -4,13 +4,22 @@
|
||||
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.utils import flt
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, getdate
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.report.utils import (
|
||||
get_advance_taxes_and_charges,
|
||||
get_conditions,
|
||||
get_journal_entries,
|
||||
get_opening_row,
|
||||
get_party_details,
|
||||
get_payment_entries,
|
||||
get_query_columns,
|
||||
get_taxes_query,
|
||||
get_values_for_columns,
|
||||
)
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -21,9 +30,15 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
include_payments = filters.get("include_payments")
|
||||
if filters.get("include_payments") and not filters.get("supplier"):
|
||||
frappe.throw(_("Please select a supplier for fetching payments."))
|
||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||
if filters.get("include_payments"):
|
||||
invoice_list += get_payments(filters)
|
||||
|
||||
columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||
invoice_list, additional_table_columns
|
||||
invoice_list, additional_table_columns, include_payments
|
||||
)
|
||||
|
||||
if not invoice_list:
|
||||
@@ -33,14 +48,28 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(
|
||||
invoice_list, invoice_expense_map, expense_accounts
|
||||
invoice_list, invoice_expense_map, expense_accounts, include_payments
|
||||
)
|
||||
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||
suppliers = list(set(d.supplier for d in invoice_list))
|
||||
supplier_details = get_supplier_details(suppliers)
|
||||
supplier_details = get_party_details("Supplier", suppliers)
|
||||
|
||||
company_currency = frappe.get_cached_value("Company", filters.company, "default_currency")
|
||||
|
||||
res = []
|
||||
if include_payments:
|
||||
opening_row = get_opening_row(
|
||||
"Supplier", filters.supplier, getdate(filters.from_date), filters.company
|
||||
)[0]
|
||||
res.append(
|
||||
{
|
||||
"payable_account": opening_row.account,
|
||||
"debit": flt(opening_row.debit),
|
||||
"credit": flt(opening_row.credit),
|
||||
"balance": flt(opening_row.balance),
|
||||
}
|
||||
)
|
||||
|
||||
data = []
|
||||
for inv in invoice_list:
|
||||
# invoice details
|
||||
@@ -48,24 +77,23 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
purchase_receipt = list(set(invoice_po_pr_map.get(inv.name, {}).get("purchase_receipt", [])))
|
||||
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
|
||||
|
||||
row = [
|
||||
inv.name,
|
||||
inv.posting_date,
|
||||
inv.supplier,
|
||||
inv.supplier_name,
|
||||
*get_values_for_columns(additional_table_columns, inv).values(),
|
||||
supplier_details.get(inv.supplier), # supplier_group
|
||||
inv.tax_id,
|
||||
inv.credit_to,
|
||||
inv.mode_of_payment,
|
||||
", ".join(project),
|
||||
inv.bill_no,
|
||||
inv.bill_date,
|
||||
inv.remarks,
|
||||
", ".join(purchase_order),
|
||||
", ".join(purchase_receipt),
|
||||
company_currency,
|
||||
]
|
||||
row = {
|
||||
"voucher_type": inv.doctype,
|
||||
"voucher_no": inv.name,
|
||||
"posting_date": inv.posting_date,
|
||||
"supplier_id": inv.supplier,
|
||||
"supplier_name": inv.supplier_name,
|
||||
**get_values_for_columns(additional_table_columns, inv),
|
||||
"supplier_group": supplier_details.get(inv.supplier).get("supplier_group"),
|
||||
"tax_id": supplier_details.get(inv.supplier).get("tax_id"),
|
||||
"payable_account": inv.credit_to,
|
||||
"mode_of_payment": inv.mode_of_payment,
|
||||
"project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project,
|
||||
"remarks": inv.remarks,
|
||||
"purchase_order": ", ".join(purchase_order),
|
||||
"purchase_receipt": ", ".join(purchase_receipt),
|
||||
"currency": company_currency,
|
||||
}
|
||||
|
||||
# map expense values
|
||||
base_net_total = 0
|
||||
@@ -75,14 +103,16 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
else:
|
||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||
base_net_total += expense_amount
|
||||
row.append(expense_amount)
|
||||
row.update({frappe.scrub(expense_acc): expense_amount})
|
||||
|
||||
# Add amount in unrealized account
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
row.append(flt(internal_invoice_map.get((inv.name, account))))
|
||||
row.update(
|
||||
{frappe.scrub(account + "_unrealized"): flt(internal_invoice_map.get((inv.name, account)))}
|
||||
)
|
||||
|
||||
# net total
|
||||
row.append(base_net_total or inv.base_net_total)
|
||||
row.update({"net_total": base_net_total or inv.base_net_total})
|
||||
|
||||
# tax account
|
||||
total_tax = 0
|
||||
@@ -90,45 +120,190 @@ def _execute(filters=None, additional_table_columns=None):
|
||||
if tax_acc not in expense_accounts:
|
||||
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc))
|
||||
total_tax += tax_amount
|
||||
row.append(tax_amount)
|
||||
row.update({frappe.scrub(tax_acc): tax_amount})
|
||||
|
||||
# total tax, grand total, rounded total & outstanding amount
|
||||
row += [total_tax, inv.base_grand_total, flt(inv.base_grand_total, 0), inv.outstanding_amount]
|
||||
row.update(
|
||||
{
|
||||
"total_tax": total_tax,
|
||||
"grand_total": inv.base_grand_total,
|
||||
"rounded_total": inv.base_rounded_total,
|
||||
"outstanding_amount": inv.outstanding_amount,
|
||||
}
|
||||
)
|
||||
|
||||
if inv.doctype == "Purchase Invoice":
|
||||
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||
else:
|
||||
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
res += sorted(data, key=lambda x: x["posting_date"])
|
||||
|
||||
if include_payments:
|
||||
running_balance = flt(opening_row.balance)
|
||||
for row in range(1, len(res)):
|
||||
running_balance += res[row]["debit"] - res[row]["credit"]
|
||||
res[row].update({"balance": running_balance})
|
||||
|
||||
return columns, res, None, None, None, include_payments
|
||||
|
||||
|
||||
def get_columns(invoice_list, additional_table_columns):
|
||||
def get_columns(invoice_list, additional_table_columns, include_payments=False):
|
||||
"""return columns based on filters"""
|
||||
columns = [
|
||||
_("Invoice") + ":Link/Purchase Invoice:120",
|
||||
_("Posting Date") + ":Date:80",
|
||||
_("Supplier Id") + "::120",
|
||||
_("Supplier Name") + "::120",
|
||||
{
|
||||
"label": _("Voucher Type"),
|
||||
"fieldname": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Voucher"),
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
||||
{
|
||||
"label": _("Supplier"),
|
||||
"fieldname": "supplier_id",
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Supplier Name"), "fieldname": "supplier_name", "fieldtype": "Data", "width": 120},
|
||||
]
|
||||
|
||||
if additional_table_columns:
|
||||
if additional_table_columns and not include_payments:
|
||||
columns += additional_table_columns
|
||||
|
||||
columns += [
|
||||
_("Supplier Group") + ":Link/Supplier Group:120",
|
||||
_("Tax Id") + "::80",
|
||||
_("Payable Account") + ":Link/Account:120",
|
||||
_("Mode of Payment") + ":Link/Mode of Payment:80",
|
||||
_("Project") + ":Link/Project:80",
|
||||
_("Bill No") + "::120",
|
||||
_("Bill Date") + ":Date:80",
|
||||
_("Remarks") + "::150",
|
||||
_("Purchase Order") + ":Link/Purchase Order:100",
|
||||
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Supplier Group"),
|
||||
"fieldname": "supplier_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Supplier Group",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||
{
|
||||
"label": _("Payable Account"),
|
||||
"fieldname": "payable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Bill No"), "fieldname": "bill_no", "fieldtype": "Data", "width": 120},
|
||||
{"label": _("Bill Date"), "fieldname": "bill_date", "fieldtype": "Date", "width": 80},
|
||||
{
|
||||
"label": _("Purchase Order"),
|
||||
"fieldname": "purchase_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Purchase Receipt"),
|
||||
"fieldname": "purchase_receipt",
|
||||
"fieldtype": "Link",
|
||||
"options": "Purchase Receipt",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
else:
|
||||
columns += [
|
||||
{
|
||||
"fieldname": "payable_account",
|
||||
"label": _("Payable Account"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 120,
|
||||
},
|
||||
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "balance", "label": _("Balance"), "fieldtype": "Currency", "width": 120},
|
||||
]
|
||||
|
||||
account_columns, accounts = get_account_columns(invoice_list, include_payments)
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ account_columns[0]
|
||||
+ account_columns[1]
|
||||
+ [
|
||||
{
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
+ account_columns[2]
|
||||
+ [
|
||||
{
|
||||
"label": _("Total Tax"),
|
||||
"fieldname": "total_tax",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
columns += [{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 120}]
|
||||
return columns, accounts[0], accounts[2], accounts[1]
|
||||
|
||||
|
||||
def get_account_columns(invoice_list, include_payments):
|
||||
expense_accounts = []
|
||||
tax_accounts = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
|
||||
expense_columns = []
|
||||
tax_columns = []
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
expense_accounts = frappe.db.sql_list(
|
||||
"""select distinct expense_account
|
||||
@@ -139,15 +314,18 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple([inv.name for inv in invoice_list]),
|
||||
)
|
||||
|
||||
tax_accounts = frappe.db.sql_list(
|
||||
"""select distinct account_head
|
||||
from `tabPurchase Taxes and Charges` where parenttype = 'Purchase Invoice'
|
||||
and docstatus = 1 and (account_head is not null and account_head != '')
|
||||
and category in ('Total', 'Valuation and Total')
|
||||
and parent in (%s) order by account_head"""
|
||||
% ", ".join(["%s"] * len(invoice_list)),
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
purchase_taxes_query = get_taxes_query(
|
||||
invoice_list, "Purchase Taxes and Charges", "Purchase Invoice"
|
||||
)
|
||||
purchase_tax_accounts = purchase_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = purchase_tax_accounts
|
||||
|
||||
if include_payments:
|
||||
advance_taxes_query = get_taxes_query(
|
||||
invoice_list, "Advance Taxes and Charges", "Payment Entry"
|
||||
)
|
||||
advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = set(tax_accounts + advance_tax_accounts)
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||
"""SELECT distinct unrealized_profit_loss_account
|
||||
@@ -158,107 +336,102 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
|
||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
||||
unrealized_profit_loss_account_columns = [
|
||||
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
|
||||
]
|
||||
tax_columns = [
|
||||
(account + ":Currency/currency:120")
|
||||
for account in tax_accounts
|
||||
if account not in expense_accounts
|
||||
]
|
||||
for account in expense_accounts:
|
||||
expense_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ expense_columns
|
||||
+ unrealized_profit_loss_account_columns
|
||||
+ [_("Net Total") + ":Currency/currency:120"]
|
||||
+ tax_columns
|
||||
+ [
|
||||
_("Total Tax") + ":Currency/currency:120",
|
||||
_("Grand Total") + ":Currency/currency:120",
|
||||
_("Rounded Total") + ":Currency/currency:120",
|
||||
_("Outstanding Amount") + ":Currency/currency:120",
|
||||
]
|
||||
)
|
||||
for account in tax_accounts:
|
||||
if account not in expense_accounts:
|
||||
tax_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
return columns, expense_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
for account in unrealized_profit_loss_accounts:
|
||||
unrealized_profit_loss_account_columns.append(
|
||||
{
|
||||
"label": account,
|
||||
"fieldname": frappe.scrub(account),
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
)
|
||||
|
||||
columns = [expense_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||
accounts = [expense_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " and company=%(company)s"
|
||||
if filters.get("supplier"):
|
||||
conditions += " and supplier = %(supplier)s"
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " and posting_date>=%(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and posting_date<=%(to_date)s"
|
||||
|
||||
if filters.get("mode_of_payment"):
|
||||
conditions += " and ifnull(mode_of_payment, '') = %(mode_of_payment)s"
|
||||
|
||||
if filters.get("cost_center"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.cost_center, '') = %(cost_center)s)"""
|
||||
|
||||
if filters.get("warehouse"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.warehouse, '') = %(warehouse)s)"""
|
||||
|
||||
if filters.get("item_group"):
|
||||
conditions += """ and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
and ifnull(`tabPurchase Invoice Item`.item_group, '') = %(item_group)s)"""
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
if accounting_dimensions:
|
||||
common_condition = """
|
||||
and exists(select name from `tabPurchase Invoice Item`
|
||||
where parent=`tabPurchase Invoice`.name
|
||||
"""
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabPurchase Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
return columns, accounts
|
||||
|
||||
|
||||
def get_invoices(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select
|
||||
name, posting_date, credit_to, supplier, supplier_name, tax_id, bill_no, bill_date,
|
||||
remarks, base_net_total, base_grand_total, outstanding_amount,
|
||||
mode_of_payment {0}
|
||||
from `tabPurchase Invoice`
|
||||
where docstatus = 1 {1}
|
||||
order by posting_date desc, name desc""".format(
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
pi = frappe.qb.DocType("Purchase Invoice")
|
||||
invoice_item = frappe.qb.DocType("Purchase Invoice Item")
|
||||
query = (
|
||||
frappe.qb.from_(pi)
|
||||
.inner_join(invoice_item)
|
||||
.on(pi.name == invoice_item.parent)
|
||||
.select(
|
||||
ConstantColumn("Purchase Invoice").as_("doctype"),
|
||||
pi.name,
|
||||
pi.posting_date,
|
||||
pi.credit_to,
|
||||
pi.supplier,
|
||||
pi.supplier_name,
|
||||
pi.tax_id,
|
||||
pi.bill_no,
|
||||
pi.bill_date,
|
||||
pi.remarks,
|
||||
pi.base_net_total,
|
||||
pi.base_grand_total,
|
||||
pi.outstanding_amount,
|
||||
pi.mode_of_payment,
|
||||
)
|
||||
.where((pi.docstatus == 1))
|
||||
.orderby(pi.posting_date, pi.name, order=Order.desc)
|
||||
)
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
query = query.select(col)
|
||||
if filters.get("supplier"):
|
||||
query = query.where(pi.supplier == filters.supplier)
|
||||
query = get_conditions(
|
||||
filters, query, doctype="Purchase Invoice", child_doctype="Purchase Invoice Item"
|
||||
)
|
||||
if filters.get("include_payments"):
|
||||
party_account = get_party_account(
|
||||
"Supplier", filters.get("supplier"), filters.get("company"), include_advance=True
|
||||
)
|
||||
query = query.where(pi.credit_to.isin(party_account))
|
||||
invoices = query.run(as_dict=True)
|
||||
return invoices
|
||||
|
||||
|
||||
def get_payments(filters):
|
||||
args = frappe._dict(
|
||||
account="credit_to",
|
||||
account_fieldname="paid_to",
|
||||
party="supplier",
|
||||
party_name="supplier_name",
|
||||
party_account=get_party_account(
|
||||
"Supplier", filters.supplier, filters.company, include_advance=True
|
||||
),
|
||||
)
|
||||
payment_entries = get_payment_entries(filters, args)
|
||||
journal_entries = get_journal_entries(filters, args)
|
||||
return payment_entries + journal_entries
|
||||
|
||||
|
||||
def get_invoice_expense_map(invoice_list):
|
||||
@@ -300,7 +473,9 @@ def get_internal_invoice_map(invoice_list):
|
||||
return internal_invoice_map
|
||||
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||
def get_invoice_tax_map(
|
||||
invoice_list, invoice_expense_map, expense_accounts, include_payments=False
|
||||
):
|
||||
tax_details = frappe.db.sql(
|
||||
"""
|
||||
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
||||
@@ -315,6 +490,9 @@ def get_invoice_tax_map(invoice_list, invoice_expense_map, expense_accounts):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if include_payments:
|
||||
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||
|
||||
invoice_tax_map = {}
|
||||
for d in tax_details:
|
||||
if d.account_head in expense_accounts:
|
||||
@@ -382,17 +560,3 @@ def get_account_details(invoice_list):
|
||||
account_map[acc.name] = acc.parent_account
|
||||
|
||||
return account_map
|
||||
|
||||
|
||||
def get_supplier_details(suppliers):
|
||||
supplier_details = {}
|
||||
for supp in frappe.db.sql(
|
||||
"""select name, supplier_group from `tabSupplier`
|
||||
where name in (%s)"""
|
||||
% ", ".join(["%s"] * len(suppliers)),
|
||||
tuple(suppliers),
|
||||
as_dict=1,
|
||||
):
|
||||
supplier_details.setdefault(supp.name, supp.supplier_group)
|
||||
|
||||
return supplier_details
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import add_months, getdate, today
|
||||
|
||||
from erpnext.accounts.report.purchase_register.purchase_register import execute
|
||||
|
||||
|
||||
class TestPurchaseRegister(FrappeTestCase):
|
||||
def test_purchase_register(self):
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
|
||||
|
||||
filters = frappe._dict(
|
||||
company="_Test Company 6", from_date=add_months(today(), -1), to_date=today()
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice()
|
||||
|
||||
report_results = execute(filters)
|
||||
first_row = frappe._dict(report_results[1][0])
|
||||
self.assertEqual(first_row.voucher_type, "Purchase Invoice")
|
||||
self.assertEqual(first_row.voucher_no, pi.name)
|
||||
self.assertEqual(first_row.payable_account, "Creditors - _TC6")
|
||||
self.assertEqual(first_row.net_total, 1000)
|
||||
self.assertEqual(first_row.total_tax, 100)
|
||||
self.assertEqual(first_row.grand_total, 1100)
|
||||
|
||||
def test_purchase_register_ledger_view(self):
|
||||
frappe.db.sql("delete from `tabPurchase Invoice` where company='_Test Company 6'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 6'")
|
||||
|
||||
filters = frappe._dict(
|
||||
company="_Test Company 6",
|
||||
from_date=add_months(today(), -1),
|
||||
to_date=today(),
|
||||
include_payments=True,
|
||||
supplier="_Test Supplier",
|
||||
)
|
||||
|
||||
pi = make_purchase_invoice()
|
||||
pe = make_payment_entry()
|
||||
|
||||
report_results = execute(filters)
|
||||
first_row = frappe._dict(report_results[1][2])
|
||||
self.assertEqual(first_row.voucher_type, "Payment Entry")
|
||||
self.assertEqual(first_row.voucher_no, pe.name)
|
||||
self.assertEqual(first_row.payable_account, "Creditors - _TC6")
|
||||
self.assertEqual(first_row.debit, 0)
|
||||
self.assertEqual(first_row.credit, 600)
|
||||
self.assertEqual(first_row.balance, 500)
|
||||
|
||||
|
||||
def make_purchase_invoice():
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
|
||||
|
||||
gst_acc = create_account(
|
||||
account_name="GST",
|
||||
account_type="Tax",
|
||||
parent_account="Duties and Taxes - _TC6",
|
||||
company="_Test Company 6",
|
||||
account_currency="INR",
|
||||
)
|
||||
create_warehouse(warehouse_name="_Test Warehouse - _TC6", company="_Test Company 6")
|
||||
create_cost_center(cost_center_name="_Test Cost Center", company="_Test Company 6")
|
||||
pi = create_purchase_invoice_with_taxes()
|
||||
pi.submit()
|
||||
return pi
|
||||
|
||||
|
||||
def create_purchase_invoice_with_taxes():
|
||||
return frappe.get_doc(
|
||||
{
|
||||
"doctype": "Purchase Invoice",
|
||||
"posting_date": today(),
|
||||
"supplier": "_Test Supplier",
|
||||
"company": "_Test Company 6",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"taxes_and_charges": "",
|
||||
"currency": "INR",
|
||||
"credit_to": "Creditors - _TC6",
|
||||
"items": [
|
||||
{
|
||||
"doctype": "Purchase Invoice Item",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"item_code": "_Test Item",
|
||||
"qty": 1,
|
||||
"rate": 1000,
|
||||
"expense_account": "Stock Received But Not Billed - _TC6",
|
||||
}
|
||||
],
|
||||
"taxes": [
|
||||
{
|
||||
"account_head": "GST - _TC6",
|
||||
"cost_center": "_Test Cost Center - _TC6",
|
||||
"add_deduct_tax": "Add",
|
||||
"category": "Valuation and Total",
|
||||
"charge_type": "Actual",
|
||||
"description": "Shipping Charges",
|
||||
"doctype": "Purchase Taxes and Charges",
|
||||
"parentfield": "taxes",
|
||||
"rate": 100,
|
||||
"tax_amount": 100.0,
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def make_payment_entry():
|
||||
frappe.set_user("Administrator")
|
||||
from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
|
||||
|
||||
return create_payment_entry(
|
||||
company="_Test Company 6",
|
||||
party_type="Supplier",
|
||||
party="_Test Supplier",
|
||||
payment_type="Pay",
|
||||
paid_from="Cash - _TC6",
|
||||
paid_to="Creditors - _TC6",
|
||||
paid_amount=600,
|
||||
save=1,
|
||||
submit=1,
|
||||
)
|
||||
@@ -64,6 +64,12 @@ frappe.query_reports["Sales Register"] = {
|
||||
"label": __("Item Group"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Item Group"
|
||||
},
|
||||
{
|
||||
"fieldname": "include_payments",
|
||||
"label": __("Show Ledger View"),
|
||||
"fieldtype": "Check",
|
||||
"default": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,13 +5,22 @@
|
||||
import frappe
|
||||
from frappe import _, msgprint
|
||||
from frappe.model.meta import get_field_precision
|
||||
from frappe.utils import flt
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.utils import flt, getdate
|
||||
from pypika import Order
|
||||
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.accounts.report.utils import (
|
||||
get_advance_taxes_and_charges,
|
||||
get_conditions,
|
||||
get_journal_entries,
|
||||
get_opening_row,
|
||||
get_party_details,
|
||||
get_payment_entries,
|
||||
get_query_columns,
|
||||
get_taxes_query,
|
||||
get_values_for_columns,
|
||||
)
|
||||
from erpnext.accounts.report.utils import get_query_columns, get_values_for_columns
|
||||
|
||||
|
||||
def execute(filters=None):
|
||||
@@ -22,9 +31,15 @@ def _execute(filters, additional_table_columns=None):
|
||||
if not filters:
|
||||
filters = frappe._dict({})
|
||||
|
||||
include_payments = filters.get("include_payments")
|
||||
if filters.get("include_payments") and not filters.get("customer"):
|
||||
frappe.throw(_("Please select a customer for fetching payments."))
|
||||
invoice_list = get_invoices(filters, get_query_columns(additional_table_columns))
|
||||
if filters.get("include_payments"):
|
||||
invoice_list += get_payments(filters)
|
||||
|
||||
columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts = get_columns(
|
||||
invoice_list, additional_table_columns
|
||||
invoice_list, additional_table_columns, include_payments
|
||||
)
|
||||
|
||||
if not invoice_list:
|
||||
@@ -34,13 +49,29 @@ def _execute(filters, additional_table_columns=None):
|
||||
invoice_income_map = get_invoice_income_map(invoice_list)
|
||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||
invoice_income_map, invoice_tax_map = get_invoice_tax_map(
|
||||
invoice_list, invoice_income_map, income_accounts
|
||||
invoice_list, invoice_income_map, income_accounts, include_payments
|
||||
)
|
||||
# Cost Center & Warehouse Map
|
||||
invoice_cc_wh_map = get_invoice_cc_wh_map(invoice_list)
|
||||
invoice_so_dn_map = get_invoice_so_dn_map(invoice_list)
|
||||
company_currency = frappe.get_cached_value("Company", filters.get("company"), "default_currency")
|
||||
mode_of_payments = get_mode_of_payments([inv.name for inv in invoice_list])
|
||||
customers = list(set(d.customer for d in invoice_list))
|
||||
customer_details = get_party_details("Customer", customers)
|
||||
|
||||
res = []
|
||||
if include_payments:
|
||||
opening_row = get_opening_row(
|
||||
"Customer", filters.customer, getdate(filters.from_date), filters.company
|
||||
)[0]
|
||||
res.append(
|
||||
{
|
||||
"receivable_account": opening_row.account,
|
||||
"debit": flt(opening_row.debit),
|
||||
"credit": flt(opening_row.credit),
|
||||
"balance": flt(opening_row.balance),
|
||||
}
|
||||
)
|
||||
|
||||
data = []
|
||||
for inv in invoice_list:
|
||||
@@ -51,14 +82,15 @@ def _execute(filters, additional_table_columns=None):
|
||||
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
|
||||
|
||||
row = {
|
||||
"invoice": inv.name,
|
||||
"voucher_type": inv.doctype,
|
||||
"voucher_no": inv.name,
|
||||
"posting_date": inv.posting_date,
|
||||
"customer": inv.customer,
|
||||
"customer_name": inv.customer_name,
|
||||
**get_values_for_columns(additional_table_columns, inv),
|
||||
"customer_group": inv.get("customer_group"),
|
||||
"territory": inv.get("territory"),
|
||||
"tax_id": inv.get("tax_id"),
|
||||
"customer_group": customer_details.get(inv.customer).get("customer_group"),
|
||||
"territory": customer_details.get(inv.customer).get("territory"),
|
||||
"tax_id": customer_details.get(inv.customer).get("tax_id"),
|
||||
"receivable_account": inv.debit_to,
|
||||
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
||||
"project": inv.project,
|
||||
@@ -116,19 +148,36 @@ def _execute(filters, additional_table_columns=None):
|
||||
}
|
||||
)
|
||||
|
||||
if inv.doctype == "Sales Invoice":
|
||||
row.update({"debit": inv.base_grand_total, "credit": 0.0})
|
||||
else:
|
||||
row.update({"debit": 0.0, "credit": inv.base_grand_total})
|
||||
data.append(row)
|
||||
|
||||
return columns, data
|
||||
res += sorted(data, key=lambda x: x["posting_date"])
|
||||
|
||||
if include_payments:
|
||||
running_balance = flt(opening_row.balance)
|
||||
for row in range(1, len(res)):
|
||||
running_balance += res[row]["debit"] - res[row]["credit"]
|
||||
res[row].update({"balance": running_balance})
|
||||
|
||||
return columns, res, None, None, None, include_payments
|
||||
|
||||
|
||||
def get_columns(invoice_list, additional_table_columns):
|
||||
def get_columns(invoice_list, additional_table_columns, include_payments=False):
|
||||
"""return columns based on filters"""
|
||||
columns = [
|
||||
{
|
||||
"label": _("Invoice"),
|
||||
"fieldname": "invoice",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Invoice",
|
||||
"label": _("Voucher Type"),
|
||||
"fieldname": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Voucher"),
|
||||
"fieldname": "voucher_no",
|
||||
"fieldtype": "Dynamic Link",
|
||||
"options": "voucher_type",
|
||||
"width": 120,
|
||||
},
|
||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
||||
@@ -142,83 +191,156 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
{"label": _("Customer Name"), "fieldname": "customer_name", "fieldtype": "Data", "width": 120},
|
||||
]
|
||||
|
||||
if additional_table_columns:
|
||||
if additional_table_columns and not include_payments:
|
||||
columns += additional_table_columns
|
||||
|
||||
columns += [
|
||||
if not include_payments:
|
||||
columns += [
|
||||
{
|
||||
"label": _("Customer Group"),
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||
{
|
||||
"label": _("Receivable Account"),
|
||||
"fieldname": "receivable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 100},
|
||||
{
|
||||
"label": _("Sales Order"),
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Delivery Note"),
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"options": "Delivery Note",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
]
|
||||
else:
|
||||
columns += [
|
||||
{
|
||||
"fieldname": "receivable_account",
|
||||
"label": _("Receivable Account"),
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 120,
|
||||
},
|
||||
{"fieldname": "debit", "label": _("Debit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "credit", "label": _("Credit"), "fieldtype": "Currency", "width": 120},
|
||||
{"fieldname": "balance", "label": _("Balance"), "fieldtype": "Currency", "width": 120},
|
||||
]
|
||||
|
||||
account_columns, accounts = get_account_columns(invoice_list, include_payments)
|
||||
|
||||
net_total_column = [
|
||||
{
|
||||
"label": _("Customer Group"),
|
||||
"fieldname": "customer_group",
|
||||
"fieldtype": "Link",
|
||||
"options": "Customer Group",
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Territory"),
|
||||
"fieldname": "territory",
|
||||
"fieldtype": "Link",
|
||||
"options": "Territory",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 120},
|
||||
{
|
||||
"label": _("Receivable Account"),
|
||||
"fieldname": "receivable_account",
|
||||
"fieldtype": "Link",
|
||||
"options": "Account",
|
||||
"width": 80,
|
||||
},
|
||||
{
|
||||
"label": _("Mode Of Payment"),
|
||||
"fieldname": "mode_of_payment",
|
||||
"fieldtype": "Data",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Project"),
|
||||
"fieldname": "project",
|
||||
"fieldtype": "Link",
|
||||
"options": "Project",
|
||||
"width": 80,
|
||||
},
|
||||
{"label": _("Owner"), "fieldname": "owner", "fieldtype": "Data", "width": 150},
|
||||
{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150},
|
||||
{
|
||||
"label": _("Sales Order"),
|
||||
"fieldname": "sales_order",
|
||||
"fieldtype": "Link",
|
||||
"options": "Sales Order",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Delivery Note"),
|
||||
"fieldname": "delivery_note",
|
||||
"fieldtype": "Link",
|
||||
"options": "Delivery Note",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Cost Center"),
|
||||
"fieldname": "cost_center",
|
||||
"fieldtype": "Link",
|
||||
"options": "Cost Center",
|
||||
"width": 100,
|
||||
},
|
||||
{
|
||||
"label": _("Warehouse"),
|
||||
"fieldname": "warehouse",
|
||||
"fieldtype": "Link",
|
||||
"options": "Warehouse",
|
||||
"width": 100,
|
||||
},
|
||||
{"fieldname": "currency", "label": _("Currency"), "fieldtype": "Data", "width": 80},
|
||||
}
|
||||
]
|
||||
|
||||
total_columns = [
|
||||
{
|
||||
"label": _("Tax Total"),
|
||||
"fieldname": "tax_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
if not include_payments:
|
||||
total_columns += [
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ account_columns[0]
|
||||
+ account_columns[2]
|
||||
+ net_total_column
|
||||
+ account_columns[1]
|
||||
+ total_columns
|
||||
)
|
||||
columns += [{"label": _("Remarks"), "fieldname": "remarks", "fieldtype": "Data", "width": 150}]
|
||||
return columns, accounts[0], accounts[1], accounts[2]
|
||||
|
||||
|
||||
def get_account_columns(invoice_list, include_payments):
|
||||
income_accounts = []
|
||||
tax_accounts = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
|
||||
income_columns = []
|
||||
tax_columns = []
|
||||
unrealized_profit_loss_accounts = []
|
||||
unrealized_profit_loss_account_columns = []
|
||||
|
||||
if invoice_list:
|
||||
@@ -230,14 +352,16 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
|
||||
tax_accounts = frappe.db.sql_list(
|
||||
"""select distinct account_head
|
||||
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
|
||||
and docstatus = 1 and base_tax_amount_after_discount_amount != 0
|
||||
and parent in (%s) order by account_head"""
|
||||
% ", ".join(["%s"] * len(invoice_list)),
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
)
|
||||
sales_taxes_query = get_taxes_query(invoice_list, "Sales Taxes and Charges", "Sales Invoice")
|
||||
sales_tax_accounts = sales_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = sales_tax_accounts
|
||||
|
||||
if include_payments:
|
||||
advance_taxes_query = get_taxes_query(
|
||||
invoice_list, "Advance Taxes and Charges", "Payment Entry"
|
||||
)
|
||||
advance_tax_accounts = advance_taxes_query.run(as_dict=True, pluck="account_head")
|
||||
tax_accounts = set(tax_accounts + advance_tax_accounts)
|
||||
|
||||
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||
"""SELECT distinct unrealized_profit_loss_account
|
||||
@@ -283,133 +407,71 @@ def get_columns(invoice_list, additional_table_columns):
|
||||
}
|
||||
)
|
||||
|
||||
net_total_column = [
|
||||
{
|
||||
"label": _("Net Total"),
|
||||
"fieldname": "net_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
}
|
||||
]
|
||||
columns = [income_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||
accounts = [income_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||
|
||||
total_columns = [
|
||||
{
|
||||
"label": _("Tax Total"),
|
||||
"fieldname": "tax_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Grand Total"),
|
||||
"fieldname": "grand_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Rounded Total"),
|
||||
"fieldname": "rounded_total",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
{
|
||||
"label": _("Outstanding Amount"),
|
||||
"fieldname": "outstanding_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "currency",
|
||||
"width": 120,
|
||||
},
|
||||
]
|
||||
|
||||
columns = (
|
||||
columns
|
||||
+ income_columns
|
||||
+ unrealized_profit_loss_account_columns
|
||||
+ net_total_column
|
||||
+ tax_columns
|
||||
+ total_columns
|
||||
)
|
||||
|
||||
return columns, income_accounts, tax_accounts, unrealized_profit_loss_accounts
|
||||
|
||||
|
||||
def get_conditions(filters):
|
||||
conditions = ""
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False) or []
|
||||
accounting_dimensions_list = [d.fieldname for d in accounting_dimensions]
|
||||
|
||||
if filters.get("company"):
|
||||
conditions += " and company=%(company)s"
|
||||
|
||||
if filters.get("customer") and "customer" not in accounting_dimensions_list:
|
||||
conditions += " and customer = %(customer)s"
|
||||
|
||||
if filters.get("from_date"):
|
||||
conditions += " and posting_date >= %(from_date)s"
|
||||
if filters.get("to_date"):
|
||||
conditions += " and posting_date <= %(to_date)s"
|
||||
|
||||
if filters.get("owner"):
|
||||
conditions += " and owner = %(owner)s"
|
||||
|
||||
def get_sales_invoice_item_field_condition(field, table="Sales Invoice Item") -> str:
|
||||
if not filters.get(field) or field in accounting_dimensions_list:
|
||||
return ""
|
||||
return f""" and exists(select name from `tab{table}`
|
||||
where parent=`tabSales Invoice`.name
|
||||
and ifnull(`tab{table}`.{field}, '') = %({field})s)"""
|
||||
|
||||
conditions += get_sales_invoice_item_field_condition("mode_of_payment", "Sales Invoice Payment")
|
||||
conditions += get_sales_invoice_item_field_condition("cost_center")
|
||||
conditions += get_sales_invoice_item_field_condition("warehouse")
|
||||
conditions += get_sales_invoice_item_field_condition("brand")
|
||||
conditions += get_sales_invoice_item_field_condition("item_group")
|
||||
|
||||
if accounting_dimensions:
|
||||
common_condition = """
|
||||
and exists(select name from `tabSales Invoice Item`
|
||||
where parent=`tabSales Invoice`.name
|
||||
"""
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
else:
|
||||
conditions += (
|
||||
common_condition
|
||||
+ "and ifnull(`tabSales Invoice`.{0}, '') in %({0})s)".format(dimension.fieldname)
|
||||
)
|
||||
|
||||
return conditions
|
||||
return columns, accounts
|
||||
|
||||
|
||||
def get_invoices(filters, additional_query_columns):
|
||||
conditions = get_conditions(filters)
|
||||
return frappe.db.sql(
|
||||
"""
|
||||
select name, posting_date, debit_to, project, customer,
|
||||
customer_name, owner, remarks, territory, tax_id, customer_group,
|
||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
||||
is_internal_customer, represents_company, company {0}
|
||||
from `tabSales Invoice`
|
||||
where docstatus = 1 {1}
|
||||
order by posting_date desc, name desc""".format(
|
||||
additional_query_columns, conditions
|
||||
),
|
||||
filters,
|
||||
as_dict=1,
|
||||
si = frappe.qb.DocType("Sales Invoice")
|
||||
invoice_item = frappe.qb.DocType("Sales Invoice Item")
|
||||
invoice_payment = frappe.qb.DocType("Sales Invoice Payment")
|
||||
query = (
|
||||
frappe.qb.from_(si)
|
||||
.inner_join(invoice_item)
|
||||
.on(si.name == invoice_item.parent)
|
||||
.left_join(invoice_payment)
|
||||
.on(si.name == invoice_payment.parent)
|
||||
.select(
|
||||
ConstantColumn("Sales Invoice").as_("doctype"),
|
||||
si.name,
|
||||
si.posting_date,
|
||||
si.debit_to,
|
||||
si.project,
|
||||
si.customer,
|
||||
si.customer_name,
|
||||
si.owner,
|
||||
si.remarks,
|
||||
si.territory,
|
||||
si.tax_id,
|
||||
si.customer_group,
|
||||
si.base_net_total,
|
||||
si.base_grand_total,
|
||||
si.base_rounded_total,
|
||||
si.outstanding_amount,
|
||||
si.is_internal_customer,
|
||||
si.represents_company,
|
||||
si.company,
|
||||
)
|
||||
.where((si.docstatus == 1))
|
||||
.orderby(si.posting_date, si.name, order=Order.desc)
|
||||
)
|
||||
if additional_query_columns:
|
||||
for col in additional_query_columns:
|
||||
query = query.select(col)
|
||||
if filters.get("customer"):
|
||||
query = query.where(si.customer == filters.customer)
|
||||
query = get_conditions(
|
||||
filters, query, doctype="Sales Invoice", child_doctype="Sales Invoice Item"
|
||||
)
|
||||
invoices = query.run(as_dict=True)
|
||||
return invoices
|
||||
|
||||
|
||||
def get_payments(filters):
|
||||
args = frappe._dict(
|
||||
account="debit_to",
|
||||
account_fieldname="paid_from",
|
||||
party="customer",
|
||||
party_name="customer_name",
|
||||
party_account=get_party_account(
|
||||
"Customer", filters.customer, filters.company, include_advance=True
|
||||
),
|
||||
)
|
||||
payment_entries = get_payment_entries(filters, args)
|
||||
journal_entries = get_journal_entries(filters, args)
|
||||
return payment_entries + journal_entries
|
||||
|
||||
|
||||
def get_invoice_income_map(invoice_list):
|
||||
@@ -447,7 +509,7 @@ def get_internal_invoice_map(invoice_list):
|
||||
return internal_invoice_map
|
||||
|
||||
|
||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||
def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts, include_payments=False):
|
||||
tax_details = frappe.db.sql(
|
||||
"""select parent, account_head,
|
||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
||||
@@ -457,6 +519,9 @@ def get_invoice_tax_map(invoice_list, invoice_income_map, income_accounts):
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
if include_payments:
|
||||
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||
|
||||
invoice_tax_map = {}
|
||||
for d in tax_details:
|
||||
if d.account_head in income_accounts:
|
||||
@@ -475,7 +540,7 @@ def get_invoice_so_dn_map(invoice_list):
|
||||
si_items = frappe.db.sql(
|
||||
"""select parent, sales_order, delivery_note, so_detail
|
||||
from `tabSales Invoice Item` where parent in (%s)
|
||||
and (ifnull(sales_order, '') != '' or ifnull(delivery_note, '') != '')"""
|
||||
and (sales_order != '' or delivery_note != '')"""
|
||||
% ", ".join(["%s"] * len(invoice_list)),
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
as_dict=1,
|
||||
@@ -510,7 +575,7 @@ def get_invoice_cc_wh_map(invoice_list):
|
||||
si_items = frappe.db.sql(
|
||||
"""select parent, cost_center, warehouse
|
||||
from `tabSales Invoice Item` where parent in (%s)
|
||||
and (ifnull(cost_center, '') != '' or ifnull(warehouse, '') != '')"""
|
||||
and (cost_center != '' or warehouse != '')"""
|
||||
% ", ".join(["%s"] * len(invoice_list)),
|
||||
tuple(inv.name for inv in invoice_list),
|
||||
as_dict=1,
|
||||
|
||||
118
erpnext/accounts/report/trial_balance/test_trial_balance.py
Normal file
118
erpnext/accounts/report/trial_balance/test_trial_balance.py
Normal file
@@ -0,0 +1,118 @@
|
||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# MIT License. See license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
from frappe.utils import today
|
||||
|
||||
from erpnext.accounts.report.trial_balance.trial_balance import execute
|
||||
|
||||
|
||||
class TestTrialBalance(FrappeTestCase):
|
||||
def setUp(self):
|
||||
from erpnext.accounts.doctype.account.test_account import create_account
|
||||
from erpnext.accounts.doctype.cost_center.test_cost_center import create_cost_center
|
||||
from erpnext.accounts.utils import get_fiscal_year
|
||||
|
||||
self.company = create_company()
|
||||
create_cost_center(
|
||||
cost_center_name="Test Cost Center",
|
||||
company="Trial Balance Company",
|
||||
parent_cost_center="Trial Balance Company - TBC",
|
||||
)
|
||||
create_account(
|
||||
account_name="Offsetting",
|
||||
company="Trial Balance Company",
|
||||
parent_account="Temporary Accounts - TBC",
|
||||
)
|
||||
self.fiscal_year = get_fiscal_year(today(), company="Trial Balance Company")[0]
|
||||
create_accounting_dimension()
|
||||
|
||||
def test_offsetting_entries_for_accounting_dimensions(self):
|
||||
"""
|
||||
Checks if Trial Balance Report is balanced when filtered using a particular Accounting Dimension
|
||||
"""
|
||||
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
|
||||
|
||||
frappe.db.sql("delete from `tabSales Invoice` where company='Trial Balance Company'")
|
||||
frappe.db.sql("delete from `tabGL Entry` where company='Trial Balance Company'")
|
||||
|
||||
branch1 = frappe.new_doc("Branch")
|
||||
branch1.branch = "Location 1"
|
||||
branch1.insert(ignore_if_duplicate=True)
|
||||
branch2 = frappe.new_doc("Branch")
|
||||
branch2.branch = "Location 2"
|
||||
branch2.insert(ignore_if_duplicate=True)
|
||||
|
||||
si = create_sales_invoice(
|
||||
company=self.company,
|
||||
debit_to="Debtors - TBC",
|
||||
cost_center="Test Cost Center - TBC",
|
||||
income_account="Sales - TBC",
|
||||
do_not_submit=1,
|
||||
)
|
||||
si.branch = "Location 1"
|
||||
si.items[0].branch = "Location 2"
|
||||
si.save()
|
||||
si.submit()
|
||||
|
||||
filters = frappe._dict(
|
||||
{"company": self.company, "fiscal_year": self.fiscal_year, "branch": ["Location 1"]}
|
||||
)
|
||||
total_row = execute(filters)[1][-1]
|
||||
self.assertEqual(total_row["debit"], total_row["credit"])
|
||||
|
||||
def tearDown(self):
|
||||
clear_dimension_defaults("Branch")
|
||||
disable_dimension()
|
||||
|
||||
|
||||
def create_company(**args):
|
||||
args = frappe._dict(args)
|
||||
company = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": args.company_name or "Trial Balance Company",
|
||||
"country": args.country or "India",
|
||||
"default_currency": args.currency or "INR",
|
||||
}
|
||||
)
|
||||
company.insert(ignore_if_duplicate=True)
|
||||
return company.name
|
||||
|
||||
|
||||
def create_accounting_dimension(**args):
|
||||
args = frappe._dict(args)
|
||||
document_type = args.document_type or "Branch"
|
||||
if frappe.db.exists("Accounting Dimension", document_type):
|
||||
accounting_dimension = frappe.get_doc("Accounting Dimension", document_type)
|
||||
accounting_dimension.disabled = 0
|
||||
else:
|
||||
accounting_dimension = frappe.new_doc("Accounting Dimension")
|
||||
accounting_dimension.document_type = document_type
|
||||
accounting_dimension.insert()
|
||||
|
||||
accounting_dimension.set("dimension_defaults", [])
|
||||
accounting_dimension.append(
|
||||
"dimension_defaults",
|
||||
{
|
||||
"company": args.company or "Trial Balance Company",
|
||||
"automatically_post_balancing_accounting_entry": 1,
|
||||
"offsetting_account": args.offsetting_account or "Offsetting - TBC",
|
||||
},
|
||||
)
|
||||
accounting_dimension.save()
|
||||
|
||||
|
||||
def disable_dimension(**args):
|
||||
args = frappe._dict(args)
|
||||
document_type = args.document_type or "Branch"
|
||||
dimension = frappe.get_doc("Accounting Dimension", document_type)
|
||||
dimension.disabled = 1
|
||||
dimension.save()
|
||||
|
||||
|
||||
def clear_dimension_defaults(dimension_name):
|
||||
accounting_dimension = frappe.get_doc("Accounting Dimension", dimension_name)
|
||||
accounting_dimension.dimension_defaults = []
|
||||
accounting_dimension.save()
|
||||
@@ -37,13 +37,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
"fieldname": "cost_center",
|
||||
|
||||
@@ -142,14 +142,20 @@ def get_opening_balances(filters):
|
||||
def get_rootwise_opening_balances(filters, report_type):
|
||||
gle = []
|
||||
|
||||
last_period_closing_voucher = frappe.db.get_all(
|
||||
"Period Closing Voucher",
|
||||
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)},
|
||||
fields=["posting_date", "name"],
|
||||
order_by="posting_date desc",
|
||||
limit=1,
|
||||
last_period_closing_voucher = ""
|
||||
ignore_closing_balances = frappe.db.get_single_value(
|
||||
"Accounts Settings", "ignore_account_closing_balance"
|
||||
)
|
||||
|
||||
if not ignore_closing_balances:
|
||||
last_period_closing_voucher = frappe.db.get_all(
|
||||
"Period Closing Voucher",
|
||||
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)},
|
||||
fields=["posting_date", "name"],
|
||||
order_by="posting_date desc",
|
||||
limit=1,
|
||||
)
|
||||
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
|
||||
if last_period_closing_voucher:
|
||||
@@ -253,7 +259,7 @@ def get_opening_balance(
|
||||
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
|
||||
cost_center = frappe.qb.DocType("Cost Center")
|
||||
opening_balance = opening_balance.where(
|
||||
closing_balance.cost_center.in_(
|
||||
closing_balance.cost_center.isin(
|
||||
frappe.qb.from_(cost_center)
|
||||
.select("name")
|
||||
.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
|
||||
|
||||
@@ -36,13 +36,13 @@ frappe.query_reports["Trial Balance for Party"] = {
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
{
|
||||
"fieldname":"party_type",
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
import frappe
|
||||
from frappe.query_builder.custom import ConstantColumn
|
||||
from frappe.query_builder.functions import Sum
|
||||
from frappe.utils import flt, formatdate, get_datetime_str, get_table_name
|
||||
from pypika import Order
|
||||
|
||||
from erpnext import get_company_currency, get_default_company
|
||||
from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_accounting_dimensions,
|
||||
get_dimension_with_children,
|
||||
)
|
||||
from erpnext.accounts.doctype.fiscal_year.fiscal_year import get_from_and_to_date
|
||||
from erpnext.accounts.party import get_party_account
|
||||
from erpnext.setup.utils import get_exchange_rate
|
||||
|
||||
__exchange_rates = {}
|
||||
@@ -165,7 +173,7 @@ def get_query_columns(report_columns):
|
||||
else:
|
||||
columns.append(fieldname)
|
||||
|
||||
return ", " + ", ".join(columns)
|
||||
return columns
|
||||
|
||||
|
||||
def get_values_for_columns(report_columns, report_row):
|
||||
@@ -179,3 +187,200 @@ def get_values_for_columns(report_columns, report_row):
|
||||
values[fieldname] = report_row.get(fieldname)
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def get_party_details(party_type, party_list):
|
||||
party_details = {}
|
||||
party = frappe.qb.DocType(party_type)
|
||||
query = frappe.qb.from_(party).select(party.name, party.tax_id).where(party.name.isin(party_list))
|
||||
if party_type == "Supplier":
|
||||
query = query.select(party.supplier_group)
|
||||
else:
|
||||
query = query.select(party.customer_group, party.territory)
|
||||
|
||||
party_detail_list = query.run(as_dict=True)
|
||||
for party_dict in party_detail_list:
|
||||
party_details[party_dict.name] = party_dict
|
||||
return party_details
|
||||
|
||||
|
||||
def get_taxes_query(invoice_list, doctype, parenttype):
|
||||
taxes = frappe.qb.DocType(doctype)
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(taxes)
|
||||
.select(taxes.account_head)
|
||||
.distinct()
|
||||
.where(
|
||||
(taxes.parenttype == parenttype)
|
||||
& (taxes.docstatus == 1)
|
||||
& (taxes.account_head.isnotnull())
|
||||
& (taxes.parent.isin([inv.name for inv in invoice_list]))
|
||||
)
|
||||
.orderby(taxes.account_head)
|
||||
)
|
||||
|
||||
if doctype == "Purchase Taxes and Charges":
|
||||
return query.where(taxes.category.isin(["Total", "Valuation and Total"]))
|
||||
elif doctype == "Sales Taxes and Charges":
|
||||
return query
|
||||
return query.where(taxes.charge_type.isin(["On Paid Amount", "Actual"]))
|
||||
|
||||
|
||||
def get_journal_entries(filters, args):
|
||||
je = frappe.qb.DocType("Journal Entry")
|
||||
journal_account = frappe.qb.DocType("Journal Entry Account")
|
||||
query = (
|
||||
frappe.qb.from_(je)
|
||||
.inner_join(journal_account)
|
||||
.on(je.name == journal_account.parent)
|
||||
.select(
|
||||
je.voucher_type.as_("doctype"),
|
||||
je.name,
|
||||
je.posting_date,
|
||||
journal_account.account.as_(args.account),
|
||||
journal_account.party.as_(args.party),
|
||||
journal_account.party.as_(args.party_name),
|
||||
je.bill_no,
|
||||
je.bill_date,
|
||||
je.remark.as_("remarks"),
|
||||
je.total_amount.as_("base_net_total"),
|
||||
je.total_amount.as_("base_grand_total"),
|
||||
je.mode_of_payment,
|
||||
journal_account.project,
|
||||
)
|
||||
.where(
|
||||
(je.voucher_type == "Journal Entry")
|
||||
& (journal_account.party == filters.get(args.party))
|
||||
& (journal_account.account.isin(args.party_account))
|
||||
)
|
||||
.orderby(je.posting_date, je.name, order=Order.desc)
|
||||
)
|
||||
query = get_conditions(filters, query, doctype="Journal Entry", payments=True)
|
||||
journal_entries = query.run(as_dict=True)
|
||||
return journal_entries
|
||||
|
||||
|
||||
def get_payment_entries(filters, args):
|
||||
pe = frappe.qb.DocType("Payment Entry")
|
||||
query = (
|
||||
frappe.qb.from_(pe)
|
||||
.select(
|
||||
ConstantColumn("Payment Entry").as_("doctype"),
|
||||
pe.name,
|
||||
pe.posting_date,
|
||||
pe[args.account_fieldname].as_(args.account),
|
||||
pe.party.as_(args.party),
|
||||
pe.party_name.as_(args.party_name),
|
||||
pe.remarks,
|
||||
pe.paid_amount.as_("base_net_total"),
|
||||
pe.paid_amount_after_tax.as_("base_grand_total"),
|
||||
pe.mode_of_payment,
|
||||
pe.project,
|
||||
pe.cost_center,
|
||||
)
|
||||
.where(
|
||||
(pe.party == filters.get(args.party)) & (pe[args.account_fieldname].isin(args.party_account))
|
||||
)
|
||||
.orderby(pe.posting_date, pe.name, order=Order.desc)
|
||||
)
|
||||
query = get_conditions(filters, query, doctype="Payment Entry", payments=True)
|
||||
payment_entries = query.run(as_dict=True)
|
||||
return payment_entries
|
||||
|
||||
|
||||
def get_conditions(filters, query, doctype, child_doctype=None, payments=False):
|
||||
parent_doc = frappe.qb.DocType(doctype)
|
||||
if child_doctype:
|
||||
child_doc = frappe.qb.DocType(child_doctype)
|
||||
|
||||
if parent_doc.get_table_name() == "tabSales Invoice":
|
||||
if filters.get("owner"):
|
||||
query = query.where(parent_doc.owner == filters.owner)
|
||||
if filters.get("mode_of_payment"):
|
||||
payment_doc = frappe.qb.DocType("Sales Invoice Payment")
|
||||
query = query.where(payment_doc.mode_of_payment == filters.mode_of_payment)
|
||||
if not payments:
|
||||
if filters.get("brand"):
|
||||
query = query.where(child_doc.brand == filters.brand)
|
||||
else:
|
||||
if filters.get("mode_of_payment"):
|
||||
query = query.where(parent_doc.mode_of_payment == filters.mode_of_payment)
|
||||
|
||||
if filters.get("company"):
|
||||
query = query.where(parent_doc.company == filters.company)
|
||||
if filters.get("from_date"):
|
||||
query = query.where(parent_doc.posting_date >= filters.from_date)
|
||||
if filters.get("to_date"):
|
||||
query = query.where(parent_doc.posting_date <= filters.to_date)
|
||||
|
||||
if payments:
|
||||
if filters.get("cost_center"):
|
||||
query = query.where(parent_doc.cost_center == filters.cost_center)
|
||||
else:
|
||||
if filters.get("cost_center"):
|
||||
query = query.where(child_doc.cost_center == filters.cost_center)
|
||||
if filters.get("warehouse"):
|
||||
query = query.where(child_doc.warehouse == filters.warehouse)
|
||||
if filters.get("item_group"):
|
||||
query = query.where(child_doc.item_group == filters.item_group)
|
||||
|
||||
if parent_doc.get_table_name() != "tabJournal Entry":
|
||||
query = filter_invoices_based_on_dimensions(filters, query, parent_doc)
|
||||
return query
|
||||
|
||||
|
||||
def get_advance_taxes_and_charges(invoice_list):
|
||||
adv_taxes = frappe.qb.DocType("Advance Taxes and Charges")
|
||||
return (
|
||||
frappe.qb.from_(adv_taxes)
|
||||
.select(
|
||||
adv_taxes.parent,
|
||||
adv_taxes.account_head,
|
||||
(
|
||||
frappe.qb.terms.Case()
|
||||
.when(adv_taxes.add_deduct_tax == "Add", Sum(adv_taxes.base_tax_amount))
|
||||
.else_(Sum(adv_taxes.base_tax_amount) * -1)
|
||||
).as_("tax_amount"),
|
||||
)
|
||||
.where(
|
||||
(adv_taxes.parent.isin([inv.name for inv in invoice_list]))
|
||||
& (adv_taxes.charge_type.isin(["On Paid Amount", "Actual"]))
|
||||
& (adv_taxes.base_tax_amount != 0)
|
||||
)
|
||||
.groupby(adv_taxes.parent, adv_taxes.account_head, adv_taxes.add_deduct_tax)
|
||||
).run(as_dict=True)
|
||||
|
||||
|
||||
def filter_invoices_based_on_dimensions(filters, query, parent_doc):
|
||||
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||
if accounting_dimensions:
|
||||
for dimension in accounting_dimensions:
|
||||
if filters.get(dimension.fieldname):
|
||||
if frappe.get_cached_value("DocType", dimension.document_type, "is_tree"):
|
||||
filters[dimension.fieldname] = get_dimension_with_children(
|
||||
dimension.document_type, filters.get(dimension.fieldname)
|
||||
)
|
||||
fieldname = dimension.fieldname
|
||||
query = query.where(parent_doc[fieldname] == filters.fieldname)
|
||||
return query
|
||||
|
||||
|
||||
def get_opening_row(party_type, party, from_date, company):
|
||||
party_account = get_party_account(party_type, party, company, include_advance=True)
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
return (
|
||||
frappe.qb.from_(gle)
|
||||
.select(
|
||||
ConstantColumn("Opening").as_("account"),
|
||||
Sum(gle.debit).as_("debit"),
|
||||
Sum(gle.credit).as_("credit"),
|
||||
(Sum(gle.debit) - Sum(gle.credit)).as_("balance"),
|
||||
)
|
||||
.where(
|
||||
(gle.account.isin(party_account))
|
||||
& (gle.party == party)
|
||||
& (gle.posting_date < from_date)
|
||||
& (gle.is_cancelled == 0)
|
||||
)
|
||||
).run(as_dict=True)
|
||||
|
||||
@@ -46,6 +46,7 @@ def get_data(filters):
|
||||
.select(
|
||||
gle.voucher_type, gle.voucher_no, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit")
|
||||
)
|
||||
.where(gle.is_cancelled == 0)
|
||||
.groupby(gle.voucher_no)
|
||||
)
|
||||
query = apply_filters(query, filters, gle)
|
||||
|
||||
80
erpnext/accounts/test/accounts_mixin.py
Normal file
80
erpnext/accounts/test/accounts_mixin.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import frappe
|
||||
|
||||
from erpnext.stock.doctype.item.test_item import create_item
|
||||
|
||||
|
||||
class AccountsTestMixin:
|
||||
def create_customer(self, customer_name, currency=None):
|
||||
if not frappe.db.exists("Customer", customer_name):
|
||||
customer = frappe.new_doc("Customer")
|
||||
customer.customer_name = customer_name
|
||||
customer.type = "Individual"
|
||||
|
||||
if currency:
|
||||
customer.default_currency = currency
|
||||
customer.save()
|
||||
self.customer = customer.name
|
||||
else:
|
||||
self.customer = customer_name
|
||||
|
||||
def create_supplier(self, supplier_name, currency=None):
|
||||
if not frappe.db.exists("Supplier", supplier_name):
|
||||
supplier = frappe.new_doc("Supplier")
|
||||
supplier.supplier_name = supplier_name
|
||||
supplier.supplier_type = "Individual"
|
||||
supplier.supplier_group = "Local"
|
||||
|
||||
if currency:
|
||||
supplier.default_currency = currency
|
||||
supplier.save()
|
||||
self.supplier = supplier.name
|
||||
else:
|
||||
self.supplier = supplier_name
|
||||
|
||||
def create_item(self, item_name, is_stock=0, warehouse=None, company=None):
|
||||
item = create_item(item_name, is_stock_item=is_stock, warehouse=warehouse, company=company)
|
||||
self.item = item.name
|
||||
|
||||
def create_company(self, company_name="_Test Company", abbr="_TC"):
|
||||
self.company_abbr = abbr
|
||||
if frappe.db.exists("Company", company_name):
|
||||
company = frappe.get_doc("Company", company_name)
|
||||
else:
|
||||
company = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Company",
|
||||
"company_name": company_name,
|
||||
"country": "India",
|
||||
"default_currency": "INR",
|
||||
"create_chart_of_accounts_based_on": "Standard Template",
|
||||
"chart_of_accounts": "Standard",
|
||||
}
|
||||
)
|
||||
company = company.save()
|
||||
|
||||
self.company = company.name
|
||||
self.cost_center = company.cost_center
|
||||
self.warehouse = "Stores - " + abbr
|
||||
self.finished_warehouse = "Finished Goods - " + abbr
|
||||
self.income_account = "Sales - " + abbr
|
||||
self.expense_account = "Cost of Goods Sold - " + abbr
|
||||
self.debit_to = "Debtors - " + abbr
|
||||
self.debit_usd = "Debtors USD - " + abbr
|
||||
self.cash = "Cash - " + abbr
|
||||
self.creditors = "Creditors - " + abbr
|
||||
|
||||
# create bank account
|
||||
bank_account = "HDFC - " + abbr
|
||||
if frappe.db.exists("Account", bank_account):
|
||||
self.bank = bank_account
|
||||
else:
|
||||
bank_acc = frappe.get_doc(
|
||||
{
|
||||
"doctype": "Account",
|
||||
"account_name": "HDFC",
|
||||
"parent_account": "Bank Accounts - " + abbr,
|
||||
"company": self.company,
|
||||
}
|
||||
)
|
||||
bank_acc.save()
|
||||
self.bank = bank_acc.name
|
||||
@@ -576,7 +576,11 @@ def update_reference_in_journal_entry(d, journal_entry, do_not_save=False):
|
||||
# new row with references
|
||||
new_row = journal_entry.append("accounts")
|
||||
|
||||
new_row.update((frappe.copy_doc(jv_detail)).as_dict())
|
||||
# Copy field values into new row
|
||||
[
|
||||
new_row.set(field, jv_detail.get(field))
|
||||
for field in frappe.get_meta("Journal Entry Account").get_fieldnames_with_value()
|
||||
]
|
||||
|
||||
new_row.set(d["dr_or_cr"], d["allocated_amount"])
|
||||
new_row.set(
|
||||
|
||||
@@ -141,7 +141,7 @@ frappe.ui.form.on('Asset', {
|
||||
frm.trigger("set_depr_posting_failure_alert");
|
||||
}
|
||||
|
||||
frm.trigger("setup_chart");
|
||||
frm.trigger("setup_chart_and_depr_schedule_view");
|
||||
}
|
||||
|
||||
frm.trigger("toggle_reference_doc");
|
||||
@@ -206,7 +206,43 @@ frappe.ui.form.on('Asset', {
|
||||
})
|
||||
},
|
||||
|
||||
setup_chart: async function(frm) {
|
||||
render_depreciation_schedule_view: function(frm, depr_schedule) {
|
||||
let wrapper = $(frm.fields_dict["depreciation_schedule_view"].wrapper).empty();
|
||||
|
||||
let data = [];
|
||||
|
||||
depr_schedule.forEach((sch) => {
|
||||
const row = [
|
||||
sch['idx'],
|
||||
frappe.format(sch['schedule_date'], { fieldtype: 'Date' }),
|
||||
frappe.format(sch['depreciation_amount'], { fieldtype: 'Currency' }),
|
||||
frappe.format(sch['accumulated_depreciation_amount'], { fieldtype: 'Currency' }),
|
||||
sch['journal_entry'] || ''
|
||||
];
|
||||
data.push(row);
|
||||
});
|
||||
|
||||
let datatable = new frappe.DataTable(wrapper.get(0), {
|
||||
columns: [
|
||||
{name: __("No."), editable: false, resizable: false, format: value => value, width: 60},
|
||||
{name: __("Schedule Date"), editable: false, resizable: false, width: 270},
|
||||
{name: __("Depreciation Amount"), editable: false, resizable: false, width: 164},
|
||||
{name: __("Accumulated Depreciation Amount"), editable: false, resizable: false, width: 164},
|
||||
{name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 312}
|
||||
],
|
||||
data: data,
|
||||
serialNoColumn: false,
|
||||
checkboxColumn: true,
|
||||
cellHeight: 35
|
||||
});
|
||||
|
||||
datatable.style.setStyle(`.dt-scrollable`, {'font-size': '0.75rem', 'margin-bottom': '1rem'});
|
||||
datatable.style.setStyle(`.dt-cell--col-1`, {'text-align': 'center'});
|
||||
datatable.style.setStyle(`.dt-cell--col-2`, {'font-weight': 600});
|
||||
datatable.style.setStyle(`.dt-cell--col-3`, {'font-weight': 600});
|
||||
},
|
||||
|
||||
setup_chart_and_depr_schedule_view: async function(frm) {
|
||||
if(frm.doc.finance_books.length > 1) {
|
||||
return
|
||||
}
|
||||
@@ -228,7 +264,7 @@ frappe.ui.form.on('Asset', {
|
||||
"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
|
||||
{
|
||||
asset_name: frm.doc.name,
|
||||
status: frm.doc.docstatus ? "Active" : "Draft",
|
||||
status: "Active",
|
||||
finance_book: frm.doc.finance_books[0].finance_book || null
|
||||
}
|
||||
)).message;
|
||||
@@ -246,6 +282,9 @@ frappe.ui.form.on('Asset', {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.toggle_display(["depreciation_schedule_view"], 1);
|
||||
frm.events.render_depreciation_schedule_view(frm, depr_schedule);
|
||||
} else {
|
||||
if(frm.doc.opening_accumulated_depreciation) {
|
||||
x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
"column_break_33",
|
||||
"opening_accumulated_depreciation",
|
||||
"number_of_depreciations_booked",
|
||||
"is_fully_depreciated",
|
||||
"section_break_36",
|
||||
"finance_books",
|
||||
"section_break_33",
|
||||
@@ -52,6 +53,8 @@
|
||||
"column_break_24",
|
||||
"frequency_of_depreciation",
|
||||
"next_depreciation_date",
|
||||
"depreciation_schedule_sb",
|
||||
"depreciation_schedule_view",
|
||||
"insurance_details",
|
||||
"policy_number",
|
||||
"insurer",
|
||||
@@ -203,6 +206,7 @@
|
||||
"fieldname": "disposal_date",
|
||||
"fieldtype": "Date",
|
||||
"label": "Disposal Date",
|
||||
"no_copy": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
@@ -242,19 +246,17 @@
|
||||
"label": "Is Existing Asset"
|
||||
},
|
||||
{
|
||||
"depends_on": "is_existing_asset",
|
||||
"depends_on": "eval:(doc.is_existing_asset)",
|
||||
"fieldname": "opening_accumulated_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"label": "Opening Accumulated Depreciation",
|
||||
"no_copy": 1,
|
||||
"options": "Company:company:default_currency"
|
||||
},
|
||||
{
|
||||
"depends_on": "eval:(doc.is_existing_asset && doc.opening_accumulated_depreciation)",
|
||||
"depends_on": "eval:(doc.is_existing_asset)",
|
||||
"fieldname": "number_of_depreciations_booked",
|
||||
"fieldtype": "Int",
|
||||
"label": "Number of Depreciations Booked",
|
||||
"no_copy": 1
|
||||
"label": "Number of Depreciations Booked"
|
||||
},
|
||||
{
|
||||
"collapsible": 1,
|
||||
@@ -487,6 +489,24 @@
|
||||
"options": "\nSuccessful\nFailed",
|
||||
"print_hide": 1,
|
||||
"read_only": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "depreciation_schedule_sb",
|
||||
"fieldtype": "Section Break",
|
||||
"label": "Depreciation Schedule"
|
||||
},
|
||||
{
|
||||
"fieldname": "depreciation_schedule_view",
|
||||
"fieldtype": "HTML",
|
||||
"hidden": 1,
|
||||
"label": "Depreciation Schedule View"
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"depends_on": "eval:(doc.is_existing_asset)",
|
||||
"fieldname": "is_fully_depreciated",
|
||||
"fieldtype": "Check",
|
||||
"label": "Is Fully Depreciated"
|
||||
}
|
||||
],
|
||||
"idx": 72,
|
||||
@@ -513,6 +533,11 @@
|
||||
"link_doctype": "Asset Depreciation Schedule",
|
||||
"link_fieldname": "asset"
|
||||
},
|
||||
{
|
||||
"group": "Activity",
|
||||
"link_doctype": "Asset Activity",
|
||||
"link_fieldname": "asset"
|
||||
},
|
||||
{
|
||||
"group": "Journal Entry",
|
||||
"link_doctype": "Journal Entry",
|
||||
@@ -520,7 +545,7 @@
|
||||
"table_fieldname": "accounts"
|
||||
}
|
||||
],
|
||||
"modified": "2023-03-30 15:07:41.542374",
|
||||
"modified": "2023-07-28 20:12:44.819616",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset",
|
||||
@@ -564,4 +589,4 @@
|
||||
"states": [],
|
||||
"title_field": "asset_name",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,11 @@ from frappe.utils import (
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||
from erpnext.assets.doctype.asset.depreciation import (
|
||||
get_comma_separated_links,
|
||||
get_depreciation_accounts,
|
||||
get_disposal_account_and_cost_center,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
cancel_asset_depr_schedules,
|
||||
@@ -58,9 +60,19 @@ class Asset(AccountsController):
|
||||
self.make_asset_movement()
|
||||
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
||||
self.make_gl_entries()
|
||||
if not self.split_from:
|
||||
make_draft_asset_depr_schedules_if_not_present(self)
|
||||
if self.calculate_depreciation and not self.split_from:
|
||||
asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
|
||||
convert_draft_asset_depr_schedules_into_active(self)
|
||||
if asset_depr_schedules_names:
|
||||
asset_depr_schedules_links = get_comma_separated_links(
|
||||
asset_depr_schedules_names, "Asset Depreciation Schedule"
|
||||
)
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
||||
).format(asset_depr_schedules_links)
|
||||
)
|
||||
add_asset_activity(self.name, _("Asset submitted"))
|
||||
|
||||
def on_cancel(self):
|
||||
self.validate_cancellation()
|
||||
@@ -71,10 +83,29 @@ class Asset(AccountsController):
|
||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
||||
make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
|
||||
self.db_set("booked_fixed_asset", 0)
|
||||
add_asset_activity(self.name, _("Asset cancelled"))
|
||||
|
||||
def after_insert(self):
|
||||
if not self.split_from:
|
||||
make_draft_asset_depr_schedules(self)
|
||||
if self.calculate_depreciation and not self.split_from:
|
||||
asset_depr_schedules_names = make_draft_asset_depr_schedules(self)
|
||||
asset_depr_schedules_links = get_comma_separated_links(
|
||||
asset_depr_schedules_names, "Asset Depreciation Schedule"
|
||||
)
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
|
||||
).format(asset_depr_schedules_links)
|
||||
)
|
||||
if not frappe.db.exists(
|
||||
{
|
||||
"doctype": "Asset Activity",
|
||||
"asset": self.name,
|
||||
}
|
||||
):
|
||||
add_asset_activity(self.name, _("Asset created"))
|
||||
|
||||
def after_delete(self):
|
||||
add_asset_activity(self.name, _("Asset deleted"))
|
||||
|
||||
def validate_asset_and_reference(self):
|
||||
if self.purchase_invoice or self.purchase_receipt:
|
||||
@@ -176,8 +207,11 @@ class Asset(AccountsController):
|
||||
|
||||
if not self.calculate_depreciation:
|
||||
return
|
||||
elif not self.finance_books:
|
||||
frappe.throw(_("Enter depreciation details"))
|
||||
else:
|
||||
if not self.finance_books:
|
||||
frappe.throw(_("Enter depreciation details"))
|
||||
if self.is_fully_depreciated:
|
||||
frappe.throw(_("Depreciation cannot be calculated for fully depreciated assets"))
|
||||
|
||||
if self.is_existing_asset:
|
||||
return
|
||||
@@ -258,7 +292,7 @@ class Asset(AccountsController):
|
||||
depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
|
||||
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
|
||||
frappe.throw(
|
||||
_("Opening Accumulated Depreciation must be less than equal to {0}").format(
|
||||
_("Opening Accumulated Depreciation must be less than or equal to {0}").format(
|
||||
depreciable_amount
|
||||
)
|
||||
)
|
||||
@@ -394,7 +428,9 @@ class Asset(AccountsController):
|
||||
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
|
||||
value_after_depreciation = self.finance_books[idx].value_after_depreciation
|
||||
|
||||
if flt(value_after_depreciation) <= expected_value_after_useful_life:
|
||||
if (
|
||||
flt(value_after_depreciation) <= expected_value_after_useful_life or self.is_fully_depreciated
|
||||
):
|
||||
status = "Fully Depreciated"
|
||||
elif flt(value_after_depreciation) < flt(self.gross_purchase_amount):
|
||||
status = "Partially Depreciated"
|
||||
@@ -880,6 +916,13 @@ def update_existing_asset(asset, remaining_qty, new_asset_name):
|
||||
},
|
||||
)
|
||||
|
||||
add_asset_activity(
|
||||
asset.name,
|
||||
_("Asset updated after being split into Asset {0}").format(
|
||||
get_link_to_form("Asset", new_asset_name)
|
||||
),
|
||||
)
|
||||
|
||||
for row in asset.get("finance_books"):
|
||||
value_after_depreciation = flt(
|
||||
(row.value_after_depreciation * remaining_qty) / asset.asset_quantity
|
||||
@@ -947,6 +990,15 @@ def create_new_asset_after_split(asset, split_qty):
|
||||
(row.expected_value_after_useful_life * split_qty) / asset.asset_quantity
|
||||
)
|
||||
|
||||
new_asset.insert()
|
||||
|
||||
add_asset_activity(
|
||||
new_asset.name,
|
||||
_("Asset created after being split from Asset {0}").format(
|
||||
get_link_to_form("Asset", asset.name)
|
||||
),
|
||||
)
|
||||
|
||||
new_asset.submit()
|
||||
new_asset.set_status()
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
get_checks_for_pl_and_bs_accounts,
|
||||
)
|
||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
get_asset_depr_schedule_doc,
|
||||
get_asset_depr_schedule_name,
|
||||
@@ -325,6 +326,8 @@ def scrap_asset(asset_name):
|
||||
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
|
||||
asset.set_status("Scrapped")
|
||||
|
||||
add_asset_activity(asset_name, _("Asset scrapped"))
|
||||
|
||||
frappe.msgprint(_("Asset scrapped via Journal Entry {0}").format(je.name))
|
||||
|
||||
|
||||
@@ -349,6 +352,8 @@ def restore_asset(asset_name):
|
||||
|
||||
asset.set_status()
|
||||
|
||||
add_asset_activity(asset_name, _("Asset restored"))
|
||||
|
||||
|
||||
def depreciate_asset(asset_doc, date, notes):
|
||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
||||
@@ -398,6 +403,15 @@ def reverse_depreciation_entry_made_after_disposal(asset, date):
|
||||
|
||||
reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
|
||||
reverse_journal_entry.posting_date = nowdate()
|
||||
|
||||
for account in reverse_journal_entry.accounts:
|
||||
account.update(
|
||||
{
|
||||
"reference_type": "Asset",
|
||||
"reference_name": asset.name,
|
||||
}
|
||||
)
|
||||
|
||||
frappe.flags.is_reverse_depr_entry = True
|
||||
reverse_journal_entry.submit()
|
||||
|
||||
|
||||
0
erpnext/assets/doctype/asset_activity/__init__.py
Normal file
0
erpnext/assets/doctype/asset_activity/__init__.py
Normal file
8
erpnext/assets/doctype/asset_activity/asset_activity.js
Normal file
8
erpnext/assets/doctype/asset_activity/asset_activity.js
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
// For license information, please see license.txt
|
||||
|
||||
// frappe.ui.form.on("Asset Activity", {
|
||||
// refresh(frm) {
|
||||
|
||||
// },
|
||||
// });
|
||||
109
erpnext/assets/doctype/asset_activity/asset_activity.json
Normal file
109
erpnext/assets/doctype/asset_activity/asset_activity.json
Normal file
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"actions": [],
|
||||
"creation": "2023-07-28 12:41:13.232505",
|
||||
"default_view": "List",
|
||||
"doctype": "DocType",
|
||||
"editable_grid": 1,
|
||||
"engine": "InnoDB",
|
||||
"field_order": [
|
||||
"asset",
|
||||
"column_break_vkdy",
|
||||
"date",
|
||||
"column_break_kkxv",
|
||||
"user",
|
||||
"section_break_romx",
|
||||
"subject"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "asset",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"in_standard_filter": 1,
|
||||
"label": "Asset",
|
||||
"options": "Asset",
|
||||
"print_width": "165",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"width": "165"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_vkdy",
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_romx",
|
||||
"fieldtype": "Section Break"
|
||||
},
|
||||
{
|
||||
"fieldname": "subject",
|
||||
"fieldtype": "Small Text",
|
||||
"in_list_view": 1,
|
||||
"label": "Subject",
|
||||
"print_width": "518",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"width": "518"
|
||||
},
|
||||
{
|
||||
"default": "now",
|
||||
"fieldname": "date",
|
||||
"fieldtype": "Datetime",
|
||||
"in_list_view": 1,
|
||||
"label": "Date",
|
||||
"print_width": "158",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"width": "158"
|
||||
},
|
||||
{
|
||||
"fieldname": "user",
|
||||
"fieldtype": "Link",
|
||||
"in_list_view": 1,
|
||||
"label": "User",
|
||||
"options": "User",
|
||||
"print_width": "150",
|
||||
"read_only": 1,
|
||||
"reqd": 1,
|
||||
"width": "150"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_kkxv",
|
||||
"fieldtype": "Column Break"
|
||||
}
|
||||
],
|
||||
"in_create": 1,
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2023-08-01 11:09:52.584482",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Activity",
|
||||
"owner": "Administrator",
|
||||
"permissions": [
|
||||
{
|
||||
"email": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Accounts User",
|
||||
"share": 1
|
||||
},
|
||||
{
|
||||
"email": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "Quality Manager",
|
||||
"share": 1
|
||||
}
|
||||
],
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": []
|
||||
}
|
||||
20
erpnext/assets/doctype/asset_activity/asset_activity.py
Normal file
20
erpnext/assets/doctype/asset_activity/asset_activity.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
|
||||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
from frappe.model.document import Document
|
||||
|
||||
|
||||
class AssetActivity(Document):
|
||||
pass
|
||||
|
||||
|
||||
def add_asset_activity(asset, subject):
|
||||
frappe.get_doc(
|
||||
{
|
||||
"doctype": "Asset Activity",
|
||||
"asset": asset,
|
||||
"subject": subject,
|
||||
"user": frappe.session.user,
|
||||
}
|
||||
).insert(ignore_permissions=True, ignore_links=True)
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
|
||||
# See license.txt
|
||||
|
||||
# import frappe
|
||||
from frappe.tests.utils import FrappeTestCase
|
||||
|
||||
|
||||
class TestAssetActivity(FrappeTestCase):
|
||||
pass
|
||||
@@ -18,6 +18,7 @@ from erpnext.assets.doctype.asset.depreciation import (
|
||||
reset_depreciation_schedule,
|
||||
reverse_depreciation_entry_made_after_disposal,
|
||||
)
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||
from erpnext.controllers.stock_controller import StockController
|
||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
||||
@@ -519,6 +520,13 @@ class AssetCapitalization(StockController):
|
||||
"fixed_asset_account", item=self.target_item_code, company=asset_doc.company
|
||||
)
|
||||
|
||||
add_asset_activity(
|
||||
asset_doc.name,
|
||||
_("Asset created after Asset Capitalization {0} was submitted").format(
|
||||
get_link_to_form("Asset Capitalization", self.name)
|
||||
),
|
||||
)
|
||||
|
||||
frappe.msgprint(
|
||||
_(
|
||||
"Asset {0} has been created. Please set the depreciation details if any and submit it."
|
||||
@@ -542,9 +550,30 @@ class AssetCapitalization(StockController):
|
||||
|
||||
def set_consumed_asset_status(self, asset):
|
||||
if self.docstatus == 1:
|
||||
asset.set_status("Capitalized" if self.target_is_fixed_asset else "Decapitalized")
|
||||
if self.target_is_fixed_asset:
|
||||
asset.set_status("Capitalized")
|
||||
add_asset_activity(
|
||||
asset.name,
|
||||
_("Asset capitalized after Asset Capitalization {0} was submitted").format(
|
||||
get_link_to_form("Asset Capitalization", self.name)
|
||||
),
|
||||
)
|
||||
else:
|
||||
asset.set_status("Decapitalized")
|
||||
add_asset_activity(
|
||||
asset.name,
|
||||
_("Asset decapitalized after Asset Capitalization {0} was submitted").format(
|
||||
get_link_to_form("Asset Capitalization", self.name)
|
||||
),
|
||||
)
|
||||
else:
|
||||
asset.set_status()
|
||||
add_asset_activity(
|
||||
asset.name,
|
||||
_("Asset restored after Asset Capitalization {0} was cancelled").format(
|
||||
get_link_to_form("Asset Capitalization", self.name)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@@ -571,6 +571,8 @@ def get_wdv_or_dd_depr_amount(
|
||||
|
||||
|
||||
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
|
||||
asset_depr_schedules_names = []
|
||||
|
||||
for row in asset_doc.get("finance_books"):
|
||||
draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
|
||||
asset_doc.name, "Draft", row.finance_book
|
||||
@@ -581,12 +583,20 @@ def make_draft_asset_depr_schedules_if_not_present(asset_doc):
|
||||
)
|
||||
|
||||
if not draft_asset_depr_schedule_name and not active_asset_depr_schedule_name:
|
||||
make_draft_asset_depr_schedule(asset_doc, row)
|
||||
name = make_draft_asset_depr_schedule(asset_doc, row)
|
||||
asset_depr_schedules_names.append(name)
|
||||
|
||||
return asset_depr_schedules_names
|
||||
|
||||
|
||||
def make_draft_asset_depr_schedules(asset_doc):
|
||||
asset_depr_schedules_names = []
|
||||
|
||||
for row in asset_doc.get("finance_books"):
|
||||
make_draft_asset_depr_schedule(asset_doc, row)
|
||||
name = make_draft_asset_depr_schedule(asset_doc, row)
|
||||
asset_depr_schedules_names.append(name)
|
||||
|
||||
return asset_depr_schedules_names
|
||||
|
||||
|
||||
def make_draft_asset_depr_schedule(asset_doc, row):
|
||||
@@ -596,6 +606,8 @@ def make_draft_asset_depr_schedule(asset_doc, row):
|
||||
|
||||
asset_depr_schedule_doc.insert()
|
||||
|
||||
return asset_depr_schedule_doc.name
|
||||
|
||||
|
||||
def update_draft_asset_depr_schedules(asset_doc):
|
||||
for row in asset_doc.get("finance_books"):
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
import frappe
|
||||
from frappe import _
|
||||
from frappe.model.document import Document
|
||||
from frappe.utils import get_link_to_form
|
||||
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
|
||||
|
||||
class AssetMovement(Document):
|
||||
@@ -128,5 +131,24 @@ class AssetMovement(Document):
|
||||
current_location = latest_movement_entry[0][0]
|
||||
current_employee = latest_movement_entry[0][1]
|
||||
|
||||
frappe.db.set_value("Asset", d.asset, "location", current_location)
|
||||
frappe.db.set_value("Asset", d.asset, "custodian", current_employee)
|
||||
frappe.db.set_value("Asset", d.asset, "location", current_location, update_modified=False)
|
||||
frappe.db.set_value("Asset", d.asset, "custodian", current_employee, update_modified=False)
|
||||
|
||||
if current_location and current_employee:
|
||||
add_asset_activity(
|
||||
d.asset,
|
||||
_("Asset received at Location {0} and issued to Employee {1}").format(
|
||||
get_link_to_form("Location", current_location),
|
||||
get_link_to_form("Employee", current_employee),
|
||||
),
|
||||
)
|
||||
elif current_location:
|
||||
add_asset_activity(
|
||||
d.asset,
|
||||
_("Asset transferred to Location {0}").format(get_link_to_form("Location", current_location)),
|
||||
)
|
||||
elif current_employee:
|
||||
add_asset_activity(
|
||||
d.asset,
|
||||
_("Asset issued to Employee {0}").format(get_link_to_form("Employee", current_employee)),
|
||||
)
|
||||
|
||||
@@ -8,6 +8,7 @@ from frappe.utils import add_months, cint, flt, get_link_to_form, getdate, time_
|
||||
import erpnext
|
||||
from erpnext.accounts.general_ledger import make_gl_entries
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_account
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
get_depr_schedule,
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
||||
@@ -25,8 +26,14 @@ class AssetRepair(AccountsController):
|
||||
self.calculate_total_repair_cost()
|
||||
|
||||
def update_status(self):
|
||||
if self.repair_status == "Pending":
|
||||
if self.repair_status == "Pending" and self.asset_doc.status != "Out of Order":
|
||||
frappe.db.set_value("Asset", self.asset, "status", "Out of Order")
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
_("Asset out of order due to Asset Repair {0}").format(
|
||||
get_link_to_form("Asset Repair", self.name)
|
||||
),
|
||||
)
|
||||
else:
|
||||
self.asset_doc.set_status()
|
||||
|
||||
@@ -68,6 +75,13 @@ class AssetRepair(AccountsController):
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||
self.asset_doc.save()
|
||||
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
_("Asset updated after completion of Asset Repair {0}").format(
|
||||
get_link_to_form("Asset Repair", self.name)
|
||||
),
|
||||
)
|
||||
|
||||
def before_cancel(self):
|
||||
self.asset_doc = frappe.get_doc("Asset", self.asset)
|
||||
|
||||
@@ -95,6 +109,13 @@ class AssetRepair(AccountsController):
|
||||
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||
self.asset_doc.save()
|
||||
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
_("Asset updated after cancellation of Asset Repair {0}").format(
|
||||
get_link_to_form("Asset Repair", self.name)
|
||||
),
|
||||
)
|
||||
|
||||
def after_delete(self):
|
||||
frappe.get_doc("Asset", self.asset).set_status()
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
||||
)
|
||||
from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
|
||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
||||
from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
|
||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||
get_asset_depr_schedule_doc,
|
||||
get_depreciation_amount,
|
||||
@@ -27,9 +28,21 @@ class AssetValueAdjustment(Document):
|
||||
def on_submit(self):
|
||||
self.make_depreciation_entry()
|
||||
self.reschedule_depreciations(self.new_asset_value)
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
_("Asset's value adjusted after submission of Asset Value Adjustment {0}").format(
|
||||
get_link_to_form("Asset Value Adjustment", self.name)
|
||||
),
|
||||
)
|
||||
|
||||
def on_cancel(self):
|
||||
self.reschedule_depreciations(self.current_asset_value)
|
||||
add_asset_activity(
|
||||
self.asset,
|
||||
_("Asset's value adjusted after cancellation of Asset Value Adjustment {0}").format(
|
||||
get_link_to_form("Asset Value Adjustment", self.name)
|
||||
),
|
||||
)
|
||||
|
||||
def validate_date(self):
|
||||
asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date")
|
||||
@@ -74,12 +87,16 @@ class AssetValueAdjustment(Document):
|
||||
"account": accumulated_depreciation_account,
|
||||
"credit_in_account_currency": self.difference_amount,
|
||||
"cost_center": depreciation_cost_center or self.cost_center,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": self.asset,
|
||||
}
|
||||
|
||||
debit_entry = {
|
||||
"account": depreciation_expense_account,
|
||||
"debit_in_account_currency": self.difference_amount,
|
||||
"cost_center": depreciation_cost_center or self.cost_center,
|
||||
"reference_type": "Asset",
|
||||
"reference_name": self.asset,
|
||||
}
|
||||
|
||||
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
},
|
||||
{
|
||||
"allow_on_submit": 1,
|
||||
"depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= get_today())",
|
||||
"depends_on": "eval:(doc.docstatus==1 && !doc.journal_entry && doc.schedule_date <= frappe.datetime.now_date())",
|
||||
"fieldname": "make_depreciation_entry",
|
||||
"fieldtype": "Button",
|
||||
"label": "Make Depreciation Entry"
|
||||
@@ -61,7 +61,7 @@
|
||||
],
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-03-13 23:17:15.849950",
|
||||
"modified": "2023-07-26 12:56:48.718736",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Depreciation Schedule",
|
||||
|
||||
0
erpnext/assets/report/asset_activity/__init__.py
Normal file
0
erpnext/assets/report/asset_activity/__init__.py
Normal file
33
erpnext/assets/report/asset_activity/asset_activity.json
Normal file
33
erpnext/assets/report/asset_activity/asset_activity.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"columns": [],
|
||||
"creation": "2023-08-01 11:14:46.581234",
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"json": "{}",
|
||||
"letterhead": null,
|
||||
"modified": "2023-08-01 11:14:46.581234",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Asset Activity",
|
||||
"owner": "Administrator",
|
||||
"prepared_report": 0,
|
||||
"ref_doctype": "Asset Activity",
|
||||
"report_name": "Asset Activity",
|
||||
"report_type": "Report Builder",
|
||||
"roles": [
|
||||
{
|
||||
"role": "System Manager"
|
||||
},
|
||||
{
|
||||
"role": "Accounts User"
|
||||
},
|
||||
{
|
||||
"role": "Quality Manager"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"add_total_row": 0,
|
||||
"add_total_row": 1,
|
||||
"columns": [],
|
||||
"creation": "2019-09-23 16:35:02.836134",
|
||||
"disable_prepared_report": 1,
|
||||
"disabled": 0,
|
||||
"docstatus": 0,
|
||||
"doctype": "Report",
|
||||
"filters": [],
|
||||
"idx": 0,
|
||||
"is_standard": "Yes",
|
||||
"modified": "2019-10-22 13:00:31.539726",
|
||||
"letterhead": null,
|
||||
"modified": "2023-07-26 21:03:20.722628",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Assets",
|
||||
"name": "Fixed Asset Register",
|
||||
|
||||
@@ -54,12 +54,12 @@ def get_conditions(filters):
|
||||
conditions["cost_center"] = filters.get("cost_center")
|
||||
|
||||
if status:
|
||||
# In Store assets are those that are not sold or scrapped
|
||||
# In Store assets are those that are not sold or scrapped or capitalized or decapitalized
|
||||
operand = "not in"
|
||||
if status not in "In Location":
|
||||
operand = "in"
|
||||
|
||||
conditions["status"] = (operand, ["Sold", "Scrapped"])
|
||||
conditions["status"] = (operand, ["Sold", "Scrapped", "Capitalized", "Decapitalized"])
|
||||
|
||||
return conditions
|
||||
|
||||
@@ -71,36 +71,6 @@ def get_data(filters):
|
||||
pr_supplier_map = get_purchase_receipt_supplier_map()
|
||||
pi_supplier_map = get_purchase_invoice_supplier_map()
|
||||
|
||||
group_by = frappe.scrub(filters.get("group_by"))
|
||||
|
||||
if group_by == "asset_category":
|
||||
fields = ["asset_category", "gross_purchase_amount", "opening_accumulated_depreciation"]
|
||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields, group_by=group_by)
|
||||
|
||||
elif group_by == "location":
|
||||
fields = ["location", "gross_purchase_amount", "opening_accumulated_depreciation"]
|
||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields, group_by=group_by)
|
||||
|
||||
else:
|
||||
fields = [
|
||||
"name as asset_id",
|
||||
"asset_name",
|
||||
"status",
|
||||
"department",
|
||||
"company",
|
||||
"cost_center",
|
||||
"calculate_depreciation",
|
||||
"purchase_receipt",
|
||||
"asset_category",
|
||||
"purchase_date",
|
||||
"gross_purchase_amount",
|
||||
"location",
|
||||
"available_for_use_date",
|
||||
"purchase_invoice",
|
||||
"opening_accumulated_depreciation",
|
||||
]
|
||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||
|
||||
assets_linked_to_fb = get_assets_linked_to_fb(filters)
|
||||
|
||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
||||
@@ -114,6 +84,31 @@ def get_data(filters):
|
||||
|
||||
depreciation_amount_map = get_asset_depreciation_amount_map(filters, finance_book)
|
||||
|
||||
group_by = frappe.scrub(filters.get("group_by"))
|
||||
|
||||
if group_by in ("asset_category", "location"):
|
||||
data = get_group_by_data(group_by, conditions, assets_linked_to_fb, depreciation_amount_map)
|
||||
return data
|
||||
|
||||
fields = [
|
||||
"name as asset_id",
|
||||
"asset_name",
|
||||
"status",
|
||||
"department",
|
||||
"company",
|
||||
"cost_center",
|
||||
"calculate_depreciation",
|
||||
"purchase_receipt",
|
||||
"asset_category",
|
||||
"purchase_date",
|
||||
"gross_purchase_amount",
|
||||
"location",
|
||||
"available_for_use_date",
|
||||
"purchase_invoice",
|
||||
"opening_accumulated_depreciation",
|
||||
]
|
||||
assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||
|
||||
for asset in assets_record:
|
||||
if (
|
||||
assets_linked_to_fb
|
||||
@@ -136,7 +131,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),
|
||||
"depreciated_amount": depreciation_amount_map.get(asset.asset_id) or 0.0,
|
||||
"available_for_use_date": asset.available_for_use_date,
|
||||
"location": asset.location,
|
||||
"asset_category": asset.asset_category,
|
||||
@@ -230,12 +225,11 @@ def get_assets_linked_to_fb(filters):
|
||||
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
|
||||
start_date = (
|
||||
filters.from_date if filters.filter_based_on == "Date Range" else filters.year_start_date
|
||||
)
|
||||
end_date = filters.to_date if filters.filter_based_on == "Date Range" else filters.year_end_date
|
||||
|
||||
asset = frappe.qb.DocType("Asset")
|
||||
gle = frappe.qb.DocType("GL Entry")
|
||||
@@ -256,25 +250,77 @@ def get_asset_depreciation_amount_map(filters, finance_book):
|
||||
)
|
||||
.where(gle.debit != 0)
|
||||
.where(gle.is_cancelled == 0)
|
||||
.where(company.name == filters.company)
|
||||
.where(asset.docstatus == 1)
|
||||
.groupby(asset.name)
|
||||
)
|
||||
|
||||
if filters.only_existing_assets:
|
||||
query = query.where(asset.is_existing_asset == 1)
|
||||
if filters.asset_category:
|
||||
query = query.where(asset.asset_category == filters.asset_category)
|
||||
if filters.cost_center:
|
||||
query = query.where(asset.cost_center == filters.cost_center)
|
||||
if filters.status:
|
||||
if filters.status == "In Location":
|
||||
query = query.where(asset.status.notin(["Sold", "Scrapped", "Capitalized", "Decapitalized"]))
|
||||
else:
|
||||
query = query.where(asset.status.isin(["Sold", "Scrapped", "Capitalized", "Decapitalized"]))
|
||||
if finance_book:
|
||||
query = query.where(
|
||||
(gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull())
|
||||
)
|
||||
else:
|
||||
query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull()))
|
||||
|
||||
if filters.filter_based_on in ("Date Range", "Fiscal Year"):
|
||||
query = query.where(gle.posting_date <= date)
|
||||
query = query.where(gle.posting_date >= start_date)
|
||||
query = query.where(gle.posting_date <= end_date)
|
||||
|
||||
query = query.groupby(asset.name)
|
||||
|
||||
asset_depr_amount_map = query.run()
|
||||
|
||||
return dict(asset_depr_amount_map)
|
||||
|
||||
|
||||
def get_group_by_data(group_by, conditions, assets_linked_to_fb, depreciation_amount_map):
|
||||
fields = [
|
||||
group_by,
|
||||
"name",
|
||||
"gross_purchase_amount",
|
||||
"opening_accumulated_depreciation",
|
||||
"calculate_depreciation",
|
||||
]
|
||||
assets = frappe.db.get_all("Asset", filters=conditions, fields=fields)
|
||||
|
||||
data = []
|
||||
|
||||
for a in assets:
|
||||
if assets_linked_to_fb and a.calculate_depreciation and a.name not in assets_linked_to_fb:
|
||||
continue
|
||||
|
||||
a["depreciated_amount"] = depreciation_amount_map.get(a["name"], 0.0)
|
||||
a["asset_value"] = (
|
||||
a["gross_purchase_amount"] - a["opening_accumulated_depreciation"] - a["depreciated_amount"]
|
||||
)
|
||||
|
||||
del a["name"]
|
||||
del a["calculate_depreciation"]
|
||||
|
||||
idx = ([i for i, d in enumerate(data) if a[group_by] == d[group_by]] or [None])[0]
|
||||
if idx is None:
|
||||
data.append(a)
|
||||
else:
|
||||
for field in (
|
||||
"gross_purchase_amount",
|
||||
"opening_accumulated_depreciation",
|
||||
"depreciated_amount",
|
||||
"asset_value",
|
||||
):
|
||||
data[idx][field] = data[idx][field] + a[field]
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_purchase_receipt_supplier_map():
|
||||
return frappe._dict(
|
||||
frappe.db.sql(
|
||||
@@ -313,35 +359,35 @@ def get_columns(filters):
|
||||
"fieldtype": "Link",
|
||||
"fieldname": frappe.scrub(filters.get("group_by")),
|
||||
"options": filters.get("group_by"),
|
||||
"width": 120,
|
||||
"width": 216,
|
||||
},
|
||||
{
|
||||
"label": _("Gross Purchase Amount"),
|
||||
"fieldname": "gross_purchase_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "company:currency",
|
||||
"width": 100,
|
||||
"width": 250,
|
||||
},
|
||||
{
|
||||
"label": _("Opening Accumulated Depreciation"),
|
||||
"fieldname": "opening_accumulated_depreciation",
|
||||
"fieldtype": "Currency",
|
||||
"options": "company:currency",
|
||||
"width": 90,
|
||||
"width": 250,
|
||||
},
|
||||
{
|
||||
"label": _("Depreciated Amount"),
|
||||
"fieldname": "depreciated_amount",
|
||||
"fieldtype": "Currency",
|
||||
"options": "company:currency",
|
||||
"width": 100,
|
||||
"width": 250,
|
||||
},
|
||||
{
|
||||
"label": _("Asset Value"),
|
||||
"fieldname": "asset_value",
|
||||
"fieldtype": "Currency",
|
||||
"options": "company:currency",
|
||||
"width": 100,
|
||||
"width": 250,
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -183,6 +183,17 @@
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
},
|
||||
{
|
||||
"dependencies": "Asset Activity",
|
||||
"hidden": 0,
|
||||
"is_query_report": 0,
|
||||
"label": "Asset Activity",
|
||||
"link_count": 0,
|
||||
"link_to": "Asset Activity",
|
||||
"link_type": "Report",
|
||||
"onboard": 0,
|
||||
"type": "Link"
|
||||
}
|
||||
],
|
||||
"modified": "2023-05-24 14:47:20.243146",
|
||||
|
||||
@@ -65,7 +65,7 @@ frappe.ui.form.on("Purchase Order", {
|
||||
get_materials_from_supplier: function(frm) {
|
||||
let po_details = [];
|
||||
|
||||
if (frm.doc.supplied_items && (frm.doc.per_received == 100 || frm.doc.status === 'Closed')) {
|
||||
if (frm.doc.supplied_items && (flt(frm.doc.per_received, 2) == 100 || frm.doc.status === 'Closed')) {
|
||||
frm.doc.supplied_items.forEach(d => {
|
||||
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
|
||||
po_details.push(d.name)
|
||||
@@ -184,7 +184,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
}
|
||||
|
||||
if(!in_list(["Closed", "Delivered"], doc.status)) {
|
||||
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received) < 100 && flt(this.frm.doc.per_billed) < 100) {
|
||||
if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received, 2) < 100 && flt(this.frm.doc.per_billed, 2) < 100) {
|
||||
// Don't add Update Items button if the PO is following the new subcontracting flow.
|
||||
if (!(this.frm.doc.is_subcontracted && !this.frm.doc.is_old_subcontracting_flow)) {
|
||||
this.frm.add_custom_button(__('Update Items'), () => {
|
||||
@@ -198,7 +198,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
}
|
||||
}
|
||||
if (this.frm.has_perm("submit")) {
|
||||
if(flt(doc.per_billed, 6) < 100 || flt(doc.per_received, 6) < 100) {
|
||||
if(flt(doc.per_billed, 2) < 100 || flt(doc.per_received, 2) < 100) {
|
||||
if (doc.status != "On Hold") {
|
||||
this.frm.add_custom_button(__('Hold'), () => this.hold_purchase_order(), __("Status"));
|
||||
} else{
|
||||
@@ -221,7 +221,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
}
|
||||
if(doc.status != "Closed") {
|
||||
if (doc.status != "On Hold") {
|
||||
if(flt(doc.per_received) < 100 && allow_receipt) {
|
||||
if(flt(doc.per_received, 2) < 100 && allow_receipt) {
|
||||
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
|
||||
if (doc.is_subcontracted) {
|
||||
if (doc.is_old_subcontracting_flow) {
|
||||
@@ -234,11 +234,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
}
|
||||
}
|
||||
}
|
||||
if(flt(doc.per_billed) < 100)
|
||||
if(flt(doc.per_billed, 2) < 100)
|
||||
cur_frm.add_custom_button(__('Purchase Invoice'),
|
||||
this.make_purchase_invoice, __('Create'));
|
||||
|
||||
if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
|
||||
if(flt(doc.per_billed, 2) < 100 && doc.status != "Delivered") {
|
||||
this.frm.add_custom_button(
|
||||
__('Payment'),
|
||||
() => this.make_payment_entry(),
|
||||
@@ -246,17 +246,11 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
||||
);
|
||||
}
|
||||
|
||||
if(flt(doc.per_billed) < 100) {
|
||||
if(flt(doc.per_billed, 2) < 100) {
|
||||
this.frm.add_custom_button(__('Payment Request'),
|
||||
function() { me.make_payment_request() }, __('Create'));
|
||||
}
|
||||
|
||||
if(!doc.auto_repeat) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __('Create'))
|
||||
}
|
||||
|
||||
if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
|
||||
let me = this;
|
||||
let internal = me.frm.doc.is_internal_supplier;
|
||||
|
||||
@@ -27,13 +27,13 @@ frappe.query_reports["Procurement Tracker"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
fieldname:"to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -35,14 +35,14 @@ frappe.query_reports["Purchase Analytics"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname:"to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -206,9 +206,11 @@ def post_process(doctype, data):
|
||||
)
|
||||
|
||||
if doc.get("per_delivered"):
|
||||
doc.status_percent += flt(doc.per_delivered)
|
||||
doc.status_percent += flt(doc.per_delivered, 2)
|
||||
doc.status_display.append(
|
||||
_("Delivered") if doc.per_delivered == 100 else _("{0}% Delivered").format(doc.per_delivered)
|
||||
_("Delivered")
|
||||
if flt(doc.per_delivered, 2) == 100
|
||||
else _("{0}% Delivered").format(doc.per_delivered)
|
||||
)
|
||||
|
||||
if hasattr(doc, "set_indicator"):
|
||||
|
||||
@@ -6,13 +6,13 @@ frappe.query_reports["Campaign Efficiency"] = {
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
"fieldname": "from_date",
|
||||
"label": __("From Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_start_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
},
|
||||
{
|
||||
"fieldname": "to_date",
|
||||
"label": __("To Date"),
|
||||
"fieldtype": "Date",
|
||||
"default": frappe.defaults.get_user_default("year_end_date"),
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
}
|
||||
]};
|
||||
|
||||
@@ -410,11 +410,11 @@ scheduler_events = {
|
||||
],
|
||||
},
|
||||
"all": [
|
||||
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
||||
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
|
||||
],
|
||||
"hourly": [
|
||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
||||
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
||||
"erpnext.projects.doctype.project.project.hourly_reminder",
|
||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||
],
|
||||
|
||||
@@ -93,6 +93,7 @@ class BOMUpdateLog(Document):
|
||||
else:
|
||||
frappe.enqueue(
|
||||
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
|
||||
queue="long",
|
||||
update_doc=self,
|
||||
now=frappe.flags.in_test,
|
||||
enqueue_after_commit=True,
|
||||
|
||||
@@ -157,12 +157,19 @@ def get_next_higher_level_boms(
|
||||
def get_leaf_boms() -> List[str]:
|
||||
"Get BOMs that have no dependencies."
|
||||
|
||||
return frappe.db.sql_list(
|
||||
"""select name from `tabBOM` bom
|
||||
where docstatus=1 and is_active=1
|
||||
and not exists(select bom_no from `tabBOM Item`
|
||||
where parent=bom.name and ifnull(bom_no, '')!='')"""
|
||||
)
|
||||
bom = frappe.qb.DocType("BOM")
|
||||
bom_item = frappe.qb.DocType("BOM Item")
|
||||
|
||||
boms = (
|
||||
frappe.qb.from_(bom)
|
||||
.left_join(bom_item)
|
||||
.on((bom.name == bom_item.parent) & (bom_item.bom_no != ""))
|
||||
.select(bom.name)
|
||||
.where((bom.docstatus == 1) & (bom.is_active == 1) & (bom_item.bom_no.isnull()))
|
||||
.distinct()
|
||||
).run(pluck=True)
|
||||
|
||||
return boms
|
||||
|
||||
|
||||
def _generate_dependence_map() -> defaultdict:
|
||||
|
||||
@@ -544,12 +544,12 @@ class JobCard(Document):
|
||||
if self.for_quantity and flt(total_completed_qty, precision) != flt(
|
||||
self.for_quantity, precision
|
||||
):
|
||||
total_completed_qty = bold(_("Total Completed Qty"))
|
||||
total_completed_qty_label = bold(_("Total Completed Qty"))
|
||||
qty_to_manufacture = bold(_("Qty to Manufacture"))
|
||||
|
||||
frappe.throw(
|
||||
_("The {0} ({1}) must be equal to {2} ({3})").format(
|
||||
total_completed_qty,
|
||||
total_completed_qty_label,
|
||||
bold(flt(total_completed_qty, precision)),
|
||||
qty_to_manufacture,
|
||||
bold(self.for_quantity),
|
||||
|
||||
@@ -9,19 +9,25 @@ frappe.ui.form.on('Production Plan', {
|
||||
item.temporary_name = item.name;
|
||||
});
|
||||
},
|
||||
|
||||
setup(frm) {
|
||||
frm.trigger("setup_queries");
|
||||
|
||||
frm.custom_make_buttons = {
|
||||
'Work Order': 'Work Order / Subcontract PO',
|
||||
'Material Request': 'Material Request',
|
||||
};
|
||||
},
|
||||
|
||||
frm.fields_dict['po_items'].grid.get_field('warehouse').get_query = function(doc) {
|
||||
setup_queries(frm) {
|
||||
frm.set_query("sales_order", "sales_orders", () => {
|
||||
return {
|
||||
query: "erpnext.manufacturing.doctype.production_plan.production_plan.sales_order_query",
|
||||
filters: {
|
||||
company: doc.company
|
||||
company: frm.doc.company,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query('for_warehouse', function(doc) {
|
||||
return {
|
||||
@@ -42,32 +48,40 @@ frappe.ui.form.on('Production Plan', {
|
||||
};
|
||||
});
|
||||
|
||||
frm.fields_dict['po_items'].grid.get_field('item_code').get_query = function(doc) {
|
||||
frm.set_query("item_code", "po_items", (doc, cdt, cdn) => {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.item_query",
|
||||
filters:{
|
||||
'is_stock_item': 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.fields_dict['po_items'].grid.get_field('bom_no').get_query = function(doc, cdt, cdn) {
|
||||
frm.set_query("bom_no", "po_items", (doc, cdt, cdn) => {
|
||||
var d = locals[cdt][cdn];
|
||||
if (d.item_code) {
|
||||
return {
|
||||
query: "erpnext.controllers.queries.bom",
|
||||
filters:{'item': cstr(d.item_code), 'docstatus': 1}
|
||||
filters:{'item': d.item_code, 'docstatus': 1}
|
||||
}
|
||||
} else frappe.msgprint(__("Please enter Item first"));
|
||||
}
|
||||
});
|
||||
|
||||
frm.fields_dict['mr_items'].grid.get_field('warehouse').get_query = function(doc) {
|
||||
frm.set_query("warehouse", "mr_items", (doc) => {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
frm.set_query("warehouse", "po_items", (doc) => {
|
||||
return {
|
||||
filters: {
|
||||
company: doc.company
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refresh(frm) {
|
||||
@@ -436,7 +450,7 @@ frappe.ui.form.on("Production Plan Item", {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
frappe.ui.form.on("Material Request Plan Item", {
|
||||
@@ -467,31 +481,36 @@ frappe.ui.form.on("Material Request Plan Item", {
|
||||
|
||||
frappe.ui.form.on("Production Plan Sales Order", {
|
||||
sales_order(frm, cdt, cdn) {
|
||||
const { sales_order } = locals[cdt][cdn];
|
||||
let row = locals[cdt][cdn];
|
||||
const sales_order = row.sales_order;
|
||||
if (!sales_order) {
|
||||
return;
|
||||
}
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details",
|
||||
args: { sales_order },
|
||||
callback(r) {
|
||||
const {transaction_date, customer, grand_total} = r.message;
|
||||
frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date);
|
||||
frappe.model.set_value(cdt, cdn, 'customer', customer);
|
||||
frappe.model.set_value(cdt, cdn, 'grand_total', grand_total);
|
||||
}
|
||||
});
|
||||
|
||||
if (row.sales_order) {
|
||||
frm.call({
|
||||
method: "validate_sales_orders",
|
||||
doc: frm.doc,
|
||||
args: {
|
||||
sales_order: row.sales_order,
|
||||
},
|
||||
callback(r) {
|
||||
frappe.call({
|
||||
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details",
|
||||
args: { sales_order },
|
||||
callback(r) {
|
||||
const {transaction_date, customer, grand_total} = r.message;
|
||||
frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date);
|
||||
frappe.model.set_value(cdt, cdn, 'customer', customer);
|
||||
frappe.model.set_value(cdt, cdn, 'grand_total', grand_total);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cur_frm.fields_dict['sales_orders'].grid.get_field("sales_order").get_query = function() {
|
||||
return{
|
||||
filters: [
|
||||
['Sales Order','docstatus', '=' ,1]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
frappe.tour['Production Plan'] = [
|
||||
{
|
||||
fieldname: "get_items_from",
|
||||
|
||||
@@ -228,10 +228,10 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "To know more about projected quantity, <a href=\"https://erpnext.com/docs/user/manual/en/stock/projected-quantity\" style=\"text-decoration: underline;\" target=\"_blank\">click here</a>.",
|
||||
"description": "If enabled, the system won't create material requests for the available items.",
|
||||
"fieldname": "ignore_existing_ordered_qty",
|
||||
"fieldtype": "Check",
|
||||
"label": "Ignore Existing Projected Quantity"
|
||||
"label": "Ignore Available Stock"
|
||||
},
|
||||
{
|
||||
"fieldname": "column_break_25",
|
||||
@@ -339,7 +339,7 @@
|
||||
"depends_on": "eval:doc.get_items_from == 'Sales Order'",
|
||||
"fieldname": "combine_items",
|
||||
"fieldtype": "Check",
|
||||
"label": "Consolidate Items"
|
||||
"label": "Consolidate Sales Order Items"
|
||||
},
|
||||
{
|
||||
"fieldname": "section_break_25",
|
||||
@@ -399,7 +399,7 @@
|
||||
},
|
||||
{
|
||||
"default": "0",
|
||||
"description": "System consider the projected quantity to check available or will be available sub-assembly items ",
|
||||
"description": "If this checkbox is enabled, then the system won\u2019t run the MRP for the available sub-assembly items.",
|
||||
"fieldname": "skip_available_sub_assembly_item",
|
||||
"fieldtype": "Check",
|
||||
"label": "Skip Available Sub Assembly Items"
|
||||
@@ -422,7 +422,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_submittable": 1,
|
||||
"links": [],
|
||||
"modified": "2023-05-22 23:36:31.770517",
|
||||
"modified": "2023-07-28 13:37:43.926686",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Manufacturing",
|
||||
"name": "Production Plan",
|
||||
|
||||
@@ -39,6 +39,36 @@ class ProductionPlan(Document):
|
||||
self.set_status()
|
||||
self._rename_temporary_references()
|
||||
validate_uom_is_integer(self, "stock_uom", "planned_qty")
|
||||
self.validate_sales_orders()
|
||||
|
||||
@frappe.whitelist()
|
||||
def validate_sales_orders(self, sales_order=None):
|
||||
sales_orders = []
|
||||
|
||||
if sales_order:
|
||||
sales_orders.append(sales_order)
|
||||
else:
|
||||
sales_orders = [row.sales_order for row in self.sales_orders if row.sales_order]
|
||||
|
||||
data = sales_order_query(filters={"company": self.company, "sales_orders": sales_orders})
|
||||
|
||||
title = _("Production Plan Already Submitted")
|
||||
if not data:
|
||||
msg = _("No items are available in the sales order {0} for production").format(sales_orders[0])
|
||||
if len(sales_orders) > 1:
|
||||
sales_orders = ", ".join(sales_orders)
|
||||
msg = _("No items are available in sales orders {0} for production").format(sales_orders)
|
||||
|
||||
frappe.throw(msg, title=title)
|
||||
|
||||
data = [d[0] for d in data]
|
||||
|
||||
for sales_order in sales_orders:
|
||||
if sales_order not in data:
|
||||
frappe.throw(
|
||||
_("No items are available in the sales order {0} for production").format(sales_order),
|
||||
title=title,
|
||||
)
|
||||
|
||||
def set_pending_qty_in_row_without_reference(self):
|
||||
"Set Pending Qty in independent rows (not from SO or MR)."
|
||||
@@ -205,6 +235,7 @@ class ProductionPlan(Document):
|
||||
).as_("pending_qty"),
|
||||
so_item.description,
|
||||
so_item.name,
|
||||
so_item.bom_no,
|
||||
)
|
||||
.distinct()
|
||||
.where(
|
||||
@@ -342,7 +373,7 @@ class ProductionPlan(Document):
|
||||
"item_code": data.item_code,
|
||||
"description": data.description or item_details.description,
|
||||
"stock_uom": item_details and item_details.stock_uom or "",
|
||||
"bom_no": item_details and item_details.bom_no or "",
|
||||
"bom_no": data.bom_no or item_details and item_details.bom_no or "",
|
||||
"planned_qty": data.pending_qty,
|
||||
"pending_qty": data.pending_qty,
|
||||
"planned_start_date": now_datetime(),
|
||||
@@ -401,11 +432,50 @@ class ProductionPlan(Document):
|
||||
|
||||
def on_submit(self):
|
||||
self.update_bin_qty()
|
||||
self.update_sales_order()
|
||||
|
||||
def on_cancel(self):
|
||||
self.db_set("status", "Cancelled")
|
||||
self.delete_draft_work_order()
|
||||
self.update_bin_qty()
|
||||
self.update_sales_order()
|
||||
|
||||
def update_sales_order(self):
|
||||
sales_orders = [row.sales_order for row in self.po_items if row.sales_order]
|
||||
if sales_orders:
|
||||
so_wise_planned_qty = self.get_so_wise_planned_qty(sales_orders)
|
||||
|
||||
for row in self.po_items:
|
||||
if not row.sales_order and not row.sales_order_item:
|
||||
continue
|
||||
|
||||
key = (row.sales_order, row.sales_order_item)
|
||||
frappe.db.set_value(
|
||||
"Sales Order Item",
|
||||
row.sales_order_item,
|
||||
"production_plan_qty",
|
||||
flt(so_wise_planned_qty.get(key)),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_so_wise_planned_qty(sales_orders):
|
||||
so_wise_planned_qty = frappe._dict()
|
||||
data = frappe.get_all(
|
||||
"Production Plan Item",
|
||||
fields=["sales_order", "sales_order_item", "SUM(planned_qty) as qty"],
|
||||
filters={
|
||||
"sales_order": ("in", sales_orders),
|
||||
"docstatus": 1,
|
||||
"sales_order_item": ("is", "set"),
|
||||
},
|
||||
group_by="sales_order, sales_order_item",
|
||||
)
|
||||
|
||||
for row in data:
|
||||
key = (row.sales_order, row.sales_order_item)
|
||||
so_wise_planned_qty[key] = row.qty
|
||||
|
||||
return so_wise_planned_qty
|
||||
|
||||
def update_bin_qty(self):
|
||||
for d in self.mr_items:
|
||||
@@ -719,6 +789,9 @@ class ProductionPlan(Document):
|
||||
sub_assembly_items_store = [] # temporary store to process all subassembly items
|
||||
|
||||
for row in self.po_items:
|
||||
if self.skip_available_sub_assembly_item and not row.warehouse:
|
||||
frappe.throw(_("Row #{0}: Please select the FG Warehouse in Assembly Items").format(row.idx))
|
||||
|
||||
if not row.item_code:
|
||||
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
|
||||
|
||||
@@ -1142,7 +1215,7 @@ def get_sales_orders(self):
|
||||
& (so.docstatus == 1)
|
||||
& (so.status.notin(["Stopped", "Closed"]))
|
||||
& (so.company == self.company)
|
||||
& (so_item.qty > so_item.work_order_qty)
|
||||
& (so_item.qty > so_item.production_plan_qty)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1566,7 +1639,6 @@ def get_reserved_qty_for_production_plan(item_code, warehouse):
|
||||
def get_raw_materials_of_sub_assembly_items(
|
||||
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
|
||||
):
|
||||
|
||||
bei = frappe.qb.DocType("BOM Item")
|
||||
bom = frappe.qb.DocType("BOM")
|
||||
item = frappe.qb.DocType("Item")
|
||||
@@ -1609,7 +1681,10 @@ def get_raw_materials_of_sub_assembly_items(
|
||||
|
||||
for item in items:
|
||||
key = (item.item_code, item.bom_no)
|
||||
if item.bom_no and key in sub_assembly_items:
|
||||
if item.bom_no and key not in sub_assembly_items:
|
||||
continue
|
||||
|
||||
if item.bom_no:
|
||||
planned_qty = flt(sub_assembly_items[key])
|
||||
get_raw_materials_of_sub_assembly_items(
|
||||
item_details,
|
||||
@@ -1626,3 +1701,42 @@ def get_raw_materials_of_sub_assembly_items(
|
||||
item_details.setdefault(item.get("item_code"), item)
|
||||
|
||||
return item_details
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def sales_order_query(
|
||||
doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None
|
||||
):
|
||||
frappe.has_permission("Production Plan", throw=True)
|
||||
|
||||
if not filters:
|
||||
filters = {}
|
||||
|
||||
so_table = frappe.qb.DocType("Sales Order")
|
||||
table = frappe.qb.DocType("Sales Order Item")
|
||||
|
||||
query = (
|
||||
frappe.qb.from_(so_table)
|
||||
.join(table)
|
||||
.on(table.parent == so_table.name)
|
||||
.select(table.parent)
|
||||
.distinct()
|
||||
.where((table.qty > table.production_plan_qty) & (table.docstatus == 1))
|
||||
)
|
||||
|
||||
if filters.get("company"):
|
||||
query = query.where(so_table.company == filters.get("company"))
|
||||
|
||||
if filters.get("sales_orders"):
|
||||
query = query.where(so_table.name.isin(filters.get("sales_orders")))
|
||||
|
||||
if txt:
|
||||
query = query.where(table.item_code.like(f"{txt}%"))
|
||||
|
||||
if page_len:
|
||||
query = query.limit(page_len)
|
||||
|
||||
if start:
|
||||
query = query.offset(start)
|
||||
|
||||
return query.run()
|
||||
|
||||
@@ -225,6 +225,102 @@ class TestProductionPlan(FrappeTestCase):
|
||||
|
||||
self.assertEqual(sales_orders, [])
|
||||
|
||||
def test_donot_allow_to_make_multiple_pp_against_same_so(self):
|
||||
item = "Test SO Production Item 1"
|
||||
create_item(item)
|
||||
|
||||
raw_material = "Test SO RM Production Item 1"
|
||||
create_item(raw_material)
|
||||
|
||||
if not frappe.db.get_value("BOM", {"item": item}):
|
||||
make_bom(item=item, raw_materials=[raw_material])
|
||||
|
||||
so = make_sales_order(item_code=item, qty=4)
|
||||
pln = frappe.new_doc("Production Plan")
|
||||
pln.company = so.company
|
||||
pln.get_items_from = "Sales Order"
|
||||
|
||||
pln.append(
|
||||
"sales_orders",
|
||||
{
|
||||
"sales_order": so.name,
|
||||
"sales_order_date": so.transaction_date,
|
||||
"customer": so.customer,
|
||||
"grand_total": so.grand_total,
|
||||
},
|
||||
)
|
||||
|
||||
pln.get_so_items()
|
||||
pln.submit()
|
||||
|
||||
pln = frappe.new_doc("Production Plan")
|
||||
pln.company = so.company
|
||||
pln.get_items_from = "Sales Order"
|
||||
|
||||
pln.append(
|
||||
"sales_orders",
|
||||
{
|
||||
"sales_order": so.name,
|
||||
"sales_order_date": so.transaction_date,
|
||||
"customer": so.customer,
|
||||
"grand_total": so.grand_total,
|
||||
},
|
||||
)
|
||||
|
||||
pln.get_so_items()
|
||||
self.assertRaises(frappe.ValidationError, pln.save)
|
||||
|
||||
def test_so_based_bill_of_material(self):
|
||||
item = "Test SO Production Item 1"
|
||||
create_item(item)
|
||||
|
||||
raw_material = "Test SO RM Production Item 1"
|
||||
create_item(raw_material)
|
||||
|
||||
bom1 = make_bom(item=item, raw_materials=[raw_material])
|
||||
|
||||
so = make_sales_order(item_code=item, qty=4)
|
||||
|
||||
# Create new BOM and assign to new sales order
|
||||
bom2 = make_bom(item=item, raw_materials=[raw_material])
|
||||
so2 = make_sales_order(item_code=item, qty=4)
|
||||
|
||||
pln1 = frappe.new_doc("Production Plan")
|
||||
pln1.company = so.company
|
||||
pln1.get_items_from = "Sales Order"
|
||||
|
||||
pln1.append(
|
||||
"sales_orders",
|
||||
{
|
||||
"sales_order": so.name,
|
||||
"sales_order_date": so.transaction_date,
|
||||
"customer": so.customer,
|
||||
"grand_total": so.grand_total,
|
||||
},
|
||||
)
|
||||
|
||||
pln1.get_so_items()
|
||||
|
||||
self.assertEqual(pln1.po_items[0].bom_no, bom1.name)
|
||||
|
||||
pln2 = frappe.new_doc("Production Plan")
|
||||
pln2.company = so2.company
|
||||
pln2.get_items_from = "Sales Order"
|
||||
|
||||
pln2.append(
|
||||
"sales_orders",
|
||||
{
|
||||
"sales_order": so2.name,
|
||||
"sales_order_date": so2.transaction_date,
|
||||
"customer": so2.customer,
|
||||
"grand_total": so2.grand_total,
|
||||
},
|
||||
)
|
||||
|
||||
pln2.get_so_items()
|
||||
|
||||
self.assertEqual(pln2.po_items[0].bom_no, bom2.name)
|
||||
|
||||
def test_production_plan_combine_items(self):
|
||||
"Test combining FG items in Production Plan."
|
||||
item = "Test Production Item 1"
|
||||
|
||||
@@ -114,7 +114,7 @@ class Workstation(Document):
|
||||
|
||||
if schedule_date in tuple(get_holidays(self.holiday_list)):
|
||||
schedule_date = add_days(schedule_date, 1)
|
||||
self.validate_workstation_holiday(schedule_date, skip_holiday_list_check=True)
|
||||
return self.validate_workstation_holiday(schedule_date, skip_holiday_list_check=True)
|
||||
|
||||
return schedule_date
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@ frappe.query_reports["Job Card Summary"] = {
|
||||
label: __("From Posting Date"),
|
||||
fieldname:"from_date",
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
label: __("To Posting Date"),
|
||||
fieldname:"to_date",
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -16,14 +16,14 @@ frappe.query_reports["Production Analytics"] = {
|
||||
fieldname: "from_date",
|
||||
label: __("From Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_start_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
fieldname:"to_date",
|
||||
label: __("To Date"),
|
||||
fieldtype: "Date",
|
||||
default: frappe.defaults.get_user_default("year_end_date"),
|
||||
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||
reqd: 1
|
||||
},
|
||||
{
|
||||
|
||||
@@ -6,7 +6,9 @@ def execute():
|
||||
frappe.reload_doc("selling", "doctype", "sales_order_item")
|
||||
|
||||
for doctype in ["Sales Order", "Material Request"]:
|
||||
condition = " and child_doc.stock_qty > child_doc.produced_qty and doc.per_delivered < 100"
|
||||
condition = (
|
||||
" and child_doc.stock_qty > child_doc.produced_qty and ROUND(doc.per_delivered, 2) < 100"
|
||||
)
|
||||
if doctype == "Material Request":
|
||||
condition = " and doc.per_ordered < 100 and doc.material_request_type = 'Manufacture'"
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ def get_issued_items_cost():
|
||||
"""select se.project, sum(se_item.amount) as amount
|
||||
from `tabStock Entry` se, `tabStock Entry Detail` se_item
|
||||
where se.name = se_item.parent and se.docstatus = 1 and ifnull(se_item.t_warehouse, '') = ''
|
||||
and ifnull(se.project, '') != '' group by se.project""",
|
||||
and se.project != '' group by se.project""",
|
||||
as_dict=1,
|
||||
)
|
||||
|
||||
|
||||
@@ -172,9 +172,9 @@ erpnext.taxes_and_totals = class TaxesAndTotals extends erpnext.payments {
|
||||
|
||||
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
|
||||
|
||||
if (!this.discount_amount_applied && cur_frm) {
|
||||
cur_frm.cscript.validate_taxes_and_charges(tax.doctype, tax.name);
|
||||
me.validate_inclusive_tax(tax);
|
||||
if (!this.discount_amount_applied) {
|
||||
erpnext.accounts.taxes.validate_taxes_and_charges(tax.doctype, tax.name);
|
||||
erpnext.accounts.taxes.validate_inclusive_tax(tax);
|
||||
}
|
||||
frappe.model.round_floats_in(tax);
|
||||
});
|
||||
|
||||
@@ -991,6 +991,16 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
this.frm.set_df_property("conversion_rate", "read_only", erpnext.stale_rate_allowed() ? 0 : 1);
|
||||
}
|
||||
|
||||
apply_discount_on_item(doc, cdt, cdn, field) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
if(!item.price_list_rate) {
|
||||
item[field] = 0.0;
|
||||
} else {
|
||||
this.price_list_rate(doc, cdt, cdn);
|
||||
}
|
||||
this.set_gross_profit(item);
|
||||
}
|
||||
|
||||
shipping_rule() {
|
||||
var me = this;
|
||||
if(this.frm.doc.shipping_rule) {
|
||||
@@ -1661,6 +1671,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
||||
() => {
|
||||
if(args.items.length) {
|
||||
me._set_values_for_item_list(r.message.children);
|
||||
$.each(r.message.children || [], function(i, d) {
|
||||
me.apply_discount_on_item(d, d.doctype, d.name, 'discount_percentage');
|
||||
});
|
||||
}
|
||||
},
|
||||
() => { me.in_apply_price_list = false; }
|
||||
|
||||
@@ -28,7 +28,7 @@ erpnext.get_purchase_trends_filters = function() {
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options":'Fiscal Year',
|
||||
"default": frappe.sys_defaults.fiscal_year
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today())
|
||||
},
|
||||
{
|
||||
"fieldname":"period_based_on",
|
||||
|
||||
@@ -48,7 +48,7 @@ erpnext.get_sales_trends_filters = function() {
|
||||
"label": __("Fiscal Year"),
|
||||
"fieldtype": "Link",
|
||||
"options":'Fiscal Year',
|
||||
"default": frappe.sys_defaults.fiscal_year
|
||||
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||
},
|
||||
{
|
||||
"fieldname":"company",
|
||||
|
||||
@@ -400,7 +400,7 @@ $.extend(erpnext.utils, {
|
||||
});
|
||||
},
|
||||
|
||||
get_fiscal_year: function(date) {
|
||||
get_fiscal_year: function(date, with_dates=false) {
|
||||
if(!date) {
|
||||
date = frappe.datetime.get_today();
|
||||
}
|
||||
@@ -414,7 +414,10 @@ $.extend(erpnext.utils, {
|
||||
async: false,
|
||||
callback: function(r) {
|
||||
if (r.message) {
|
||||
fiscal_year = r.message[0];
|
||||
if (with_dates)
|
||||
fiscal_year = r.message;
|
||||
else
|
||||
fiscal_year = r.message[0];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -142,16 +142,6 @@ erpnext.sales_common = {
|
||||
this.apply_discount_on_item(doc, cdt, cdn, 'discount_amount');
|
||||
}
|
||||
|
||||
apply_discount_on_item(doc, cdt, cdn, field) {
|
||||
var item = frappe.get_doc(cdt, cdn);
|
||||
if(!item.price_list_rate) {
|
||||
item[field] = 0.0;
|
||||
} else {
|
||||
this.price_list_rate(doc, cdt, cdn);
|
||||
}
|
||||
this.set_gross_profit(item);
|
||||
}
|
||||
|
||||
commission_rate() {
|
||||
this.calculate_commission();
|
||||
}
|
||||
|
||||
@@ -62,10 +62,10 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"fetch_from": "process_owner.full_name",
|
||||
"fetch_from": "procedure.process_owner_full_name",
|
||||
"fieldname": "full_name",
|
||||
"fieldtype": "Data",
|
||||
"hidden": 1,
|
||||
"read_only": 1,
|
||||
"label": "Full Name"
|
||||
},
|
||||
{
|
||||
@@ -81,7 +81,7 @@
|
||||
],
|
||||
"index_web_pages_for_search": 1,
|
||||
"links": [],
|
||||
"modified": "2021-02-26 15:27:47.247814",
|
||||
"modified": "2023-07-31 08:10:47.247814",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Quality Management",
|
||||
"name": "Non Conformance",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"actions": [],
|
||||
"autoname": "format:{####}",
|
||||
"creation": "2019-05-26 15:03:43.996455",
|
||||
"doctype": "DocType",
|
||||
@@ -12,7 +13,6 @@
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
"fetch_from": "goal.objective",
|
||||
"fieldname": "objective",
|
||||
"fieldtype": "Text",
|
||||
"in_list_view": 1,
|
||||
@@ -38,14 +38,17 @@
|
||||
}
|
||||
],
|
||||
"istable": 1,
|
||||
"modified": "2019-05-26 16:12:54.832058",
|
||||
"links": [],
|
||||
"modified": "2023-07-28 18:10:23.351246",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Quality Management",
|
||||
"name": "Quality Goal Objective",
|
||||
"naming_rule": "Expression",
|
||||
"owner": "Administrator",
|
||||
"permissions": [],
|
||||
"quick_entry": 1,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"track_changes": 1
|
||||
}
|
||||
@@ -56,6 +56,7 @@
|
||||
"fieldtype": "Column Break"
|
||||
},
|
||||
{
|
||||
"default": "Open",
|
||||
"columns": 2,
|
||||
"fieldname": "status",
|
||||
"fieldtype": "Select",
|
||||
@@ -67,7 +68,7 @@
|
||||
"index_web_pages_for_search": 1,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"modified": "2020-10-27 16:28:20.908637",
|
||||
"modified": "2023-07-31 09:20:20.908637",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Quality Management",
|
||||
"name": "Quality Review Objective",
|
||||
@@ -76,4 +77,4 @@
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"track_changes": 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,12 +126,6 @@ erpnext.selling.QuotationController = class QuotationController extends erpnext.
|
||||
});
|
||||
}
|
||||
|
||||
if(!doc.auto_repeat) {
|
||||
cur_frm.add_custom_button(__('Subscription'), function() {
|
||||
erpnext.utils.make_subscription(doc.doctype, doc.name)
|
||||
}, __('Create'))
|
||||
}
|
||||
|
||||
cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user