diff options
| author | Joseph Caburnay <jcb@odoo.com> | 2019-08-08 08:21:00 +0000 |
|---|---|---|
| committer | Pierre Masereel <pim@odoo.com> | 2019-08-09 14:45:44 +0000 |
| commit | 8becb17b8a3aa18b92629c4ddd33630ea4b1444e (patch) | |
| tree | f03ccc427aa9f78d3d19dbbfb0c6c6f14eaf66b6 | |
| parent | b234e97f87c577a7cd85230c4f42277b39e8c842 (diff) | |
[TEST] point_of_sale: create one account move by session
We've adapted the tests to work with the new implementation
of payments and session closure. And we've added some new
ones to have a better coverage on point_of_sale module.
TASK-ID: 1862388
closes odoo/odoo#33578
Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
| -rw-r--r-- | addons/point_of_sale/tests/__init__.py | 8 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/common.py | 398 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_anglo_saxon.py | 38 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_frontend.py | 18 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_point_of_sale_flow.py | 108 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_pos_basic_config.py | 423 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_pos_multiple_receivable_accounts.py | 199 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_pos_multiple_sale_accounts.py | 220 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_pos_other_currency_config.py | 170 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_pos_products_with_tax.py | 286 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_pos_setup.py | 69 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_pos_stock_account.py | 194 | ||||
| -rw-r--r-- | addons/point_of_sale/tests/test_pos_with_fiscal_position.py | 379 |
13 files changed, 2433 insertions, 77 deletions
diff --git a/addons/point_of_sale/tests/__init__.py b/addons/point_of_sale/tests/__init__.py index 24294101606..d003102e887 100644 --- a/addons/point_of_sale/tests/__init__.py +++ b/addons/point_of_sale/tests/__init__.py @@ -5,3 +5,11 @@ from . import test_point_of_sale_flow from . import test_frontend from . import test_point_of_sale_ui from . import test_anglo_saxon +from . import test_pos_setup +from . import test_pos_basic_config +from . import test_pos_products_with_tax +from . import test_pos_multiple_sale_accounts +from . import test_pos_multiple_receivable_accounts +from . import test_pos_other_currency_config +from . import test_pos_with_fiscal_position +from . import test_pos_stock_account diff --git a/addons/point_of_sale/tests/common.py b/addons/point_of_sale/tests/common.py index d83f1db666b..dd2a2666fcd 100644 --- a/addons/point_of_sale/tests/common.py +++ b/addons/point_of_sale/tests/common.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- -from odoo.tests import common +from random import randint +from odoo import fields +from odoo.tests.common import TransactionCase, Form +from odoo.tools import float_is_zero -class TestPointOfSaleCommon(common.TransactionCase): + +class TestPointOfSaleCommon(TransactionCase): def setUp(self): super(TestPointOfSaleCommon, self).setUp() @@ -11,16 +15,16 @@ class TestPointOfSaleCommon(common.TransactionCase): self.PosMakePayment = self.env['pos.make.payment'] self.PosOrder = self.env['pos.order'] self.PosSession = self.env['pos.session'] - company = self.env.ref('base.main_company') - self.company_id = company.id + self.company = self.env.ref('base.main_company') + self.company_id = self.company.id coa = self.env['account.chart.template'].search([ - ('currency_id', '=', company.currency_id.id), + ('currency_id', '=', self.company.currency_id.id), ], limit=1) test_sale_journal = self.env['account.journal'].create({'name': 'Sales Journal - Test', 'code': 'TSJ', 'type': 'sale', 'company_id': self.company_id}) - company.write({'anglo_saxon_accounting': coa.use_anglo_saxon, + self.company.write({'anglo_saxon_accounting': coa.use_anglo_saxon, 'bank_account_code_prefix': coa.bank_account_code_prefix, 'cash_account_code_prefix': coa.cash_account_code_prefix, 'transfer_account_code_prefix': coa.transfer_account_code_prefix, @@ -34,21 +38,19 @@ class TestPointOfSaleCommon(common.TransactionCase): self.pos_config.write({ 'journal_id': test_sale_journal.id, 'invoice_journal_id': test_sale_journal.id, - 'journal_ids': [(0, 0, {'name': 'Cash Journal - Test', - 'code': 'TSC', - 'type': 'cash', - 'company_id': self.company_id, - 'journal_user': True})], - }) + }) self.led_lamp = self.env.ref('point_of_sale.led_lamp') self.whiteboard_pen = self.env.ref('point_of_sale.whiteboard_pen') self.newspaper_rack = self.env.ref('point_of_sale.newspaper_rack') - # create a new session - self.pos_order_session0 = self.env['pos.session'].create({ - 'user_id': 1, - 'config_id': self.pos_config.id + self.cash_payment_method = self.pos_config.payment_method_ids.filtered(lambda pm: pm.name == 'Cash') + self.bank_payment_method = self.pos_config.payment_method_ids.filtered(lambda pm: pm.name == 'Bank') + self.credit_payment_method = self.env['pos.payment.method'].create({ + 'name': 'Credit', + 'receivable_account_id': self.company.account_default_pos_receivable_account_id.id, + 'split_transactions': True, }) + self.pos_config.write({'payment_method_ids': [(4, self.credit_payment_method.id)]}) # create a VAT tax of 10%, included in the public price Tax = self.env['account.tax'] @@ -85,3 +87,367 @@ class TestPointOfSaleCommon(common.TransactionCase): # I assign those 5 percent taxes on the PCSC349 product as a sale taxes self.product4.write( {'taxes_id': [(6, 0, [account_tax_05_incl.id, account_tax_05_incl_chicago.id])]}) + + # Set account_id in the generated repartition lines. Automatically, nothing is set. + invoice_rep_lines = (account_tax_05_incl | account_tax_05_incl_chicago | account_tax_10_incl).mapped('invoice_repartition_line_ids') + refund_rep_lines = (account_tax_05_incl | account_tax_05_incl_chicago | account_tax_10_incl).mapped('refund_repartition_line_ids') + + tax_received_account = self.company.account_sale_tax_id.mapped('invoice_repartition_line_ids.account_id') + (invoice_rep_lines | refund_rep_lines).write({'account_id': tax_received_account.id}) + + +class TestPoSCommon(TransactionCase): + """ Set common values for different special test cases. + + The idea is to set up common values here for the tests + and implement different special scenarios by inheriting + this class. + """ + + def setUp(self): + super(TestPoSCommon, self).setUp() + + self.pos_manager = self.env.ref('base.user_admin') + self.env = self.env(user=self.pos_manager) + + # Set basic defaults + self.company = self.env.ref('base.main_company') + self.pos_sale_journal = self.env['account.journal'].create({ + 'type': 'sale', + 'name': 'Point of Sale Test', + 'code': 'POST', + 'company_id': self.company.id, + 'sequence': 20 + }) + self.invoice_journal = self.env['account.journal'].create({ + 'type': 'sale', + 'name': 'Invoice Journal Test', + 'code': 'INVT', + 'company_id': self.company.id, + 'sequence': 21 + }) + self.receivable_account = self.pos_manager.partner_id.property_account_receivable_id + self.tax_received_account = self.company.account_sale_tax_id.mapped('invoice_repartition_line_ids.account_id') + self.pos_receivable_account = self.company.account_default_pos_receivable_account_id + self.other_receivable_account = self.env['account.account'].create({ + 'name': 'Other Receivable', + 'code': 'RCV00' , + 'user_type_id': self.env['account.account.type'].create({'name': 'RCV type', 'type': 'receivable'}).id, + 'reconcile': True, + }) + + # company_currency can be different from `base.USD` depending on the localization installed + self.company_currency = self.company.currency_id + # other_currency is a currency different from the company_currency + # sometimes company_currency is different from USD, so handle appropriately. + self.other_currency = self.env.ref('base.EUR') if self.company_currency == self.env.ref('base.USD') else self.env.ref('base.USD') + + self.currency_pricelist = self.env['product.pricelist'].create({ + 'name': 'Public Pricelist', + 'currency_id': self.company_currency.id, + }) + # Set Point of Sale configurations + # basic_config + # - derived from 'point_of_sale.pos_config_main' with added invoice_journal_id and credit payment method. + # other_currency_config + # - pos.config set to have currency different from company currency. + self.basic_config = self._create_basic_config() + self.other_currency_config = self._create_other_currency_config() + + # Set product categories + # categ_basic + # - just the plain 'product.product_category_all' + # categ_anglo + # - product category with fifo and real_time valuations + # - used for checking anglo saxon accounting behavior + self.categ_basic = self.env.ref('product.product_category_all') + self.categ_anglo = self._create_categ_anglo() + + # other basics + self.sale_account = self.categ_basic.property_account_income_categ_id + self.other_sale_account = self.env['account.account'].search([ + ('company_id', '=', self.company.id), + ('user_type_id', '=', self.env.ref('account.data_account_type_revenue').id), + ('id', '!=', self.sale_account.id) + ], limit=1) + + # Set customers + self.customer = self.env['res.partner'].create({'name': 'Test Customer'}) + self.other_customer = self.env['res.partner'].create({'name': 'Other Customer', 'property_account_receivable_id': self.other_receivable_account.id}) + + # Set taxes + # self.taxes => dict + # keys: 'tax7', 'tax10'(price_include=True), 'tax_group_7_10' + self.taxes = self._create_taxes() + + ##################### + ## private methods ## + ##################### + + def _create_basic_config(self): + new_config = Form(self.env['pos.config']) + new_config.name = 'PoS Shop Test' + new_config.module_account = True + new_config.invoice_journal_id = self.invoice_journal + new_config.journal_id = self.pos_sale_journal + new_config.available_pricelist_ids.clear() + new_config.available_pricelist_ids.add(self.currency_pricelist) + new_config.pricelist_id = self.currency_pricelist + config = new_config.save() + cash_journal = config.payment_method_ids.filtered(lambda pm: pm.is_cash_count)[:1].cash_journal_id + cash_split_pm = self.env['pos.payment.method'].create({ + 'name': 'Split (Cash) PM', + 'receivable_account_id': self.pos_receivable_account.id, + 'split_transactions': True, + 'is_cash_count': True, + 'cash_journal_id': cash_journal.id, + }) + config.write({'payment_method_ids': [(4,cash_split_pm.id,0)]}) + return config + + def _create_other_currency_config(self): + (self.other_currency.rate_ids | self.company_currency.rate_ids).unlink() + self.env['res.currency.rate'].create({ + 'rate': 0.5, + 'currency_id': self.other_currency.id, + }) + other_cash_journal = self.env['account.journal'].create({ + 'name': 'Cash Other', + 'type': 'cash', + 'company_id': self.company.id, + 'code': 'CSHO', + 'sequence': 10, + 'currency_id': self.other_currency.id + }) + other_invoice_journal = self.env['account.journal'].create({ + 'name': 'Customer Invoice Other', + 'type': 'sale', + 'company_id': self.company.id, + 'code': 'INVO', + 'sequence': 11, + 'currency_id': self.other_currency.id + }) + other_sales_journal = self.env['account.journal'].create({ + 'name':'PoS Sale Other', + 'type': 'sale', + 'code': 'POSO', + 'company_id': self.company.id, + 'sequence': 12, + 'currency_id': self.other_currency.id + }) + other_pricelist = self.env['product.pricelist'].create({ + 'name': 'Public Pricelist Other', + 'currency_id': self.other_currency.id, + }) + other_cash_payment_method = self.env['pos.payment.method'].create({ + 'name': 'Cash Other', + 'receivable_account_id': self.pos_receivable_account.id, + 'is_cash_count': True, + 'cash_journal_id': other_cash_journal.id, + }) + other_bank_payment_method = self.env['pos.payment.method'].create({ + 'name': 'Bank Other', + 'receivable_account_id': self.pos_receivable_account.id, + }) + + new_config = Form(self.env['pos.config']) + new_config.name = 'Shop Other' + new_config.invoice_journal_id = other_invoice_journal + new_config.journal_id = other_sales_journal + new_config.use_pricelist = True + new_config.available_pricelist_ids.clear() + new_config.available_pricelist_ids.add(other_pricelist) + new_config.pricelist_id = other_pricelist + new_config.payment_method_ids.clear() + new_config.payment_method_ids.add(other_cash_payment_method) + new_config.payment_method_ids.add(other_bank_payment_method) + config = new_config.save() + return config + + def _create_categ_anglo(self): + return self.env['product.category'].create({ + 'name': 'Anglo', + 'parent_id': False, + 'property_cost_method': 'fifo', + 'property_valuation': 'real_time', + }) + + def _create_taxes(self): + """ Create taxes + + tax7: 7%, excluded in product price + tax10: 10%, included in product price + """ + tax7 = self.env['account.tax'].create({'name': 'Tax 7%', 'amount': 7}) + tax10 = self.env['account.tax'].create({'name': 'Tax 10%', 'amount': 10, 'price_include': True, 'include_base_amount': False}) + (tax7 | tax10).mapped('invoice_repartition_line_ids').write({'account_id': self.tax_received_account.id}) + (tax7 | tax10).mapped('refund_repartition_line_ids').write({'account_id': self.tax_received_account.id}) + + tax_group_7_10 = tax7.copy() + with Form(tax_group_7_10) as tax: + tax.name = 'Tax 7+10%' + tax.amount_type = 'group' + tax.children_tax_ids.add(tax7) + tax.children_tax_ids.add(tax10) + + return { + 'tax7': tax7, + 'tax10': tax10, + 'tax_group_7_10': tax_group_7_10 + } + + #################### + ## public methods ## + #################### + + def create_random_uid(self): + return ('%05d-%03d-%04d' % (randint(1, 99999), randint(1, 999), randint(1, 9999))) + + def create_ui_order_data(self, product_quantity_pairs, customer=False, is_invoiced=False, payments=None, uid=None): + """ Mocks the order_data generated by the pos ui. + + This is useful in making orders in an open pos session without making tours. + Its functionality is tested in test_pos_create_ui_order_data.py. + + Before use, make sure that self is set with: + 1. pricelist -> the pricelist of the current session + 2. currency -> currency of the current session + 3. pos_session -> the current session, equivalent to config.current_session_id + 4. cash_pm -> first cash payment method in the current session + 5. config -> the active pos.config + + The above values should be set when `self.open_new_session` is called. + + :param list(tuple) product_quantity_pairs: pair of `ordered product` and `quantity` + :param list(tuple) payments: pair of `payment_method` and `amount` + """ + default_fiscal_position = self.config.default_fiscal_position_id + fiscal_position = customer.property_account_position_id if customer else default_fiscal_position + + def create_order_line(product, quantity): + price_unit = self.pricelist.get_product_price(product, quantity, False) + tax_ids = fiscal_position.map_tax(product.taxes_id) if fiscal_position else product.taxes_id + tax_values = ( + tax_ids.compute_all(price_unit, self.currency, quantity) + if tax_ids + else { + 'total_excluded': price_unit * quantity, + 'total_included': price_unit * quantity, + } + ) + return (0, 0, { + 'discount': 0, + 'id': randint(1, 1000000), + 'pack_lot_ids': [], + 'price_unit': price_unit, + 'product_id': product.id, + 'price_subtotal': tax_values['total_excluded'], + 'price_subtotal_incl': tax_values['total_included'], + 'qty': quantity, + 'tax_ids': [(6, 0, tax_ids.ids)] + }) + + def create_payment(payment_method, amount): + return (0, 0, { + 'amount': amount, + 'name': fields.Datetime.now(), + 'payment_method_id': payment_method.id, + }) + + uid = uid or self.create_random_uid() + + # 1. generate the order lines + order_lines = [create_order_line(product, quantity) for product, quantity in product_quantity_pairs] + + # 2. generate the payments + total_amount_incl = sum(line[2]['price_subtotal_incl'] for line in order_lines) + if payments is None: + payments = [create_payment(self.cash_pm, total_amount_incl)] + else: + payments = [ + create_payment(pm, amount) + for pm, amount in payments + ] + + # 3. complete the fields of the order_data + total_amount_base = sum(line[2]['price_subtotal'] for line in order_lines) + return { + 'data': { + 'amount_paid': sum(payment[2]['amount'] for payment in payments), + 'amount_return': 0, + 'amount_tax': total_amount_incl - total_amount_base, + 'amount_total': total_amount_incl, + 'creation_date': fields.Datetime.to_string(fields.Datetime.now()), + 'fiscal_position_id': fiscal_position.id, + 'pricelist_id': self.config.pricelist_id.id, + 'lines': order_lines, + 'name': 'Order %s' % uid, + 'partner_id': customer and customer.id, + 'pos_session_id': self.pos_session.id, + 'sequence_number': 2, + 'statement_ids': payments, + 'uid': uid, + 'user_id': self.pos_manager.id, + }, + 'id': uid, + 'to_invoice': is_invoiced, + } + + def create_product(self, name, category, lst_price, standard_price=None, tax_ids=None, sale_account=None): + product = self.env['product.product'].create({ + 'type': 'product', + 'available_in_pos': True, + 'taxes_id': [(5, 0, 0)] if not tax_ids else [(6, 0, tax_ids)], + 'name': name, + 'categ_id': category.id, + 'lst_price': lst_price, + 'standard_price': standard_price if standard_price else 0.0, + }) + product.invoice_policy = 'delivery' + if sale_account: + product.property_account_income_id = sale_account + return product + + def adjust_inventory(self, products, quantities): + """ Adjust inventory of the given products + """ + inventory = self.env['stock.inventory'].create({ + 'name': 'Inventory adjustment' + }) + for product, qty in zip(products, quantities): + self.env['stock.inventory.line'].create({ + 'product_id': product.id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, + 'inventory_id': inventory.id, + 'product_qty': qty, + 'location_id': self.env.ref('stock.stock_location_components').id, + }) + inventory._action_start() + inventory.action_validate() + + def open_new_session(self): + """ Used to open new pos session in each configuration. + + - The idea is to properly set values that are constant + and commonly used in an open pos session. + - Calling this method is also a prerequisite for using + `self.create_ui_order_data` function. + + Fields: + * config : the pos.config currently being used. + Its value is set at `self.setUp` of the inheriting + test class. + * session : the current_session_id of config + * currency : currency of the current pos.session + * pricelist : the default pricelist of the session + * cash_pm : cash payment method of the session + * bank_pm : bank payment method of the session + * cash_split_pm : credit payment method of the session + """ + self.config.open_session_cb() + self.pos_session = self.config.current_session_id + self.currency = self.pos_session.currency_id + self.pricelist = self.pos_session.config_id.pricelist_id + self.cash_pm = self.pos_session.payment_method_ids.filtered(lambda pm: pm.is_cash_count and not pm.split_transactions)[:1] + self.bank_pm = self.pos_session.payment_method_ids.filtered(lambda pm: not pm.is_cash_count and not pm.split_transactions)[:1] + self.cash_split_pm = self.pos_session.payment_method_ids.filtered(lambda pm: pm.is_cash_count and pm.split_transactions)[:1] diff --git a/addons/point_of_sale/tests/test_anglo_saxon.py b/addons/point_of_sale/tests/test_anglo_saxon.py index b56f9217697..6a3d56989b4 100644 --- a/addons/point_of_sale/tests/test_anglo_saxon.py +++ b/addons/point_of_sale/tests/test_anglo_saxon.py @@ -48,6 +48,13 @@ class TestAngloSaxonCommon(common.TransactionCase): self.sale_journal = self.env['account.journal'].create({'name': 'SALE journal', 'type': 'sale', 'code': 'INV00'}) self.pos_config.invoice_journal_id = self.sale_journal self.pos_config.journal_ids = [self.cash_journal.id] + self.cash_payment_method = self.env['pos.payment.method'].create({ + 'name': 'Cash Test', + 'is_cash_count': True, + 'cash_journal_id': self.cash_journal.id, + 'receivable_account_id': self.account.id, + }) + self.pos_config.write({'payment_method_ids': [(6, 0, self.cash_payment_method.ids)]}) class TestAngloSaxonFlow(TestAngloSaxonCommon): @@ -56,17 +63,8 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon): # This test will check that the correct journal entries are created when a product in real time valuation # is sold in a company using anglo-saxon self.pos_config.open_session_cb() - self.pos_config.current_session_id.write({'journal_ids': [(6, 0, [self.cash_journal.id])]}) + current_session = self.pos_config.current_session_id self.cash_journal.loss_account_id = self.account - self.pos_statement = self.Statement.create({ - 'balance_start': 0.0, - 'balance_end_real': 0.0, - 'date': time.strftime('%Y-%m-%d'), - 'journal_id': self.cash_journal.id, - 'company_id': self.company.id, - 'name': 'pos session test', - }) - self.pos_config.current_session_id.write({'statement_ids': [(6, 0, [self.pos_statement.id])]}) # I create a PoS order with 1 unit of New product at 450 EUR self.pos_order_pos0 = self.PosOrder.create({ @@ -93,7 +91,7 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon): context_make_payment = {"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id} self.pos_make_payment_0 = self.PosMakePayment.with_context(context_make_payment).create({ 'amount': 450.0, - 'journal_id': self.cash_journal.id, + 'payment_method_id': self.cash_payment_method.id, }) # I click on the validate button to register the payment. @@ -110,10 +108,14 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon): current_session_id.action_pos_session_close() self.assertEqual(current_session_id.state, 'closed', 'Check that session is closed') + # Check if there is account_move in the order. + # There shouldn't be because the order is not invoiced. + self.assertFalse(self.pos_order_pos0.account_move, 'There should be no invoice in the order.') + # I test that the generated journal entries are correct. account_output = self.category.property_stock_account_output_categ_id expense_account = self.category.property_account_expense_categ_id - aml = self.pos_order_pos0.account_move.line_ids + aml = current_session.move_id.line_ids aml_output = aml.filtered(lambda l: l.account_id.id == account_output.id) aml_expense = aml.filtered(lambda l: l.account_id.id == expense_account.id) self.assertEqual(aml_output.credit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching") @@ -140,8 +142,8 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon): self.assertEqual(self.product.value_svl, 30, "Value should be (5*5 + 5*1) = 30") self.assertEqual(self.product.quantity_svl, 10) - self.pos_config.open_session_cb() self.pos_config.module_account = True + self.pos_config.open_session_cb() pos_order_values = { 'company_id': self.company.id, @@ -172,6 +174,7 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon): context_make_payment = {"active_ids": [pos_order_pos0.id], "active_id": pos_order_pos0.id} self.pos_make_payment_0 = self.PosMakePayment.with_context(context_make_payment).create({ 'amount': 7 * 450.0, + 'payment_method_id': self.cash_payment_method.id, }) # register the payment @@ -183,8 +186,11 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon): current_session_id.action_pos_session_validate() # check the anglo saxon move lines - line = pos_order_pos0.account_move.line_ids.filtered(lambda l: l.debit and l.account_id == self.category.property_account_expense_categ_id) - self.assertEqual(pos_order_pos0.account_move.journal_id, self.pos_config.journal_id) + # with uninvoiced orders, the account_move field of pos.order is empty. + # the accounting lines are in move_id of pos.session. + session_move = pos_order_pos0.session_id.move_id + line = session_move.line_ids.filtered(lambda l: l.debit and l.account_id == self.category.property_account_expense_categ_id) + self.assertEqual(session_move.journal_id, self.pos_config.journal_id) self.assertEqual(line.debit, 27, 'As it is a fifo product, the move\'s value should be 5*5 + 2*1') def test_fifo_valuation_with_invoice(self): @@ -194,7 +200,7 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon): context_make_payment = {"active_ids": [pos_order_pos0.id], "active_id": pos_order_pos0.id} self.pos_make_payment_0 = self.PosMakePayment.with_context(context_make_payment).create({ 'amount': 7 * 450.0, - 'journal_id': self.cash_journal.id, + 'payment_method_id': self.cash_payment_method.id, }) # register the payment diff --git a/addons/point_of_sale/tests/test_frontend.py b/addons/point_of_sale/tests/test_frontend.py index a6466a72c56..593c88876dc 100644 --- a/addons/point_of_sale/tests/test_frontend.py +++ b/addons/point_of_sale/tests/test_frontend.py @@ -27,6 +27,14 @@ class TestUi(odoo.tests.HttpCase): 'fields_id': field.id, 'value': 'account.account,' + str(account_receivable.id)}) + cash_journal = journal_obj.create({ + 'name': 'Cash Test', + 'type': 'cash', + 'company_id': main_company.id, + 'code': 'CSH', + 'sequence': 10, + }) + # test an extra price on an attribute pear = env.ref('point_of_sale.whiteboard') attribute_value = env['product.attribute.value'].create({ @@ -287,11 +295,11 @@ class TestUi(odoo.tests.HttpCase): })], 'journal_id': test_sale_journal.id, 'invoice_journal_id': test_sale_journal.id, - 'journal_ids': [(0, 0, {'name': 'Cash Journal - Test', - 'code': 'TSC', - 'type': 'cash', - 'company_id': main_company.id, - 'journal_user': True})], + 'payment_method_ids': [(0, 0, { 'name': 'Cash', + 'is_cash_count': True, + 'cash_journal_id': cash_journal.id, + 'receivable_account_id': account_receivable.id, + })], 'use_pricelist': True, 'pricelist_id': public_pricelist.id, 'available_pricelist_ids': [(4, pricelist.id) for pricelist in all_pricelists], diff --git a/addons/point_of_sale/tests/test_point_of_sale_flow.py b/addons/point_of_sale/tests/test_point_of_sale_flow.py index ca0d12f330d..3466b4ca112 100644 --- a/addons/point_of_sale/tests/test_point_of_sale_flow.py +++ b/addons/point_of_sale/tests/test_point_of_sale_flow.py @@ -4,18 +4,13 @@ import time import odoo from odoo import fields from odoo.tools import float_compare, mute_logger, test_reports +from odoo.tests.common import Form from odoo.addons.point_of_sale.tests.common import TestPointOfSaleCommon @odoo.tests.tagged('post_install', '-at_install') class TestPointOfSaleFlow(TestPointOfSaleCommon): - def test_register_open(self): - """ - In order to test the Point of Sale module, I will open all cash registers through the wizard - """ - # open all statements/cash registers - self.env['pos.open.statement'].create({}).open_statement() def test_order_refund(self): self.pos_config.open_session_cb() @@ -47,10 +42,18 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): })], 'amount_total': 1710.0, 'amount_tax': 0.0, - 'amount_paid': 1710.0, + 'amount_paid': 0.0, 'amount_return': 0.0, }) + payment_context = {"active_ids": order.ids, "active_id": order.id} + order_payment = self.PosMakePayment.with_context(**payment_context).create({ + 'amount': order.amount_total, + 'payment_method_id': self.cash_payment_method.id + }) + order_payment.with_context(**payment_context).check() + self.assertAlmostEqual(order.amount_total, order.amount_paid, msg='Order should be fully paid.') + # I create a refund refund_action = order.refund() refund = self.PosOrder.browse(refund_action['res_id']) @@ -60,13 +63,18 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): payment_context = {"active_ids": refund.ids, "active_id": refund.id} refund_payment = self.PosMakePayment.with_context(**payment_context).create({ - 'amount': refund.amount_total + 'amount': refund.amount_total, + 'payment_method_id': self.cash_payment_method.id, }) # I click on the validate button to register the payment. refund_payment.with_context(**payment_context).check() self.assertEqual(refund.state, 'paid', "The refund is not marked as paid") + self.assertTrue(refund.payment_ids.payment_method_id.is_cash_count, msg='There should only be one payment and paid in cash.') + + current_session.action_pos_session_closing_control() + self.assertEqual(current_session.state, 'closed', msg='State of current session should be closed.') def test_order_to_picking(self): """ @@ -287,7 +295,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): # I close the session to generate the journal entries self.pos_config.current_session_id.action_pos_session_closing_control() - def test_order_to_invoice(self): def compute_tax(product, price, qty=1, taxes=None): @@ -350,13 +357,16 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): # I generate an invoice from the order res = self.pos_order_pos1.action_pos_order_invoice() - self.assertIn('res_id', res, "No invoice created") + self.assertIn('res_id', res, "Invoice should be created") # I test that the total of the attached invoice is correct invoice = self.env['account.move'].browse(res['res_id']) self.assertAlmostEqual( invoice.amount_total, self.pos_order_pos1.amount_total, places=2, msg="Invoice not correct") + # I close the session to generate the journal entries + current_session.action_pos_session_closing_control() + """In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports""" # I select the period and journal for the bank statement @@ -450,11 +460,9 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): 'sequence_number': 2, 'statement_ids': [[0, 0, - {'account_id': self.env.user.partner_id.property_account_receivable_id.id, - 'amount': untax + atax, - 'journal_id': self.pos_config.journal_ids[0].id, + {'amount': untax + atax, 'name': fields.Datetime.now(), - 'statement_id': current_session.statement_ids[0].id}]], + 'payment_method_id': self.cash_payment_method.id}]], 'uid': '00042-003-0014', 'user_id': self.env.uid}, 'id': '00042-003-0014', @@ -486,11 +494,9 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): 'sequence_number': self.pos_config.journal_id.id, 'statement_ids': [[0, 0, - {'account_id': self.env.user.partner_id.property_account_receivable_id.id, - 'amount': untax + atax, - 'journal_id': self.pos_config.journal_ids[0].id, + {'amount': untax + atax, 'name': fields.Datetime.now(), - 'statement_id': current_session.statement_ids[0].id}]], + 'payment_method_id': self.credit_payment_method.id}]], 'uid': '00043-003-0014', 'user_id': self.env.uid}, 'id': '00043-003-0014', @@ -522,11 +528,9 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): 'sequence_number': self.pos_config.journal_id.id, 'statement_ids': [[0, 0, - {'account_id': self.env.user.partner_id.property_account_receivable_id.id, - 'amount': untax + atax, - 'journal_id': self.pos_config.journal_ids[0].id, + {'amount': untax + atax, 'name': fields.Datetime.now(), - 'statement_id': current_session.statement_ids[0].id}]], + 'payment_method_id': self.bank_payment_method.id}]], 'uid': '00044-003-0014', 'user_id': self.env.uid}, 'id': '00044-003-0014', @@ -588,19 +592,39 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): untax = res['total_excluded'] return untax, sum(tax.get('amount', 0.0) for tax in res['taxes']) + # make a config that has currency different from the company + eur_pricelist = self.partner1.property_product_pricelist.copy(default={'currency_id': self.env.ref('base.EUR').id}) + sale_journal = self.env['account.journal'].create({ + 'name': 'PoS Sale EUR', + 'type': 'sale', + 'code': 'POSE', + 'company_id': self.company.id, + 'sequence': 12, + 'currency_id': self.env.ref('base.EUR').id + }) + eur_config = self.pos_config.create({ + 'name': 'Shop EUR Test', + 'module_account': False, + 'journal_id': sale_journal.id, + 'use_pricelist': True, + 'available_pricelist_ids': [(6, 0, eur_pricelist.ids)], + 'pricelist_id': eur_pricelist.id, + 'payment_method_ids': [(6, 0, self.bank_payment_method.ids)] + }) + # I click on create a new session button - self.pos_config.open_session_cb() - current_session = self.pos_config.current_session_id + eur_config.open_session_cb() + current_session = eur_config.current_session_id # I create a PoS order with 2 units of PCSC234 at 450 EUR (Tax Incl) # and 3 units of PCSC349 at 300 EUR. (Tax Excl) - untax1, atax1 = compute_tax(self.product3, 450*0.95, 2) - untax2, atax2 = compute_tax(self.product4, 300*0.95, 3) + untax1, atax1 = compute_tax(self.product3, 450, 2) + untax2, atax2 = compute_tax(self.product4, 300, 3) self.pos_order_pos0 = self.PosOrder.create({ 'company_id': self.company_id, 'session_id': current_session.id, - 'pricelist_id': self.partner1.property_product_pricelist.copy(default={'currency_id': self.env.ref('base.EUR').id}).id, + 'pricelist_id': eur_pricelist.id, 'partner_id': self.partner1.id, 'lines': [(0, 0, { 'name': "OL/0001", @@ -630,14 +654,15 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): # I check that the total of the order is now equal to (450*2 + # 300*3*1.05)*0.95 self.assertLess( - abs(self.pos_order_pos0.amount_total - (450 * 2 + 300 * 3 * 1.05) * 0.95), + abs(self.pos_order_pos0.amount_total - (450 * 2 + 300 * 3 * 1.05)), 0.01, 'The order has a wrong total including tax and discounts') # I click on the "Make Payment" wizard to pay the PoS order with a # partial amount of 100.0 EUR context_make_payment = {"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id} self.pos_make_payment_0 = self.PosMakePayment.with_context(context_make_payment).create({ - 'amount': 100.0 + 'amount': 100.0, + 'payment_method_id': self.bank_payment_method.id, }) # I click on the validate button to register the payment. @@ -652,14 +677,15 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): defs = self.pos_make_payment_0.with_context({'active_id': self.pos_order_pos0.id}).default_get(['amount']) self.assertLess( - abs(defs['amount'] - ((450 * 2 + 300 * 3 * 1.05) * 0.95 - 100.0)), 0.01, "The remaining balance is incorrect.") + abs(defs['amount'] - ((450 * 2 + 300 * 3 * 1.05) - 100.0)), 0.01, "The remaining balance is incorrect.") #'I pay the remaining balance. context_make_payment = { "active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id} self.pos_make_payment_1 = self.PosMakePayment.with_context(context_make_payment).create({ - 'amount': (450 * 2 + 300 * 3 * 1.05) * 0.95 - 100.0 + 'amount': (450 * 2 + 300 * 3 * 1.05) - 100.0, + 'payment_method_id': self.bank_payment_method.id, }) # I click on the validate button to register the payment. @@ -669,20 +695,20 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): self.assertEqual(self.pos_order_pos0.state, 'paid', 'Order should be in paid state.') # I generate the journal entries - self.pos_order_pos0._create_account_move_line() + current_session.action_pos_session_validate() # I test that the generated journal entry is attached to the PoS order - self.assertTrue(self.pos_order_pos0.account_move, "Journal entry has not been attached to Pos order.") + self.assertTrue(current_session.move_id, "Journal entry should have been attached to the session.") # Check the amounts - debit_lines = self.pos_order_pos0.account_move.mapped('line_ids.debit') - credit_lines = self.pos_order_pos0.account_move.mapped('line_ids.credit') - amount_currency_lines = self.pos_order_pos0.account_move.mapped('line_ids.amount_currency') - for a, b in zip(sorted(debit_lines), [0.0, 0.0, 0.0, 0.0, 879.55]): + debit_lines = current_session.move_id.mapped('line_ids.debit') + credit_lines = current_session.move_id.mapped('line_ids.credit') + amount_currency_lines = current_session.move_id.mapped('line_ids.amount_currency') + for a, b in zip(sorted(debit_lines), [0.0, 0.0, 0.0, 0.0, 922.5]): self.assertAlmostEqual(a, b) - for a, b in zip(sorted(credit_lines), [0.0, 22.5, 40.91, 388.64, 427.5]): + for a, b in zip(sorted(credit_lines), [0.0, 22.5, 40.91, 409.09, 450]): self.assertAlmostEqual(a, b) - for a, b in zip(sorted(amount_currency_lines), [-855.0, -777.27, -81.82, -45.0, 1752.75]): + for a, b in zip(sorted(amount_currency_lines), [-900, -818.18, -81.82, -45, 1845]): self.assertAlmostEqual(a, b) def test_order_to_invoice_no_tax(self): @@ -731,7 +757,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): # I check that the order is marked as paid and there is no invoice # attached to it self.assertEqual(self.pos_order_pos1.state, 'paid', "Order should be in paid state.") - self.assertFalse(self.pos_order_pos1.account_move, 'Invoice should not be attached to order.') + self.assertFalse(self.pos_order_pos1.account_move, 'Invoice should not be attached to order yet.') # I generate an invoice from the order res = self.pos_order_pos1.action_pos_order_invoice() @@ -744,3 +770,5 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon): for iline in invoice.invoice_line_ids: self.assertFalse(iline.tax_ids) + + self.pos_config.current_session_id.action_pos_session_closing_control() diff --git a/addons/point_of_sale/tests/test_pos_basic_config.py b/addons/point_of_sale/tests/test_pos_basic_config.py new file mode 100644 index 00000000000..bf34c1d25fb --- /dev/null +++ b/addons/point_of_sale/tests/test_pos_basic_config.py @@ -0,0 +1,423 @@ +import odoo +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +@odoo.tests.tagged('post_install', '-at_install') +class TestPoSBasicConfig(TestPoSCommon): + """ Test PoS with basic configuration + + The tests contain base scenarios in using pos. + More specialized cases are tested in other tests. + """ + + def setUp(self): + super(TestPoSBasicConfig, self).setUp() + self.config = self.basic_config + self.product1 = self.create_product('Product 1', self.categ_basic, 10.0, 5) + self.product2 = self.create_product('Product 2', self.categ_basic, 20.0, 10) + self.product3 = self.create_product('Product 3', self.categ_basic, 30.0, 15) + self.adjust_inventory([self.product1, self.product2, self.product3], [100, 50, 50]) + + def test_orders_no_invoiced(self): + """ Test for orders without invoice + + 3 orders + - first 2 orders with cash payment + - last order with bank payment + + Orders + ====== + +---------+----------+-----------+----------+-----+-------+ + | order | payments | invoiced? | product | qty | total | + +---------+----------+-----------+----------+-----+-------+ + | order 1 | cash | no | product1 | 10 | 100 | + | | | | product2 | 5 | 100 | + +---------+----------+-----------+----------+-----+-------+ + | order 2 | cash | no | product2 | 7 | 140 | + | | | | product3 | 1 | 30 | + +---------+----------+-----------+----------+-----+-------+ + | order 3 | bank | no | product1 | 1 | 10 | + | | | | product2 | 3 | 60 | + | | | | product3 | 5 | 150 | + +---------+----------+-----------+----------+-----+-------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale | -590 | + | pos receivable cash | 370 | + | pos receivable bank | 220 | + +---------------------+---------+ + | Total balance | 0.0 | + +---------------------+---------+ + """ + start_qty_available = { + self.product1: self.product1.qty_available, + self.product2: self.product2.qty_available, + self.product3: self.product3.qty_available, + } + + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 5)])) + orders.append(self.create_ui_order_data([(self.product2, 7), (self.product3, 1)])) + orders.append(self.create_ui_order_data( + [(self.product1, 1), (self.product3, 5), (self.product2, 3)], + payments=[(self.bank_pm, 220)] + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # check product qty_available after syncing the order + self.assertEqual( + self.product1.qty_available + 11, + start_qty_available[self.product1], + ) + self.assertEqual( + self.product2.qty_available + 15, + start_qty_available[self.product2], + ) + self.assertEqual( + self.product3.qty_available + 6, + start_qty_available[self.product3], + ) + + # picking and stock moves should be in done state + for order in self.pos_session.order_ids: + self.assertEqual( + order.picking_id.state, + 'done', + 'Picking should be in done state.' + ) + move_lines = order.picking_id.move_lines + self.assertEqual( + move_lines.mapped('state'), + ['done'] * len(move_lines), + 'Move Lines should be in done state.' + ) + + # close the session + self.pos_session.action_pos_session_validate() + + # check accounting values after the session is closed + session_move = self.pos_session.move_id + + sales_line = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sales_line.balance, -590.0, msg='Sales line balance should be equal to total orders amount.') + + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(receivable_line_bank.balance, 220.0, msg='Bank receivable should be equal to the total bank payments.') + + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 370.0, msg='Cash receivable should be equal to the total cash payments.') + + self.assertTrue(receivable_line_cash.full_reconcile_id, msg='Cash receivable line should be fully-reconciled.') + + def test_orders_with_invoiced(self): + """ Test for orders: one with invoice + + 3 orders + - order 1, paid by cash + - order 2, paid by bank + - order 3, paid by bank, invoiced + + Orders + ====== + +---------+----------+---------------+----------+-----+-------+ + | order | payments | invoiced? | product | qty | total | + +---------+----------+---------------+----------+-----+-------+ + | order 1 | cash | no | product1 | 6 | 60 | + | | | | product2 | 3 | 60 | + | | | | product3 | 1 | 30 | + +---------+----------+---------------+----------+-----+-------+ + | order 2 | bank | no | product1 | 1 | 10 | + | | | | product2 | 20 | 400 | + +---------+----------+---------------+----------+-----+-------+ + | order 3 | bank | yes, customer | product1 | 10 | 100 | + | | | | product3 | 1 | 30 | + +---------+----------+---------------+----------+-----+-------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale | -560 | + | pos receivable cash | 150 | + | pos receivable bank | 540 | + | receivable | -130 | + +---------------------+---------+ + | Total balance | 0.0 | + +---------------------+---------+ + """ + start_qty_available = { + self.product1: self.product1.qty_available, + self.product2: self.product2.qty_available, + self.product3: self.product3.qty_available, + } + + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data( + [(self.product3, 1), (self.product1, 6), (self.product2, 3)], + payments=[(self.cash_pm, 150)], + )) + orders.append(self.create_ui_order_data( + [(self.product2, 20), (self.product1, 1)], + payments=[(self.bank_pm, 410)], + )) + orders.append(self.create_ui_order_data( + [(self.product1, 10), (self.product3, 1)], + payments=[(self.bank_pm, 130)], + customer=self.customer, + is_invoiced=True, + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # check product qty_available after syncing the order + self.assertEqual( + self.product1.qty_available + 17, + start_qty_available[self.product1], + ) + self.assertEqual( + self.product2.qty_available + 23, + start_qty_available[self.product2], + ) + self.assertEqual( + self.product3.qty_available + 2, + start_qty_available[self.product3], + ) + + # picking and stock moves should be in done state + # no exception for invoiced orders + for order in self.pos_session.order_ids: + self.assertEqual( + order.picking_id.state, + 'done', + 'Picking should be in done state.' + ) + move_lines = order.picking_id.move_lines + self.assertEqual( + move_lines.mapped('state'), + ['done'] * len(move_lines), + 'Move Lines should be in done state.' + ) + + # check account move in the invoiced order + invoiced_order = self.pos_session.order_ids.filtered(lambda order: order.account_move) + self.assertEqual(1, len(invoiced_order), 'Only one order is invoiced in this test.') + invoice = invoiced_order.account_move + self.assertAlmostEqual(invoice.amount_total, 130, msg='Amount total should be 130. Product is untaxed.') + invoice_receivable_line = invoice.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + sales_line = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sales_line.balance, -(orders_total - invoice.amount_total), msg='Sales line should be total order minus invoiced order.') + + pos_receivable_line_bank = session_move.line_ids.filtered( + lambda line: self.bank_pm.name in line.name and line.account_id == self.bank_pm.receivable_account_id + ) + self.assertAlmostEqual(pos_receivable_line_bank.balance, 540.0, msg='Bank receivable should be equal to the total bank payments.') + + pos_receivable_line_cash = session_move.line_ids.filtered( + lambda line: self.cash_pm.name in line.name and line.account_id == self.bank_pm.receivable_account_id + ) + self.assertAlmostEqual(pos_receivable_line_cash.balance, 150.0, msg='Cash receivable should be equal to the total cash payments.') + + receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertAlmostEqual(receivable_line.balance, -invoice.amount_total) + + # cash receivable and invoice receivable lines should be fully reconciled + self.assertTrue(pos_receivable_line_cash.full_reconcile_id) + self.assertTrue(receivable_line.full_reconcile_id) + + # matching number of the receivable lines should be the same + self.assertEqual(receivable_line.full_reconcile_id, invoice_receivable_line.full_reconcile_id) + + def test_return_order(self): + """ Test return order + + 2 orders + - 2nd order is returned + + Orders + ====== + +------------------+----------+-----------+----------+-----+-------+ + | order | payments | invoiced? | product | qty | total | + +------------------+----------+-----------+----------+-----+-------+ + | order 1 | bank | no | product1 | 1 | 10 | + | | | | product2 | 5 | 100 | + +------------------+----------+-----------+----------+-----+-------+ + | order 2 | cash | no | product1 | 3 | 30 | + | | | | product2 | 2 | 40 | + | | | | product3 | 1 | 30 | + +------------------+----------+-----------+----------+-----+-------+ + | order 3 (return) | cash | no | product1 | -3 | -30 | + | | | | product2 | -2 | -40 | + | | | | product3 | -1 | -30 | + +------------------+----------+-----------+----------+-----+-------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale (sales) | -210 | + | sale (refund) | 100 | + | pos receivable bank | 110 | + +---------------------+---------+ + | Total balance | 0.0 | + +---------------------+---------+ + """ + start_qty_available = { + self.product1: self.product1.qty_available, + self.product2: self.product2.qty_available, + self.product3: self.product3.qty_available, + } + + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data( + [(self.product1, 1), (self.product2, 5)], + payments=[(self.bank_pm, 110)] + )) + orders.append(self.create_ui_order_data( + [(self.product1, 3), (self.product2, 2), (self.product3, 1)], + payments=[(self.cash_pm, 100)], + uid='12345-123-1234' + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(2, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # return order + order_to_return = self.pos_session.order_ids.filtered(lambda order: '12345-123-1234' in order.pos_reference) + order_to_return.refund() + refund_order = self.pos_session.order_ids.filtered(lambda order: order.state == 'draft') + + # check if amount to pay + self.assertAlmostEqual(refund_order.amount_total - refund_order.amount_paid, -100) + + # pay the refund + context_make_payment = {"active_ids": [refund_order.id], "active_id": refund_order.id} + make_payment = self.env['pos.make.payment'].with_context(context_make_payment).create({ + 'payment_method_id': self.cash_pm.id, + 'amount': -100, + }) + make_payment.check() + self.assertEqual(refund_order.state, 'paid', 'Payment is registered, order should be paid.') + self.assertAlmostEqual(refund_order.amount_paid, -100.0, msg='Amount paid for return order should be negative.') + + # check product qty_available after syncing the order + self.assertEqual( + self.product1.qty_available + 1, + start_qty_available[self.product1], + ) + self.assertEqual( + self.product2.qty_available + 5, + start_qty_available[self.product2], + ) + self.assertEqual( + self.product3.qty_available, + start_qty_available[self.product3], + ) + + # picking and stock moves should be in done state + # no exception of return orders + for order in self.pos_session.order_ids: + self.assertEqual( + order.picking_id.state, + 'done', + 'Picking should be in done state.' + ) + move_lines = order.picking_id.move_lines + self.assertEqual( + move_lines.mapped('state'), + ['done'] * len(move_lines), + 'Move Lines should be in done state.' + ) + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + sale_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(len(sale_lines), 2, 'There should be lines for both sales and refund.') + self.assertAlmostEqual(sum(sale_lines.mapped('balance')), -110.0) + + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(receivable_line_bank.balance, 110.0) + + # net cash in the session is zero, thus, there should be no receivable cash line. + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertFalse(receivable_line_cash, 'There should be no receivable cash line because both the order and return order are paid with cash - they cancelled.') + + def test_split_cash_payments(self): + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data( + [(self.product1, 10), (self.product2, 5)], + payments=[(self.cash_split_pm, 100), (self.bank_pm, 100)] + )) + orders.append(self.create_ui_order_data( + [(self.product2, 7), (self.product3, 1)], + payments=[(self.cash_split_pm, 70), (self.bank_pm, 100)] + )) + orders.append(self.create_ui_order_data( + [(self.product1, 1), (self.product3, 5), (self.product2, 3)], + payments=[(self.cash_split_pm, 120), (self.bank_pm, 100)] + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + account_move = self.pos_session.move_id + + bank_receivable_lines = account_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertEqual(len(bank_receivable_lines), 1, msg='Bank receivable lines should only have one line because it\'s supposed to be combined.') + self.assertAlmostEqual(bank_receivable_lines.balance, 300.0, msg='Bank receivable should be equal to the total bank payments.') + + cash_receivable_lines = account_move.line_ids.filtered(lambda line: self.cash_split_pm.name in line.name) + self.assertEqual(len(cash_receivable_lines), 3, msg='There should be a number of cash receivable lines because the cash_pm is `split_transactions`.') + self.assertAlmostEqual(sum(cash_receivable_lines.mapped('balance')), 290, msg='Total cash receivable balance should be equal to the total cash payments.') + + for line in cash_receivable_lines: + self.assertTrue(line.full_reconcile_id, msg='Each cash receivable line should be fully-reconciled.')
\ No newline at end of file diff --git a/addons/point_of_sale/tests/test_pos_multiple_receivable_accounts.py b/addons/point_of_sale/tests/test_pos_multiple_receivable_accounts.py new file mode 100644 index 00000000000..f8748d73528 --- /dev/null +++ b/addons/point_of_sale/tests/test_pos_multiple_receivable_accounts.py @@ -0,0 +1,199 @@ +import odoo +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +@odoo.tests.tagged('post_install', '-at_install') +class TestPoSMultipleReceivableAccounts(TestPoSCommon): + """ Test for invoiced orders with customers having receivable account different from default + + Thus, for this test, there are two receivable accounts involved and are set in the + customers. + self.customer -> self.receivable_account + self.other_customer -> self.other_receivable_account + + NOTE That both receivable accounts above are different from the pos receivable account. + """ + + def setUp(self): + super(TestPoSMultipleReceivableAccounts, self).setUp() + self.config = self.basic_config + self.product1 = self.create_product( + 'Product 1', + self.categ_basic, + lst_price=10.99, + standard_price=5.0, + tax_ids=self.taxes['tax7'].ids, + ) + self.product2 = self.create_product( + 'Product 2', + self.categ_basic, + lst_price=19.99, + standard_price=10.0, + tax_ids=self.taxes['tax10'].ids, + sale_account=self.other_sale_account, + ) + self.product3 = self.create_product( + 'Product 3', + self.categ_basic, + lst_price=30.99, + standard_price=15.0, + tax_ids=self.taxes['tax_group_7_10'].ids, + ) + self.adjust_inventory([self.product1, self.product2, self.product3], [100, 50, 50]) + + def test_01_invoiced_order_from_other_customer(self): + """ + Orders + ====== + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + | order 1 | cash | no | product1 | 10 | 109.9 | 7.69 [7%] | 117.59 | + | | | | product2 | 10 | 181.73 | 18.17 [10%] | 199.9 | + | | | | product3 | 10 | 281.73 | 19.72 [7%] + 28.17 [10%] | 329.62 | + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + | order 2 | bank | no | product1 | 5 | 54.95 | 3.85 [7%] | 58.80 | + | | | | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + | order 3 | bank | yes | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + | | | | product3 | 5 | 140.86 | 9.86 [7%] + 14.09 [10%] | 164.81 | + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale_account | -164.85 | + | sale_account | -281.73 | + | other_sale_account | -272.59 | + | tax 7% | -31.26 | + | tax 10% | -55.43 | + | pos receivable cash | 647.11 | + | pos receivable bank | 423.51 | + | other receivable | -264.76 | + +---------------------+---------+ + | Total balance | 0.00 | + +---------------------+---------+ + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 10), (self.product3, 10)])) + orders.append(self.create_ui_order_data( + [(self.product1, 5), (self.product2, 5)], + payments=[(self.bank_pm, 158.75)], + )) + orders.append(self.create_ui_order_data( + [(self.product2, 5), (self.product3, 5)], + payments=[(self.bank_pm, 264.76)], + customer=self.other_customer, + is_invoiced=True, + uid='09876-098-0987', + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # check if there is one invoiced order + self.assertEqual(len(self.pos_session.order_ids.filtered(lambda order: order.state == 'invoiced')), 1, 'There should only be one invoiced order.') + + # close the session + self.pos_session.action_pos_session_validate() + + session_move = self.pos_session.move_id + # There should be no line corresponding the original receivable account + # But there should be a line for other_receivable_account because + # that is the property_account_receivable_id of the customer + # of the invoiced order. + receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertFalse(receivable_line, msg='There should be no move line for the original receivable account.') + other_receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.other_receivable_account) + self.assertAlmostEqual(other_receivable_line.balance, -264.76) + + def test_02_all_orders_invoiced_mixed_customers(self): + """ + Orders + ====== + +---------+----------+---------------------+----------+-----+---------+--------------------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+---------------------+----------+-----+---------+--------------------------+--------+ + | order 1 | cash | yes, other_customer | product1 | 10 | 109.90 | 7.69 [7%] | 117.59 | + | | | | product2 | 10 | 181.73 | 18.17 [10%] | 199.90 | + | | | | product3 | 10 | 281.73 | 19.72 [7%] + 28.17 [10%] | 329.62 | + +---------+----------+---------------------+----------+-----+---------+--------------------------+--------+ + | order 2 | bank | yes, customer | product1 | 5 | 54.95 | 3.85 [7%] | 58.80 | + | | | | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + +---------+----------+---------------------+----------+-----+---------+--------------------------+--------+ + | order 3 | bank | yes, other customer | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + | | | | product3 | 5 | 140.86 | 9.86 [7%] + 14.09 [10%] | 164.81 | + +---------+----------+---------------------+----------+-----+---------+--------------------------+--------+ + + Expected Result + =============== + +------------------+---------+ + | account | balance | + +------------------+---------+ + | receivable cash | 647.11 | + | receivable bank | 423.51 | + | other receivable | -911.87 | + | receivable | -158.75 | + +------------------+---------+ + | Total balance | 0.00 | + +------------------+---------+ + + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data( + [(self.product1, 10), (self.product2, 10), (self.product3, 10)], + customer=self.other_customer, + is_invoiced=True, + uid='09876-098-0987', + )) + orders.append(self.create_ui_order_data( + [(self.product1, 5), (self.product2, 5)], + payments=[(self.bank_pm, 158.75)], + customer=self.customer, + is_invoiced=True, + uid='09876-098-0988', + )) + orders.append(self.create_ui_order_data( + [(self.product2, 5), (self.product3, 5)], + payments=[(self.bank_pm, 264.76)], + customer=self.other_customer, + is_invoiced=True, + uid='09876-098-0989', + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # check if there is one invoiced order + self.assertEqual(len(self.pos_session.order_ids.filtered(lambda order: order.state == 'invoiced')), 3, 'All orders should be invoiced.') + + # close the session + self.pos_session.action_pos_session_validate() + + session_move = self.pos_session.move_id + + receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertAlmostEqual(receivable_line.balance, -158.75) + other_receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.other_receivable_account) + self.assertAlmostEqual(other_receivable_line.balance, -911.87) + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(receivable_line_bank.balance, 423.51) + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 647.11) diff --git a/addons/point_of_sale/tests/test_pos_multiple_sale_accounts.py b/addons/point_of_sale/tests/test_pos_multiple_sale_accounts.py new file mode 100644 index 00000000000..44b9fc39197 --- /dev/null +++ b/addons/point_of_sale/tests/test_pos_multiple_sale_accounts.py @@ -0,0 +1,220 @@ +import odoo +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +@odoo.tests.tagged('post_install', '-at_install') +class TestPoSMultipleSaleAccounts(TestPoSCommon): + """ Test to orders containing products with different sale accounts + + keywords/phrases: Different Income Accounts + + In this test, two sale (income) accounts are involved: + self.sale_account -> default for products because it is in the category + self.other_sale_account -> manually set to self.product2 + """ + + def setUp(self): + super(TestPoSMultipleSaleAccounts, self).setUp() + self.config = self.basic_config + self.product1 = self.create_product( + 'Product 1', + self.categ_basic, + lst_price=10.99, + standard_price=5.0, + tax_ids=self.taxes['tax7'].ids, + ) + self.product2 = self.create_product( + 'Product 2', + self.categ_basic, + lst_price=19.99, + standard_price=10.0, + tax_ids=self.taxes['tax10'].ids, + sale_account=self.other_sale_account, + ) + self.product3 = self.create_product( + 'Product 3', + self.categ_basic, + lst_price=30.99, + standard_price=15.0, + tax_ids=self.taxes['tax_group_7_10'].ids, + ) + self.adjust_inventory([self.product1, self.product2, self.product3], [100, 50, 50]) + + def test_01_check_product_properties(self): + self.assertEqual(self.product2.property_account_income_id, self.other_sale_account, 'Income account for the product2 should be the other sale account.') + self.assertFalse(self.product1.property_account_income_id, msg='Income account for product1 should not be set.') + self.assertFalse(self.product3.property_account_income_id, msg='Income account for product3 should not be set.') + self.assertEqual(self.product1.categ_id.property_account_income_categ_id, self.sale_account) + self.assertEqual(self.product3.categ_id.property_account_income_categ_id, self.sale_account) + + def test_02_orders_without_invoice(self): + """ orders without invoice + + Orders + ====== + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + | order 1 | cash | no | product1 | 10 | 109.9 | 7.69 [7%] | 117.59 | + | | | | product2 | 10 | 181.73 | 18.17 [10%] | 199.9 | + | | | | product3 | 10 | 281.73 | 19.72 [7%] + 28.17 [10%] | 329.62 | + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + | order 2 | cash | no | product1 | 5 | 54.95 | 3.85 [7%] | 58.80 | + | | | | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + | order 3 | bank | no | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + | | | | product3 | 5 | 140.86 | 9.86 [7%] + 14.09 [10%] | 164.81 | + +---------+----------+-----------+----------+-----+---------+--------------------------+--------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale_account | -164.85 | (for the 7% base amount) + | sale_account | -422.59 | (for the 7+10% base amount) + | other_sale_account | -363.45 | + | tax 7% | -41.12 | + | tax 10% | -78.61 | + | pos receivable bank | 264.76 | + | pos receivable cash | 805.86 | + +---------------------+---------+ + | Total balance | 0.00 | + +---------------------+---------+ + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 10), (self.product3, 10)])) + orders.append(self.create_ui_order_data([(self.product1, 5), (self.product2, 5)])) + orders.append(self.create_ui_order_data([(self.product2, 5), (self.product3, 5)], payments=[(self.bank_pm, 264.76)])) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + sale_account_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + for balance, amount in zip(sorted(sale_account_lines.mapped('balance')), sorted([-164.85, -422.59])): + self.assertAlmostEqual(balance, amount) + + other_sale_account_line = session_move.line_ids.filtered(lambda line: line.account_id == self.other_sale_account) + self.assertAlmostEqual(other_sale_account_line.balance, -363.45) + + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(receivable_line_bank.balance, 264.76) + + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 805.86) + + manually_calculated_taxes = (-41.12, -78.61) + tax_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.tax_received_account) + self.assertAlmostEqual(sum(manually_calculated_taxes), sum(tax_lines.mapped('balance'))) + for t1, t2 in zip(sorted(manually_calculated_taxes), sorted(tax_lines.mapped('balance'))): + self.assertAlmostEqual(t1, t2, msg='Taxes should be correctly combined.') + + self.assertTrue(receivable_line_cash.full_reconcile_id) + + def test_03_orders_with_invoice(self): + """ orders with invoice + + Orders + ====== + +---------+----------+---------------+----------+-----+---------+--------------------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+---------------+----------+-----+---------+--------------------------+--------+ + | order 1 | cash | no | product1 | 10 | 109.9 | 7.69 [7%] | 117.59 | + | | | | product2 | 10 | 181.73 | 18.17 [10%] | 199.9 | + | | | | product3 | 10 | 281.73 | 19.72 [7%] + 28.17 [10%] | 329.62 | + +---------+----------+---------------+----------+-----+---------+--------------------------+--------+ + | order 2 | bank | no | product1 | 5 | 54.95 | 3.85 [7%] | 58.80 | + | | | | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + +---------+----------+---------------+----------+-----+---------+--------------------------+--------+ + | order 3 | bank | yes, customer | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + | | | | product3 | 5 | 140.86 | 9.86 [7%] + 14.09 [10%] | 164.81 | + +---------+----------+---------------+----------+-----+---------+--------------------------+--------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale_account | -164.85 | (for the 7% base amount) + | sale_account | -281.73 | (for the 7+10% base amount) + | other_sale_account | -272.59 | + | tax 7% | -31.26 | + | tax 10% | -55.43 | + | pos receivable cash | 647.11 | + | pos receivable bank | 423.51 | + | receivable | -264.76 | + +---------------------+---------+ + | Total balance | 0.00 | + +---------------------+---------+ + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 10), (self.product3, 10)])) + orders.append(self.create_ui_order_data( + [(self.product1, 5), (self.product2, 5)], + payments=[(self.bank_pm, 158.75)], + )) + orders.append(self.create_ui_order_data( + [(self.product2, 5), (self.product3, 5)], + payments=[(self.bank_pm, 264.76)], + customer=self.customer, + is_invoiced=True, + uid='09876-098-0987', + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # check if there is one invoiced order + self.assertEqual(len(self.pos_session.order_ids.filtered(lambda order: order.state == 'invoiced')), 1, 'There should only be one invoiced order.') + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + sale_account_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + for balance, amount in zip(sorted(sale_account_lines.mapped('balance')), sorted([-164.85, -281.73])): + self.assertAlmostEqual(balance, amount) + + other_sale_account_line = session_move.line_ids.filtered(lambda line: line.account_id == self.other_sale_account) + self.assertAlmostEqual(other_sale_account_line.balance, -272.59) + + pos_receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(pos_receivable_line_bank.balance, 423.51) + + pos_receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertAlmostEqual(pos_receivable_line_cash.balance, 647.11) + + manually_calculated_taxes = (-31.26, -55.43) + tax_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.tax_received_account) + self.assertAlmostEqual(sum(manually_calculated_taxes), sum(tax_lines.mapped('balance'))) + for t1, t2 in zip(sorted(manually_calculated_taxes), sorted(tax_lines.mapped('balance'))): + self.assertAlmostEqual(t1, t2, msg='Taxes should be correctly combined.') + + receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertAlmostEqual(receivable_line.balance, -264.76) + + self.assertTrue(pos_receivable_line_cash.full_reconcile_id) + self.assertTrue(receivable_line.full_reconcile_id) diff --git a/addons/point_of_sale/tests/test_pos_other_currency_config.py b/addons/point_of_sale/tests/test_pos_other_currency_config.py new file mode 100644 index 00000000000..5e286411d38 --- /dev/null +++ b/addons/point_of_sale/tests/test_pos_other_currency_config.py @@ -0,0 +1,170 @@ +import odoo +from odoo.tests.common import Form +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +@odoo.tests.tagged('post_install', '-at_install') +class TestPoSOtherCurrencyConfig(TestPoSCommon): + """ Test PoS with basic configuration + """ + + def setUp(self): + super(TestPoSOtherCurrencyConfig, self).setUp() + self.config = self.other_currency_config + self.product1 = self.create_product('Product 1', self.categ_basic, 10.0, 5) + self.product2 = self.create_product('Product 2', self.categ_basic, 20.0, 10) + self.product3 = self.create_product('Product 3', self.categ_basic, 30.0, 15) + self.adjust_inventory([self.product1, self.product2, self.product3], [100, 50, 50]) + # change the price of product2 to 12.99 fixed. No need to convert. + pricelist_item = self.env['product.pricelist.item'].create({ + 'product_tmpl_id': self.product2.product_tmpl_id.id, + 'fixed_price': 12.99, + }) + self.config.pricelist_id.write({'item_ids': [(6, 0, (self.config.pricelist_id.item_ids | pricelist_item).ids)]}) + + def test_01_check_product_cost(self): + # Product price should be half of the original price because currency rate is 0.5. + # (see `self._create_other_currency_config` method) + # Except for product2 where the price is specified in the pricelist. + self.assertAlmostEqual(self.config.pricelist_id.get_product_price(self.product1, 1, self.customer), 5.00) + self.assertAlmostEqual(self.config.pricelist_id.get_product_price(self.product2, 1, self.customer), 12.99) + self.assertAlmostEqual(self.config.pricelist_id.get_product_price(self.product3, 1, self.customer), 15.00) + + def test_02_orders_without_invoice(self): + """ orders without invoice + + Orders + ====== + +---------+----------+-----------+----------+-----+-------+ + | order | payments | invoiced? | product | qty | total | + +---------+----------+-----------+----------+-----+-------+ + | order 1 | cash | no | product1 | 10 | 50 | + | | | | product2 | 10 | 129.9 | + | | | | product3 | 10 | 150 | + +---------+----------+-----------+----------+-----+-------+ + | order 2 | cash | no | product1 | 5 | 25 | + | | | | product2 | 5 | 64.95 | + +---------+----------+-----------+----------+-----+-------+ + | order 3 | bank | no | product2 | 5 | 64.95 | + | | | | product3 | 5 | 75 | + +---------+----------+-----------+----------+-----+-------+ + + Expected Result + =============== + +---------------------+---------+-----------------+ + | account | balance | amount_currency | + +---------------------+---------+-----------------+ + | sale_account | -1119.6 | -559.80 | + | pos receivable bank | 279.9 | 139.95 | + | pos receivable cash | 839.7 | 419.85 | + +---------------------+---------+-----------------+ + | Total balance | 0.0 | 0.00 | + +---------------------+---------+-----------------+ + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 10), (self.product3, 10)])) + orders.append(self.create_ui_order_data([(self.product1, 5), (self.product2, 5)])) + orders.append(self.create_ui_order_data([(self.product2, 5), (self.product3, 5)], payments=[(self.bank_pm, 139.95)])) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # close the session + self.pos_session.action_pos_session_validate() + session_move = self.pos_session.move_id + + sale_account_line = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sale_account_line.balance, -1119.6) + self.assertAlmostEqual(sale_account_line.amount_currency, -559.80) + + pos_receivable_line_bank = session_move.line_ids.filtered(lambda line: line.account_id == self.pos_receivable_account and self.bank_pm.name in line.name) + self.assertAlmostEqual(pos_receivable_line_bank.balance, 279.9) + self.assertAlmostEqual(pos_receivable_line_bank.amount_currency, 139.95) + + pos_receivable_line_cash = session_move.line_ids.filtered(lambda line: line.account_id == self.pos_receivable_account and self.cash_pm.name in line.name) + self.assertAlmostEqual(pos_receivable_line_cash.balance, 839.7) + self.assertAlmostEqual(pos_receivable_line_cash.amount_currency, 419.85) + + def test_03_orders_with_invoice(self): + """ orders with invoice + + Orders + ====== + +---------+----------+---------------+----------+-----+-------+ + | order | payments | invoiced? | product | qty | total | + +---------+----------+---------------+----------+-----+-------+ + | order 1 | cash | no | product1 | 10 | 50 | + | | | | product2 | 10 | 129.9 | + | | | | product3 | 10 | 150 | + +---------+----------+---------------+----------+-----+-------+ + | order 2 | cash | yes, customer | product1 | 5 | 25 | + | | | | product2 | 5 | 64.95 | + +---------+----------+---------------+----------+-----+-------+ + | order 3 | bank | yes, customer | product2 | 5 | 64.95 | + | | | | product3 | 5 | 75 | + +---------+----------+---------------+----------+-----+-------+ + + Expected Result + =============== + +---------------------+---------+-----------------+ + | account | balance | amount_currency | + +---------------------+---------+-----------------+ + | sale_account | -659.8 | -329.90 | + | pos receivable bank | 279.9 | 139.95 | + | pos receivable cash | 839.7 | 419.85 | + | invoice receivable | -459.8 | -229.90 | + +---------------------+---------+-----------------+ + | Total balance | 0.0 | 0.00 | + +---------------------+---------+-----------------+ + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 10), (self.product3, 10)])) + orders.append(self.create_ui_order_data( + [(self.product1, 5), (self.product2, 5)], + customer=self.customer, + is_invoiced=True, + )) + orders.append(self.create_ui_order_data( + [(self.product2, 5), (self.product3, 5)], + payments=[(self.bank_pm, 139.95)], + customer=self.customer, + is_invoiced=True, + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # close the session + self.pos_session.action_pos_session_validate() + session_move = self.pos_session.move_id + + sale_account_line = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sale_account_line.balance, -659.8) + self.assertAlmostEqual(sale_account_line.amount_currency, -329.9) + + pos_receivable_line_bank = session_move.line_ids.filtered(lambda line: line.account_id == self.pos_receivable_account and self.bank_pm.name in line.name) + self.assertAlmostEqual(pos_receivable_line_bank.balance, 279.9) + self.assertAlmostEqual(pos_receivable_line_bank.amount_currency, 139.95) + + pos_receivable_line_cash = session_move.line_ids.filtered(lambda line: line.account_id == self.pos_receivable_account and self.cash_pm.name in line.name) + self.assertAlmostEqual(pos_receivable_line_cash.balance, 839.7) + self.assertAlmostEqual(pos_receivable_line_cash.amount_currency, 419.85) + + invoice_receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertAlmostEqual(invoice_receivable_line.balance, -459.8) + self.assertAlmostEqual(invoice_receivable_line.amount_currency, -229.9) diff --git a/addons/point_of_sale/tests/test_pos_products_with_tax.py b/addons/point_of_sale/tests/test_pos_products_with_tax.py new file mode 100644 index 00000000000..22caf7fc134 --- /dev/null +++ b/addons/point_of_sale/tests/test_pos_products_with_tax.py @@ -0,0 +1,286 @@ +import odoo +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +@odoo.tests.tagged('post_install', '-at_install') +class TestPoSProductsWithTax(TestPoSCommon): + """ Test normal configuration PoS selling products with tax + """ + + def setUp(self): + super(TestPoSProductsWithTax, self).setUp() + self.config = self.basic_config + self.product1 = self.create_product( + 'Product 1', + self.categ_basic, + 10.0, + 5.0, + tax_ids=self.taxes['tax7'].ids, + ) + self.product2 = self.create_product( + 'Product 2', + self.categ_basic, + 20.0, + 10.0, + tax_ids=self.taxes['tax10'].ids, + ) + self.product3 = self.create_product( + 'Product 3', + self.categ_basic, + 30.0, + 15.0, + tax_ids=self.taxes['tax_group_7_10'].ids, + ) + self.adjust_inventory([self.product1, self.product2, self.product3], [100, 50, 50]) + + def test_orders_no_invoiced(self): + """ Test for orders without invoice + + Orders + ====== + +---------+----------+-----------+----------+-----+---------+-----------------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+-----------+----------+-----+---------+-----------------------+--------+ + | order 1 | cash | no | product1 | 10 | 100 | 7 | 107 | + | | | | product2 | 5 | 90.91 | 9.09 | 100 | + +---------+----------+-----------+----------+-----+---------+-----------------------+--------+ + | order 2 | cash | no | product2 | 7 | 127.27 | 12.73 | 140 | + | | | | product3 | 4 | 109.09 | 10.91[10%] + 7.64[7%] | 127.64 | + +---------+----------+-----------+----------+-----+---------+-----------------------+--------+ + | order 3 | bank | no | product1 | 1 | 10 | 0.7 | 10.7 | + | | | | product2 | 3 | 54.55 | 5.45 | 60 | + | | | | product3 | 5 | 136.36 | 13.64[10%] + 9.55[7%] | 159.55 | + +---------+----------+-----------+----------+-----+---------+-----------------------+--------+ + + Calculated taxes + ================ + total tax 7% only + group tax (10+7%) + (7 + 0.7) + (7.64 + 9.55) = 7.7 + 17.19 = 24.89 + total tax 10% only + group tax (10+7%) + (9.09 + 12.73 + 5.45) + (10.91 + 13.64) = 27.27 + 24.55 = 51.82 + + Thus, manually_calculated_taxes = (-24,89, -51.82) + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 5)])) + orders.append(self.create_ui_order_data([(self.product2, 7), (self.product3, 4)])) + orders.append(self.create_ui_order_data( + [(self.product1, 1), (self.product3, 5), (self.product2, 3)], + payments=[(self.bank_pm, 230.25)] + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, 'Total order amount should be equal to the total payment amount.') + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + sales_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sum(sales_lines.mapped('balance')), -628.18, msg='Sales line balance should be equal to untaxed orders amount.') + + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(receivable_line_bank.balance, 230.25, msg='Bank receivable should be equal to the total bank payments.') + + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 474.64, msg='Cash receivable should be equal to the total cash payments.') + + tax_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.tax_received_account) + + manually_calculated_taxes = (-24.89, -51.82) + self.assertAlmostEqual(sum(manually_calculated_taxes), sum(tax_lines.mapped('balance'))) + for t1, t2 in zip(sorted(manually_calculated_taxes), sorted(tax_lines.mapped('balance'))): + self.assertAlmostEqual(t1, t2, msg='Taxes should be correctly combined.') + + base_amounts = (355.45, 518.18) + self.assertAlmostEqual(sum(base_amounts), sum(tax_lines.mapped('tax_base_amount'))) + + self.assertTrue(receivable_line_cash.full_reconcile_id, 'Cash receivable line should be fully-reconciled.') + + def test_orders_with_invoiced(self): + """ Test for orders: one with invoice + + Orders + ====== + +---------+----------+---------------+----------+-----+---------+---------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+---------------+----------+-----+---------+---------------+--------+ + | order 1 | cash | no | product1 | 6 | 60 | 4.2 | 64.2 | + | | | | product2 | 3 | 54.55 | 5.45 | 60 | + | | | | product3 | 1 | 27.27 | 2.73 + 1.91 | 31.91 | + +---------+----------+---------------+----------+-----+---------+---------------+--------+ + | order 2 | bank | no | product1 | 1 | 10 | 0.7 | 10.7 | + | | | | product2 | 20 | 363.64 | 36.36 | 400 | + +---------+----------+---------------+----------+-----+---------+---------------+--------+ + | order 3 | bank | yes, customer | product1 | 10 | 100 | 7 | 107 | + | | | | product3 | 10 | 272.73 | 27.27 + 19.09 | 319.09 | + +---------+----------+---------------+----------+-----+---------+---------------+--------+ + + Calculated taxes + ================ + total tax 7% only + 4.2 + 0.7 => 4.9 + 1.91 = 6.81 + total tax 10% only + 5.45 + 36.36 => 41.81 + 2.73 = 44.54 + + Thus, manually_calculated_taxes = (-6.81, -44.54) + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data( + [(self.product3, 1), (self.product1, 6), (self.product2, 3)], + payments=[(self.cash_pm, 156.11)], + )) + orders.append(self.create_ui_order_data( + [(self.product2, 20), (self.product1, 1)], + payments=[(self.bank_pm, 410.7)], + )) + orders.append(self.create_ui_order_data( + [(self.product1, 10), (self.product3, 10)], + payments=[(self.bank_pm, 426.09)], + customer=self.customer, + is_invoiced=True, + uid='09876-098-0987', + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # check account move in the invoiced order + invoiced_order = self.pos_session.order_ids.filtered(lambda order: '09876-098-0987' in order.pos_reference) + self.assertEqual(1, len(invoiced_order), 'Only one order is invoiced in this test.') + invoice = invoiced_order.account_move + self.assertAlmostEqual(invoice.amount_total, 426.09) + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + # check sales line + # should not include tax amounts + sales_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sum(sales_lines.mapped('balance')), -515.46) + + # check receivable line + # should be equivalent to receivable in the invoice + # should also be fully-reconciled + receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertAlmostEqual(receivable_line.balance, -426.09) + self.assertTrue(receivable_line.full_reconcile_id, msg='Receivable line for invoices should be fully reconciled.') + + pos_receivable_line_bank = session_move.line_ids.filtered( + lambda line: self.bank_pm.name in line.name and line.account_id == self.bank_pm.receivable_account_id + ) + self.assertAlmostEqual(pos_receivable_line_bank.balance, 836.79) + + pos_receivable_line_cash = session_move.line_ids.filtered( + lambda line: self.cash_pm.name in line.name and line.account_id == self.bank_pm.receivable_account_id + ) + self.assertAlmostEqual(pos_receivable_line_cash.balance, 156.11) + self.assertTrue(pos_receivable_line_cash.full_reconcile_id) + + receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertAlmostEqual(receivable_line.balance, -invoice.amount_total) + + tax_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.tax_received_account) + + manually_calculated_taxes = (-6.81, -44.54) + self.assertAlmostEqual(sum(manually_calculated_taxes), sum(tax_lines.mapped('balance'))) + for t1, t2 in zip(sorted(manually_calculated_taxes), sorted(tax_lines.mapped('balance'))): + self.assertAlmostEqual(t1, t2, msg='Taxes should be correctly combined.') + + base_amounts = (97.27, 445.46) # computation does not include invoiced order. + self.assertAlmostEqual(sum(base_amounts), sum(tax_lines.mapped('tax_base_amount'))) + + def test_return_order(self): + """ Test return order + + Order (invoiced) + ====== + +----------+----------+---------------+----------+-----+---------+-------------+-------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +----------+----------+---------------+----------+-----+---------+-------------+-------+ + | order 1 | cash | yes, customer | product1 | 3 | 30 | 2.1 | 32.1 | + | | | | product2 | 2 | 36.36 | 3.64 | 40 | + | | | | product3 | 1 | 27.27 | 2.73 + 1.91 | 31.91 | + +----------+----------+---------------+----------+-----+---------+-------------+-------+ + + The order is invoiced so the tax of the invoiced order is in the account_move of the order. + However, the return order is not invoiced, thus, the journal items are in the session_move, + which will contain the tax lines of the returned products. + + manually_calculated_taxes = (4.01, 6.37) + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data( + [(self.product1, 3), (self.product2, 2), (self.product3, 1)], + payments=[(self.cash_pm, 104.01)], + customer=self.customer, + is_invoiced=True, + uid='12345-123-1234', + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(1, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # return order + order_to_return = self.pos_session.order_ids.filtered(lambda order: '12345-123-1234' in order.pos_reference) + order_to_return.refund() + + refund_order = self.pos_session.order_ids.filtered(lambda order: order.state == 'draft') + context_make_payment = {"active_ids": [refund_order.id], "active_id": refund_order.id} + make_payment = self.env['pos.make.payment'].with_context(context_make_payment).create({ + 'payment_method_id': self.cash_pm.id, + 'amount': -104.01, + }) + make_payment.check() + self.assertEqual(refund_order.state, 'paid', 'Payment is registered, order should be paid.') + self.assertAlmostEqual(refund_order.amount_paid, -104.01, msg='Amount paid for return order should be negative.') + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + # instead of credit, the sales line should be debit + sales_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sum(sales_lines.mapped('balance')), 93.63) + + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertFalse(receivable_line_bank, msg='There should be no bank receivable line because no bank payment made.') + + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertFalse(receivable_line_cash, msg='There should be no cash receivable line because it is combined with the original cash payment.') + + manually_calculated_taxes = (4.01, 6.37) # should be positive since it is return order + tax_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.tax_received_account) + self.assertAlmostEqual(sum(manually_calculated_taxes), sum(tax_lines.mapped('balance'))) + for t1, t2 in zip(sorted(manually_calculated_taxes), sorted(tax_lines.mapped('balance'))): + self.assertAlmostEqual(t1, t2, msg='Taxes should be correctly combined and should be debit.') diff --git a/addons/point_of_sale/tests/test_pos_setup.py b/addons/point_of_sale/tests/test_pos_setup.py new file mode 100644 index 00000000000..49be252de9f --- /dev/null +++ b/addons/point_of_sale/tests/test_pos_setup.py @@ -0,0 +1,69 @@ +import odoo +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +@odoo.tests.tagged('post_install', '-at_install') +class TestPoSSetup(TestPoSCommon): + """ This group of tests is for sanity check in setting up global records which will be used + in each testing. + + If a test fails here, then it means there are inconsistencies in what we expect in the setup. + """ + def setUp(self): + super(TestPoSSetup, self).setUp() + self.config = self.basic_config + self.products = [ + self.create_product('Product 1', self.categ_basic, lst_price=10.0, standard_price=5), + self.create_product('Product 2', self.categ_basic, lst_price=20.0, standard_price=10), + self.create_product('Product 3', self.categ_basic, lst_price=30.0, standard_price=15), + ] + + def test_basic_config_values(self): + config = self.basic_config + self.assertEqual(config.currency_id, self.company_currency) + self.assertEqual(config.pricelist_id.currency_id, self.company_currency) + + def test_other_currency_config_values(self): + config = self.other_currency_config + self.assertEqual(config.currency_id, self.other_currency) + self.assertEqual(config.pricelist_id.currency_id, self.other_currency) + + def test_product_categories(self): + # check basic product category + # it is expected to have standard and manual_periodic valuation + self.assertEqual(self.categ_basic.property_cost_method, 'standard') + self.assertEqual(self.categ_basic.property_valuation, 'manual_periodic') + # check anglo saxon product category + # this product categ is expected to have fifo and real_time valuation + self.assertEqual(self.categ_anglo.property_cost_method, 'fifo') + self.assertEqual(self.categ_anglo.property_valuation, 'real_time') + + def test_product_price(self): + def get_price(pricelist, product): + return pricelist.get_product_price(product, 1, self.customer) + + # check usd pricelist + pricelist = self.basic_config.pricelist_id + for product in self.products: + self.assertAlmostEqual(get_price(pricelist, product), product.lst_price) + + # check eur pricelist + # exchange rate to the other currency is set to 0.5, thus, lst_price + # is expected to have half its original value. + pricelist = self.other_currency_config.pricelist_id + for product in self.products: + self.assertAlmostEqual(get_price(pricelist, product), product.lst_price * 0.5) + + def test_taxes(self): + tax7 = self.taxes['tax7'] + self.assertEqual(tax7.name, 'Tax 7%') + self.assertAlmostEqual(tax7.amount, 7) + self.assertEqual(tax7.invoice_repartition_line_ids.mapped('account_id').id, self.tax_received_account.id) + tax10 = self.taxes['tax10'] + self.assertEqual(tax10.name, 'Tax 10%') + self.assertAlmostEqual(tax10.amount, 10) + self.assertEqual(tax10.price_include, True) + self.assertEqual(tax10.invoice_repartition_line_ids.mapped('account_id').id, self.tax_received_account.id) + tax_group_7_10 = self.taxes['tax_group_7_10'] + self.assertEqual(tax_group_7_10.name, 'Tax 7+10%') + self.assertEqual(tax_group_7_10.amount_type, 'group') + self.assertEqual(sorted(tax_group_7_10.children_tax_ids.ids), sorted((tax7 | tax10).ids)) diff --git a/addons/point_of_sale/tests/test_pos_stock_account.py b/addons/point_of_sale/tests/test_pos_stock_account.py new file mode 100644 index 00000000000..6616d70d1f7 --- /dev/null +++ b/addons/point_of_sale/tests/test_pos_stock_account.py @@ -0,0 +1,194 @@ +import odoo +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +@odoo.tests.tagged('post_install', '-at_install') +class TestPoSStock(TestPoSCommon): + """ Tests for anglo saxon accounting scenario. + """ + def setUp(self): + super(TestPoSStock, self).setUp() + self.config = self.basic_config + self.product1 = self.create_product('Product 1', self.categ_anglo, 10.0, 5.0) + self.product2 = self.create_product('Product 2', self.categ_anglo, 20.0, 10.0) + self.product3 = self.create_product('Product 3', self.categ_basic, 30.0, 15.0) + # start inventory with 10 items for each product + self.adjust_inventory([self.product1, self.product2, self.product3], [10, 10, 10]) + + # change cost(standard_price) of anglo products + # then set inventory from 10 -> 15 + self.product1.write({'standard_price': 6.0}) + self.product2.write({'standard_price': 6.0}) + self.adjust_inventory([self.product1, self.product2, self.product3], [15, 15, 15]) + + # change cost(standard_price) of anglo products + # then set inventory from 15 -> 25 + self.product1.write({'standard_price': 13.0}) + self.product2.write({'standard_price': 13.0}) + self.adjust_inventory([self.product1, self.product2, self.product3], [25, 25, 25]) + + self.output_account = self.categ_anglo.property_stock_account_output_categ_id + self.expense_account = self.categ_anglo.property_account_expense_categ_id + self.valuation_account = self.categ_anglo.property_stock_valuation_account_id + + def test_01_orders_no_invoiced(self): + """ + + Orders + ====== + +---------+----------+-----+-------------+------------+ + | order | product | qty | total price | total cost | + +---------+----------+-----+-------------+------------+ + | order 1 | product1 | 10 | 100.0 | 50.0 | -> 10 items at cost of 5.0 is consumed, remains 5 items at 6.0 and 10 items at 13.0 + | | product2 | 10 | 200.0 | 100.0 | -> 10 items at cost of 10.0 is consumed, remains 5 items at 6.0 and 10 items at 13.0 + +---------+----------+-----+-------------+------------+ + | order 2 | product2 | 7 | 140.0 | 56.0 | -> 5 items at cost of 6.0 and 2 items at cost of 13.0, remains 8 items at cost of 13.0 + | | product3 | 7 | 210.0 | 0.0 | + +---------+----------+-----+-------------+------------+ + | order 3 | product1 | 6 | 60.0 | 43.0 | -> 5 items at cost of 6.0 and 1 item at cost of 13.0, remains 9 items at cost of 13.0 + | | product2 | 6 | 120.0 | 78.0 | -> 6 items at cost of 13.0, remains 2 items at cost of 13.0 + | | product3 | 6 | 180.0 | 0.0 | + +---------+----------+-----+-------------+------------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale_account | -1010.0 | + | pos_receivable-cash | 1010.0 | + | expense_account | 327.0 | + | output_account | -327.0 | + +---------------------+---------+ + | Total balance | 0.00 | + +---------------------+---------+ + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 10)])) + orders.append(self.create_ui_order_data([(self.product2, 7), (self.product3, 7)])) + orders.append(self.create_ui_order_data([(self.product1, 6), (self.product2, 6), (self.product3, 6)])) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + self.assertAlmostEqual(orders_total, 1010.0, msg='The orders\'s total amount should equal the computed.') + + # check product qty_available after syncing the order + self.assertEqual(self.product1.qty_available, 9) + self.assertEqual(self.product2.qty_available, 2) + self.assertEqual(self.product3.qty_available, 12) + + # picking and stock moves should be in done state + for order in self.pos_session.order_ids: + self.assertEqual(order.picking_id.state, 'done', 'Picking should be in done state.') + self.assertTrue(all(state == 'done' for state in order.picking_id.move_lines.mapped('state')), 'Move Lines should be in done state.' ) + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + account_move = self.pos_session.move_id + + sales_line = account_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sales_line.balance, -orders_total, msg='Sales line balance should be equal to total orders amount.') + + receivable_line_cash = account_move.line_ids.filtered(lambda line: self.pos_receivable_account == line.account_id and self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 1010.0, msg='Cash receivable should be equal to the total cash payments.') + + expense_line = account_move.line_ids.filtered(lambda line: line.account_id == self.expense_account) + self.assertAlmostEqual(expense_line.balance, 327.0) + + output_line = account_move.line_ids.filtered(lambda line: line.account_id == self.output_account) + self.assertAlmostEqual(output_line.balance, -327.0) + + self.assertTrue(receivable_line_cash.full_reconcile_id, msg='Cash receivable line should be fully-reconciled.') + self.assertTrue(output_line.full_reconcile_id, msg='The stock output account line should be fully-reconciled.') + + def test_02_orders_with_invoice(self): + """ + + Orders + ====== + Same with test_01 but order 3 is invoiced. + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale_account | -650.0 | + | pos_receivable-cash | 1010.0 | + | receivable | -360.0 | + | expense_account | 206.0 | + | output_account | -206.0 | + +---------------------+---------+ + | Total balance | 0.00 | + +---------------------+---------+ + """ + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data([(self.product1, 10), (self.product2, 10)])) + orders.append(self.create_ui_order_data([(self.product2, 7), (self.product3, 7)])) + invoiced_uid = self.create_random_uid() + orders.append(self.create_ui_order_data( + [(self.product1, 6), (self.product2, 6), (self.product3, 6)], + is_invoiced=True, + customer=self.customer, + uid=invoiced_uid, + )) + + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + self.assertAlmostEqual(orders_total, 1010.0, msg='The orders\'s total amount should equal the computed.') + + # check product qty_available after syncing the order + self.assertEqual(self.product1.qty_available, 9) + self.assertEqual(self.product2.qty_available, 2) + self.assertEqual(self.product3.qty_available, 12) + + # picking and stock moves should be in done state + for order in self.pos_session.order_ids: + self.assertEqual(order.picking_id.state, 'done', 'Picking should be in done state.') + self.assertTrue(all(state == 'done' for state in order.picking_id.move_lines.mapped('state')), 'Move Lines should be in done state.' ) + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + account_move = self.pos_session.move_id + + sales_line = account_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertAlmostEqual(sales_line.balance, -650.0) + + receivable_line = account_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertAlmostEqual(receivable_line.balance, -360.0, msg='Receivable line balance should equal the negative of total amount of invoiced orders.') + + receivable_line_cash = account_move.line_ids.filtered(lambda line: self.pos_receivable_account == line.account_id and self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 1010.0, msg='Cash receivable should be equal to the total cash payments.') + + expense_line = account_move.line_ids.filtered(lambda line: line.account_id == self.expense_account) + self.assertAlmostEqual(expense_line.balance, 206.0) + + output_line = account_move.line_ids.filtered(lambda line: line.account_id == self.output_account) + self.assertAlmostEqual(output_line.balance, -206.0) + + # check order journal entry + invoiced_order = self.pos_session.order_ids.filtered(lambda order: invoiced_uid in order.pos_reference) + invoiced_output_account_lines = invoiced_order.account_move.line_ids.filtered(lambda line: line.account_id == self.output_account) + self.assertAlmostEqual(sum(invoiced_output_account_lines.mapped('balance')), -121.0) + + self.assertTrue(receivable_line_cash.full_reconcile_id, msg='Cash receivable line should be fully-reconciled.') + self.assertTrue(output_line.full_reconcile_id, msg='The stock output account line should be fully-reconciled.') diff --git a/addons/point_of_sale/tests/test_pos_with_fiscal_position.py b/addons/point_of_sale/tests/test_pos_with_fiscal_position.py new file mode 100644 index 00000000000..b41c001866e --- /dev/null +++ b/addons/point_of_sale/tests/test_pos_with_fiscal_position.py @@ -0,0 +1,379 @@ +import odoo +from odoo.addons.point_of_sale.tests.common import TestPoSCommon + +@odoo.tests.tagged('post_install', '-at_install') +class TestPoSWithFiscalPosition(TestPoSCommon): + """ Tests to pos orders with fiscal position. + + keywords/phrases: fiscal position + """ + + def setUp(self): + super(TestPoSWithFiscalPosition, self).setUp() + self.config = self.basic_config + + self.new_tax_17 = self.env['account.tax'].create({'name': 'New Tax 17%', 'amount': 17}) + self.new_tax_17.invoice_repartition_line_ids.write({'account_id': self.tax_received_account.id}) + + self.fpos = self._create_fiscal_position() + self.fpos_no_tax_dest = self._create_fiscal_position_no_tax_dest() + + self.product1 = self.create_product( + 'Product 1', + self.categ_basic, + lst_price=10.99, + standard_price=5.0, + tax_ids=self.taxes['tax7'].ids, + ) + self.product2 = self.create_product( + 'Product 2', + self.categ_basic, + lst_price=19.99, + standard_price=10.0, + tax_ids=self.taxes['tax10'].ids, + ) + self.product3 = self.create_product( + 'Product 3', + self.categ_basic, + lst_price=30.99, + standard_price=15.0, + tax_ids=self.taxes['tax7'].ids, + ) + self.adjust_inventory([self.product1, self.product2, self.product3], [100, 50, 50]) + + def _create_fiscal_position(self): + fpos = self.env['account.fiscal.position'].create({'name': 'Test Fiscal Position'}) + + account_fpos = self.env['account.fiscal.position.account'].create({ + 'position_id': fpos.id, + 'account_src_id': self.sale_account.id, + 'account_dest_id': self.other_sale_account.id, + }) + tax_fpos = self.env['account.fiscal.position.tax'].create({ + 'position_id': fpos.id, + 'tax_src_id': self.taxes['tax7'].id, + 'tax_dest_id': self.new_tax_17.id, + }) + fpos.write({ + 'account_ids': [(6, 0, account_fpos.ids)], + 'tax_ids': [(6, 0, tax_fpos.ids)], + }) + return fpos + + def _create_fiscal_position_no_tax_dest(self): + fpos_no_tax_dest = self.env['account.fiscal.position'].create({'name': 'Test Fiscal Position'}) + account_fpos = self.env['account.fiscal.position.account'].create({ + 'position_id': fpos_no_tax_dest.id, + 'account_src_id': self.sale_account.id, + 'account_dest_id': self.other_sale_account.id, + }) + tax_fpos = self.env['account.fiscal.position.tax'].create({ + 'position_id': fpos_no_tax_dest.id, + 'tax_src_id': self.taxes['tax7'].id, + }) + fpos_no_tax_dest.write({ + 'account_ids': [(6, 0, account_fpos.ids)], + 'tax_ids': [(6, 0, tax_fpos.ids)], + }) + return fpos_no_tax_dest + + def test_01_no_invoice_fpos(self): + """ orders without invoice + + Orders + ====== + +---------+----------+---------------+----------+-----+---------+-----------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+---------------+----------+-----+---------+-----------------+--------+ + | order 1 | cash | yes, customer | product1 | 10 | 109.90 | 18.68 [7%->17%] | 128.58 | + | | | | product2 | 10 | 181.73 | 18.17 [10%] | 199.90 | + | | | | product3 | 10 | 309.90 | 52.68 [7%->17%] | 362.58 | + +---------+----------+---------------+----------+-----+---------+-----------------+--------+ + | order 2 | cash | yes, customer | product1 | 5 | 54.95 | 9.34 [7%->17%] | 64.29 | + | | | | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + +---------+----------+---------------+----------+-----+---------+-----------------+--------+ + | order 3 | bank | no | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + | | | | product3 | 5 | 154.95 | 10.85 [7%] | 165.8 | + +---------+----------+---------------+----------+-----+---------+-----------------+--------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale_account | -154.95 | (for the 7% base amount) + | sale_account | -90.86 | (for the 10% base amount) + | other_sale_account | -474.75 | (for the 17% base amount) + | other_sale_account | -272.59 | (for the 10% base amount) + | tax 17% | -80.70 | + | tax 10% | -36.35 | + | tax 7% | -10.85 | + | pos receivable bank | 265.75 | + | pos receivable cash | 855.30 | + +---------------------+---------+ + | Total balance | 0.0 | + +---------------------+---------+ + """ + self.customer.write({'property_account_position_id': self.fpos.id}) + self.open_new_session() + + # create orders + orders = [] + orders.append(self.create_ui_order_data( + [(self.product1, 10), (self.product2, 10), (self.product3, 10)], + customer=self.customer + )) + orders.append(self.create_ui_order_data( + [(self.product1, 5), (self.product2, 5)], + customer=self.customer, + )) + orders.append(self.create_ui_order_data( + [(self.product2, 5), (self.product3, 5)], + payments=[(self.bank_pm, 265.75)], + )) + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + sale_account_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + lines_balance = [-154.95, -90.86] + self.assertEqual(len(sale_account_lines), len(lines_balance)) + for balance, amount in zip(sorted(sale_account_lines.mapped('balance')), sorted(lines_balance)): + self.assertAlmostEqual(balance, amount) + + other_sale_account_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.other_sale_account) + lines_balance = [-474.75, -272.59] + self.assertEqual(len(other_sale_account_lines), len(lines_balance)) + for balance, amount in zip(sorted(other_sale_account_lines.mapped('balance')), sorted(lines_balance)): + self.assertAlmostEqual(balance, amount) + + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(receivable_line_bank.balance, 265.75) + + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 855.3) + + manually_calculated_taxes = (-80.7, -36.35, -10.85) + tax_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.tax_received_account) + self.assertAlmostEqual(len(manually_calculated_taxes), len(tax_lines.mapped('balance'))) + for t1, t2 in zip(sorted(manually_calculated_taxes), sorted(tax_lines.mapped('balance'))): + self.assertAlmostEqual(t1, t2, msg='Taxes should be correctly combined.') + + self.assertTrue(receivable_line_cash.full_reconcile_id) + + def test_02_no_invoice_fpos_no_tax_dest(self): + """ Customer with fiscal position that maps a tax to no tax. + + Orders + ====== + +---------+----------+---------------+----------+-----+---------+-------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+---------------+----------+-----+---------+-------------+--------+ + | order 1 | bank | yes, customer | product1 | 10 | 109.90 | 0 | 109.90 | + | | | | product2 | 10 | 181.73 | 18.17 [10%] | 199.90 | + | | | | product3 | 10 | 309.90 | 0 | 309.90 | + +---------+----------+---------------+----------+-----+---------+-------------+--------+ + | order 2 | cash | yes, customer | product1 | 5 | 54.95 | 0 | 54.95 | + | | | | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + +---------+----------+---------------+----------+-----+---------+-------------+--------+ + | order 3 | bank | no | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + | | | | product3 | 5 | 154.95 | 10.85 [7%] | 165.80 | + +---------+----------+---------------+----------+-----+---------+-------------+--------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | sale_account | -154.95 | (for the 7% base amount) + | sale_account | -90.86 | (for the 10% base amount) + | other_sale_account | -272.59 | (for the 10% base amount) + | other_sale_account | -474.75 | (no tax) + | tax 10% | -36.35 | + | tax 7% | -10.85 | + | pos receivable bank | 885.45 | + | pos receivable cash | 154.9 | + +---------------------+---------+ + | Total balance | 0.0 | + +---------------------+---------+ + """ + self.customer.write({'property_account_position_id': self.fpos_no_tax_dest.id}) + self.open_new_session() + # create orders + orders = [] + orders.append(self.create_ui_order_data( + [(self.product1, 10), (self.product2, 10), (self.product3, 10)], + customer=self.customer, + payments=[(self.bank_pm, 619.7)], + )) + orders.append(self.create_ui_order_data( + [(self.product1, 5), (self.product2, 5)], + customer=self.customer, + )) + orders.append(self.create_ui_order_data( + [(self.product2, 5), (self.product3, 5)], + payments=[(self.bank_pm, 265.75)], + )) + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + sale_account_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + lines_balance = [-154.95, -90.86] + self.assertEqual(len(sale_account_lines), len(lines_balance)) + for balance, amount in zip(sorted(sale_account_lines.mapped('balance')), sorted(lines_balance)): + self.assertAlmostEqual(balance, amount) + + other_sale_account_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.other_sale_account) + lines_balance = [-474.75, -272.59] + self.assertEqual(len(other_sale_account_lines), len(lines_balance)) + for balance, amount in zip(sorted(other_sale_account_lines.mapped('balance')), sorted(lines_balance)): + self.assertAlmostEqual(balance, amount) + + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(receivable_line_bank.balance, 885.45) + + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 154.9) + + manually_calculated_taxes = [-36.35, -10.85] + tax_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.tax_received_account) + self.assertAlmostEqual(len(manually_calculated_taxes), len(tax_lines.mapped('balance'))) + for t1, t2 in zip(sorted(manually_calculated_taxes), sorted(tax_lines.mapped('balance'))): + self.assertAlmostEqual(t1, t2, msg='Taxes should be correctly combined.') + + self.assertTrue(receivable_line_cash.full_reconcile_id) + + def test_03_invoiced_fpos(self): + """ Invoice 2 orders. + + Orders + ====== + +---------+----------+---------------------+----------+-----+---------+-----------------+--------+ + | order | payments | invoiced? | product | qty | untaxed | tax | total | + +---------+----------+---------------------+----------+-----+---------+-----------------+--------+ + | order 1 | bank | yes, customer | product1 | 10 | 109.90 | 18.68 [7%->17%] | 128.58 | + | | | | product2 | 10 | 181.73 | 18.17 [10%] | 199.90 | + | | | | product3 | 10 | 309.90 | 52.68 [7%->17%] | 362.58 | + +---------+----------+---------------------+----------+-----+---------+-----------------+--------+ + | order 2 | cash | no, customer | product1 | 5 | 54.95 | 9.34 [7%->17%] | 64.29 | + | | | | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + +---------+----------+---------------------+----------+-----+---------+-----------------+--------+ + | order 3 | cash | yes, other_customer | product2 | 5 | 90.86 | 9.09 [10%] | 99.95 | + | | | | product3 | 5 | 154.95 | 10.85 [7%] | 165.80 | + +---------+----------+---------------------+----------+-----+---------+-----------------+--------+ + + Expected Result + =============== + +---------------------+---------+ + | account | balance | + +---------------------+---------+ + | other_sale_account | -54.95 | (for the 17% base amount) + | other_sale_account | -90.86 | (for the 10% base amount) + | tax 10% | -9.09 | + | tax 17% | -9.34 | + | pos receivable cash | 429.99 | + | pos receivable bank | 691.06 | + | receivable | -691.06 | + | other receivable | -265.75 | + +---------------------+---------+ + | Total balance | 0.0 | + +---------------------+---------+ + """ + self.customer.write({'property_account_position_id': self.fpos.id}) + self.open_new_session() + # create orders + orders = [] + uid1 = self.create_random_uid() + orders.append(self.create_ui_order_data( + [(self.product1, 10), (self.product2, 10), (self.product3, 10)], + customer=self.customer, + payments=[(self.bank_pm, 691.06)], + is_invoiced=True, + uid=uid1 + )) + orders.append(self.create_ui_order_data( + [(self.product1, 5), (self.product2, 5)], + customer=self.customer, + )) + uid2 = self.create_random_uid() + orders.append(self.create_ui_order_data( + [(self.product2, 5), (self.product3, 5)], + customer=self.other_customer, + is_invoiced=True, + uid=uid2, + )) + # sync orders + order = self.env['pos.order'].create_from_ui(orders) + + # check values before closing the session + self.assertEqual(3, self.pos_session.order_count) + orders_total = sum(order.amount_total for order in self.pos_session.order_ids) + self.assertAlmostEqual(orders_total, self.pos_session.total_payments_amount, msg='Total order amount should be equal to the total payment amount.') + + invoiced_order_1 = self.pos_session.order_ids.filtered(lambda order: uid1 in order.pos_reference) + invoiced_order_2 = self.pos_session.order_ids.filtered(lambda order: uid2 in order.pos_reference) + + self.assertTrue(invoiced_order_1, msg='Invoiced order 1 should exist.') + self.assertTrue(invoiced_order_2, msg='Invoiced order 2 should exist.') + self.assertTrue(invoiced_order_1.account_move, msg='Invoiced order 1 should have invoice (account_move).') + self.assertTrue(invoiced_order_2.account_move, msg='Invoiced order 2 should have invoice (account_move).') + + # NOTE Tests of values in the invoice accounting lines is not done here. + + # close the session + self.pos_session.action_pos_session_validate() + + # check values after the session is closed + session_move = self.pos_session.move_id + + sale_account_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.sale_account) + self.assertFalse(sale_account_lines, msg='There should be no self.sale_account lines.') + + other_sale_account_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.other_sale_account) + lines_balance = [-54.95, -90.86] + self.assertEqual(len(other_sale_account_lines), len(lines_balance)) + for balance, amount in zip(sorted(other_sale_account_lines.mapped('balance')), sorted(lines_balance)): + self.assertAlmostEqual(balance, amount) + + receivable_line_bank = session_move.line_ids.filtered(lambda line: self.bank_pm.name in line.name) + self.assertAlmostEqual(receivable_line_bank.balance, 691.06) + + receivable_line_cash = session_move.line_ids.filtered(lambda line: self.cash_pm.name in line.name) + self.assertAlmostEqual(receivable_line_cash.balance, 429.99) + + manually_calculated_taxes = [-9.09, -9.34] + tax_lines = session_move.line_ids.filtered(lambda line: line.account_id == self.tax_received_account) + self.assertAlmostEqual(len(manually_calculated_taxes), len(tax_lines.mapped('balance'))) + for t1, t2 in zip(sorted(manually_calculated_taxes), sorted(tax_lines.mapped('balance'))): + self.assertAlmostEqual(t1, t2, msg='Taxes should be correctly combined.') + + receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.receivable_account) + self.assertAlmostEqual(receivable_line.balance, -691.06, msg='That is not the correct receivable line balance.') + + other_receivable_line = session_move.line_ids.filtered(lambda line: line.account_id == self.other_receivable_account) + self.assertAlmostEqual(other_receivable_line.balance, -265.75, msg='That is not the correct other receivable line balance.') + + self.assertTrue(receivable_line_cash.full_reconcile_id) + self.assertTrue(receivable_line.full_reconcile_id) + self.assertTrue(other_receivable_line.full_reconcile_id) |
