Merge pull request #29701 from nextchamp-saqib/consolidation-round-off-err
fix(pos): incorrect grand_total in case of inclusive taxes on item
This commit is contained in:
commit
b35b637d01
@ -84,12 +84,20 @@ class POSInvoiceMergeLog(Document):
|
|||||||
sales_invoice.set_posting_time = 1
|
sales_invoice.set_posting_time = 1
|
||||||
sales_invoice.posting_date = getdate(self.posting_date)
|
sales_invoice.posting_date = getdate(self.posting_date)
|
||||||
sales_invoice.save()
|
sales_invoice.save()
|
||||||
|
self.write_off_fractional_amount(sales_invoice, data)
|
||||||
sales_invoice.submit()
|
sales_invoice.submit()
|
||||||
|
|
||||||
self.consolidated_invoice = sales_invoice.name
|
self.consolidated_invoice = sales_invoice.name
|
||||||
|
|
||||||
return sales_invoice.name
|
return sales_invoice.name
|
||||||
|
|
||||||
|
def write_off_fractional_amount(self, invoice, data):
|
||||||
|
pos_invoice_grand_total = sum(d.grand_total for d in data)
|
||||||
|
|
||||||
|
if abs(pos_invoice_grand_total - invoice.grand_total) < 1:
|
||||||
|
invoice.write_off_amount += -1 * (pos_invoice_grand_total - invoice.grand_total)
|
||||||
|
invoice.save()
|
||||||
|
|
||||||
def process_merging_into_credit_note(self, data):
|
def process_merging_into_credit_note(self, data):
|
||||||
credit_note = self.get_new_sales_invoice()
|
credit_note = self.get_new_sales_invoice()
|
||||||
credit_note.is_return = 1
|
credit_note.is_return = 1
|
||||||
@ -102,6 +110,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
# TODO: return could be against multiple sales invoice which could also have been consolidated?
|
||||||
# credit_note.return_against = self.consolidated_invoice
|
# credit_note.return_against = self.consolidated_invoice
|
||||||
credit_note.save()
|
credit_note.save()
|
||||||
|
self.write_off_fractional_amount(credit_note, data)
|
||||||
credit_note.submit()
|
credit_note.submit()
|
||||||
|
|
||||||
self.consolidated_credit_note = credit_note.name
|
self.consolidated_credit_note = credit_note.name
|
||||||
@ -135,9 +144,15 @@ class POSInvoiceMergeLog(Document):
|
|||||||
i.uom == item.uom and i.net_rate == item.net_rate and i.warehouse == item.warehouse):
|
i.uom == item.uom and i.net_rate == item.net_rate and i.warehouse == item.warehouse):
|
||||||
found = True
|
found = True
|
||||||
i.qty = i.qty + item.qty
|
i.qty = i.qty + item.qty
|
||||||
|
i.amount = i.amount + item.net_amount
|
||||||
|
i.net_amount = i.amount
|
||||||
|
i.base_amount = i.base_amount + item.base_net_amount
|
||||||
|
i.base_net_amount = i.base_amount
|
||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
item.rate = item.net_rate
|
item.rate = item.net_rate
|
||||||
|
item.amount = item.net_amount
|
||||||
|
item.base_amount = item.base_net_amount
|
||||||
item.price_list_rate = 0
|
item.price_list_rate = 0
|
||||||
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
si_item = map_child_doc(item, invoice, {"doctype": "Sales Invoice Item"})
|
||||||
items.append(si_item)
|
items.append(si_item)
|
||||||
@ -169,6 +184,7 @@ class POSInvoiceMergeLog(Document):
|
|||||||
found = True
|
found = True
|
||||||
if not found:
|
if not found:
|
||||||
payments.append(payment)
|
payments.append(payment)
|
||||||
|
|
||||||
rounding_adjustment += doc.rounding_adjustment
|
rounding_adjustment += doc.rounding_adjustment
|
||||||
rounded_total += doc.rounded_total
|
rounded_total += doc.rounded_total
|
||||||
base_rounding_adjustment += doc.base_rounding_adjustment
|
base_rounding_adjustment += doc.base_rounding_adjustment
|
||||||
|
@ -12,6 +12,7 @@ from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_inv
|
|||||||
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import (
|
||||||
consolidate_pos_invoices,
|
consolidate_pos_invoices,
|
||||||
)
|
)
|
||||||
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
|
|
||||||
|
|
||||||
class TestPOSInvoiceMergeLog(unittest.TestCase):
|
class TestPOSInvoiceMergeLog(unittest.TestCase):
|
||||||
@ -150,3 +151,132 @@ class TestPOSInvoiceMergeLog(unittest.TestCase):
|
|||||||
frappe.set_user("Administrator")
|
frappe.set_user("Administrator")
|
||||||
frappe.db.sql("delete from `tabPOS Profile`")
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
frappe.db.sql("delete from `tabPOS Invoice`")
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
|
||||||
|
def test_consolidation_round_off_error_1(self):
|
||||||
|
'''
|
||||||
|
Test round off error in consolidated invoice creation if POS Invoice has inclusive tax
|
||||||
|
'''
|
||||||
|
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
try:
|
||||||
|
make_stock_entry(
|
||||||
|
to_warehouse="_Test Warehouse - _TC",
|
||||||
|
item_code="_Test Item",
|
||||||
|
rate=8000,
|
||||||
|
qty=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
init_user_and_profile()
|
||||||
|
|
||||||
|
inv = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
|
||||||
|
inv.append("taxes", {
|
||||||
|
"account_head": "_Test Account VAT - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "VAT",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 7.5,
|
||||||
|
"included_in_print_rate": 1
|
||||||
|
})
|
||||||
|
inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 30000
|
||||||
|
})
|
||||||
|
inv.insert()
|
||||||
|
inv.submit()
|
||||||
|
|
||||||
|
inv2 = create_pos_invoice(qty=3, rate=10000, do_not_save=True)
|
||||||
|
inv2.append("taxes", {
|
||||||
|
"account_head": "_Test Account VAT - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "VAT",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 7.5,
|
||||||
|
"included_in_print_rate": 1
|
||||||
|
})
|
||||||
|
inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 30000
|
||||||
|
})
|
||||||
|
inv2.insert()
|
||||||
|
inv2.submit()
|
||||||
|
|
||||||
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
|
inv.load_from_db()
|
||||||
|
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
|
||||||
|
self.assertEqual(consolidated_invoice.outstanding_amount, 0)
|
||||||
|
self.assertEqual(consolidated_invoice.status, 'Paid')
|
||||||
|
|
||||||
|
finally:
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
def test_consolidation_round_off_error_2(self):
|
||||||
|
'''
|
||||||
|
Test the same case as above but with an Unpaid POS Invoice
|
||||||
|
'''
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
|
||||||
|
try:
|
||||||
|
make_stock_entry(
|
||||||
|
to_warehouse="_Test Warehouse - _TC",
|
||||||
|
item_code="_Test Item",
|
||||||
|
rate=8000,
|
||||||
|
qty=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
init_user_and_profile()
|
||||||
|
|
||||||
|
inv = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
|
||||||
|
inv.append("taxes", {
|
||||||
|
"account_head": "_Test Account VAT - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "VAT",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 7.5,
|
||||||
|
"included_in_print_rate": 1
|
||||||
|
})
|
||||||
|
inv.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60000
|
||||||
|
})
|
||||||
|
inv.insert()
|
||||||
|
inv.submit()
|
||||||
|
|
||||||
|
inv2 = create_pos_invoice(qty=6, rate=10000, do_not_save=True)
|
||||||
|
inv2.append("taxes", {
|
||||||
|
"account_head": "_Test Account VAT - _TC",
|
||||||
|
"charge_type": "On Net Total",
|
||||||
|
"cost_center": "_Test Cost Center - _TC",
|
||||||
|
"description": "VAT",
|
||||||
|
"doctype": "Sales Taxes and Charges",
|
||||||
|
"rate": 7.5,
|
||||||
|
"included_in_print_rate": 1
|
||||||
|
})
|
||||||
|
inv2.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60000
|
||||||
|
})
|
||||||
|
inv2.insert()
|
||||||
|
inv2.submit()
|
||||||
|
|
||||||
|
inv3 = create_pos_invoice(qty=3, rate=600, do_not_save=True)
|
||||||
|
inv3.append('payments', {
|
||||||
|
'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 1000
|
||||||
|
})
|
||||||
|
inv3.insert()
|
||||||
|
inv3.submit()
|
||||||
|
|
||||||
|
consolidate_pos_invoices()
|
||||||
|
|
||||||
|
inv.load_from_db()
|
||||||
|
consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
|
||||||
|
self.assertEqual(consolidated_invoice.outstanding_amount, 800)
|
||||||
|
self.assertNotEqual(consolidated_invoice.status, 'Paid')
|
||||||
|
|
||||||
|
finally:
|
||||||
|
frappe.set_user("Administrator")
|
||||||
|
frappe.db.sql("delete from `tabPOS Profile`")
|
||||||
|
frappe.db.sql("delete from `tabPOS Invoice`")
|
||||||
|
@ -285,7 +285,7 @@ class SalesInvoice(SellingController):
|
|||||||
filters={ invoice_or_credit_note: self.name },
|
filters={ invoice_or_credit_note: self.name },
|
||||||
pluck="pos_closing_entry"
|
pluck="pos_closing_entry"
|
||||||
)
|
)
|
||||||
if pos_closing_entry:
|
if pos_closing_entry and pos_closing_entry[0]:
|
||||||
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
|
msg = _("To cancel a {} you need to cancel the POS Closing Entry {}.").format(
|
||||||
frappe.bold("Consolidated Sales Invoice"),
|
frappe.bold("Consolidated Sales Invoice"),
|
||||||
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
|
get_link_to_form("POS Closing Entry", pos_closing_entry[0])
|
||||||
|
@ -106,6 +106,9 @@ class calculate_taxes_and_totals(object):
|
|||||||
self.doc.conversion_rate = flt(self.doc.conversion_rate)
|
self.doc.conversion_rate = flt(self.doc.conversion_rate)
|
||||||
|
|
||||||
def calculate_item_values(self):
|
def calculate_item_values(self):
|
||||||
|
if self.doc.get('is_consolidated'):
|
||||||
|
return
|
||||||
|
|
||||||
if not self.discount_amount_applied:
|
if not self.discount_amount_applied:
|
||||||
for item in self.doc.get("items"):
|
for item in self.doc.get("items"):
|
||||||
self.doc.round_floats_in(item)
|
self.doc.round_floats_in(item)
|
||||||
@ -647,12 +650,12 @@ class calculate_taxes_and_totals(object):
|
|||||||
def calculate_change_amount(self):
|
def calculate_change_amount(self):
|
||||||
self.doc.change_amount = 0.0
|
self.doc.change_amount = 0.0
|
||||||
self.doc.base_change_amount = 0.0
|
self.doc.base_change_amount = 0.0
|
||||||
|
grand_total = self.doc.rounded_total or self.doc.grand_total
|
||||||
|
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
|
||||||
|
|
||||||
if self.doc.doctype == "Sales Invoice" \
|
if self.doc.doctype == "Sales Invoice" \
|
||||||
and self.doc.paid_amount > self.doc.grand_total and not self.doc.is_return \
|
and self.doc.paid_amount > grand_total and not self.doc.is_return \
|
||||||
and any(d.type == "Cash" for d in self.doc.payments):
|
and any(d.type == "Cash" for d in self.doc.payments):
|
||||||
grand_total = self.doc.rounded_total or self.doc.grand_total
|
|
||||||
base_grand_total = self.doc.base_rounded_total or self.doc.base_grand_total
|
|
||||||
|
|
||||||
self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
|
self.doc.change_amount = flt(self.doc.paid_amount - grand_total +
|
||||||
self.doc.write_off_amount, self.doc.precision("change_amount"))
|
self.doc.write_off_amount, self.doc.precision("change_amount"))
|
||||||
|
@ -1,15 +1,25 @@
|
|||||||
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
|
||||||
# MIT License. See license.txt
|
# MIT License. See license.txt
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import frappe
|
||||||
|
|
||||||
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
|
||||||
from erpnext.selling.page.point_of_sale.point_of_sale import get_items
|
from erpnext.selling.page.point_of_sale.point_of_sale import get_items
|
||||||
from erpnext.stock.doctype.item.test_item import make_item
|
from erpnext.stock.doctype.item.test_item import make_item
|
||||||
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
|
||||||
from erpnext.tests.utils import ERPNextTestCase
|
|
||||||
|
|
||||||
|
|
||||||
class TestPointOfSale(ERPNextTestCase):
|
class TestPointOfSale(unittest.TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls) -> None:
|
||||||
|
frappe.db.savepoint('before_test_point_of_sale')
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls) -> None:
|
||||||
|
frappe.db.rollback(save_point='before_test_point_of_sale')
|
||||||
|
|
||||||
def test_item_search(self):
|
def test_item_search(self):
|
||||||
"""
|
"""
|
||||||
Test Stock and Service Item Search.
|
Test Stock and Service Item Search.
|
||||||
|
Loading…
Reference in New Issue
Block a user