Merge pull request #26359 from GangaManoj/enable-discount-accounting

feat: Enable discount accounting
This commit is contained in:
Deepesh Garg
2021-08-14 18:05:29 +05:30
committed by GitHub
17 changed files with 458 additions and 471 deletions

View File

@@ -20,6 +20,7 @@
"book_asset_depreciation_entry_automatically", "book_asset_depreciation_entry_automatically",
"unlink_advance_payment_on_cancelation_of_order", "unlink_advance_payment_on_cancelation_of_order",
"post_change_gl_entries", "post_change_gl_entries",
"enable_discount_accounting",
"tax_settings_section", "tax_settings_section",
"determine_address_tax_category_from", "determine_address_tax_category_from",
"column_break_19", "column_break_19",
@@ -260,6 +261,13 @@
"fieldname": "post_change_gl_entries", "fieldname": "post_change_gl_entries",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Create Ledger Entries for Change Amount" "label": "Create Ledger Entries for Change Amount"
},
{
"default": "0",
"description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
"fieldname": "enable_discount_accounting",
"fieldtype": "Check",
"label": "Enable Discount Accounting"
} }
], ],
"icon": "icon-cog", "icon": "icon-cog",

View File

@@ -21,6 +21,7 @@ class AccountsSettings(Document):
self.validate_stale_days() self.validate_stale_days()
self.enable_payment_schedule_in_print() self.enable_payment_schedule_in_print()
self.toggle_discount_accounting_fields()
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:
@@ -33,3 +34,22 @@ class AccountsSettings(Document):
for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"): for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False)
make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False) make_property_setter(doctype, "payment_schedule", "print_hide", 0 if show_in_print else 1, "Check", validate_fields_for_doctype=False)
def toggle_discount_accounting_fields(self):
enable_discount_accounting = cint(self.enable_discount_accounting)
for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
make_property_setter(doctype, "discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
if enable_discount_accounting:
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
else:
make_property_setter(doctype, "discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
for doctype in ["Sales Invoice", "Purchase Invoice"]:
make_property_setter(doctype, "additional_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)
if enable_discount_accounting:
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "eval: doc.discount_amount", "Code", validate_fields_for_doctype=False)
else:
make_property_setter(doctype, "additional_discount_account", "mandatory_depends_on", "", "Code", validate_fields_for_doctype=False)
make_property_setter("Item", "default_discount_account", "hidden", not(enable_discount_accounting), "Check", validate_fields_for_doctype=False)

View File

@@ -366,7 +366,7 @@ erpnext.accounts.PurchaseInvoice = class PurchaseInvoice extends erpnext.buying.
items_add(doc, cdt, cdn) { items_add(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn); var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, this.frm.script_manager.copy_from_first_row("items", row,
["expense_account", "cost_center", "project"]); ["expense_account", "discount_account", "cost_center", "project"]);
} }
on_submit() { on_submit() {
@@ -500,6 +500,16 @@ frappe.ui.form.on("Purchase Invoice", {
'Payment Entry': 'Payment' 'Payment Entry': 'Payment'
} }
frm.set_query("additional_discount_account", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
report_type: "Profit and Loss",
}
};
});
frm.fields_dict['items'].grid.get_field('deferred_expense_account').get_query = function(doc) { frm.fields_dict['items'].grid.get_field('deferred_expense_account').get_query = function(doc) {
return { return {
filters: { filters: {
@@ -509,6 +519,16 @@ frappe.ui.form.on("Purchase Invoice", {
} }
} }
} }
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
return {
filters: {
'report_type': 'Profit and Loss',
'company': doc.company,
"is_group": 0
}
}
}
}, },
refresh: function(frm) { refresh: function(frm) {

View File

@@ -96,6 +96,7 @@
"section_break_44", "section_break_44",
"apply_discount_on", "apply_discount_on",
"base_discount_amount", "base_discount_amount",
"additional_discount_account",
"column_break_46", "column_break_46",
"additional_discount_percentage", "additional_discount_percentage",
"discount_amount", "discount_amount",
@@ -1691,9 +1692,13 @@
"label": "Per Received", "label": "Per Received",
"no_copy": 1, "no_copy": 1,
"print_hide": 1, "print_hide": 1,
"read_only": 1, "read_only": 1
"show_days": 1, },
"show_seconds": 1 {
"fieldname": "additional_discount_account",
"fieldtype": "Link",
"label": "Additional Discount Account",
"options": "Account"
}, },
{ {
"default": "0", "default": "0",

View File

@@ -448,6 +448,7 @@ class PurchaseInvoice(BuyingController):
self.make_supplier_gl_entry(gl_entries) self.make_supplier_gl_entry(gl_entries)
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
self.make_discount_gl_entries(gl_entries)
if self.check_asset_cwip_enabled(): if self.check_asset_cwip_enabled():
self.get_asset_gl_entry(gl_entries) self.get_asset_gl_entry(gl_entries)
@@ -612,7 +613,7 @@ class PurchaseInvoice(BuyingController):
if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account) if (not item.enable_deferred_expense or self.is_return) else item.deferred_expense_account)
if not item.is_fixed_asset: if not item.is_fixed_asset:
amount = flt(item.base_net_amount, item.precision("base_net_amount")) dummy, amount = self.get_amount_and_base_amount(item, self.enable_discount_accounting)
else: else:
amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount")) amount = flt(item.base_net_amount + item.item_tax_amount, item.precision("base_net_amount"))
@@ -854,8 +855,10 @@ class PurchaseInvoice(BuyingController):
def make_tax_gl_entries(self, gl_entries): def make_tax_gl_entries(self, gl_entries):
# tax table gl entries # tax table gl entries
valuation_tax = {} valuation_tax = {}
for tax in self.get("taxes"): for tax in self.get("taxes"):
if tax.category in ("Total", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount): amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting)
if tax.category in ("Total", "Valuation and Total") and flt(base_amount):
account_currency = get_account_currency(tax.account_head) account_currency = get_account_currency(tax.account_head)
dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit" dr_or_cr = "debit" if tax.add_deduct_tax == "Add" else "credit"
@@ -864,21 +867,21 @@ class PurchaseInvoice(BuyingController):
self.get_gl_dict({ self.get_gl_dict({
"account": tax.account_head, "account": tax.account_head,
"against": self.supplier, "against": self.supplier,
dr_or_cr: tax.base_tax_amount_after_discount_amount, dr_or_cr: base_amount,
dr_or_cr + "_in_account_currency": tax.base_tax_amount_after_discount_amount \ dr_or_cr + "_in_account_currency": base_amount
if account_currency==self.company_currency \ if account_currency==self.company_currency
else tax.tax_amount_after_discount_amount, else amount,
"cost_center": tax.cost_center "cost_center": tax.cost_center
}, account_currency, item=tax) }, account_currency, item=tax)
) )
# accumulate valuation tax # accumulate valuation tax
if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(tax.base_tax_amount_after_discount_amount) \ if self.is_opening == "No" and tax.category in ("Valuation", "Valuation and Total") and flt(base_amount) \
and not self.is_internal_transfer(): and not self.is_internal_transfer():
if self.auto_accounting_for_stock and not tax.cost_center: if self.auto_accounting_for_stock and not tax.cost_center:
frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category))) frappe.throw(_("Cost Center is required in row {0} in Taxes table for type {1}").format(tax.idx, _(tax.category)))
valuation_tax.setdefault(tax.name, 0) valuation_tax.setdefault(tax.name, 0)
valuation_tax[tax.name] += \ valuation_tax[tax.name] += \
(tax.add_deduct_tax == "Add" and 1 or -1) * flt(tax.base_tax_amount_after_discount_amount) (tax.add_deduct_tax == "Add" and 1 or -1) * flt(base_amount)
if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax: if self.is_opening == "No" and self.negative_expense_to_be_booked and valuation_tax:
# credit valuation tax amount in "Expenses Included In Valuation" # credit valuation tax amount in "Expenses Included In Valuation"
@@ -919,6 +922,13 @@ class PurchaseInvoice(BuyingController):
"remarks": self.remarks or "Accounting Entry for Stock" "remarks": self.remarks or "Accounting Entry for Stock"
}, item=tax)) }, item=tax))
@property
def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"):
self._enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
return self._enable_discount_accounting
def make_internal_transfer_gl_entries(self, gl_entries): def make_internal_transfer_gl_entries(self, gl_entries):
if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges): if self.is_internal_transfer() and flt(self.base_total_taxes_and_charges):
account_currency = get_account_currency(self.unrealized_profit_loss_account) account_currency = get_account_currency(self.unrealized_profit_loss_account)

View File

@@ -251,6 +251,50 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount) self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
def test_purchase_invoice_with_discount_accounting_enabled(self):
enable_discount_accounting()
discount_account = create_account(account_name="Discount Account",
parent_account="Indirect Expenses - _TC", company="_Test Company")
pi = make_purchase_invoice(discount_account=discount_account, rate=45)
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 225.0, nowdate()],
["Discount Account - _TC", 0.0, 25.0, nowdate()]
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
enable_discount_accounting(enable=0)
def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
enable_discount_accounting()
additional_discount_account = create_account(account_name="Discount Account",
parent_account="Indirect Expenses - _TC", company="_Test Company")
pi = make_purchase_invoice(do_not_save=1, parent_cost_center="Main - _TC")
pi.apply_discount_on = "Grand Total"
pi.additional_discount_account = additional_discount_account
pi.additional_discount_percentage = 10
pi.disable_rounded_total = 1
pi.append("taxes", {
"charge_type": "On Net Total",
"account_head": "_Test Account VAT - _TC",
"cost_center": "Main - _TC",
"description": "Test",
"rate": 10
})
pi.submit()
expected_gle = [
["_Test Account Cost for Goods Sold - _TC", 250.0, 0.0, nowdate()],
["_Test Account VAT - _TC", 25.0, 0.0, nowdate()],
["Creditors - _TC", 0.0, 247.5, nowdate()],
["Discount Account - _TC", 0.0, 27.5, nowdate()]
]
check_gl_entries(self, pi.name, expected_gle, nowdate())
def test_purchase_invoice_change_naming_series(self): def test_purchase_invoice_change_naming_series(self):
pi = frappe.copy_doc(test_records[1]) pi = frappe.copy_doc(test_records[1])
pi.insert() pi.insert()
@@ -1161,6 +1205,18 @@ class TestPurchaseInvoice(unittest.TestCase):
self.assertEqual(expected_gle[i][0], gle.account) self.assertEqual(expected_gle[i][0], gle.account)
self.assertEqual(expected_gle[i][1], gle.amount) self.assertEqual(expected_gle[i][1], gle.amount)
def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
from `tabGL Entry`
where voucher_type='Purchase Invoice' and voucher_no=%s and posting_date >= %s
order by posting_date asc, account asc""", (voucher_no, posting_date), as_dict=1)
for i, gle in enumerate(gl_entries):
doc.assertEqual(expected_gle[i][0], gle.account)
doc.assertEqual(expected_gle[i][1], gle.debit)
doc.assertEqual(expected_gle[i][2], gle.credit)
doc.assertEqual(getdate(expected_gle[i][3]), gle.posting_date)
def update_tax_witholding_category(company, account, date): def update_tax_witholding_category(company, account, date):
from erpnext.accounts.utils import get_fiscal_year from erpnext.accounts.utils import get_fiscal_year
@@ -1191,6 +1247,11 @@ def unlink_payment_on_cancel_of_invoice(enable=1):
accounts_settings.unlink_payment_on_cancellation_of_invoice = enable accounts_settings.unlink_payment_on_cancellation_of_invoice = enable
accounts_settings.save() accounts_settings.save()
def enable_discount_accounting(enable=1):
accounts_settings = frappe.get_doc("Accounts Settings")
accounts_settings.enable_discount_accounting = enable
accounts_settings.save()
def make_purchase_invoice(**args): def make_purchase_invoice(**args):
pi = frappe.new_doc("Purchase Invoice") pi = frappe.new_doc("Purchase Invoice")
args = frappe._dict(args) args = frappe._dict(args)
@@ -1213,6 +1274,7 @@ def make_purchase_invoice(**args):
pi.return_against = args.return_against pi.return_against = args.return_against
pi.is_subcontracted = args.is_subcontracted or "No" pi.is_subcontracted = args.is_subcontracted or "No"
pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC" pi.supplier_warehouse = args.supplier_warehouse or "_Test Warehouse 1 - _TC"
pi.cost_center = args.parent_cost_center
pi.append("items", { pi.append("items", {
"item_code": args.item or args.item_code or "_Test Item", "item_code": args.item or args.item_code or "_Test Item",
@@ -1221,7 +1283,10 @@ def make_purchase_invoice(**args):
"received_qty": args.received_qty or 0, "received_qty": args.received_qty or 0,
"rejected_qty": args.rejected_qty or 0, "rejected_qty": args.rejected_qty or 0,
"rate": args.rate or 50, "rate": args.rate or 50,
'expense_account': args.expense_account or '_Test Account Cost for Goods Sold - _TC', "price_list_rate": args.price_list_rate or 50,
"expense_account": args.expense_account or '_Test Account Cost for Goods Sold - _TC',
"discount_account": args.discount_account or None,
"discount_amount": args.discount_amount or 0,
"conversion_factor": 1.0, "conversion_factor": 1.0,
"serial_no": args.serial_no, "serial_no": args.serial_no,
"stock_uom": args.uom or "_Test UOM", "stock_uom": args.uom or "_Test UOM",

View File

@@ -73,6 +73,7 @@
"manufacturer_part_no", "manufacturer_part_no",
"accounting", "accounting",
"expense_account", "expense_account",
"discount_account",
"col_break5", "col_break5",
"is_fixed_asset", "is_fixed_asset",
"asset_location", "asset_location",
@@ -850,6 +851,12 @@
"options": "Company:company:default_currency", "options": "Company:company:default_currency",
"print_hide": 1, "print_hide": 1,
"read_only": 1 "read_only": 1
},
{
"fieldname": "discount_account",
"fieldtype": "Link",
"label": "Discount Account",
"options": "Account"
} }
], ],
"idx": 1, "idx": 1,

View File

@@ -347,7 +347,7 @@ erpnext.accounts.SalesInvoiceController = class SalesInvoiceController extends e
items_add(doc, cdt, cdn) { items_add(doc, cdt, cdn) {
var row = frappe.get_doc(cdt, cdn); var row = frappe.get_doc(cdt, cdn);
this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "cost_center"]); this.frm.script_manager.copy_from_first_row("items", row, ["income_account", "discount_account", "cost_center"]);
} }
set_dynamic_labels() { set_dynamic_labels() {
@@ -510,7 +510,6 @@ cur_frm.set_query("income_account", "items", function(doc) {
} }
}); });
// Cost Center in Details Table // Cost Center in Details Table
// ----------------------------- // -----------------------------
cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) { cur_frm.fields_dict["items"].grid.get_field("cost_center").get_query = function(doc) {
@@ -592,6 +591,16 @@ frappe.ui.form.on('Sales Invoice', {
}; };
}); });
frm.set_query("additional_discount_account", function() {
return {
filters: {
company: frm.doc.company,
is_group: 0,
report_type: "Profit and Loss",
}
};
});
frm.custom_make_buttons = { frm.custom_make_buttons = {
'Delivery Note': 'Delivery', 'Delivery Note': 'Delivery',
'Sales Invoice': 'Return / Credit Note', 'Sales Invoice': 'Return / Credit Note',
@@ -618,6 +627,17 @@ frappe.ui.form.on('Sales Invoice', {
} }
} }
// discount account
frm.fields_dict['items'].grid.get_field('discount_account').get_query = function(doc) {
return {
filters: {
'report_type': 'Profit and Loss',
'company': doc.company,
"is_group": 0
}
}
}
frm.fields_dict['items'].grid.get_field('deferred_revenue_account').get_query = function(doc) { frm.fields_dict['items'].grid.get_field('deferred_revenue_account').get_query = function(doc) {
return { return {
filters: { filters: {

View File

@@ -106,6 +106,7 @@
"section_break_49", "section_break_49",
"apply_discount_on", "apply_discount_on",
"base_discount_amount", "base_discount_amount",
"additional_discount_account",
"column_break_51", "column_break_51",
"additional_discount_percentage", "additional_discount_percentage",
"discount_amount", "discount_amount",
@@ -2332,6 +2333,12 @@
"fieldtype": "Check", "fieldtype": "Check",
"label": "Disable Rounded Total" "label": "Disable Rounded Total"
}, },
{
"fieldname": "additional_discount_account",
"fieldtype": "Link",
"label": "Additional Discount Account",
"options": "Account"
},
{ {
"allow_on_submit": 1, "allow_on_submit": 1,
"fieldname": "dispatch_address_name", "fieldname": "dispatch_address_name",

View File

@@ -848,6 +848,7 @@ class SalesInvoice(SellingController):
self.allocate_advance_taxes(gl_entries) self.allocate_advance_taxes(gl_entries)
self.make_item_gl_entries(gl_entries) self.make_item_gl_entries(gl_entries)
self.make_discount_gl_entries(gl_entries)
# merge gl entries before adding pos entries # merge gl entries before adding pos entries
gl_entries = merge_similar_entries(gl_entries) gl_entries = merge_similar_entries(gl_entries)
@@ -888,17 +889,19 @@ class SalesInvoice(SellingController):
def make_tax_gl_entries(self, gl_entries): def make_tax_gl_entries(self, gl_entries):
for tax in self.get("taxes"): for tax in self.get("taxes"):
amount, base_amount = self.get_tax_amounts(tax, self.enable_discount_accounting)
if flt(tax.base_tax_amount_after_discount_amount): if flt(tax.base_tax_amount_after_discount_amount):
account_currency = get_account_currency(tax.account_head) account_currency = get_account_currency(tax.account_head)
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": tax.account_head, "account": tax.account_head,
"against": self.customer, "against": self.customer,
"credit": flt(tax.base_tax_amount_after_discount_amount, "credit": flt(base_amount,
tax.precision("tax_amount_after_discount_amount")), tax.precision("tax_amount_after_discount_amount")),
"credit_in_account_currency": (flt(tax.base_tax_amount_after_discount_amount, "credit_in_account_currency": (flt(base_amount,
tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else tax.precision("base_tax_amount_after_discount_amount")) if account_currency==self.company_currency else
flt(tax.tax_amount_after_discount_amount, tax.precision("tax_amount_after_discount_amount"))), flt(amount, tax.precision("tax_amount_after_discount_amount"))),
"cost_center": tax.cost_center "cost_center": tax.cost_center
}, account_currency, item=tax) }, account_currency, item=tax)
) )
@@ -917,6 +920,7 @@ class SalesInvoice(SellingController):
def make_item_gl_entries(self, gl_entries): def make_item_gl_entries(self, gl_entries):
# income account gl entries # income account gl entries
for item in self.get("items"): for item in self.get("items"):
if flt(item.base_net_amount, item.precision("base_net_amount")): if flt(item.base_net_amount, item.precision("base_net_amount")):
if item.is_fixed_asset: if item.is_fixed_asset:
@@ -950,15 +954,17 @@ class SalesInvoice(SellingController):
income_account = (item.income_account income_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account) if (not item.enable_deferred_revenue or self.is_return) else item.deferred_revenue_account)
amount, base_amount = self.get_amount_and_base_amount(item, self.enable_discount_accounting)
account_currency = get_account_currency(income_account) account_currency = get_account_currency(income_account)
gl_entries.append( gl_entries.append(
self.get_gl_dict({ self.get_gl_dict({
"account": income_account, "account": income_account,
"against": self.customer, "against": self.customer,
"credit": flt(item.base_net_amount, item.precision("base_net_amount")), "credit": flt(base_amount, item.precision("base_net_amount")),
"credit_in_account_currency": (flt(item.base_net_amount, item.precision("base_net_amount")) "credit_in_account_currency": (flt(base_amount, item.precision("base_net_amount"))
if account_currency==self.company_currency if account_currency==self.company_currency
else flt(item.net_amount, item.precision("net_amount"))), else flt(amount, item.precision("net_amount"))),
"cost_center": item.cost_center, "cost_center": item.cost_center,
"project": item.project or self.project "project": item.project or self.project
}, account_currency, item=item) }, account_currency, item=item)
@@ -1052,6 +1058,13 @@ class SalesInvoice(SellingController):
return True return True
return False return False
@property
def enable_discount_accounting(self):
if not hasattr(self, "_enable_discount_accounting"):
self._enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
return self._enable_discount_accounting
def set_asset_status(self, asset): def set_asset_status(self, asset):
if self.is_return: if self.is_return:
asset.set_status() asset.set_status()

View File

@@ -2102,6 +2102,54 @@ class TestSalesInvoice(unittest.TestCase):
sales_invoice.save() sales_invoice.save()
self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC") self.assertEqual(sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC")
def test_sales_invoice_with_discount_accounting_enabled(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
enable_discount_accounting()
discount_account = create_account(account_name="Discount Account",
parent_account="Indirect Expenses - _TC", company="_Test Company")
si = create_sales_invoice(discount_account=discount_account, discount_percentage=10, rate=90)
expected_gle = [
["Debtors - _TC", 90.0, 0.0, nowdate()],
["Discount Account - _TC", 10.0, 0.0, nowdate()],
["Sales - _TC", 0.0, 100.0, nowdate()]
]
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
enable_discount_accounting(enable=0)
def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import enable_discount_accounting
enable_discount_accounting()
additional_discount_account = create_account(account_name="Discount Account",
parent_account="Indirect Expenses - _TC", company="_Test Company")
si = create_sales_invoice(parent_cost_center='Main - _TC', do_not_save=1)
si.apply_discount_on = "Grand Total"
si.additional_discount_account = additional_discount_account
si.additional_discount_percentage = 20
si.append("taxes", {
"charge_type": "On Net Total",
"account_head": "_Test Account VAT - _TC",
"cost_center": "Main - _TC",
"description": "Test",
"rate": 10
})
si.submit()
expected_gle = [
["_Test Account VAT - _TC", 0.0, 10.0, nowdate()],
["Debtors - _TC", 88, 0.0, nowdate()],
["Discount Account - _TC", 22.0, 0.0, nowdate()],
["Sales - _TC", 0.0, 100.0, nowdate()]
]
check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
enable_discount_accounting(enable=0)
def test_asset_depreciation_on_sale(self): def test_asset_depreciation_on_sale(self):
""" """
Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on Sept 30. Tests if an Asset set to depreciate yearly on June 30, that gets sold on Sept 30, creates an additional depreciation entry on Sept 30.
@@ -2318,6 +2366,7 @@ def create_sales_invoice(**args):
si.currency=args.currency or "INR" si.currency=args.currency or "INR"
si.conversion_rate = args.conversion_rate or 1 si.conversion_rate = args.conversion_rate or 1
si.naming_series = args.naming_series or "T-SINV-" si.naming_series = args.naming_series or "T-SINV-"
si.cost_center = args.parent_cost_center
si.append("items", { si.append("items", {
"item_code": args.item or args.item_code or "_Test Item", "item_code": args.item or args.item_code or "_Test Item",
@@ -2329,8 +2378,11 @@ def create_sales_invoice(**args):
"uom": args.uom or "Nos", "uom": args.uom or "Nos",
"stock_uom": args.uom or "Nos", "stock_uom": args.uom or "Nos",
"rate": args.rate if args.get("rate") is not None else 100, "rate": args.rate if args.get("rate") is not None else 100,
"price_list_rate": args.price_list_rate if args.get("price_list_rate") is not None else 100,
"income_account": args.income_account or "Sales - _TC", "income_account": args.income_account or "Sales - _TC",
"expense_account": args.expense_account or "Cost of Goods Sold - _TC", "expense_account": args.expense_account or "Cost of Goods Sold - _TC",
"discount_account": args.discount_account or None,
"discount_amount": args.discount_amount or 0,
"asset": args.asset or None, "asset": args.asset or None,
"cost_center": args.cost_center or "_Test Cost Center - _TC", "cost_center": args.cost_center or "_Test Cost Center - _TC",
"serial_no": args.serial_no, "serial_no": args.serial_no,

View File

@@ -63,6 +63,7 @@
"finance_book", "finance_book",
"col_break4", "col_break4",
"expense_account", "expense_account",
"discount_account",
"deferred_revenue", "deferred_revenue",
"deferred_revenue_account", "deferred_revenue_account",
"service_stop_date", "service_stop_date",
@@ -821,6 +822,12 @@
"no_copy": 1, "no_copy": 1,
"options": "currency", "options": "currency",
"read_only": 1 "read_only": 1
},
{
"fieldname": "discount_account",
"fieldtype": "Link",
"label": "Discount Account",
"options": "Account"
} }
], ],
"idx": 1, "idx": 1,

View File

@@ -813,6 +813,89 @@ class AccountsController(TransactionBase):
tax_map[tax.account_head] -= allocated_amount tax_map[tax.account_head] -= allocated_amount
allocated_tax_map[tax.account_head] -= allocated_amount allocated_tax_map[tax.account_head] -= allocated_amount
def get_amount_and_base_amount(self, item, enable_discount_accounting):
amount = item.net_amount
base_amount = item.base_net_amount
if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account'):
amount = item.amount
base_amount = item.base_amount
return amount, base_amount
def get_tax_amounts(self, tax, enable_discount_accounting):
amount = tax.tax_amount_after_discount_amount
base_amount = tax.base_tax_amount_after_discount_amount
if enable_discount_accounting and self.get('discount_amount') and self.get('additional_discount_account') \
and self.get('apply_discount_on') == 'Grand Total':
amount = tax.tax_amount
base_amount = tax.base_tax_amount
return amount, base_amount
def make_discount_gl_entries(self, gl_entries):
enable_discount_accounting = cint(frappe.db.get_single_value('Accounts Settings', 'enable_discount_accounting'))
if enable_discount_accounting:
if self.doctype == "Purchase Invoice":
dr_or_cr = "credit"
rev_dr_cr = "debit"
supplier_or_customer = self.supplier
else:
dr_or_cr = "debit"
rev_dr_cr = "credit"
supplier_or_customer = self.customer
for item in self.get("items"):
if item.get('discount_amount') and item.get('discount_account'):
discount_amount = item.discount_amount * item.qty
if self.doctype == "Purchase Invoice":
income_or_expense_account = (item.expense_account
if (not item.enable_deferred_expense or self.is_return)
else item.deferred_expense_account)
else:
income_or_expense_account = (item.income_account
if (not item.enable_deferred_revenue or self.is_return)
else item.deferred_revenue_account)
account_currency = get_account_currency(item.discount_account)
gl_entries.append(
self.get_gl_dict({
"account": item.discount_account,
"against": supplier_or_customer,
dr_or_cr: flt(discount_amount, item.precision('discount_amount')),
dr_or_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
item.precision('discount_amount')),
"cost_center": item.cost_center,
"project": item.project
}, account_currency, item=item)
)
account_currency = get_account_currency(income_or_expense_account)
gl_entries.append(
self.get_gl_dict({
"account": income_or_expense_account,
"against": supplier_or_customer,
rev_dr_cr: flt(discount_amount, item.precision('discount_amount')),
rev_dr_cr + "_in_account_currency": flt(discount_amount * self.get('conversion_rate'),
item.precision('discount_amount')),
"cost_center": item.cost_center,
"project": item.project or self.project
}, account_currency, item=item)
)
if self.get('discount_amount') and self.get('additional_discount_account'):
gl_entries.append(
self.get_gl_dict({
"account": self.additional_discount_account,
"against": supplier_or_customer,
dr_or_cr: self.discount_amount,
"cost_center": self.cost_center
}, item=self)
)
def allocate_advance_taxes(self, gl_entries): def allocate_advance_taxes(self, gl_entries):
tax_map = self.get_tax_map() tax_map = self.get_tax_map()
for pe in self.get("advances"): for pe in self.get("advances"):

View File

@@ -260,6 +260,17 @@ $.extend(erpnext.item, {
} }
} }
frm.fields_dict["item_defaults"].grid.get_field("default_discount_account").get_query = function(doc, cdt, cdn) {
const row = locals[cdt][cdn];
return {
filters: {
'report_type': 'Profit and Loss',
'company': row.company,
"is_group": 0
}
};
};
frm.fields_dict["item_defaults"].grid.get_field("buying_cost_center").get_query = function(doc, cdt, cdn) { frm.fields_dict["item_defaults"].grid.get_field("buying_cost_center").get_query = function(doc, cdt, cdn) {
const row = locals[cdt][cdn]; const row = locals[cdt][cdn];
return { return {

View File

@@ -1067,7 +1067,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"max_attachments": 1, "max_attachments": 1,
"modified": "2021-03-18 14:04:38.575519", "modified": "2021-07-13 01:29:06.071827",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item", "name": "Item",

View File

@@ -1,464 +1,118 @@
{ {
"allow_copy": 0, "actions": [],
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"beta": 0,
"creation": "2018-05-03 02:29:24.444341", "creation": "2018-05-03 02:29:24.444341",
"custom": 0,
"docstatus": 0,
"doctype": "DocType", "doctype": "DocType",
"document_type": "",
"editable_grid": 1, "editable_grid": 1,
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [
"company",
"default_warehouse",
"column_break_3",
"default_price_list",
"default_discount_account",
"purchase_defaults",
"buying_cost_center",
"default_supplier",
"column_break_8",
"expense_account",
"selling_defaults",
"selling_cost_center",
"column_break_12",
"income_account"
],
"fields": [ "fields": [
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "company", "fieldname": "company",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 1, "ignore_user_permissions": 1,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Company", "label": "Company",
"length": 0,
"no_copy": 0,
"options": "Company", "options": "Company",
"permlevel": 0, "reqd": 1
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_warehouse", "fieldname": "default_warehouse",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Default Warehouse", "label": "Default Warehouse",
"length": 0, "options": "Warehouse"
"no_copy": 0,
"options": "Warehouse",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_3", "fieldname": "column_break_3",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_price_list", "fieldname": "default_price_list",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1, "in_list_view": 1,
"in_standard_filter": 0,
"label": "Default Price List", "label": "Default Price List",
"length": 0, "options": "Price List"
"no_copy": 0,
"options": "Price List",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "purchase_defaults", "fieldname": "purchase_defaults",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Purchase Defaults"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Purchase Defaults",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "buying_cost_center", "fieldname": "buying_cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Buying Cost Center", "label": "Default Buying Cost Center",
"length": 0, "options": "Cost Center"
"no_copy": 0,
"options": "Cost Center",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "default_supplier", "fieldname": "default_supplier",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Supplier", "label": "Default Supplier",
"length": 0, "options": "Supplier"
"no_copy": 0,
"options": "Supplier",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_8", "fieldname": "column_break_8",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "expense_account", "fieldname": "expense_account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Expense Account", "label": "Default Expense Account",
"length": 0, "options": "Account"
"no_copy": 0,
"options": "Account",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "selling_defaults", "fieldname": "selling_defaults",
"fieldtype": "Section Break", "fieldtype": "Section Break",
"hidden": 0, "label": "Sales Defaults"
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Sales Defaults",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "selling_cost_center", "fieldname": "selling_cost_center",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Selling Cost Center", "label": "Default Selling Cost Center",
"length": 0, "options": "Cost Center"
"no_copy": 0,
"options": "Cost Center",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "column_break_12", "fieldname": "column_break_12",
"fieldtype": "Column Break", "fieldtype": "Column Break"
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}, },
{ {
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "income_account", "fieldname": "income_account",
"fieldtype": "Link", "fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Default Income Account", "label": "Default Income Account",
"length": 0, "options": "Account"
"no_copy": 0, },
"options": "Account", {
"permlevel": 0, "fieldname": "default_discount_account",
"precision": "", "fieldtype": "Link",
"print_hide": 0, "label": "Default Discount Account",
"print_hide_if_no_value": 0, "options": "Account"
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
} }
], ],
"has_web_view": 0,
"hide_heading": 0,
"hide_toolbar": 0,
"idx": 0,
"image_view": 0,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 1, "istable": 1,
"max_attachments": 0, "links": [],
"modified": "2018-12-07 11:48:07.638935", "modified": "2021-07-13 01:26:03.860065",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Stock", "module": "Stock",
"name": "Item Default", "name": "Item Default",
"name_case": "",
"owner": "Administrator", "owner": "Administrator",
"permissions": [], "permissions": [],
"quick_entry": 1, "quick_entry": 1,
"read_only": 0,
"read_only_onload": 0,
"show_name_in_global_search": 0,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"track_changes": 1, "track_changes": 1
"track_seen": 0,
"track_views": 0
} }

View File

@@ -286,6 +286,7 @@ def get_basic_details(args, item, overwrite_warehouse=True):
"warehouse": warehouse, "warehouse": warehouse,
"income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults), "income_account": get_default_income_account(args, item_defaults, item_group_defaults, brand_defaults),
"expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) , "expense_account": expense_account or get_default_expense_account(args, item_defaults, item_group_defaults, brand_defaults) ,
"discount_account": None or get_default_discount_account(args, item_defaults),
"cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults), "cost_center": get_default_cost_center(args, item_defaults, item_group_defaults, brand_defaults),
'has_serial_no': item.has_serial_no, 'has_serial_no': item.has_serial_no,
'has_batch_no': item.has_batch_no, 'has_batch_no': item.has_batch_no,
@@ -588,6 +589,10 @@ def get_default_expense_account(args, item, item_group, brand):
or brand.get("expense_account") or brand.get("expense_account")
or args.expense_account) or args.expense_account)
def get_default_discount_account(args, item):
return (item.get("default_discount_account")
or args.discount_account)
def get_default_deferred_account(args, item, fieldname=None): def get_default_deferred_account(args, item, fieldname=None):
if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"): if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
return (item.get(fieldname) return (item.get(fieldname)