summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Caburnay <jcb@odoo.com>2019-08-08 08:21:00 +0000
committerPierre Masereel <pim@odoo.com>2019-08-09 14:45:44 +0000
commit8becb17b8a3aa18b92629c4ddd33630ea4b1444e (patch)
treef03ccc427aa9f78d3d19dbbfb0c6c6f14eaf66b6
parentb234e97f87c577a7cd85230c4f42277b39e8c842 (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__.py8
-rw-r--r--addons/point_of_sale/tests/common.py398
-rw-r--r--addons/point_of_sale/tests/test_anglo_saxon.py38
-rw-r--r--addons/point_of_sale/tests/test_frontend.py18
-rw-r--r--addons/point_of_sale/tests/test_point_of_sale_flow.py108
-rw-r--r--addons/point_of_sale/tests/test_pos_basic_config.py423
-rw-r--r--addons/point_of_sale/tests/test_pos_multiple_receivable_accounts.py199
-rw-r--r--addons/point_of_sale/tests/test_pos_multiple_sale_accounts.py220
-rw-r--r--addons/point_of_sale/tests/test_pos_other_currency_config.py170
-rw-r--r--addons/point_of_sale/tests/test_pos_products_with_tax.py286
-rw-r--r--addons/point_of_sale/tests/test_pos_setup.py69
-rw-r--r--addons/point_of_sale/tests/test_pos_stock_account.py194
-rw-r--r--addons/point_of_sale/tests/test_pos_with_fiscal_position.py379
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)