mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 03:09:09 +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"
|
"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(
|
def _book_deferred_revenue_or_expense(
|
||||||
item,
|
item,
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ frappe.ui.form.on('Account', {
|
|||||||
frm.add_custom_button(__('General Ledger'), function () {
|
frm.add_custom_button(__('General Ledger'), function () {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
"account": frm.doc.name,
|
"account": frm.doc.name,
|
||||||
"from_date": frappe.sys_defaults.year_start_date,
|
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
"to_date": frappe.sys_defaults.year_end_date,
|
"to_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[2],
|
||||||
"company": frm.doc.company
|
"company": frm.doc.company
|
||||||
};
|
};
|
||||||
frappe.set_route("query-report", "General Ledger");
|
frappe.set_route("query-report", "General Ledger");
|
||||||
|
|||||||
@@ -194,8 +194,8 @@ frappe.treeview_settings["Account"] = {
|
|||||||
click: function(node, btn) {
|
click: function(node, btn) {
|
||||||
frappe.route_options = {
|
frappe.route_options = {
|
||||||
"account": node.label,
|
"account": node.label,
|
||||||
"from_date": frappe.sys_defaults.year_start_date,
|
"from_date": erpnext.utils.get_fiscal_year(frappe.datetime.get_today(), true)[1],
|
||||||
"to_date": frappe.sys_defaults.year_end_date,
|
"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()
|
"company": frappe.treeview_settings['Account'].treeview.page.fields_dict.company.get_value()
|
||||||
};
|
};
|
||||||
frappe.set_route("query-report", "General Ledger");
|
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()) {
|
if (!frm.is_new()) {
|
||||||
frm.add_custom_button(__('Show {0}', [frm.doc.document_type]), function () {
|
frm.add_custom_button(__('Show {0}', [frm.doc.document_type]), function () {
|
||||||
frappe.set_route("List", frm.doc.document_type);
|
frappe.set_route("List", frm.doc.document_type);
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ class AccountingDimension(Document):
|
|||||||
if not self.is_new():
|
if not self.is_new():
|
||||||
self.validate_document_type_change()
|
self.validate_document_type_change()
|
||||||
|
|
||||||
|
self.validate_dimension_defaults()
|
||||||
|
|
||||||
def validate_document_type_change(self):
|
def validate_document_type_change(self):
|
||||||
doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
|
doctype_before_save = frappe.db.get_value("Accounting Dimension", self.name, "document_type")
|
||||||
if doctype_before_save != self.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.")
|
message += _("Please create a new Accounting Dimension if required.")
|
||||||
frappe.throw(message)
|
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):
|
def after_insert(self):
|
||||||
if frappe.flags.in_test:
|
if frappe.flags.in_test:
|
||||||
make_dimension_in_accounting_doctypes(doc=self)
|
make_dimension_in_accounting_doctypes(doc=self)
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
"reference_document",
|
"reference_document",
|
||||||
"default_dimension",
|
"default_dimension",
|
||||||
"mandatory_for_bs",
|
"mandatory_for_bs",
|
||||||
"mandatory_for_pl"
|
"mandatory_for_pl",
|
||||||
|
"column_break_lqns",
|
||||||
|
"automatically_post_balancing_accounting_entry",
|
||||||
|
"offsetting_account"
|
||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
@@ -50,6 +53,23 @@
|
|||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
"label": "Mandatory For Profit and Loss Account"
|
"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,
|
"istable": 1,
|
||||||
|
|||||||
@@ -58,6 +58,7 @@
|
|||||||
"closing_settings_tab",
|
"closing_settings_tab",
|
||||||
"period_closing_settings_section",
|
"period_closing_settings_section",
|
||||||
"acc_frozen_upto",
|
"acc_frozen_upto",
|
||||||
|
"ignore_account_closing_balance",
|
||||||
"column_break_25",
|
"column_break_25",
|
||||||
"frozen_accounts_modifier",
|
"frozen_accounts_modifier",
|
||||||
"tab_break_dpet",
|
"tab_break_dpet",
|
||||||
@@ -406,6 +407,13 @@
|
|||||||
"fieldname": "enable_fuzzy_matching",
|
"fieldname": "enable_fuzzy_matching",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Enable Fuzzy Matching"
|
"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",
|
"icon": "icon-cog",
|
||||||
@@ -413,7 +421,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"issingle": 1,
|
"issingle": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-06-15 16:35:45.123456",
|
"modified": "2023-07-27 15:05:34.000264",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Accounts Settings",
|
"name": "Accounts Settings",
|
||||||
|
|||||||
@@ -14,21 +14,32 @@ from erpnext.stock.utils import check_pending_reposting
|
|||||||
|
|
||||||
|
|
||||||
class AccountsSettings(Document):
|
class AccountsSettings(Document):
|
||||||
def on_update(self):
|
|
||||||
frappe.clear_cache()
|
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
frappe.db.set_default(
|
old_doc = self.get_doc_before_save()
|
||||||
"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
|
clear_cache = False
|
||||||
)
|
|
||||||
|
|
||||||
frappe.db.set_default(
|
if old_doc.add_taxes_from_item_tax_template != self.add_taxes_from_item_tax_template:
|
||||||
"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
|
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.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):
|
def validate_stale_days(self):
|
||||||
if not self.allow_stale and cint(self.stale_days) <= 0:
|
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:
|
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)
|
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"
|
if(frm.doc.payment_type == "Receive"
|
||||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_received_amount + total_deductions
|
&& 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)) {
|
&& 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;
|
- frm.doc.base_total_allocated_amount) / frm.doc.source_exchange_rate;
|
||||||
} else if (frm.doc.payment_type == "Pay"
|
} else if (frm.doc.payment_type == "Pay"
|
||||||
&& frm.doc.base_total_allocated_amount < frm.doc.base_paid_amount - total_deductions
|
&& 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)) {
|
&& 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;
|
+ 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.")
|
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):
|
if (
|
||||||
frappe.throw(fail_message.format(d.idx))
|
d.payment_term
|
||||||
|
and (
|
||||||
if d.payment_term and (
|
(flt(d.allocated_amount)) > 0
|
||||||
(flt(d.allocated_amount)) > 0
|
and flt(d.allocated_amount) > flt(latest.payment_term_outstanding)
|
||||||
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(
|
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
|
# Check for negative outstanding invoices as well
|
||||||
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
if flt(d.allocated_amount) < 0 and flt(d.allocated_amount) < flt(latest.outstanding_amount):
|
||||||
frappe.throw(fail_message.format(d.idx))
|
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."
|
"References {0} of type {1} had no outstanding amount left before submitting the Payment Entry. Now they have a negative outstanding amount."
|
||||||
).format(
|
).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),
|
_(reference_doctype),
|
||||||
)
|
)
|
||||||
+ "<br><br>"
|
+ "<br><br>"
|
||||||
@@ -1738,7 +1742,7 @@ def get_orders_to_be_billed(
|
|||||||
{party_type} = %s
|
{party_type} = %s
|
||||||
and docstatus = 1
|
and docstatus = 1
|
||||||
and company = %s
|
and company = %s
|
||||||
and ifnull(status, "") != "Closed"
|
and status != "Closed"
|
||||||
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
and if({rounded_total_field}, {rounded_total_field}, {grand_total_field}) > advance_paid
|
||||||
and abs(100 - per_billed) > 0.01
|
and abs(100 - per_billed) > 0.01
|
||||||
{condition}
|
{condition}
|
||||||
|
|||||||
@@ -1156,6 +1156,52 @@ class TestPaymentEntry(FrappeTestCase):
|
|||||||
si3.cancel()
|
si3.cancel()
|
||||||
si3.delete()
|
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):
|
def create_payment_entry(**args):
|
||||||
payment_entry = frappe.new_doc("Payment Entry")
|
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.total_quantity, 2)
|
||||||
self.assertEqual(pcv_doc.net_total, 6700)
|
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):
|
def test_cancelling_of_pos_closing_entry(self):
|
||||||
test_user, pos_profile = init_user_and_profile()
|
test_user, pos_profile = init_user_and_profile()
|
||||||
opening_entry = create_opening_entry(pos_profile, test_user.name)
|
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}"
|
msg = f"Serial No {args.serial_no} not available for Item {args.item}"
|
||||||
frappe.throw(_(msg))
|
frappe.throw(_(msg))
|
||||||
|
|
||||||
pos_inv.append(
|
pos_invoice_item = {
|
||||||
"items",
|
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
||||||
{
|
"qty": args.qty or 1,
|
||||||
"item_code": args.item or args.item_code or "_Test Item",
|
"rate": args.rate if args.get("rate") is not None else 100,
|
||||||
"warehouse": args.warehouse or "_Test Warehouse - _TC",
|
"income_account": args.income_account or "Sales - _TC",
|
||||||
"qty": args.qty or 1,
|
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
||||||
"rate": args.rate if args.get("rate") is not None else 100,
|
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
||||||
"income_account": args.income_account or "Sales - _TC",
|
"serial_and_batch_bundle": bundle_id,
|
||||||
"expense_account": args.expense_account or "Cost of Goods Sold - _TC",
|
}
|
||||||
"cost_center": args.cost_center or "_Test Cost Center - _TC",
|
# append in pos invoice items without item_code by checking flag without_item_code
|
||||||
"serial_and_batch_bundle": bundle_id,
|
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:
|
if not args.do_not_save:
|
||||||
pos_inv.insert()
|
pos_inv.insert()
|
||||||
|
|||||||
@@ -101,12 +101,6 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
|
|||||||
cur_frm.add_custom_button(__('Return / Debit Note'),
|
cur_frm.add_custom_button(__('Return / Debit Note'),
|
||||||
this.make_debit_note, __('Create'));
|
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) {
|
if (doc.outstanding_amount > 0 && !cint(doc.is_return) && !doc.on_hold) {
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ class PurchaseInvoice(BuyingController):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (
|
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_return
|
||||||
and not self.is_internal_supplier
|
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)
|
rate = flt(sle.stock_value_difference) / flt(sle.actual_qty)
|
||||||
self.assertAlmostEqual(returned_inv.items[0].rate, rate)
|
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):
|
def set_advance_flag(company, flag, default_account):
|
||||||
frappe.db.set_value(
|
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")
|
gl = frappe.qb.DocType("GL Entry")
|
||||||
q = (
|
query = (
|
||||||
frappe.qb.from_(gl)
|
frappe.qb.from_(gl)
|
||||||
.select(gl.account, gl.debit, gl.credit, gl.posting_date)
|
.select(gl.account, gl.debit, gl.credit, gl.posting_date)
|
||||||
.where(
|
.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)
|
.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):
|
for i, gle in enumerate(gl_entries):
|
||||||
doc.assertEqual(expected_gle[i][0], gle.account)
|
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(expected_gle[i][2], gle.credit)
|
||||||
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
|
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):
|
def create_tax_witholding_category(category_name, company, account):
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
|
|||||||
@@ -443,7 +443,8 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Batch No",
|
"label": "Batch No",
|
||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "col_br_wh",
|
"fieldname": "col_br_wh",
|
||||||
@@ -890,7 +891,8 @@
|
|||||||
"label": "Serial and Batch Bundle",
|
"label": "Serial and Batch Bundle",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "eval:parent.update_stock == 1",
|
"depends_on": "eval:parent.update_stock == 1",
|
||||||
@@ -905,7 +907,7 @@
|
|||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-07-04 17:22:21.501152",
|
"modified": "2023-07-26 12:54:53.178156",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Purchase Invoice Item",
|
"name": "Purchase Invoice Item",
|
||||||
|
|||||||
@@ -163,12 +163,6 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
|
|||||||
cur_frm.cscript.make_maintenance_schedule();
|
cur_frm.cscript.make_maintenance_schedule();
|
||||||
}, __('Create'));
|
}, __('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
|
// Show buttons only when pos view is active
|
||||||
@@ -776,7 +770,6 @@ frappe.ui.form.on('Sales Invoice', {
|
|||||||
|
|
||||||
update_stock: function(frm, dt, dn) {
|
update_stock: function(frm, dt, dn) {
|
||||||
frm.events.hide_fields(frm);
|
frm.events.hide_fields(frm);
|
||||||
frm.fields_dict.items.grid.toggle_reqd("item_code", frm.doc.update_stock);
|
|
||||||
frm.trigger('reset_posting_time');
|
frm.trigger('reset_posting_time');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ from erpnext.assets.doctype.asset.depreciation import (
|
|||||||
reset_depreciation_schedule,
|
reset_depreciation_schedule,
|
||||||
reverse_depreciation_entry_made_after_disposal,
|
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.accounts_controller import validate_account_head
|
||||||
from erpnext.controllers.selling_controller import SellingController
|
from erpnext.controllers.selling_controller import SellingController
|
||||||
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
from erpnext.projects.doctype.timesheet.timesheet import get_projectwise_timesheet_data
|
||||||
@@ -113,7 +114,6 @@ class SalesInvoice(SellingController):
|
|||||||
|
|
||||||
if cint(self.update_stock):
|
if cint(self.update_stock):
|
||||||
self.validate_dropship_item()
|
self.validate_dropship_item()
|
||||||
self.validate_item_code()
|
|
||||||
self.validate_warehouse()
|
self.validate_warehouse()
|
||||||
self.update_current_stock()
|
self.update_current_stock()
|
||||||
self.validate_delivery_note()
|
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"))
|
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):
|
def validate_warehouse(self):
|
||||||
super(SalesInvoice, self).validate_warehouse()
|
super(SalesInvoice, self).validate_warehouse()
|
||||||
|
|
||||||
@@ -1182,12 +1177,13 @@ class SalesInvoice(SellingController):
|
|||||||
self.get("posting_date"),
|
self.get("posting_date"),
|
||||||
)
|
)
|
||||||
asset.db_set("disposal_date", None)
|
asset.db_set("disposal_date", None)
|
||||||
|
add_asset_activity(asset.name, _("Asset returned"))
|
||||||
|
|
||||||
if asset.calculate_depreciation:
|
if asset.calculate_depreciation:
|
||||||
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
posting_date = frappe.db.get_value("Sales Invoice", self.return_against, "posting_date")
|
||||||
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
reverse_depreciation_entry_made_after_disposal(asset, posting_date)
|
||||||
notes = _(
|
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(
|
).format(
|
||||||
get_link_to_form(asset.doctype, asset.name),
|
get_link_to_form(asset.doctype, asset.name),
|
||||||
get_link_to_form(self.doctype, self.get("name")),
|
get_link_to_form(self.doctype, self.get("name")),
|
||||||
@@ -1215,6 +1211,7 @@ class SalesInvoice(SellingController):
|
|||||||
self.get("posting_date"),
|
self.get("posting_date"),
|
||||||
)
|
)
|
||||||
asset.db_set("disposal_date", self.posting_date)
|
asset.db_set("disposal_date", self.posting_date)
|
||||||
|
add_asset_activity(asset.name, _("Asset sold"))
|
||||||
|
|
||||||
for gle in fixed_asset_gl_entries:
|
for gle in fixed_asset_gl_entries:
|
||||||
gle["against"] = self.customer
|
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)
|
doc = frappe.get_doc(ref_doc, inter_company_reference)
|
||||||
ref_party = doc.supplier if doctype in ["Sales Invoice", "Sales Order"] else doc.customer
|
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:
|
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:
|
if not frappe.get_cached_value(ref_partytype, ref_party, "represents_company") == company:
|
||||||
frappe.throw(_("Invalid Company for Inter Company Transaction."))
|
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:
|
if not company in companies:
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("{0} not allowed to transact with {1}. Please change the Company.").format(
|
_("{0} not allowed to transact with {1}. Please change the Company.").format(
|
||||||
partytype, company
|
_(partytype), company
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -604,7 +604,8 @@
|
|||||||
"hidden": 1,
|
"hidden": 1,
|
||||||
"label": "Batch No",
|
"label": "Batch No",
|
||||||
"options": "Batch",
|
"options": "Batch",
|
||||||
"read_only": 1
|
"read_only": 1,
|
||||||
|
"search_index": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "col_break5",
|
"fieldname": "col_break5",
|
||||||
@@ -894,13 +895,14 @@
|
|||||||
"label": "Serial and Batch Bundle",
|
"label": "Serial and Batch Bundle",
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"options": "Serial and Batch Bundle",
|
"options": "Serial and Batch Bundle",
|
||||||
"print_hide": 1
|
"print_hide": 1,
|
||||||
|
"search_index": 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"idx": 1,
|
"idx": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-12 13:42:24.303113",
|
"modified": "2023-07-26 12:53:22.404057",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Accounts",
|
"module": "Accounts",
|
||||||
"name": "Sales Invoice Item",
|
"name": "Sales Invoice Item",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ def make_gl_entries(
|
|||||||
):
|
):
|
||||||
if gl_map:
|
if gl_map:
|
||||||
if not cancel:
|
if not cancel:
|
||||||
|
make_acc_dimensions_offsetting_entry(gl_map)
|
||||||
validate_accounting_period(gl_map)
|
validate_accounting_period(gl_map)
|
||||||
validate_disabled_accounts(gl_map)
|
validate_disabled_accounts(gl_map)
|
||||||
gl_map = process_gl_map(gl_map, merge_entries)
|
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)
|
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):
|
def validate_disabled_accounts(gl_map):
|
||||||
accounts = [d.account for d in gl_map if d.account]
|
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:
|
if not gl_map:
|
||||||
return []
|
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:
|
if merge_entries:
|
||||||
gl_map = merge_similar_entries(gl_map, precision)
|
gl_map = merge_similar_entries(gl_map, precision)
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"columns": [],
|
||||||
"creation": "2016-04-08 14:49:58.133098",
|
"creation": "2016-04-08 14:49:58.133098",
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
"idx": 2,
|
"filters": [],
|
||||||
"is_standard": "Yes",
|
"idx": 2,
|
||||||
"modified": "2017-02-24 20:08:26.084484",
|
"is_standard": "Yes",
|
||||||
"modified_by": "Administrator",
|
"letterhead": null,
|
||||||
"module": "Accounts",
|
"modified": "2023-07-26 21:05:33.554778",
|
||||||
"name": "Asset Depreciation Ledger",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator",
|
"module": "Accounts",
|
||||||
"ref_doctype": "Asset",
|
"name": "Asset Depreciation Ledger",
|
||||||
"report_name": "Asset Depreciation Ledger",
|
"owner": "Administrator",
|
||||||
"report_type": "Script Report",
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Asset",
|
||||||
|
"report_name": "Asset Depreciation Ledger",
|
||||||
|
"report_type": "Script Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"role": "Accounts User"
|
"role": "Accounts User"
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ frappe.query_reports["Asset Depreciations and Balances"] = {
|
|||||||
"fieldname":"from_date",
|
"fieldname":"from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"to_date",
|
"fieldname":"to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "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
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
{
|
{
|
||||||
"add_total_row": 0,
|
"add_total_row": 1,
|
||||||
"apply_user_permissions": 1,
|
"columns": [],
|
||||||
"creation": "2016-04-08 14:56:37.235981",
|
"creation": "2016-04-08 14:56:37.235981",
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
"idx": 2,
|
"filters": [],
|
||||||
"is_standard": "Yes",
|
"idx": 2,
|
||||||
"modified": "2017-02-24 20:08:18.660476",
|
"is_standard": "Yes",
|
||||||
"modified_by": "Administrator",
|
"letterhead": null,
|
||||||
"module": "Accounts",
|
"modified": "2023-07-26 21:04:54.751077",
|
||||||
"name": "Asset Depreciations and Balances",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator",
|
"module": "Accounts",
|
||||||
"ref_doctype": "Asset",
|
"name": "Asset Depreciations and Balances",
|
||||||
"report_name": "Asset Depreciations and Balances",
|
"owner": "Administrator",
|
||||||
"report_type": "Script Report",
|
"prepared_report": 0,
|
||||||
|
"ref_doctype": "Asset",
|
||||||
|
"report_name": "Asset Depreciations and Balances",
|
||||||
|
"report_type": "Script Report",
|
||||||
"roles": [
|
"roles": [
|
||||||
{
|
{
|
||||||
"role": "Accounts User"
|
"role": "Accounts User"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ frappe.query_reports["Bank Clearance Summary"] = {
|
|||||||
"fieldname":"from_date",
|
"fieldname":"from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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"
|
"width": "80"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
label: __("From Fiscal Year"),
|
label: __("From Fiscal Year"),
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Fiscal Year",
|
options: "Fiscal Year",
|
||||||
default: frappe.sys_defaults.fiscal_year,
|
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
reqd: 1
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,7 @@ frappe.query_reports["Budget Variance Report"] = {
|
|||||||
label: __("To Fiscal Year"),
|
label: __("To Fiscal Year"),
|
||||||
fieldtype: "Link",
|
fieldtype: "Link",
|
||||||
options: "Fiscal Year",
|
options: "Fiscal Year",
|
||||||
default: frappe.sys_defaults.fiscal_year,
|
default: erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
reqd: 1
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from collections import defaultdict
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder import Criterion
|
||||||
from frappe.utils import flt, getdate
|
from frappe.utils import flt, getdate
|
||||||
|
|
||||||
import erpnext
|
import erpnext
|
||||||
@@ -359,6 +360,7 @@ def get_data(
|
|||||||
accounts_by_name,
|
accounts_by_name,
|
||||||
accounts,
|
accounts,
|
||||||
ignore_closing_entries=False,
|
ignore_closing_entries=False,
|
||||||
|
root_type=root_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
calculate_values(accounts_by_name, gl_entries_by_account, companies, filters, fiscal_year)
|
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_by_name,
|
||||||
accounts,
|
accounts,
|
||||||
ignore_closing_entries=False,
|
ignore_closing_entries=False,
|
||||||
|
root_type=None,
|
||||||
):
|
):
|
||||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||||
|
|
||||||
@@ -610,7 +613,6 @@ def set_gl_entries_by_account(
|
|||||||
"Company", filters.get("company"), ["lft", "rgt"]
|
"Company", filters.get("company"), ["lft", "rgt"]
|
||||||
)
|
)
|
||||||
|
|
||||||
additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
|
|
||||||
companies = frappe.db.sql(
|
companies = frappe.db.sql(
|
||||||
""" select name, default_currency from `tabCompany`
|
""" select name, default_currency from `tabCompany`
|
||||||
where lft >= %(company_lft)s and rgt <= %(company_rgt)s""",
|
where lft >= %(company_lft)s and rgt <= %(company_rgt)s""",
|
||||||
@@ -626,27 +628,42 @@ def set_gl_entries_by_account(
|
|||||||
)
|
)
|
||||||
|
|
||||||
for d in companies:
|
for d in companies:
|
||||||
gl_entries = frappe.db.sql(
|
gle = frappe.qb.DocType("GL Entry")
|
||||||
"""select gl.posting_date, gl.account, gl.debit, gl.credit, gl.is_opening, gl.company,
|
account = frappe.qb.DocType("Account")
|
||||||
gl.fiscal_year, gl.debit_in_account_currency, gl.credit_in_account_currency, gl.account_currency,
|
query = (
|
||||||
acc.account_name, acc.account_number
|
frappe.qb.from_(gle)
|
||||||
from `tabGL Entry` gl, `tabAccount` acc where acc.name = gl.account and gl.company = %(company)s and gl.is_cancelled = 0
|
.inner_join(account)
|
||||||
{additional_conditions} and gl.posting_date <= %(to_date)s and acc.lft >= %(lft)s and acc.rgt <= %(rgt)s
|
.on(account.name == gle.account)
|
||||||
order by gl.account, gl.posting_date""".format(
|
.select(
|
||||||
additional_conditions=additional_conditions
|
gle.posting_date,
|
||||||
),
|
gle.account,
|
||||||
{
|
gle.debit,
|
||||||
"from_date": from_date,
|
gle.credit,
|
||||||
"to_date": to_date,
|
gle.is_opening,
|
||||||
"lft": root_lft,
|
gle.company,
|
||||||
"rgt": root_rgt,
|
gle.fiscal_year,
|
||||||
"company": d.name,
|
gle.debit_in_account_currency,
|
||||||
"finance_book": filters.get("finance_book"),
|
gle.credit_in_account_currency,
|
||||||
"company_fb": frappe.get_cached_value("Company", d.name, "default_finance_book"),
|
gle.account_currency,
|
||||||
},
|
account.account_name,
|
||||||
as_dict=True,
|
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:
|
if filters and filters.get("presentation_currency") != d.default_currency:
|
||||||
currency_info["company"] = d.name
|
currency_info["company"] = d.name
|
||||||
currency_info["company_currency"] = d.default_currency
|
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)
|
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 = []
|
additional_conditions = []
|
||||||
|
|
||||||
if ignore_closing_entries:
|
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:
|
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"):
|
if filters.get("include_default_book_entries"):
|
||||||
additional_conditions.append(
|
additional_conditions.append((gle.finance_book.isin([finance_book, company_fb, "", None])))
|
||||||
"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
|
|
||||||
)
|
|
||||||
else:
|
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):
|
def add_total_row(out, root_type, balance_must_be, companies, company_currency):
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import unittest
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import qb
|
from frappe import qb
|
||||||
|
from frappe.tests.utils import FrappeTestCase, change_settings
|
||||||
from frappe.utils import nowdate
|
from frappe.utils import nowdate
|
||||||
|
|
||||||
from erpnext.accounts.doctype.account.test_account import create_account
|
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 (
|
from erpnext.accounts.report.deferred_revenue_and_expense.deferred_revenue_and_expense import (
|
||||||
Deferred_Revenue_and_Expense_Report,
|
Deferred_Revenue_and_Expense_Report,
|
||||||
)
|
)
|
||||||
|
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
|
||||||
from erpnext.accounts.utils import get_fiscal_year
|
from erpnext.accounts.utils import get_fiscal_year
|
||||||
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
from erpnext.buying.doctype.supplier.test_supplier import create_supplier
|
||||||
from erpnext.stock.doctype.item.test_item import create_item
|
from erpnext.stock.doctype.item.test_item import create_item
|
||||||
|
|
||||||
|
|
||||||
class TestDeferredRevenueAndExpense(unittest.TestCase):
|
class TestDeferredRevenueAndExpense(FrappeTestCase, AccountsTestMixin):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(self):
|
||||||
clear_accounts_and_items()
|
|
||||||
create_company()
|
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
|
|
||||||
def clear_old_entries(self):
|
def clear_old_entries(self):
|
||||||
@@ -51,55 +51,58 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
if deferred_invoices:
|
if deferred_invoices:
|
||||||
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
qb.from_(pinv).delete().where(pinv.name.isin(deferred_invoices)).run()
|
||||||
|
|
||||||
def test_deferred_revenue(self):
|
def setup_deferred_accounts_and_items(self):
|
||||||
self.clear_old_entries()
|
# 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
|
# created deferred expense accounts, if not found
|
||||||
deferred_revenue_account = create_account(
|
self.deferred_expense_account = create_account(
|
||||||
account_name="Deferred Revenue",
|
account_name="Deferred Expense",
|
||||||
parent_account="Current Liabilities - _CD",
|
parent_account="Current Assets - " + self.company_abbr,
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
)
|
)
|
||||||
|
|
||||||
acc_settings = frappe.get_doc("Accounts Settings", "Accounts Settings")
|
def setUp(self):
|
||||||
acc_settings.book_deferred_entries_based_on = "Months"
|
self.create_company()
|
||||||
acc_settings.save()
|
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")
|
def tearDown(self):
|
||||||
customer.customer_name = "_Test Customer DR"
|
frappe.db.rollback()
|
||||||
customer.type = "Individual"
|
|
||||||
customer.insert()
|
|
||||||
|
|
||||||
item = create_item(
|
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||||
"_Test Internet Subscription",
|
def test_deferred_revenue(self):
|
||||||
is_stock_item=0,
|
self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company)
|
||||||
warehouse="All Warehouses - _CD",
|
item = frappe.get_doc("Item", self.item)
|
||||||
company="_Test Company DR",
|
|
||||||
)
|
|
||||||
item.enable_deferred_revenue = 1
|
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.no_of_months = 3
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
item=item.name,
|
item=self.item,
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
customer="_Test Customer DR",
|
customer=self.customer,
|
||||||
debit_to="Debtors - _CD",
|
debit_to=self.debit_to,
|
||||||
posting_date="2021-05-01",
|
posting_date="2021-05-01",
|
||||||
parent_cost_center="Main - _CD",
|
parent_cost_center=self.cost_center,
|
||||||
cost_center="Main - _CD",
|
cost_center=self.cost_center,
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
rate=300,
|
rate=300,
|
||||||
price_list_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].enable_deferred_revenue = 1
|
||||||
si.items[0].service_start_date = "2021-05-01"
|
si.items[0].service_start_date = "2021-05-01"
|
||||||
si.items[0].service_end_date = "2021-08-01"
|
si.items[0].service_end_date = "2021-08-01"
|
||||||
si.items[0].deferred_revenue_account = deferred_revenue_account
|
si.items[0].deferred_revenue_account = self.deferred_revenue_account
|
||||||
si.items[0].income_account = "Sales - _CD"
|
|
||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
@@ -110,7 +113,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
start_date="2021-05-01",
|
start_date="2021-05-01",
|
||||||
end_date="2021-08-01",
|
end_date="2021-08-01",
|
||||||
type="Income",
|
type="Income",
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pda.insert()
|
pda.insert()
|
||||||
@@ -120,7 +123,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": self.company,
|
||||||
"filter_based_on": "Date Range",
|
"filter_based_on": "Date Range",
|
||||||
"period_start_date": "2021-05-01",
|
"period_start_date": "2021-05-01",
|
||||||
"period_end_date": "2021-08-01",
|
"period_end_date": "2021-08-01",
|
||||||
@@ -142,57 +145,36 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(report.period_total, expected)
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||||
def test_deferred_expense(self):
|
def test_deferred_expense(self):
|
||||||
self.clear_old_entries()
|
self.create_item("_Test Office Desk", 0, self.warehouse, self.company)
|
||||||
|
item = frappe.get_doc("Item", self.item)
|
||||||
# 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",
|
|
||||||
)
|
|
||||||
item.enable_deferred_expense = 1
|
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.no_of_months_exp = 3
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
pi = make_purchase_invoice(
|
pi = make_purchase_invoice(
|
||||||
item=item.name,
|
item=self.item,
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
supplier="_Test Furniture Supplier",
|
supplier=self.supplier,
|
||||||
is_return=False,
|
is_return=False,
|
||||||
update_stock=False,
|
update_stock=False,
|
||||||
posting_date=frappe.utils.datetime.date(2021, 5, 1),
|
posting_date=frappe.utils.datetime.date(2021, 5, 1),
|
||||||
parent_cost_center="Main - _CD",
|
parent_cost_center=self.cost_center,
|
||||||
cost_center="Main - _CD",
|
cost_center=self.cost_center,
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
rate=300,
|
rate=300,
|
||||||
price_list_rate=300,
|
price_list_rate=300,
|
||||||
warehouse="All Warehouses - _CD",
|
warehouse=self.warehouse,
|
||||||
qty=1,
|
qty=1,
|
||||||
)
|
)
|
||||||
pi.set_posting_time = True
|
pi.set_posting_time = True
|
||||||
pi.items[0].enable_deferred_expense = 1
|
pi.items[0].enable_deferred_expense = 1
|
||||||
pi.items[0].service_start_date = "2021-05-01"
|
pi.items[0].service_start_date = "2021-05-01"
|
||||||
pi.items[0].service_end_date = "2021-08-01"
|
pi.items[0].service_end_date = "2021-08-01"
|
||||||
pi.items[0].deferred_expense_account = deferred_expense_account
|
pi.items[0].deferred_expense_account = self.deferred_expense_account
|
||||||
pi.items[0].expense_account = "Office Maintenance Expenses - _CD"
|
pi.items[0].expense_account = self.expense_account
|
||||||
pi.save()
|
pi.save()
|
||||||
pi.submit()
|
pi.submit()
|
||||||
|
|
||||||
@@ -203,7 +185,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
start_date="2021-05-01",
|
start_date="2021-05-01",
|
||||||
end_date="2021-08-01",
|
end_date="2021-08-01",
|
||||||
type="Expense",
|
type="Expense",
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pda.insert()
|
pda.insert()
|
||||||
@@ -213,7 +195,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": self.company,
|
||||||
"filter_based_on": "Date Range",
|
"filter_based_on": "Date Range",
|
||||||
"period_start_date": "2021-05-01",
|
"period_start_date": "2021-05-01",
|
||||||
"period_end_date": "2021-08-01",
|
"period_end_date": "2021-08-01",
|
||||||
@@ -235,52 +217,31 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
self.assertEqual(report.period_total, expected)
|
self.assertEqual(report.period_total, expected)
|
||||||
|
|
||||||
|
@change_settings("Accounts Settings", {"book_deferred_entries_based_on": "Months"})
|
||||||
def test_zero_months(self):
|
def test_zero_months(self):
|
||||||
self.clear_old_entries()
|
self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company)
|
||||||
# created deferred expense accounts, if not found
|
item = frappe.get_doc("Item", self.item)
|
||||||
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",
|
|
||||||
)
|
|
||||||
item.enable_deferred_revenue = 1
|
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.no_of_months = 0
|
||||||
item.save()
|
item.save()
|
||||||
|
|
||||||
si = create_sales_invoice(
|
si = create_sales_invoice(
|
||||||
item=item.name,
|
item=item.name,
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
customer="_Test Customer DR",
|
customer=self.customer,
|
||||||
debit_to="Debtors - _CD",
|
debit_to=self.debit_to,
|
||||||
posting_date="2021-05-01",
|
posting_date="2021-05-01",
|
||||||
parent_cost_center="Main - _CD",
|
parent_cost_center=self.cost_center,
|
||||||
cost_center="Main - _CD",
|
cost_center=self.cost_center,
|
||||||
do_not_save=True,
|
do_not_save=True,
|
||||||
rate=300,
|
rate=300,
|
||||||
price_list_rate=300,
|
price_list_rate=300,
|
||||||
)
|
)
|
||||||
|
|
||||||
si.items[0].enable_deferred_revenue = 1
|
si.items[0].enable_deferred_revenue = 1
|
||||||
si.items[0].income_account = "Sales - _CD"
|
si.items[0].income_account = self.income_account
|
||||||
si.items[0].deferred_revenue_account = deferred_revenue_account
|
si.items[0].deferred_revenue_account = self.deferred_revenue_account
|
||||||
si.items[0].income_account = "Sales - _CD"
|
|
||||||
si.save()
|
si.save()
|
||||||
si.submit()
|
si.submit()
|
||||||
|
|
||||||
@@ -291,7 +252,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
start_date="2021-05-01",
|
start_date="2021-05-01",
|
||||||
end_date="2021-08-01",
|
end_date="2021-08-01",
|
||||||
type="Income",
|
type="Income",
|
||||||
company="_Test Company DR",
|
company=self.company,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
pda.insert()
|
pda.insert()
|
||||||
@@ -301,7 +262,7 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
fiscal_year = frappe.get_doc("Fiscal Year", get_fiscal_year(date="2021-05-01"))
|
||||||
self.filters = frappe._dict(
|
self.filters = frappe._dict(
|
||||||
{
|
{
|
||||||
"company": frappe.defaults.get_user_default("Company"),
|
"company": self.company,
|
||||||
"filter_based_on": "Date Range",
|
"filter_based_on": "Date Range",
|
||||||
"period_start_date": "2021-05-01",
|
"period_start_date": "2021-05-01",
|
||||||
"period_end_date": "2021-08-01",
|
"period_end_date": "2021-08-01",
|
||||||
@@ -322,30 +283,3 @@ class TestDeferredRevenueAndExpense(unittest.TestCase):
|
|||||||
{"key": "aug_2021", "total": 0, "actual": 0},
|
{"key": "aug_2021", "total": 0, "actual": 0},
|
||||||
]
|
]
|
||||||
self.assertEqual(report.period_total, expected)
|
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",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "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
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ def get_data(
|
|||||||
filters,
|
filters,
|
||||||
gl_entries_by_account,
|
gl_entries_by_account,
|
||||||
ignore_closing_entries=ignore_closing_entries,
|
ignore_closing_entries=ignore_closing_entries,
|
||||||
|
root_type=root_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
calculate_values(
|
calculate_values(
|
||||||
@@ -417,23 +418,44 @@ def set_gl_entries_by_account(
|
|||||||
gl_entries_by_account,
|
gl_entries_by_account,
|
||||||
ignore_closing_entries=False,
|
ignore_closing_entries=False,
|
||||||
ignore_opening_entries=False,
|
ignore_opening_entries=False,
|
||||||
|
root_type=None,
|
||||||
):
|
):
|
||||||
"""Returns a dict like { "account": [gl entries], ... }"""
|
"""Returns a dict like { "account": [gl entries], ... }"""
|
||||||
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(
|
accounts_list = frappe.db.get_all(
|
||||||
"Account",
|
"Account",
|
||||||
filters={"company": company, "is_group": 0, "lft": (">=", root_lft), "rgt": ("<=", root_rgt)},
|
filters=account_filters,
|
||||||
pluck="name",
|
pluck="name",
|
||||||
)
|
)
|
||||||
|
|
||||||
if accounts_list:
|
if accounts_list:
|
||||||
# For balance sheet
|
# For balance sheet
|
||||||
if not from_date:
|
ignore_closing_balances = frappe.db.get_single_value(
|
||||||
from_date = filters["period_start_date"]
|
"Accounts Settings", "ignore_account_closing_balance"
|
||||||
|
)
|
||||||
|
if not from_date and not ignore_closing_balances:
|
||||||
last_period_closing_voucher = frappe.db.get_all(
|
last_period_closing_voucher = frappe.db.get_all(
|
||||||
"Period Closing Voucher",
|
"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"],
|
fields=["posting_date", "name"],
|
||||||
order_by="posting_date desc",
|
order_by="posting_date desc",
|
||||||
limit=1,
|
limit=1,
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ frappe.query_reports["Gross Profit"] = {
|
|||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "to_date",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "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
|
"reqd": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -309,7 +309,8 @@ def get_conditions(filters):
|
|||||||
|
|
||||||
def get_items(filters, additional_query_columns):
|
def get_items(filters, additional_query_columns):
|
||||||
conditions = get_conditions(filters)
|
conditions = get_conditions(filters)
|
||||||
|
if additional_query_columns:
|
||||||
|
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
|
|||||||
@@ -381,7 +381,8 @@ def get_group_by_conditions(filters, doctype):
|
|||||||
|
|
||||||
def get_items(filters, additional_query_columns, additional_conditions=None):
|
def get_items(filters, additional_query_columns, additional_conditions=None):
|
||||||
conditions = get_conditions(filters, additional_conditions)
|
conditions = get_conditions(filters, additional_conditions)
|
||||||
|
if additional_query_columns:
|
||||||
|
additional_query_columns = "," + ",".join(additional_query_columns)
|
||||||
return frappe.db.sql(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select
|
select
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ frappe.query_reports["Payment Period Based On Invoice Date"] = {
|
|||||||
fieldname: "from_date",
|
fieldname: "from_date",
|
||||||
label: __("From Date"),
|
label: __("From Date"),
|
||||||
fieldtype: "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",
|
fieldname:"to_date",
|
||||||
|
|||||||
@@ -50,20 +50,20 @@ def get_pos_entries(filters, group_by_field):
|
|||||||
order_by = "p.posting_date"
|
order_by = "p.posting_date"
|
||||||
select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", ""
|
select_mop_field, from_sales_invoice_payment, group_by_mop_condition = "", "", ""
|
||||||
if group_by_field == "mode_of_payment":
|
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"
|
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"
|
order_by += ", sip.mode_of_payment"
|
||||||
|
|
||||||
elif group_by_field:
|
elif group_by_field:
|
||||||
order_by += ", p.{}".format(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(
|
return frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
SELECT
|
SELECT
|
||||||
p.posting_date, p.name as pos_invoice, p.pos_profile,
|
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.owner, p.customer, p.is_return, p.base_grand_total as grand_total {select_mop_field}
|
||||||
p.customer, p.is_return {select_mop_field}
|
|
||||||
FROM
|
FROM
|
||||||
`tabPOS Invoice` p {from_sales_invoice_payment}
|
`tabPOS Invoice` p {from_sales_invoice_payment}
|
||||||
WHERE
|
WHERE
|
||||||
|
|||||||
@@ -66,13 +66,13 @@ frappe.require("assets/erpnext/js/financial_statements.js", function() {
|
|||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "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",
|
"fieldname": "show_zero_values",
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ def set_gl_entries_by_account(
|
|||||||
additional_conditions = []
|
additional_conditions = []
|
||||||
|
|
||||||
if ignore_closing_entries:
|
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:
|
if from_date:
|
||||||
additional_conditions.append("and posting_date >= %(from_date)s")
|
additional_conditions.append("and posting_date >= %(from_date)s")
|
||||||
|
|||||||
@@ -52,6 +52,12 @@ frappe.query_reports["Purchase Register"] = {
|
|||||||
"label": __("Item Group"),
|
"label": __("Item Group"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Item Group"
|
"options": "Item Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "include_payments",
|
||||||
|
"label": __("Show Ledger View"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,22 @@
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
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 (
|
from erpnext.accounts.party import get_party_account
|
||||||
get_accounting_dimensions,
|
from erpnext.accounts.report.utils import (
|
||||||
get_dimension_with_children,
|
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):
|
def execute(filters=None):
|
||||||
@@ -21,9 +30,15 @@ def _execute(filters=None, additional_table_columns=None):
|
|||||||
if not filters:
|
if not filters:
|
||||||
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))
|
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(
|
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:
|
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)
|
invoice_expense_map = get_invoice_expense_map(invoice_list)
|
||||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||||
invoice_expense_map, invoice_tax_map = get_invoice_tax_map(
|
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)
|
invoice_po_pr_map = get_invoice_po_pr_map(invoice_list)
|
||||||
suppliers = list(set(d.supplier for d in 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")
|
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 = []
|
data = []
|
||||||
for inv in invoice_list:
|
for inv in invoice_list:
|
||||||
# invoice details
|
# 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", [])))
|
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", [])))
|
project = list(set(invoice_po_pr_map.get(inv.name, {}).get("project", [])))
|
||||||
|
|
||||||
row = [
|
row = {
|
||||||
inv.name,
|
"voucher_type": inv.doctype,
|
||||||
inv.posting_date,
|
"voucher_no": inv.name,
|
||||||
inv.supplier,
|
"posting_date": inv.posting_date,
|
||||||
inv.supplier_name,
|
"supplier_id": inv.supplier,
|
||||||
*get_values_for_columns(additional_table_columns, inv).values(),
|
"supplier_name": inv.supplier_name,
|
||||||
supplier_details.get(inv.supplier), # supplier_group
|
**get_values_for_columns(additional_table_columns, inv),
|
||||||
inv.tax_id,
|
"supplier_group": supplier_details.get(inv.supplier).get("supplier_group"),
|
||||||
inv.credit_to,
|
"tax_id": supplier_details.get(inv.supplier).get("tax_id"),
|
||||||
inv.mode_of_payment,
|
"payable_account": inv.credit_to,
|
||||||
", ".join(project),
|
"mode_of_payment": inv.mode_of_payment,
|
||||||
inv.bill_no,
|
"project": ", ".join(project) if inv.doctype == "Purchase Invoice" else inv.project,
|
||||||
inv.bill_date,
|
"remarks": inv.remarks,
|
||||||
inv.remarks,
|
"purchase_order": ", ".join(purchase_order),
|
||||||
", ".join(purchase_order),
|
"purchase_receipt": ", ".join(purchase_receipt),
|
||||||
", ".join(purchase_receipt),
|
"currency": company_currency,
|
||||||
company_currency,
|
}
|
||||||
]
|
|
||||||
|
|
||||||
# map expense values
|
# map expense values
|
||||||
base_net_total = 0
|
base_net_total = 0
|
||||||
@@ -75,14 +103,16 @@ def _execute(filters=None, additional_table_columns=None):
|
|||||||
else:
|
else:
|
||||||
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
expense_amount = flt(invoice_expense_map.get(inv.name, {}).get(expense_acc))
|
||||||
base_net_total += expense_amount
|
base_net_total += expense_amount
|
||||||
row.append(expense_amount)
|
row.update({frappe.scrub(expense_acc): expense_amount})
|
||||||
|
|
||||||
# Add amount in unrealized account
|
# Add amount in unrealized account
|
||||||
for account in unrealized_profit_loss_accounts:
|
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
|
# 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
|
# tax account
|
||||||
total_tax = 0
|
total_tax = 0
|
||||||
@@ -90,45 +120,190 @@ def _execute(filters=None, additional_table_columns=None):
|
|||||||
if tax_acc not in expense_accounts:
|
if tax_acc not in expense_accounts:
|
||||||
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc))
|
tax_amount = flt(invoice_tax_map.get(inv.name, {}).get(tax_acc))
|
||||||
total_tax += tax_amount
|
total_tax += tax_amount
|
||||||
row.append(tax_amount)
|
row.update({frappe.scrub(tax_acc): tax_amount})
|
||||||
|
|
||||||
# total tax, grand total, rounded total & outstanding 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)
|
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"""
|
"""return columns based on filters"""
|
||||||
columns = [
|
columns = [
|
||||||
_("Invoice") + ":Link/Purchase Invoice:120",
|
{
|
||||||
_("Posting Date") + ":Date:80",
|
"label": _("Voucher Type"),
|
||||||
_("Supplier Id") + "::120",
|
"fieldname": "voucher_type",
|
||||||
_("Supplier Name") + "::120",
|
"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 += additional_table_columns
|
||||||
|
|
||||||
columns += [
|
if not include_payments:
|
||||||
_("Supplier Group") + ":Link/Supplier Group:120",
|
columns += [
|
||||||
_("Tax Id") + "::80",
|
{
|
||||||
_("Payable Account") + ":Link/Account:120",
|
"label": _("Supplier Group"),
|
||||||
_("Mode of Payment") + ":Link/Mode of Payment:80",
|
"fieldname": "supplier_group",
|
||||||
_("Project") + ":Link/Project:80",
|
"fieldtype": "Link",
|
||||||
_("Bill No") + "::120",
|
"options": "Supplier Group",
|
||||||
_("Bill Date") + ":Date:80",
|
"width": 120,
|
||||||
_("Remarks") + "::150",
|
},
|
||||||
_("Purchase Order") + ":Link/Purchase Order:100",
|
{"label": _("Tax Id"), "fieldname": "tax_id", "fieldtype": "Data", "width": 80},
|
||||||
_("Purchase Receipt") + ":Link/Purchase Receipt:100",
|
{
|
||||||
{"fieldname": "currency", "label": _("Currency"), "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 = []
|
expense_accounts = []
|
||||||
tax_accounts = []
|
tax_accounts = []
|
||||||
unrealized_profit_loss_accounts = []
|
unrealized_profit_loss_accounts = []
|
||||||
|
|
||||||
|
expense_columns = []
|
||||||
|
tax_columns = []
|
||||||
|
unrealized_profit_loss_account_columns = []
|
||||||
|
|
||||||
if invoice_list:
|
if invoice_list:
|
||||||
expense_accounts = frappe.db.sql_list(
|
expense_accounts = frappe.db.sql_list(
|
||||||
"""select distinct expense_account
|
"""select distinct expense_account
|
||||||
@@ -139,15 +314,18 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
tuple([inv.name for inv in invoice_list]),
|
tuple([inv.name for inv in invoice_list]),
|
||||||
)
|
)
|
||||||
|
|
||||||
tax_accounts = frappe.db.sql_list(
|
purchase_taxes_query = get_taxes_query(
|
||||||
"""select distinct account_head
|
invoice_list, "Purchase Taxes and Charges", "Purchase Invoice"
|
||||||
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_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(
|
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||||
"""SELECT distinct unrealized_profit_loss_account
|
"""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),
|
tuple(inv.name for inv in invoice_list),
|
||||||
)
|
)
|
||||||
|
|
||||||
expense_columns = [(account + ":Currency/currency:120") for account in expense_accounts]
|
for account in expense_accounts:
|
||||||
unrealized_profit_loss_account_columns = [
|
expense_columns.append(
|
||||||
(account + ":Currency/currency:120") for account in unrealized_profit_loss_accounts
|
{
|
||||||
]
|
"label": account,
|
||||||
tax_columns = [
|
"fieldname": frappe.scrub(account),
|
||||||
(account + ":Currency/currency:120")
|
"fieldtype": "Currency",
|
||||||
for account in tax_accounts
|
"options": "currency",
|
||||||
if account not in expense_accounts
|
"width": 120,
|
||||||
]
|
}
|
||||||
|
)
|
||||||
|
|
||||||
columns = (
|
for account in tax_accounts:
|
||||||
columns
|
if account not in expense_accounts:
|
||||||
+ expense_columns
|
tax_columns.append(
|
||||||
+ unrealized_profit_loss_account_columns
|
{
|
||||||
+ [_("Net Total") + ":Currency/currency:120"]
|
"label": account,
|
||||||
+ tax_columns
|
"fieldname": frappe.scrub(account),
|
||||||
+ [
|
"fieldtype": "Currency",
|
||||||
_("Total Tax") + ":Currency/currency:120",
|
"options": "currency",
|
||||||
_("Grand Total") + ":Currency/currency:120",
|
"width": 120,
|
||||||
_("Rounded Total") + ":Currency/currency:120",
|
}
|
||||||
_("Outstanding Amount") + ":Currency/currency: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):
|
return columns, accounts
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def get_invoices(filters, additional_query_columns):
|
def get_invoices(filters, additional_query_columns):
|
||||||
conditions = get_conditions(filters)
|
pi = frappe.qb.DocType("Purchase Invoice")
|
||||||
return frappe.db.sql(
|
invoice_item = frappe.qb.DocType("Purchase Invoice Item")
|
||||||
"""
|
query = (
|
||||||
select
|
frappe.qb.from_(pi)
|
||||||
name, posting_date, credit_to, supplier, supplier_name, tax_id, bill_no, bill_date,
|
.inner_join(invoice_item)
|
||||||
remarks, base_net_total, base_grand_total, outstanding_amount,
|
.on(pi.name == invoice_item.parent)
|
||||||
mode_of_payment {0}
|
.select(
|
||||||
from `tabPurchase Invoice`
|
ConstantColumn("Purchase Invoice").as_("doctype"),
|
||||||
where docstatus = 1 {1}
|
pi.name,
|
||||||
order by posting_date desc, name desc""".format(
|
pi.posting_date,
|
||||||
additional_query_columns, conditions
|
pi.credit_to,
|
||||||
),
|
pi.supplier,
|
||||||
filters,
|
pi.supplier_name,
|
||||||
as_dict=1,
|
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):
|
def get_invoice_expense_map(invoice_list):
|
||||||
@@ -300,7 +473,9 @@ def get_internal_invoice_map(invoice_list):
|
|||||||
return internal_invoice_map
|
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(
|
tax_details = frappe.db.sql(
|
||||||
"""
|
"""
|
||||||
select parent, account_head, case add_deduct_tax when "Add" then sum(base_tax_amount_after_discount_amount)
|
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,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if include_payments:
|
||||||
|
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||||
|
|
||||||
invoice_tax_map = {}
|
invoice_tax_map = {}
|
||||||
for d in tax_details:
|
for d in tax_details:
|
||||||
if d.account_head in expense_accounts:
|
if d.account_head in expense_accounts:
|
||||||
@@ -382,17 +560,3 @@ def get_account_details(invoice_list):
|
|||||||
account_map[acc.name] = acc.parent_account
|
account_map[acc.name] = acc.parent_account
|
||||||
|
|
||||||
return account_map
|
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"),
|
"label": __("Item Group"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options": "Item Group"
|
"options": "Item Group"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "include_payments",
|
||||||
|
"label": __("Show Ledger View"),
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,13 +5,22 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _, msgprint
|
from frappe import _, msgprint
|
||||||
from frappe.model.meta import get_field_precision
|
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 (
|
from erpnext.accounts.party import get_party_account
|
||||||
get_accounting_dimensions,
|
from erpnext.accounts.report.utils import (
|
||||||
get_dimension_with_children,
|
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):
|
def execute(filters=None):
|
||||||
@@ -22,9 +31,15 @@ def _execute(filters, additional_table_columns=None):
|
|||||||
if not filters:
|
if not filters:
|
||||||
filters = frappe._dict({})
|
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))
|
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(
|
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:
|
if not invoice_list:
|
||||||
@@ -34,13 +49,29 @@ def _execute(filters, additional_table_columns=None):
|
|||||||
invoice_income_map = get_invoice_income_map(invoice_list)
|
invoice_income_map = get_invoice_income_map(invoice_list)
|
||||||
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
internal_invoice_map = get_internal_invoice_map(invoice_list)
|
||||||
invoice_income_map, invoice_tax_map = get_invoice_tax_map(
|
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
|
# Cost Center & Warehouse Map
|
||||||
invoice_cc_wh_map = get_invoice_cc_wh_map(invoice_list)
|
invoice_cc_wh_map = get_invoice_cc_wh_map(invoice_list)
|
||||||
invoice_so_dn_map = get_invoice_so_dn_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")
|
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])
|
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 = []
|
data = []
|
||||||
for inv in invoice_list:
|
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", [])))
|
warehouse = list(set(invoice_cc_wh_map.get(inv.name, {}).get("warehouse", [])))
|
||||||
|
|
||||||
row = {
|
row = {
|
||||||
"invoice": inv.name,
|
"voucher_type": inv.doctype,
|
||||||
|
"voucher_no": inv.name,
|
||||||
"posting_date": inv.posting_date,
|
"posting_date": inv.posting_date,
|
||||||
"customer": inv.customer,
|
"customer": inv.customer,
|
||||||
"customer_name": inv.customer_name,
|
"customer_name": inv.customer_name,
|
||||||
**get_values_for_columns(additional_table_columns, inv),
|
**get_values_for_columns(additional_table_columns, inv),
|
||||||
"customer_group": inv.get("customer_group"),
|
"customer_group": customer_details.get(inv.customer).get("customer_group"),
|
||||||
"territory": inv.get("territory"),
|
"territory": customer_details.get(inv.customer).get("territory"),
|
||||||
"tax_id": inv.get("tax_id"),
|
"tax_id": customer_details.get(inv.customer).get("tax_id"),
|
||||||
"receivable_account": inv.debit_to,
|
"receivable_account": inv.debit_to,
|
||||||
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
"mode_of_payment": ", ".join(mode_of_payments.get(inv.name, [])),
|
||||||
"project": inv.project,
|
"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)
|
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"""
|
"""return columns based on filters"""
|
||||||
columns = [
|
columns = [
|
||||||
{
|
{
|
||||||
"label": _("Invoice"),
|
"label": _("Voucher Type"),
|
||||||
"fieldname": "invoice",
|
"fieldname": "voucher_type",
|
||||||
"fieldtype": "Link",
|
"width": 120,
|
||||||
"options": "Sales Invoice",
|
},
|
||||||
|
{
|
||||||
|
"label": _("Voucher"),
|
||||||
|
"fieldname": "voucher_no",
|
||||||
|
"fieldtype": "Dynamic Link",
|
||||||
|
"options": "voucher_type",
|
||||||
"width": 120,
|
"width": 120,
|
||||||
},
|
},
|
||||||
{"label": _("Posting Date"), "fieldname": "posting_date", "fieldtype": "Date", "width": 80},
|
{"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},
|
{"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 += 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"),
|
"label": _("Net Total"),
|
||||||
"fieldname": "customer_group",
|
"fieldname": "net_total",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Currency",
|
||||||
"options": "Customer Group",
|
"options": "currency",
|
||||||
"width": 120,
|
"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 = []
|
income_accounts = []
|
||||||
tax_accounts = []
|
tax_accounts = []
|
||||||
|
unrealized_profit_loss_accounts = []
|
||||||
|
|
||||||
income_columns = []
|
income_columns = []
|
||||||
tax_columns = []
|
tax_columns = []
|
||||||
unrealized_profit_loss_accounts = []
|
|
||||||
unrealized_profit_loss_account_columns = []
|
unrealized_profit_loss_account_columns = []
|
||||||
|
|
||||||
if invoice_list:
|
if invoice_list:
|
||||||
@@ -230,14 +352,16 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
tuple(inv.name for inv in invoice_list),
|
tuple(inv.name for inv in invoice_list),
|
||||||
)
|
)
|
||||||
|
|
||||||
tax_accounts = frappe.db.sql_list(
|
sales_taxes_query = get_taxes_query(invoice_list, "Sales Taxes and Charges", "Sales Invoice")
|
||||||
"""select distinct account_head
|
sales_tax_accounts = sales_taxes_query.run(as_dict=True, pluck="account_head")
|
||||||
from `tabSales Taxes and Charges` where parenttype = 'Sales Invoice'
|
tax_accounts = sales_tax_accounts
|
||||||
and docstatus = 1 and base_tax_amount_after_discount_amount != 0
|
|
||||||
and parent in (%s) order by account_head"""
|
if include_payments:
|
||||||
% ", ".join(["%s"] * len(invoice_list)),
|
advance_taxes_query = get_taxes_query(
|
||||||
tuple(inv.name for inv in invoice_list),
|
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(
|
unrealized_profit_loss_accounts = frappe.db.sql_list(
|
||||||
"""SELECT distinct unrealized_profit_loss_account
|
"""SELECT distinct unrealized_profit_loss_account
|
||||||
@@ -283,133 +407,71 @@ def get_columns(invoice_list, additional_table_columns):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
net_total_column = [
|
columns = [income_columns, unrealized_profit_loss_account_columns, tax_columns]
|
||||||
{
|
accounts = [income_accounts, unrealized_profit_loss_accounts, tax_accounts]
|
||||||
"label": _("Net Total"),
|
|
||||||
"fieldname": "net_total",
|
|
||||||
"fieldtype": "Currency",
|
|
||||||
"options": "currency",
|
|
||||||
"width": 120,
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
total_columns = [
|
return columns, accounts
|
||||||
{
|
|
||||||
"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
|
|
||||||
|
|
||||||
|
|
||||||
def get_invoices(filters, additional_query_columns):
|
def get_invoices(filters, additional_query_columns):
|
||||||
conditions = get_conditions(filters)
|
si = frappe.qb.DocType("Sales Invoice")
|
||||||
return frappe.db.sql(
|
invoice_item = frappe.qb.DocType("Sales Invoice Item")
|
||||||
"""
|
invoice_payment = frappe.qb.DocType("Sales Invoice Payment")
|
||||||
select name, posting_date, debit_to, project, customer,
|
query = (
|
||||||
customer_name, owner, remarks, territory, tax_id, customer_group,
|
frappe.qb.from_(si)
|
||||||
base_net_total, base_grand_total, base_rounded_total, outstanding_amount,
|
.inner_join(invoice_item)
|
||||||
is_internal_customer, represents_company, company {0}
|
.on(si.name == invoice_item.parent)
|
||||||
from `tabSales Invoice`
|
.left_join(invoice_payment)
|
||||||
where docstatus = 1 {1}
|
.on(si.name == invoice_payment.parent)
|
||||||
order by posting_date desc, name desc""".format(
|
.select(
|
||||||
additional_query_columns, conditions
|
ConstantColumn("Sales Invoice").as_("doctype"),
|
||||||
),
|
si.name,
|
||||||
filters,
|
si.posting_date,
|
||||||
as_dict=1,
|
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):
|
def get_invoice_income_map(invoice_list):
|
||||||
@@ -447,7 +509,7 @@ def get_internal_invoice_map(invoice_list):
|
|||||||
return internal_invoice_map
|
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(
|
tax_details = frappe.db.sql(
|
||||||
"""select parent, account_head,
|
"""select parent, account_head,
|
||||||
sum(base_tax_amount_after_discount_amount) as tax_amount
|
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,
|
as_dict=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if include_payments:
|
||||||
|
tax_details += get_advance_taxes_and_charges(invoice_list)
|
||||||
|
|
||||||
invoice_tax_map = {}
|
invoice_tax_map = {}
|
||||||
for d in tax_details:
|
for d in tax_details:
|
||||||
if d.account_head in income_accounts:
|
if d.account_head in income_accounts:
|
||||||
@@ -475,7 +540,7 @@ def get_invoice_so_dn_map(invoice_list):
|
|||||||
si_items = frappe.db.sql(
|
si_items = frappe.db.sql(
|
||||||
"""select parent, sales_order, delivery_note, so_detail
|
"""select parent, sales_order, delivery_note, so_detail
|
||||||
from `tabSales Invoice Item` where parent in (%s)
|
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)),
|
% ", ".join(["%s"] * len(invoice_list)),
|
||||||
tuple(inv.name for inv in invoice_list),
|
tuple(inv.name for inv in invoice_list),
|
||||||
as_dict=1,
|
as_dict=1,
|
||||||
@@ -510,7 +575,7 @@ def get_invoice_cc_wh_map(invoice_list):
|
|||||||
si_items = frappe.db.sql(
|
si_items = frappe.db.sql(
|
||||||
"""select parent, cost_center, warehouse
|
"""select parent, cost_center, warehouse
|
||||||
from `tabSales Invoice Item` where parent in (%s)
|
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)),
|
% ", ".join(["%s"] * len(invoice_list)),
|
||||||
tuple(inv.name for inv in invoice_list),
|
tuple(inv.name for inv in invoice_list),
|
||||||
as_dict=1,
|
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",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "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",
|
"fieldname": "cost_center",
|
||||||
|
|||||||
@@ -142,14 +142,20 @@ def get_opening_balances(filters):
|
|||||||
def get_rootwise_opening_balances(filters, report_type):
|
def get_rootwise_opening_balances(filters, report_type):
|
||||||
gle = []
|
gle = []
|
||||||
|
|
||||||
last_period_closing_voucher = frappe.db.get_all(
|
last_period_closing_voucher = ""
|
||||||
"Period Closing Voucher",
|
ignore_closing_balances = frappe.db.get_single_value(
|
||||||
filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)},
|
"Accounts Settings", "ignore_account_closing_balance"
|
||||||
fields=["posting_date", "name"],
|
|
||||||
order_by="posting_date desc",
|
|
||||||
limit=1,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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)
|
accounting_dimensions = get_accounting_dimensions(as_list=False)
|
||||||
|
|
||||||
if last_period_closing_voucher:
|
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"])
|
lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
|
||||||
cost_center = frappe.qb.DocType("Cost Center")
|
cost_center = frappe.qb.DocType("Cost Center")
|
||||||
opening_balance = opening_balance.where(
|
opening_balance = opening_balance.where(
|
||||||
closing_balance.cost_center.in_(
|
closing_balance.cost_center.isin(
|
||||||
frappe.qb.from_(cost_center)
|
frappe.qb.from_(cost_center)
|
||||||
.select("name")
|
.select("name")
|
||||||
.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
|
.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
|
||||||
|
|||||||
@@ -36,13 +36,13 @@ frappe.query_reports["Trial Balance for Party"] = {
|
|||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "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",
|
"fieldname":"party_type",
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import frappe
|
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 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 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.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
|
from erpnext.setup.utils import get_exchange_rate
|
||||||
|
|
||||||
__exchange_rates = {}
|
__exchange_rates = {}
|
||||||
@@ -165,7 +173,7 @@ def get_query_columns(report_columns):
|
|||||||
else:
|
else:
|
||||||
columns.append(fieldname)
|
columns.append(fieldname)
|
||||||
|
|
||||||
return ", " + ", ".join(columns)
|
return columns
|
||||||
|
|
||||||
|
|
||||||
def get_values_for_columns(report_columns, report_row):
|
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)
|
values[fieldname] = report_row.get(fieldname)
|
||||||
|
|
||||||
return values
|
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(
|
.select(
|
||||||
gle.voucher_type, gle.voucher_no, Sum(gle.debit).as_("debit"), Sum(gle.credit).as_("credit")
|
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)
|
.groupby(gle.voucher_no)
|
||||||
)
|
)
|
||||||
query = apply_filters(query, filters, gle)
|
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 with references
|
||||||
new_row = journal_entry.append("accounts")
|
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(d["dr_or_cr"], d["allocated_amount"])
|
||||||
new_row.set(
|
new_row.set(
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
frm.trigger("set_depr_posting_failure_alert");
|
frm.trigger("set_depr_posting_failure_alert");
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.trigger("setup_chart");
|
frm.trigger("setup_chart_and_depr_schedule_view");
|
||||||
}
|
}
|
||||||
|
|
||||||
frm.trigger("toggle_reference_doc");
|
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) {
|
if(frm.doc.finance_books.length > 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -228,7 +264,7 @@ frappe.ui.form.on('Asset', {
|
|||||||
"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
|
"erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule.get_depr_schedule",
|
||||||
{
|
{
|
||||||
asset_name: frm.doc.name,
|
asset_name: frm.doc.name,
|
||||||
status: frm.doc.docstatus ? "Active" : "Draft",
|
status: "Active",
|
||||||
finance_book: frm.doc.finance_books[0].finance_book || null
|
finance_book: frm.doc.finance_books[0].finance_book || null
|
||||||
}
|
}
|
||||||
)).message;
|
)).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 {
|
} else {
|
||||||
if(frm.doc.opening_accumulated_depreciation) {
|
if(frm.doc.opening_accumulated_depreciation) {
|
||||||
x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
|
x_intervals.push(frappe.format(frm.doc.creation.split(" ")[0], { fieldtype: 'Date' }));
|
||||||
|
|||||||
@@ -43,6 +43,7 @@
|
|||||||
"column_break_33",
|
"column_break_33",
|
||||||
"opening_accumulated_depreciation",
|
"opening_accumulated_depreciation",
|
||||||
"number_of_depreciations_booked",
|
"number_of_depreciations_booked",
|
||||||
|
"is_fully_depreciated",
|
||||||
"section_break_36",
|
"section_break_36",
|
||||||
"finance_books",
|
"finance_books",
|
||||||
"section_break_33",
|
"section_break_33",
|
||||||
@@ -52,6 +53,8 @@
|
|||||||
"column_break_24",
|
"column_break_24",
|
||||||
"frequency_of_depreciation",
|
"frequency_of_depreciation",
|
||||||
"next_depreciation_date",
|
"next_depreciation_date",
|
||||||
|
"depreciation_schedule_sb",
|
||||||
|
"depreciation_schedule_view",
|
||||||
"insurance_details",
|
"insurance_details",
|
||||||
"policy_number",
|
"policy_number",
|
||||||
"insurer",
|
"insurer",
|
||||||
@@ -203,6 +206,7 @@
|
|||||||
"fieldname": "disposal_date",
|
"fieldname": "disposal_date",
|
||||||
"fieldtype": "Date",
|
"fieldtype": "Date",
|
||||||
"label": "Disposal Date",
|
"label": "Disposal Date",
|
||||||
|
"no_copy": 1,
|
||||||
"read_only": 1
|
"read_only": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -242,19 +246,17 @@
|
|||||||
"label": "Is Existing Asset"
|
"label": "Is Existing Asset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"depends_on": "is_existing_asset",
|
"depends_on": "eval:(doc.is_existing_asset)",
|
||||||
"fieldname": "opening_accumulated_depreciation",
|
"fieldname": "opening_accumulated_depreciation",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"label": "Opening Accumulated Depreciation",
|
"label": "Opening Accumulated Depreciation",
|
||||||
"no_copy": 1,
|
|
||||||
"options": "Company:company:default_currency"
|
"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",
|
"fieldname": "number_of_depreciations_booked",
|
||||||
"fieldtype": "Int",
|
"fieldtype": "Int",
|
||||||
"label": "Number of Depreciations Booked",
|
"label": "Number of Depreciations Booked"
|
||||||
"no_copy": 1
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"collapsible": 1,
|
"collapsible": 1,
|
||||||
@@ -487,6 +489,24 @@
|
|||||||
"options": "\nSuccessful\nFailed",
|
"options": "\nSuccessful\nFailed",
|
||||||
"print_hide": 1,
|
"print_hide": 1,
|
||||||
"read_only": 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,
|
"idx": 72,
|
||||||
@@ -513,6 +533,11 @@
|
|||||||
"link_doctype": "Asset Depreciation Schedule",
|
"link_doctype": "Asset Depreciation Schedule",
|
||||||
"link_fieldname": "asset"
|
"link_fieldname": "asset"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"group": "Activity",
|
||||||
|
"link_doctype": "Asset Activity",
|
||||||
|
"link_fieldname": "asset"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"group": "Journal Entry",
|
"group": "Journal Entry",
|
||||||
"link_doctype": "Journal Entry",
|
"link_doctype": "Journal Entry",
|
||||||
@@ -520,7 +545,7 @@
|
|||||||
"table_fieldname": "accounts"
|
"table_fieldname": "accounts"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2023-03-30 15:07:41.542374",
|
"modified": "2023-07-28 20:12:44.819616",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Asset",
|
"name": "Asset",
|
||||||
@@ -564,4 +589,4 @@
|
|||||||
"states": [],
|
"states": [],
|
||||||
"title_field": "asset_name",
|
"title_field": "asset_name",
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ from frappe.utils import (
|
|||||||
import erpnext
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
from erpnext.accounts.general_ledger import make_reverse_gl_entries
|
||||||
from erpnext.assets.doctype.asset.depreciation import (
|
from erpnext.assets.doctype.asset.depreciation import (
|
||||||
|
get_comma_separated_links,
|
||||||
get_depreciation_accounts,
|
get_depreciation_accounts,
|
||||||
get_disposal_account_and_cost_center,
|
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_category.asset_category import get_asset_category_account
|
||||||
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
cancel_asset_depr_schedules,
|
cancel_asset_depr_schedules,
|
||||||
@@ -58,9 +60,19 @@ class Asset(AccountsController):
|
|||||||
self.make_asset_movement()
|
self.make_asset_movement()
|
||||||
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
if not self.booked_fixed_asset and self.validate_make_gl_entry():
|
||||||
self.make_gl_entries()
|
self.make_gl_entries()
|
||||||
if not self.split_from:
|
if self.calculate_depreciation and not self.split_from:
|
||||||
make_draft_asset_depr_schedules_if_not_present(self)
|
asset_depr_schedules_names = make_draft_asset_depr_schedules_if_not_present(self)
|
||||||
convert_draft_asset_depr_schedules_into_active(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):
|
def on_cancel(self):
|
||||||
self.validate_cancellation()
|
self.validate_cancellation()
|
||||||
@@ -71,10 +83,29 @@ class Asset(AccountsController):
|
|||||||
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
|
||||||
make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
|
make_reverse_gl_entries(voucher_type="Asset", voucher_no=self.name)
|
||||||
self.db_set("booked_fixed_asset", 0)
|
self.db_set("booked_fixed_asset", 0)
|
||||||
|
add_asset_activity(self.name, _("Asset cancelled"))
|
||||||
|
|
||||||
def after_insert(self):
|
def after_insert(self):
|
||||||
if not self.split_from:
|
if self.calculate_depreciation and not self.split_from:
|
||||||
make_draft_asset_depr_schedules(self)
|
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):
|
def validate_asset_and_reference(self):
|
||||||
if self.purchase_invoice or self.purchase_receipt:
|
if self.purchase_invoice or self.purchase_receipt:
|
||||||
@@ -176,8 +207,11 @@ class Asset(AccountsController):
|
|||||||
|
|
||||||
if not self.calculate_depreciation:
|
if not self.calculate_depreciation:
|
||||||
return
|
return
|
||||||
elif not self.finance_books:
|
else:
|
||||||
frappe.throw(_("Enter depreciation details"))
|
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:
|
if self.is_existing_asset:
|
||||||
return
|
return
|
||||||
@@ -258,7 +292,7 @@ class Asset(AccountsController):
|
|||||||
depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
|
depreciable_amount = flt(self.gross_purchase_amount) - flt(row.expected_value_after_useful_life)
|
||||||
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
|
if flt(self.opening_accumulated_depreciation) > depreciable_amount:
|
||||||
frappe.throw(
|
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
|
depreciable_amount
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -394,7 +428,9 @@ class Asset(AccountsController):
|
|||||||
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
|
expected_value_after_useful_life = self.finance_books[idx].expected_value_after_useful_life
|
||||||
value_after_depreciation = self.finance_books[idx].value_after_depreciation
|
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"
|
status = "Fully Depreciated"
|
||||||
elif flt(value_after_depreciation) < flt(self.gross_purchase_amount):
|
elif flt(value_after_depreciation) < flt(self.gross_purchase_amount):
|
||||||
status = "Partially Depreciated"
|
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"):
|
for row in asset.get("finance_books"):
|
||||||
value_after_depreciation = flt(
|
value_after_depreciation = flt(
|
||||||
(row.value_after_depreciation * remaining_qty) / asset.asset_quantity
|
(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
|
(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.submit()
|
||||||
new_asset.set_status()
|
new_asset.set_status()
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
|
|||||||
get_checks_for_pl_and_bs_accounts,
|
get_checks_for_pl_and_bs_accounts,
|
||||||
)
|
)
|
||||||
from erpnext.accounts.doctype.journal_entry.journal_entry import make_reverse_journal_entry
|
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 (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
get_asset_depr_schedule_doc,
|
get_asset_depr_schedule_doc,
|
||||||
get_asset_depr_schedule_name,
|
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)
|
frappe.db.set_value("Asset", asset_name, "journal_entry_for_scrap", je.name)
|
||||||
asset.set_status("Scrapped")
|
asset.set_status("Scrapped")
|
||||||
|
|
||||||
|
add_asset_activity(asset_name, _("Asset scrapped"))
|
||||||
|
|
||||||
frappe.msgprint(_("Asset scrapped via Journal Entry {0}").format(je.name))
|
frappe.msgprint(_("Asset scrapped via Journal Entry {0}").format(je.name))
|
||||||
|
|
||||||
|
|
||||||
@@ -349,6 +352,8 @@ def restore_asset(asset_name):
|
|||||||
|
|
||||||
asset.set_status()
|
asset.set_status()
|
||||||
|
|
||||||
|
add_asset_activity(asset_name, _("Asset restored"))
|
||||||
|
|
||||||
|
|
||||||
def depreciate_asset(asset_doc, date, notes):
|
def depreciate_asset(asset_doc, date, notes):
|
||||||
asset_doc.flags.ignore_validate_update_after_submit = True
|
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 = make_reverse_journal_entry(schedule.journal_entry)
|
||||||
reverse_journal_entry.posting_date = nowdate()
|
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
|
frappe.flags.is_reverse_depr_entry = True
|
||||||
reverse_journal_entry.submit()
|
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,
|
reset_depreciation_schedule,
|
||||||
reverse_depreciation_entry_made_after_disposal,
|
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.assets.doctype.asset_category.asset_category import get_asset_category_account
|
||||||
from erpnext.controllers.stock_controller import StockController
|
from erpnext.controllers.stock_controller import StockController
|
||||||
from erpnext.setup.doctype.brand.brand import get_brand_defaults
|
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
|
"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(
|
frappe.msgprint(
|
||||||
_(
|
_(
|
||||||
"Asset {0} has been created. Please set the depreciation details if any and submit it."
|
"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):
|
def set_consumed_asset_status(self, asset):
|
||||||
if self.docstatus == 1:
|
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:
|
else:
|
||||||
asset.set_status()
|
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()
|
@frappe.whitelist()
|
||||||
|
|||||||
@@ -571,6 +571,8 @@ def get_wdv_or_dd_depr_amount(
|
|||||||
|
|
||||||
|
|
||||||
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
|
def make_draft_asset_depr_schedules_if_not_present(asset_doc):
|
||||||
|
asset_depr_schedules_names = []
|
||||||
|
|
||||||
for row in asset_doc.get("finance_books"):
|
for row in asset_doc.get("finance_books"):
|
||||||
draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
|
draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
|
||||||
asset_doc.name, "Draft", row.finance_book
|
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:
|
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):
|
def make_draft_asset_depr_schedules(asset_doc):
|
||||||
|
asset_depr_schedules_names = []
|
||||||
|
|
||||||
for row in asset_doc.get("finance_books"):
|
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):
|
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()
|
asset_depr_schedule_doc.insert()
|
||||||
|
|
||||||
|
return asset_depr_schedule_doc.name
|
||||||
|
|
||||||
|
|
||||||
def update_draft_asset_depr_schedules(asset_doc):
|
def update_draft_asset_depr_schedules(asset_doc):
|
||||||
for row in asset_doc.get("finance_books"):
|
for row in asset_doc.get("finance_books"):
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.model.document import Document
|
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):
|
class AssetMovement(Document):
|
||||||
@@ -128,5 +131,24 @@ class AssetMovement(Document):
|
|||||||
current_location = latest_movement_entry[0][0]
|
current_location = latest_movement_entry[0][0]
|
||||||
current_employee = latest_movement_entry[0][1]
|
current_employee = latest_movement_entry[0][1]
|
||||||
|
|
||||||
frappe.db.set_value("Asset", d.asset, "location", current_location)
|
frappe.db.set_value("Asset", d.asset, "location", current_location, update_modified=False)
|
||||||
frappe.db.set_value("Asset", d.asset, "custodian", current_employee)
|
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
|
import erpnext
|
||||||
from erpnext.accounts.general_ledger import make_gl_entries
|
from erpnext.accounts.general_ledger import make_gl_entries
|
||||||
from erpnext.assets.doctype.asset.asset import get_asset_account
|
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 (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
get_depr_schedule,
|
get_depr_schedule,
|
||||||
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
make_new_active_asset_depr_schedules_and_cancel_current_ones,
|
||||||
@@ -25,8 +26,14 @@ class AssetRepair(AccountsController):
|
|||||||
self.calculate_total_repair_cost()
|
self.calculate_total_repair_cost()
|
||||||
|
|
||||||
def update_status(self):
|
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")
|
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:
|
else:
|
||||||
self.asset_doc.set_status()
|
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)
|
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||||
self.asset_doc.save()
|
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):
|
def before_cancel(self):
|
||||||
self.asset_doc = frappe.get_doc("Asset", self.asset)
|
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)
|
make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
|
||||||
self.asset_doc.save()
|
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):
|
def after_delete(self):
|
||||||
frappe.get_doc("Asset", self.asset).set_status()
|
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.asset import get_asset_value_after_depreciation
|
||||||
from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
|
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 (
|
from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
|
||||||
get_asset_depr_schedule_doc,
|
get_asset_depr_schedule_doc,
|
||||||
get_depreciation_amount,
|
get_depreciation_amount,
|
||||||
@@ -27,9 +28,21 @@ class AssetValueAdjustment(Document):
|
|||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.make_depreciation_entry()
|
self.make_depreciation_entry()
|
||||||
self.reschedule_depreciations(self.new_asset_value)
|
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):
|
def on_cancel(self):
|
||||||
self.reschedule_depreciations(self.current_asset_value)
|
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):
|
def validate_date(self):
|
||||||
asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date")
|
asset_purchase_date = frappe.db.get_value("Asset", self.asset, "purchase_date")
|
||||||
@@ -74,12 +87,16 @@ class AssetValueAdjustment(Document):
|
|||||||
"account": accumulated_depreciation_account,
|
"account": accumulated_depreciation_account,
|
||||||
"credit_in_account_currency": self.difference_amount,
|
"credit_in_account_currency": self.difference_amount,
|
||||||
"cost_center": depreciation_cost_center or self.cost_center,
|
"cost_center": depreciation_cost_center or self.cost_center,
|
||||||
|
"reference_type": "Asset",
|
||||||
|
"reference_name": self.asset,
|
||||||
}
|
}
|
||||||
|
|
||||||
debit_entry = {
|
debit_entry = {
|
||||||
"account": depreciation_expense_account,
|
"account": depreciation_expense_account,
|
||||||
"debit_in_account_currency": self.difference_amount,
|
"debit_in_account_currency": self.difference_amount,
|
||||||
"cost_center": depreciation_cost_center or self.cost_center,
|
"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()
|
accounting_dimensions = get_checks_for_pl_and_bs_accounts()
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
"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",
|
"fieldname": "make_depreciation_entry",
|
||||||
"fieldtype": "Button",
|
"fieldtype": "Button",
|
||||||
"label": "Make Depreciation Entry"
|
"label": "Make Depreciation Entry"
|
||||||
@@ -61,7 +61,7 @@
|
|||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-03-13 23:17:15.849950",
|
"modified": "2023-07-26 12:56:48.718736",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Depreciation Schedule",
|
"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",
|
"creation": "2019-09-23 16:35:02.836134",
|
||||||
"disable_prepared_report": 1,
|
|
||||||
"disabled": 0,
|
"disabled": 0,
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"doctype": "Report",
|
"doctype": "Report",
|
||||||
|
"filters": [],
|
||||||
"idx": 0,
|
"idx": 0,
|
||||||
"is_standard": "Yes",
|
"is_standard": "Yes",
|
||||||
"modified": "2019-10-22 13:00:31.539726",
|
"letterhead": null,
|
||||||
|
"modified": "2023-07-26 21:03:20.722628",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Assets",
|
"module": "Assets",
|
||||||
"name": "Fixed Asset Register",
|
"name": "Fixed Asset Register",
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ def get_conditions(filters):
|
|||||||
conditions["cost_center"] = filters.get("cost_center")
|
conditions["cost_center"] = filters.get("cost_center")
|
||||||
|
|
||||||
if status:
|
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"
|
operand = "not in"
|
||||||
if status not in "In Location":
|
if status not in "In Location":
|
||||||
operand = "in"
|
operand = "in"
|
||||||
|
|
||||||
conditions["status"] = (operand, ["Sold", "Scrapped"])
|
conditions["status"] = (operand, ["Sold", "Scrapped", "Capitalized", "Decapitalized"])
|
||||||
|
|
||||||
return conditions
|
return conditions
|
||||||
|
|
||||||
@@ -71,36 +71,6 @@ def get_data(filters):
|
|||||||
pr_supplier_map = get_purchase_receipt_supplier_map()
|
pr_supplier_map = get_purchase_receipt_supplier_map()
|
||||||
pi_supplier_map = get_purchase_invoice_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)
|
assets_linked_to_fb = get_assets_linked_to_fb(filters)
|
||||||
|
|
||||||
company_fb = frappe.get_cached_value("Company", filters.company, "default_finance_book")
|
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)
|
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:
|
for asset in assets_record:
|
||||||
if (
|
if (
|
||||||
assets_linked_to_fb
|
assets_linked_to_fb
|
||||||
@@ -136,7 +131,7 @@ def get_data(filters):
|
|||||||
or pi_supplier_map.get(asset.purchase_invoice),
|
or pi_supplier_map.get(asset.purchase_invoice),
|
||||||
"gross_purchase_amount": asset.gross_purchase_amount,
|
"gross_purchase_amount": asset.gross_purchase_amount,
|
||||||
"opening_accumulated_depreciation": asset.opening_accumulated_depreciation,
|
"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,
|
"available_for_use_date": asset.available_for_use_date,
|
||||||
"location": asset.location,
|
"location": asset.location,
|
||||||
"asset_category": asset.asset_category,
|
"asset_category": asset.asset_category,
|
||||||
@@ -230,12 +225,11 @@ def get_assets_linked_to_fb(filters):
|
|||||||
return assets_linked_to_fb
|
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):
|
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")
|
asset = frappe.qb.DocType("Asset")
|
||||||
gle = frappe.qb.DocType("GL Entry")
|
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.debit != 0)
|
||||||
.where(gle.is_cancelled == 0)
|
.where(gle.is_cancelled == 0)
|
||||||
|
.where(company.name == filters.company)
|
||||||
.where(asset.docstatus == 1)
|
.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:
|
if finance_book:
|
||||||
query = query.where(
|
query = query.where(
|
||||||
(gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull())
|
(gle.finance_book.isin([cstr(finance_book), ""])) | (gle.finance_book.isnull())
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull()))
|
query = query.where((gle.finance_book.isin([""])) | (gle.finance_book.isnull()))
|
||||||
|
|
||||||
if filters.filter_based_on in ("Date Range", "Fiscal Year"):
|
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()
|
asset_depr_amount_map = query.run()
|
||||||
|
|
||||||
return dict(asset_depr_amount_map)
|
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():
|
def get_purchase_receipt_supplier_map():
|
||||||
return frappe._dict(
|
return frappe._dict(
|
||||||
frappe.db.sql(
|
frappe.db.sql(
|
||||||
@@ -313,35 +359,35 @@ def get_columns(filters):
|
|||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"fieldname": frappe.scrub(filters.get("group_by")),
|
"fieldname": frappe.scrub(filters.get("group_by")),
|
||||||
"options": filters.get("group_by"),
|
"options": filters.get("group_by"),
|
||||||
"width": 120,
|
"width": 216,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Gross Purchase Amount"),
|
"label": _("Gross Purchase Amount"),
|
||||||
"fieldname": "gross_purchase_amount",
|
"fieldname": "gross_purchase_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "company:currency",
|
"options": "company:currency",
|
||||||
"width": 100,
|
"width": 250,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Opening Accumulated Depreciation"),
|
"label": _("Opening Accumulated Depreciation"),
|
||||||
"fieldname": "opening_accumulated_depreciation",
|
"fieldname": "opening_accumulated_depreciation",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "company:currency",
|
"options": "company:currency",
|
||||||
"width": 90,
|
"width": 250,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Depreciated Amount"),
|
"label": _("Depreciated Amount"),
|
||||||
"fieldname": "depreciated_amount",
|
"fieldname": "depreciated_amount",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "company:currency",
|
"options": "company:currency",
|
||||||
"width": 100,
|
"width": 250,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": _("Asset Value"),
|
"label": _("Asset Value"),
|
||||||
"fieldname": "asset_value",
|
"fieldname": "asset_value",
|
||||||
"fieldtype": "Currency",
|
"fieldtype": "Currency",
|
||||||
"options": "company:currency",
|
"options": "company:currency",
|
||||||
"width": 100,
|
"width": 250,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -183,6 +183,17 @@
|
|||||||
"link_type": "Report",
|
"link_type": "Report",
|
||||||
"onboard": 0,
|
"onboard": 0,
|
||||||
"type": "Link"
|
"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",
|
"modified": "2023-05-24 14:47:20.243146",
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ frappe.ui.form.on("Purchase Order", {
|
|||||||
get_materials_from_supplier: function(frm) {
|
get_materials_from_supplier: function(frm) {
|
||||||
let po_details = [];
|
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 => {
|
frm.doc.supplied_items.forEach(d => {
|
||||||
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
|
if (d.total_supplied_qty && d.total_supplied_qty != d.consumed_qty) {
|
||||||
po_details.push(d.name)
|
po_details.push(d.name)
|
||||||
@@ -184,7 +184,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!in_list(["Closed", "Delivered"], doc.status)) {
|
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.
|
// 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)) {
|
if (!(this.frm.doc.is_subcontracted && !this.frm.doc.is_old_subcontracting_flow)) {
|
||||||
this.frm.add_custom_button(__('Update Items'), () => {
|
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 (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") {
|
if (doc.status != "On Hold") {
|
||||||
this.frm.add_custom_button(__('Hold'), () => this.hold_purchase_order(), __("Status"));
|
this.frm.add_custom_button(__('Hold'), () => this.hold_purchase_order(), __("Status"));
|
||||||
} else{
|
} else{
|
||||||
@@ -221,7 +221,7 @@ erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends e
|
|||||||
}
|
}
|
||||||
if(doc.status != "Closed") {
|
if(doc.status != "Closed") {
|
||||||
if (doc.status != "On Hold") {
|
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'));
|
cur_frm.add_custom_button(__('Purchase Receipt'), this.make_purchase_receipt, __('Create'));
|
||||||
if (doc.is_subcontracted) {
|
if (doc.is_subcontracted) {
|
||||||
if (doc.is_old_subcontracting_flow) {
|
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'),
|
cur_frm.add_custom_button(__('Purchase Invoice'),
|
||||||
this.make_purchase_invoice, __('Create'));
|
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(
|
this.frm.add_custom_button(
|
||||||
__('Payment'),
|
__('Payment'),
|
||||||
() => this.make_payment_entry(),
|
() => 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'),
|
this.frm.add_custom_button(__('Payment Request'),
|
||||||
function() { me.make_payment_request() }, __('Create'));
|
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) {
|
if (doc.docstatus === 1 && !doc.inter_company_order_reference) {
|
||||||
let me = this;
|
let me = this;
|
||||||
let internal = me.frm.doc.is_internal_supplier;
|
let internal = me.frm.doc.is_internal_supplier;
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ frappe.query_reports["Procurement Tracker"] = {
|
|||||||
fieldname: "from_date",
|
fieldname: "from_date",
|
||||||
label: __("From Date"),
|
label: __("From Date"),
|
||||||
fieldtype: "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",
|
fieldname:"to_date",
|
||||||
label: __("To Date"),
|
label: __("To Date"),
|
||||||
fieldtype: "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",
|
fieldname: "from_date",
|
||||||
label: __("From Date"),
|
label: __("From Date"),
|
||||||
fieldtype: "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
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname:"to_date",
|
fieldname:"to_date",
|
||||||
label: __("To Date"),
|
label: __("To Date"),
|
||||||
fieldtype: "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
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -206,9 +206,11 @@ def post_process(doctype, data):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if doc.get("per_delivered"):
|
if doc.get("per_delivered"):
|
||||||
doc.status_percent += flt(doc.per_delivered)
|
doc.status_percent += flt(doc.per_delivered, 2)
|
||||||
doc.status_display.append(
|
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"):
|
if hasattr(doc, "set_indicator"):
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ frappe.query_reports["Campaign Efficiency"] = {
|
|||||||
"fieldname": "from_date",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "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",
|
"fieldname": "from_date",
|
||||||
"label": __("From Date"),
|
"label": __("From Date"),
|
||||||
"fieldtype": "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",
|
"fieldname": "to_date",
|
||||||
"label": __("To Date"),
|
"label": __("To Date"),
|
||||||
"fieldtype": "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": [
|
"all": [
|
||||||
"erpnext.projects.doctype.project.project.project_status_update_reminder",
|
|
||||||
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
|
"erpnext.crm.doctype.social_media_post.social_media_post.process_scheduled_social_media_posts",
|
||||||
],
|
],
|
||||||
"hourly": [
|
"hourly": [
|
||||||
"erpnext.erpnext_integrations.doctype.plaid_settings.plaid_settings.automatic_synchronization",
|
"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.hourly_reminder",
|
||||||
"erpnext.projects.doctype.project.project.collect_project_status",
|
"erpnext.projects.doctype.project.project.collect_project_status",
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class BOMUpdateLog(Document):
|
|||||||
else:
|
else:
|
||||||
frappe.enqueue(
|
frappe.enqueue(
|
||||||
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
|
method="erpnext.manufacturing.doctype.bom_update_log.bom_update_log.process_boms_cost_level_wise",
|
||||||
|
queue="long",
|
||||||
update_doc=self,
|
update_doc=self,
|
||||||
now=frappe.flags.in_test,
|
now=frappe.flags.in_test,
|
||||||
enqueue_after_commit=True,
|
enqueue_after_commit=True,
|
||||||
|
|||||||
@@ -157,12 +157,19 @@ def get_next_higher_level_boms(
|
|||||||
def get_leaf_boms() -> List[str]:
|
def get_leaf_boms() -> List[str]:
|
||||||
"Get BOMs that have no dependencies."
|
"Get BOMs that have no dependencies."
|
||||||
|
|
||||||
return frappe.db.sql_list(
|
bom = frappe.qb.DocType("BOM")
|
||||||
"""select name from `tabBOM` bom
|
bom_item = frappe.qb.DocType("BOM Item")
|
||||||
where docstatus=1 and is_active=1
|
|
||||||
and not exists(select bom_no from `tabBOM Item`
|
boms = (
|
||||||
where parent=bom.name and ifnull(bom_no, '')!='')"""
|
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:
|
def _generate_dependence_map() -> defaultdict:
|
||||||
|
|||||||
@@ -544,12 +544,12 @@ class JobCard(Document):
|
|||||||
if self.for_quantity and flt(total_completed_qty, precision) != flt(
|
if self.for_quantity and flt(total_completed_qty, precision) != flt(
|
||||||
self.for_quantity, precision
|
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"))
|
qty_to_manufacture = bold(_("Qty to Manufacture"))
|
||||||
|
|
||||||
frappe.throw(
|
frappe.throw(
|
||||||
_("The {0} ({1}) must be equal to {2} ({3})").format(
|
_("The {0} ({1}) must be equal to {2} ({3})").format(
|
||||||
total_completed_qty,
|
total_completed_qty_label,
|
||||||
bold(flt(total_completed_qty, precision)),
|
bold(flt(total_completed_qty, precision)),
|
||||||
qty_to_manufacture,
|
qty_to_manufacture,
|
||||||
bold(self.for_quantity),
|
bold(self.for_quantity),
|
||||||
|
|||||||
@@ -9,19 +9,25 @@ frappe.ui.form.on('Production Plan', {
|
|||||||
item.temporary_name = item.name;
|
item.temporary_name = item.name;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(frm) {
|
setup(frm) {
|
||||||
|
frm.trigger("setup_queries");
|
||||||
|
|
||||||
frm.custom_make_buttons = {
|
frm.custom_make_buttons = {
|
||||||
'Work Order': 'Work Order / Subcontract PO',
|
'Work Order': 'Work Order / Subcontract PO',
|
||||||
'Material Request': 'Material Request',
|
'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 {
|
return {
|
||||||
|
query: "erpnext.manufacturing.doctype.production_plan.production_plan.sales_order_query",
|
||||||
filters: {
|
filters: {
|
||||||
company: doc.company
|
company: frm.doc.company,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
frm.set_query('for_warehouse', function(doc) {
|
frm.set_query('for_warehouse', function(doc) {
|
||||||
return {
|
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 {
|
return {
|
||||||
query: "erpnext.controllers.queries.item_query",
|
query: "erpnext.controllers.queries.item_query",
|
||||||
filters:{
|
filters:{
|
||||||
'is_stock_item': 1,
|
'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];
|
var d = locals[cdt][cdn];
|
||||||
if (d.item_code) {
|
if (d.item_code) {
|
||||||
return {
|
return {
|
||||||
query: "erpnext.controllers.queries.bom",
|
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"));
|
} 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 {
|
return {
|
||||||
filters: {
|
filters: {
|
||||||
company: doc.company
|
company: doc.company
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
frm.set_query("warehouse", "po_items", (doc) => {
|
||||||
|
return {
|
||||||
|
filters: {
|
||||||
|
company: doc.company
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
refresh(frm) {
|
refresh(frm) {
|
||||||
@@ -436,7 +450,7 @@ frappe.ui.form.on("Production Plan Item", {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
frappe.ui.form.on("Material Request 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", {
|
frappe.ui.form.on("Production Plan Sales Order", {
|
||||||
sales_order(frm, cdt, cdn) {
|
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) {
|
if (!sales_order) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
frappe.call({
|
|
||||||
method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_so_details",
|
if (row.sales_order) {
|
||||||
args: { sales_order },
|
frm.call({
|
||||||
callback(r) {
|
method: "validate_sales_orders",
|
||||||
const {transaction_date, customer, grand_total} = r.message;
|
doc: frm.doc,
|
||||||
frappe.model.set_value(cdt, cdn, 'sales_order_date', transaction_date);
|
args: {
|
||||||
frappe.model.set_value(cdt, cdn, 'customer', customer);
|
sales_order: row.sales_order,
|
||||||
frappe.model.set_value(cdt, cdn, 'grand_total', grand_total);
|
},
|
||||||
}
|
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'] = [
|
frappe.tour['Production Plan'] = [
|
||||||
{
|
{
|
||||||
fieldname: "get_items_from",
|
fieldname: "get_items_from",
|
||||||
|
|||||||
@@ -228,10 +228,10 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"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",
|
"fieldname": "ignore_existing_ordered_qty",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Ignore Existing Projected Quantity"
|
"label": "Ignore Available Stock"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "column_break_25",
|
"fieldname": "column_break_25",
|
||||||
@@ -339,7 +339,7 @@
|
|||||||
"depends_on": "eval:doc.get_items_from == 'Sales Order'",
|
"depends_on": "eval:doc.get_items_from == 'Sales Order'",
|
||||||
"fieldname": "combine_items",
|
"fieldname": "combine_items",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Consolidate Items"
|
"label": "Consolidate Sales Order Items"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "section_break_25",
|
"fieldname": "section_break_25",
|
||||||
@@ -399,7 +399,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"default": "0",
|
"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",
|
"fieldname": "skip_available_sub_assembly_item",
|
||||||
"fieldtype": "Check",
|
"fieldtype": "Check",
|
||||||
"label": "Skip Available Sub Assembly Items"
|
"label": "Skip Available Sub Assembly Items"
|
||||||
@@ -422,7 +422,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"is_submittable": 1,
|
"is_submittable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2023-05-22 23:36:31.770517",
|
"modified": "2023-07-28 13:37:43.926686",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Manufacturing",
|
"module": "Manufacturing",
|
||||||
"name": "Production Plan",
|
"name": "Production Plan",
|
||||||
|
|||||||
@@ -39,6 +39,36 @@ class ProductionPlan(Document):
|
|||||||
self.set_status()
|
self.set_status()
|
||||||
self._rename_temporary_references()
|
self._rename_temporary_references()
|
||||||
validate_uom_is_integer(self, "stock_uom", "planned_qty")
|
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):
|
def set_pending_qty_in_row_without_reference(self):
|
||||||
"Set Pending Qty in independent rows (not from SO or MR)."
|
"Set Pending Qty in independent rows (not from SO or MR)."
|
||||||
@@ -205,6 +235,7 @@ class ProductionPlan(Document):
|
|||||||
).as_("pending_qty"),
|
).as_("pending_qty"),
|
||||||
so_item.description,
|
so_item.description,
|
||||||
so_item.name,
|
so_item.name,
|
||||||
|
so_item.bom_no,
|
||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
.where(
|
.where(
|
||||||
@@ -342,7 +373,7 @@ class ProductionPlan(Document):
|
|||||||
"item_code": data.item_code,
|
"item_code": data.item_code,
|
||||||
"description": data.description or item_details.description,
|
"description": data.description or item_details.description,
|
||||||
"stock_uom": item_details and item_details.stock_uom or "",
|
"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,
|
"planned_qty": data.pending_qty,
|
||||||
"pending_qty": data.pending_qty,
|
"pending_qty": data.pending_qty,
|
||||||
"planned_start_date": now_datetime(),
|
"planned_start_date": now_datetime(),
|
||||||
@@ -401,11 +432,50 @@ class ProductionPlan(Document):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.update_bin_qty()
|
self.update_bin_qty()
|
||||||
|
self.update_sales_order()
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
self.db_set("status", "Cancelled")
|
self.db_set("status", "Cancelled")
|
||||||
self.delete_draft_work_order()
|
self.delete_draft_work_order()
|
||||||
self.update_bin_qty()
|
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):
|
def update_bin_qty(self):
|
||||||
for d in self.mr_items:
|
for d in self.mr_items:
|
||||||
@@ -719,6 +789,9 @@ class ProductionPlan(Document):
|
|||||||
sub_assembly_items_store = [] # temporary store to process all subassembly items
|
sub_assembly_items_store = [] # temporary store to process all subassembly items
|
||||||
|
|
||||||
for row in self.po_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:
|
if not row.item_code:
|
||||||
frappe.throw(_("Row #{0}: Please select Item Code in Assembly Items").format(row.idx))
|
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.docstatus == 1)
|
||||||
& (so.status.notin(["Stopped", "Closed"]))
|
& (so.status.notin(["Stopped", "Closed"]))
|
||||||
& (so.company == self.company)
|
& (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(
|
def get_raw_materials_of_sub_assembly_items(
|
||||||
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
|
item_details, company, bom_no, include_non_stock_items, sub_assembly_items, planned_qty=1
|
||||||
):
|
):
|
||||||
|
|
||||||
bei = frappe.qb.DocType("BOM Item")
|
bei = frappe.qb.DocType("BOM Item")
|
||||||
bom = frappe.qb.DocType("BOM")
|
bom = frappe.qb.DocType("BOM")
|
||||||
item = frappe.qb.DocType("Item")
|
item = frappe.qb.DocType("Item")
|
||||||
@@ -1609,7 +1681,10 @@ def get_raw_materials_of_sub_assembly_items(
|
|||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
key = (item.item_code, item.bom_no)
|
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])
|
planned_qty = flt(sub_assembly_items[key])
|
||||||
get_raw_materials_of_sub_assembly_items(
|
get_raw_materials_of_sub_assembly_items(
|
||||||
item_details,
|
item_details,
|
||||||
@@ -1626,3 +1701,42 @@ def get_raw_materials_of_sub_assembly_items(
|
|||||||
item_details.setdefault(item.get("item_code"), item)
|
item_details.setdefault(item.get("item_code"), item)
|
||||||
|
|
||||||
return item_details
|
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, [])
|
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):
|
def test_production_plan_combine_items(self):
|
||||||
"Test combining FG items in Production Plan."
|
"Test combining FG items in Production Plan."
|
||||||
item = "Test Production Item 1"
|
item = "Test Production Item 1"
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class Workstation(Document):
|
|||||||
|
|
||||||
if schedule_date in tuple(get_holidays(self.holiday_list)):
|
if schedule_date in tuple(get_holidays(self.holiday_list)):
|
||||||
schedule_date = add_days(schedule_date, 1)
|
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
|
return schedule_date
|
||||||
|
|
||||||
|
|||||||
@@ -37,14 +37,14 @@ frappe.query_reports["Job Card Summary"] = {
|
|||||||
label: __("From Posting Date"),
|
label: __("From Posting Date"),
|
||||||
fieldname:"from_date",
|
fieldname:"from_date",
|
||||||
fieldtype: "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
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: __("To Posting Date"),
|
label: __("To Posting Date"),
|
||||||
fieldname:"to_date",
|
fieldname:"to_date",
|
||||||
fieldtype: "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,
|
reqd: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ frappe.query_reports["Production Analytics"] = {
|
|||||||
fieldname: "from_date",
|
fieldname: "from_date",
|
||||||
label: __("From Date"),
|
label: __("From Date"),
|
||||||
fieldtype: "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
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fieldname:"to_date",
|
fieldname:"to_date",
|
||||||
label: __("To Date"),
|
label: __("To Date"),
|
||||||
fieldtype: "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
|
reqd: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ def execute():
|
|||||||
frappe.reload_doc("selling", "doctype", "sales_order_item")
|
frappe.reload_doc("selling", "doctype", "sales_order_item")
|
||||||
|
|
||||||
for doctype in ["Sales Order", "Material Request"]:
|
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":
|
if doctype == "Material Request":
|
||||||
condition = " and doc.per_ordered < 100 and doc.material_request_type = 'Manufacture'"
|
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
|
"""select se.project, sum(se_item.amount) as amount
|
||||||
from `tabStock Entry` se, `tabStock Entry Detail` se_item
|
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, '') = ''
|
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,
|
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; });
|
$.each(tax_fields, function(i, fieldname) { tax[fieldname] = 0.0; });
|
||||||
|
|
||||||
if (!this.discount_amount_applied && cur_frm) {
|
if (!this.discount_amount_applied) {
|
||||||
cur_frm.cscript.validate_taxes_and_charges(tax.doctype, tax.name);
|
erpnext.accounts.taxes.validate_taxes_and_charges(tax.doctype, tax.name);
|
||||||
me.validate_inclusive_tax(tax);
|
erpnext.accounts.taxes.validate_inclusive_tax(tax);
|
||||||
}
|
}
|
||||||
frappe.model.round_floats_in(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);
|
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() {
|
shipping_rule() {
|
||||||
var me = this;
|
var me = this;
|
||||||
if(this.frm.doc.shipping_rule) {
|
if(this.frm.doc.shipping_rule) {
|
||||||
@@ -1661,6 +1671,9 @@ erpnext.TransactionController = class TransactionController extends erpnext.taxe
|
|||||||
() => {
|
() => {
|
||||||
if(args.items.length) {
|
if(args.items.length) {
|
||||||
me._set_values_for_item_list(r.message.children);
|
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; }
|
() => { me.in_apply_price_list = false; }
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ erpnext.get_purchase_trends_filters = function() {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options":'Fiscal Year',
|
"options":'Fiscal Year',
|
||||||
"default": frappe.sys_defaults.fiscal_year
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today())
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"period_based_on",
|
"fieldname":"period_based_on",
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ erpnext.get_sales_trends_filters = function() {
|
|||||||
"label": __("Fiscal Year"),
|
"label": __("Fiscal Year"),
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"options":'Fiscal Year',
|
"options":'Fiscal Year',
|
||||||
"default": frappe.sys_defaults.fiscal_year
|
"default": erpnext.utils.get_fiscal_year(frappe.datetime.get_today()),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fieldname":"company",
|
"fieldname":"company",
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ $.extend(erpnext.utils, {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
get_fiscal_year: function(date) {
|
get_fiscal_year: function(date, with_dates=false) {
|
||||||
if(!date) {
|
if(!date) {
|
||||||
date = frappe.datetime.get_today();
|
date = frappe.datetime.get_today();
|
||||||
}
|
}
|
||||||
@@ -414,7 +414,10 @@ $.extend(erpnext.utils, {
|
|||||||
async: false,
|
async: false,
|
||||||
callback: function(r) {
|
callback: function(r) {
|
||||||
if (r.message) {
|
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');
|
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() {
|
commission_rate() {
|
||||||
this.calculate_commission();
|
this.calculate_commission();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,10 +62,10 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"fetch_from": "process_owner.full_name",
|
"fetch_from": "procedure.process_owner_full_name",
|
||||||
"fieldname": "full_name",
|
"fieldname": "full_name",
|
||||||
"fieldtype": "Data",
|
"fieldtype": "Data",
|
||||||
"hidden": 1,
|
"read_only": 1,
|
||||||
"label": "Full Name"
|
"label": "Full Name"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -81,7 +81,7 @@
|
|||||||
],
|
],
|
||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2021-02-26 15:27:47.247814",
|
"modified": "2023-07-31 08:10:47.247814",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Quality Management",
|
"module": "Quality Management",
|
||||||
"name": "Non Conformance",
|
"name": "Non Conformance",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"actions": [],
|
||||||
"autoname": "format:{####}",
|
"autoname": "format:{####}",
|
||||||
"creation": "2019-05-26 15:03:43.996455",
|
"creation": "2019-05-26 15:03:43.996455",
|
||||||
"doctype": "DocType",
|
"doctype": "DocType",
|
||||||
@@ -12,7 +13,6 @@
|
|||||||
],
|
],
|
||||||
"fields": [
|
"fields": [
|
||||||
{
|
{
|
||||||
"fetch_from": "goal.objective",
|
|
||||||
"fieldname": "objective",
|
"fieldname": "objective",
|
||||||
"fieldtype": "Text",
|
"fieldtype": "Text",
|
||||||
"in_list_view": 1,
|
"in_list_view": 1,
|
||||||
@@ -38,14 +38,17 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"modified": "2019-05-26 16:12:54.832058",
|
"links": [],
|
||||||
|
"modified": "2023-07-28 18:10:23.351246",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Quality Management",
|
"module": "Quality Management",
|
||||||
"name": "Quality Goal Objective",
|
"name": "Quality Goal Objective",
|
||||||
|
"naming_rule": "Expression",
|
||||||
"owner": "Administrator",
|
"owner": "Administrator",
|
||||||
"permissions": [],
|
"permissions": [],
|
||||||
"quick_entry": 1,
|
"quick_entry": 1,
|
||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
|
"states": [],
|
||||||
"track_changes": 1
|
"track_changes": 1
|
||||||
}
|
}
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"fieldtype": "Column Break"
|
"fieldtype": "Column Break"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"default": "Open",
|
||||||
"columns": 2,
|
"columns": 2,
|
||||||
"fieldname": "status",
|
"fieldname": "status",
|
||||||
"fieldtype": "Select",
|
"fieldtype": "Select",
|
||||||
@@ -67,7 +68,7 @@
|
|||||||
"index_web_pages_for_search": 1,
|
"index_web_pages_for_search": 1,
|
||||||
"istable": 1,
|
"istable": 1,
|
||||||
"links": [],
|
"links": [],
|
||||||
"modified": "2020-10-27 16:28:20.908637",
|
"modified": "2023-07-31 09:20:20.908637",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "Quality Management",
|
"module": "Quality Management",
|
||||||
"name": "Quality Review Objective",
|
"name": "Quality Review Objective",
|
||||||
@@ -76,4 +77,4 @@
|
|||||||
"sort_field": "modified",
|
"sort_field": "modified",
|
||||||
"sort_order": "DESC",
|
"sort_order": "DESC",
|
||||||
"track_changes": 1
|
"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'));
|
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