From 456ebc32f00be56cc9972cc76ac412776581060a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=BCrker=20Tunal=C4=B1?= Date: Tue, 8 Mar 2022 07:24:33 +0300 Subject: [PATCH 1/2] fix: translate error message titles --- erpnext/stock/stock_ledger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 47a97c47fe5..dd7fb66f983 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -834,7 +834,7 @@ class update_entries_after(object): if msg_list: message = "\n\n".join(msg_list) if self.verbose: - frappe.throw(message, NegativeStockError, title='Insufficient Stock') + frappe.throw(message, NegativeStockError, title=_('Insufficient Stock')) else: raise NegativeStockError(message) @@ -1115,7 +1115,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): neg_sle[0]["posting_date"], neg_sle[0]["posting_time"], frappe.get_desk_link(neg_sle[0]["voucher_type"], neg_sle[0]["voucher_no"])) - frappe.throw(message, NegativeStockError, title='Insufficient Stock') + frappe.throw(message, NegativeStockError, title=_('Insufficient Stock')) if not args.batch_no: @@ -1129,7 +1129,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): frappe.get_desk_link('Warehouse', args.warehouse), neg_batch_sle[0]["posting_date"], neg_batch_sle[0]["posting_time"], frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"])) - frappe.throw(message, NegativeStockError, title="Insufficient Stock for Batch") + frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch")) def get_future_sle_with_negative_qty(args): From 31f20cf14441299007b2ace2b66b6d92d1ff0818 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 7 Mar 2022 18:01:07 +0530 Subject: [PATCH 2/2] fix(pos): multiple pos round off cases (cherry picked from commit 17445c7e04ff88cc5db727cb9f769647bcbebfdf) --- .../pos_invoice_merge_log.py | 9 -- .../test_pos_invoice_merge_log.py | 98 +++++++++++++++++++ .../doctype/sales_invoice/sales_invoice.py | 3 + erpnext/controllers/taxes_and_totals.py | 29 +++--- 4 files changed, 117 insertions(+), 22 deletions(-) diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py index 40ab0c50deb..41dfa226a56 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py @@ -85,20 +85,12 @@ class POSInvoiceMergeLog(Document): sales_invoice.set_posting_time = 1 sales_invoice.posting_date = getdate(self.posting_date) sales_invoice.save() - self.write_off_fractional_amount(sales_invoice, data) sales_invoice.submit() self.consolidated_invoice = 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): credit_note = self.get_new_sales_invoice() credit_note.is_return = 1 @@ -111,7 +103,6 @@ class POSInvoiceMergeLog(Document): # TODO: return could be against multiple sales invoice which could also have been consolidated? # credit_note.return_against = self.consolidated_invoice credit_note.save() - self.write_off_fractional_amount(credit_note, data) credit_note.submit() self.consolidated_credit_note = credit_note.name diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py index 5930aa097f7..89f7f18b42c 100644 --- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py +++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py @@ -5,6 +5,7 @@ import json import unittest import frappe +from frappe.tests.utils import change_settings from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return @@ -280,3 +281,100 @@ class TestPOSInvoiceMergeLog(unittest.TestCase): frappe.set_user("Administrator") frappe.db.sql("delete from `tabPOS Profile`") frappe.db.sql("delete from `tabPOS Invoice`") + + @change_settings("System Settings", {"number_format": "#,###.###", "currency_precision": 3, "float_precision": 3}) + def test_consolidation_round_off_error_3(self): + 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() + + item_rates = [69, 59, 29] + for i in [1, 2]: + inv = create_pos_invoice(is_return=1, do_not_save=1) + inv.items = [] + for rate in item_rates: + inv.append("items", { + "item_code": "_Test Item", + "warehouse": "_Test Warehouse - _TC", + "qty": -1, + "rate": rate, + "income_account": "Sales - _TC", + "expense_account": "Cost of Goods Sold - _TC", + "cost_center": "_Test Cost Center - _TC", + }) + 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": 15, + "included_in_print_rate": 1 + }) + inv.payments = [] + inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -157 + }) + inv.paid_amount = -157 + inv.save() + inv.submit() + + consolidate_pos_invoices() + + inv.load_from_db() + consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice) + self.assertEqual(consolidated_invoice.status, 'Return') + self.assertEqual(consolidated_invoice.rounding_adjustment, -0.001) + + finally: + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") + + def test_consolidation_rounding_adjustment(self): + ''' + Test if the rounding adjustment is calculated correctly + ''' + 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=1, rate=69.5, do_not_save=True) + inv.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 70 + }) + inv.insert() + inv.submit() + + inv2 = create_pos_invoice(qty=1, rate=59.5, do_not_save=True) + inv2.append('payments', { + 'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 60 + }) + 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.rounding_adjustment, 1) + + finally: + frappe.set_user("Administrator") + frappe.db.sql("delete from `tabPOS Profile`") + frappe.db.sql("delete from `tabPOS Invoice`") \ No newline at end of file diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py index 42da6b7708f..409677f3c26 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py @@ -272,6 +272,9 @@ class SalesInvoice(SellingController): self.process_common_party_accounting() def validate_pos_return(self): + if self.is_consolidated: + # pos return is already validated in pos invoice + return if self.is_pos and self.is_return: total_amount_in_payments = 0 diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py index 08d1dcea7dc..bcaf7a145a2 100644 --- a/erpnext/controllers/taxes_and_totals.py +++ b/erpnext/controllers/taxes_and_totals.py @@ -270,7 +270,8 @@ class calculate_taxes_and_totals(object): shipping_rule.apply(self.doc) def calculate_taxes(self): - if not self.doc.get('is_consolidated'): + rounding_adjustment_computed = self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment') + if not rounding_adjustment_computed: self.doc.rounding_adjustment = 0 # maintain actual tax rate based on idx @@ -326,7 +327,7 @@ class calculate_taxes_and_totals(object): if i == (len(self.doc.get("taxes")) - 1) and self.discount_amount_applied \ and self.doc.discount_amount \ and self.doc.apply_discount_on == "Grand Total" \ - and not self.doc.get('is_consolidated'): + and not rounding_adjustment_computed: self.doc.rounding_adjustment = flt(self.doc.grand_total - flt(self.doc.discount_amount) - tax.total, self.doc.precision("rounding_adjustment")) @@ -465,20 +466,22 @@ class calculate_taxes_and_totals(object): self.doc.total_net_weight += d.total_weight def set_rounded_total(self): - if not self.doc.get('is_consolidated'): - if self.doc.meta.get_field("rounded_total"): - if self.doc.is_rounded_total_disabled(): - self.doc.rounded_total = self.doc.base_rounded_total = 0 - return + if self.doc.get('is_consolidated') and self.doc.get('rounding_adjustment'): + return - self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, - self.doc.currency, self.doc.precision("rounded_total")) + if self.doc.meta.get_field("rounded_total"): + if self.doc.is_rounded_total_disabled(): + self.doc.rounded_total = self.doc.base_rounded_total = 0 + return - #if print_in_rate is set, we would have already calculated rounding adjustment - self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, - self.doc.precision("rounding_adjustment")) + self.doc.rounded_total = round_based_on_smallest_currency_fraction(self.doc.grand_total, + self.doc.currency, self.doc.precision("rounded_total")) - self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) + #if print_in_rate is set, we would have already calculated rounding adjustment + self.doc.rounding_adjustment += flt(self.doc.rounded_total - self.doc.grand_total, + self.doc.precision("rounding_adjustment")) + + self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"]) def _cleanup(self): if not self.doc.get('is_consolidated'):