19.0 vanilla

This commit is contained in:
Ernad Husremovic 2026-03-09 09:32:12 +01:00
parent 79f83631d5
commit 73afc09215
6267 changed files with 1534193 additions and 1130106 deletions

View file

@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import common_setup_methods
from . import test_point_of_sale_flow
from . import test_frontend
from . import test_performances
from . import test_point_of_sale_ui
from . import test_anglo_saxon
from . import test_continental
from . import test_point_of_sale
from . import test_pos_capture
from . import test_pos_controller
from . import test_pos_invoice_consolidation
from . import test_pos_cash_rounding
from . import test_pos_setup
from . import test_pos_simple_orders
from . import test_pos_simple_invoiced_orders
@ -18,6 +21,8 @@ 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
from . import test_js
from . import test_report_pos_order
from . import test_report_session
from . import test_res_config_settings
from . import test_pos_product_variants
from . import test_generic_localization

View file

@ -1,134 +1,363 @@
# -*- coding: utf-8 -*-
import logging
from random import randint
from datetime import datetime
from odoo import fields, tools
from odoo.fields import Command
from odoo.tests import Form
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
from odoo.tests.common import Form
from odoo.tests import tagged
import logging
_logger = logging.getLogger(__name__)
class TestPointOfSaleCommon(ValuationReconciliationTestCommon):
def archive_products(env):
# Archive all existing product to avoid noise during the tours
all_pos_product = env['product.template'].search([('available_in_pos', '=', True)])
tip = env.ref('point_of_sale.product_product_tip').product_tmpl_id
(all_pos_product - tip)._write({'active': False})
class CommonPosTest(ValuationReconciliationTestCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
def setUpClass(self):
super().setUpClass()
archive_products(self.env)
cls.company_data['company'].write({
'point_of_sale_update_stock_quantities': 'real',
self.env.user.group_ids += self.env.ref('point_of_sale.group_pos_manager')
self.env.ref('base.EUR').active = True
self.env.ref('base.USD').active = True
self.create_res_partners(self)
self.create_account_cash_rounding(self)
self.create_pos_categories(self)
self.create_account_taxes(self)
self.create_product_templates(self)
self.create_payment_methods(self)
self.create_pos_configs(self)
def create_pos_configs(self):
sale_journal_eur = 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,
})
self.pricelist_eur = self.env['product.pricelist'].create({
'name': 'Test EUR Pricelist',
'currency_id': self.env.ref('base.EUR').id
})
self.pos_config_eur = self.env['pos.config'].create({
'name': 'PoS Config EUR',
'journal_id': sale_journal_eur.id,
'use_pricelist': True,
'available_pricelist_ids': [(6, 0, self.pricelist_eur.ids)],
'pricelist_id': self.pricelist_eur.id,
'payment_method_ids': [(6, 0, self.bank_payment_method.ids)]
})
self.pos_config_usd = self.env['pos.config'].create({
'name': 'PoS Config USD',
'journal_id': self.company_data['default_journal_sale'].id,
'invoice_journal_id': self.company_data['default_journal_sale'].id,
'payment_method_ids': [
(4, self.credit_payment_method.id),
(4, self.bank_payment_method.id),
(4, self.cash_payment_method.id),
]
})
cls.AccountBankStatement = cls.env['account.bank.statement']
cls.AccountBankStatementLine = cls.env['account.bank.statement.line']
cls.PosMakePayment = cls.env['pos.make.payment']
cls.PosOrder = cls.env['pos.order']
cls.PosSession = cls.env['pos.session']
cls.company = cls.company_data['company']
cls.product3 = cls.env['product.product'].create({
'name': 'Product 3',
'list_price': 450,
def create_res_partners(self):
self.partner_mobt = self.env['res.partner'].create({
'name': 'MOBT',
})
cls.product4 = cls.env['product.product'].create({
'name': 'Product 4',
'list_price': 750,
self.partner_adgu = self.env['res.partner'].create({
'name': 'ADGU',
})
cls.partner1 = cls.env['res.partner'].create({'name': 'Partner 1'})
cls.partner4 = cls.env['res.partner'].create({'name': 'Partner 4'})
cls.pos_config = cls.env['pos.config'].create({
'name': 'Main',
'journal_id': cls.company_data['default_journal_sale'].id,
'invoice_journal_id': cls.company_data['default_journal_sale'].id,
self.partner_lowe = self.env['res.partner'].create({
'name': 'LOWE',
})
cls.led_lamp = cls.env['product.product'].create({
'name': 'LED Lamp',
'available_in_pos': True,
'list_price': 0.90,
self.partner_jcb = self.env['res.partner'].create({
'name': 'JCB',
})
cls.whiteboard_pen = cls.env['product.product'].create({
'name': 'Whiteboard Pen',
'available_in_pos': True,
'list_price': 1.20,
self.partner_moda = self.env['res.partner'].create({
'name': 'MODA',
})
cls.newspaper_rack = cls.env['product.product'].create({
'name': 'Newspaper Rack',
'available_in_pos': True,
'list_price': 1.28,
self.partner_stva = self.env['res.partner'].create({
'name': 'STVA',
})
cls.cash_payment_method = cls.env['pos.payment.method'].create({
self.partner_manv = self.env['res.partner'].create({
'name': 'MANV',
})
self.partner_vlst = self.env['res.partner'].create({
'name': 'VLST',
})
def create_account_cash_rounding(self):
self.account_cash_rounding_down = self.env['account.cash.rounding'].create({
'name': 'Rounding down',
'rounding': 0.05,
'rounding_method': 'DOWN',
'profit_account_id': self.company_data['default_account_revenue'].id,
'loss_account_id': self.company_data['default_account_expense'].id,
})
self.account_cash_rounding_up = self.env['account.cash.rounding'].create({
'name': 'Rounding up',
'rounding': 0.05,
'rounding_method': 'UP',
'profit_account_id': self.company_data['default_account_revenue'].id,
'loss_account_id': self.company_data['default_account_expense'].id,
})
self.account_cash_rounding_half = self.env['account.cash.rounding'].create({
'name': 'Rounding half',
'rounding': 0.05,
'profit_account_id': self.company_data['default_account_revenue'].id,
'loss_account_id': self.company_data['default_account_expense'].id,
})
def create_payment_methods(self):
self.cash_payment_method = self.env['pos.payment.method'].create({
'name': 'Cash',
'receivable_account_id': cls.company_data['default_account_receivable'].id,
'journal_id': cls.company_data['default_journal_cash'].id,
'company_id': cls.env.company.id,
'receivable_account_id': self.company_data['default_account_receivable'].id,
'journal_id': self.company_data['default_journal_cash'].id,
})
cls.bank_payment_method = cls.env['pos.payment.method'].create({
self.bank_payment_method = self.env['pos.payment.method'].create({
'name': 'Bank',
'journal_id': cls.company_data['default_journal_bank'].id,
'receivable_account_id': cls.company_data['default_account_receivable'].id,
'company_id': cls.env.company.id,
'journal_id': self.company_data['default_journal_bank'].id,
'receivable_account_id': self.company_data['default_account_receivable'].id,
})
cls.credit_payment_method = cls.env['pos.payment.method'].create({
self.credit_payment_method = self.env['pos.payment.method'].create({
'name': 'Credit',
'receivable_account_id': cls.company_data['default_account_receivable'].id,
'receivable_account_id': self.company_data['default_account_receivable'].id,
'split_transactions': True,
'company_id': cls.env.company.id,
})
cls.pos_config.write({'payment_method_ids': [(4, cls.credit_payment_method.id), (4, cls.bank_payment_method.id), (4, cls.cash_payment_method.id)]})
# Create POS journal
cls.pos_config.journal_id = cls.env['account.journal'].create({
'type': 'general',
'name': 'Point of Sale - Test',
'code': 'POSS - Test',
'company_id': cls.env.company.id,
'sequence': 20
})
# create a VAT tax of 10%, included in the public price
Tax = cls.env['account.tax']
account_tax_10_incl = Tax.create({
'name': 'VAT 10 perc Incl',
'amount_type': 'percent',
'amount': 10.0,
'price_include': True,
def create_pos_categories(self):
self.cat_no_tax = self.env['pos.category'].create({
'name': 'No tax',
'sequence': 0,
})
self.cat_tax_five_incl = self.env['pos.category'].create({
'name': 'Tax five incl',
'sequence': 1,
})
self.cat_tax_ten_incl = self.env['pos.category'].create({
'name': 'Tax ten incl',
'sequence': 2,
})
self.cat_tax_fiften_incl = self.env['pos.category'].create({
'name': 'Tax fifteen incl',
'sequence': 3,
})
self.cat_tax_five_excl = self.env['pos.category'].create({
'name': 'Tax five excl',
'sequence': 4,
})
self.cat_tax_ten_excl = self.env['pos.category'].create({
'name': 'Tax ten excl',
'sequence': 5,
})
self.cat_tax_fiften_excl = self.env['pos.category'].create({
'name': 'Tax fifteen excl',
'sequence': 6,
})
# assign this 10 percent tax on the [PCSC234] PC Assemble SC234 product
# as a sale tax
cls.product3.taxes_id = [(6, 0, [account_tax_10_incl.id])]
# create a VAT tax of 5%, which is added to the public price
account_tax_05_incl = Tax.create({
'name': 'VAT 5 perc Incl',
'amount_type': 'percent',
'amount': 5.0,
'price_include': False,
def create_account_taxes(self):
self.tax_five_incl = self.env['account.tax'].create({
'name': 'Tax five incl',
'amount': 5,
'price_include_override': 'tax_included',
})
self.tax_ten_incl = self.env['account.tax'].create({
'name': 'Tax ten incl',
'amount': 10,
'price_include_override': 'tax_included',
})
self.tax_fiften_incl = self.env['account.tax'].create({
'name': 'Tax fifteen incl',
'amount': 15,
'price_include_override': 'tax_included',
})
self.tax_five_excl = self.env['account.tax'].create({
'name': 'Tax five excl',
'amount': 5,
})
self.tax_ten_excl = self.env['account.tax'].create({
'name': 'Tax ten excl',
'amount': 10,
})
self.tax_fiften_excl = self.env['account.tax'].create({
'name': 'Tax fifteen excl',
'amount': 15,
})
# create a second VAT tax of 5% but this time for a child company, to
# ensure that only product taxes of the current session's company are considered
#(this tax should be ignore when computing order's taxes in following tests)
account_tax_05_incl_chicago = Tax.create({
'name': 'VAT 05 perc Excl (US)',
'amount_type': 'percent',
'amount': 5.0,
'price_include': False,
'company_id': cls.company_data_2['company'].id,
def create_product_templates(self):
self.ten_dollars_no_tax = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Ten dollars no tax',
'list_price': 10.0,
'pos_categ_ids': [(6, 0, [self.cat_no_tax.id])],
'taxes_id': [(5, 0)],
})
self.twenty_dollars_no_tax = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Twenty dollars no tax',
'list_price': 20.0,
'pos_categ_ids': [(6, 0, [self.cat_no_tax.id])],
'taxes_id': [(5, 0)],
})
self.ten_dollars_with_5_incl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Ten dollars with 5 included',
'list_price': 10.0,
'taxes_id': [(6, 0, [self.tax_five_incl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_five_incl.id])],
})
self.twenty_dollars_with_5_incl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Twenty dollars with 5 included',
'list_price': 20.0,
'taxes_id': [(6, 0, [self.tax_five_incl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_five_incl.id])],
})
self.ten_dollars_with_10_incl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Ten dollars with 10 included',
'list_price': 10.0,
'taxes_id': [(6, 0, [self.tax_ten_incl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_ten_incl.id])],
})
self.twenty_dollars_with_10_incl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Twenty dollars with 10 included',
'list_price': 20.0,
'taxes_id': [(6, 0, [self.tax_ten_incl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_ten_incl.id])],
})
self.ten_dollars_with_15_incl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Ten dollars with 15 included',
'list_price': 10.0,
'taxes_id': [(6, 0, [self.tax_fiften_incl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_fiften_incl.id])],
})
self.twenty_dollars_with_15_incl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Twenty dollars with 15 included',
'list_price': 20.0,
'taxes_id': [(6, 0, [self.tax_fiften_incl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_fiften_incl.id])],
})
self.ten_dollars_with_5_excl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Ten dollars with 5 excluded',
'list_price': 10.0,
'taxes_id': [(6, 0, [self.tax_five_excl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_five_excl.id])],
})
self.twenty_dollars_with_5_excl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Twenty dollars with 5 excluded',
'list_price': 20.0,
'taxes_id': [(6, 0, [self.tax_five_excl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_five_excl.id])],
})
self.ten_dollars_with_10_excl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Ten dollars with 10 excluded',
'list_price': 10.0,
'taxes_id': [(6, 0, [self.tax_ten_excl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_ten_excl.id])],
})
self.twenty_dollars_with_10_excl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Twenty dollars with 10 excluded',
'list_price': 20.0,
'taxes_id': [(6, 0, [self.tax_ten_excl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_ten_excl.id])],
})
self.ten_dollars_with_15_excl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Ten dollars with 15 excluded',
'list_price': 10.0,
'taxes_id': [(6, 0, [self.tax_fiften_excl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_fiften_excl.id])],
})
self.twenty_dollars_with_15_excl = self.env['product.template'].create({
'available_in_pos': True,
'name': 'Twenty dollars with 15 excluded',
'list_price': 20.0,
'taxes_id': [(6, 0, [self.tax_fiften_excl.id])],
'pos_categ_ids': [(6, 0, [self.cat_tax_fiften_excl.id])],
})
cls.product4.company_id = False
# I assign those 5 percent taxes on the PCSC349 product as a sale taxes
cls.product4.write(
{'taxes_id': [(6, 0, [account_tax_05_incl.id, account_tax_05_incl_chicago.id])]})
def create_backend_pos_order(self, data):
pos_config = data.get('pos_config', self.pos_config_usd)
order_data = data.get('order_data', {})
line_product_ids = [line_data['product_id'] for line_data in data.get('line_data', [])]
product_by_id = {p.id: p for p in self.env['product.product'].browse(line_product_ids)}
refund = False
# Set account_id in the generated repartition lines. Automatically, nothing is set.
invoice_rep_lines = (account_tax_05_incl | account_tax_10_incl).mapped('invoice_repartition_line_ids')
refund_rep_lines = (account_tax_05_incl | account_tax_10_incl).mapped('refund_repartition_line_ids')
if not pos_config.current_session_id:
pos_config.open_ui()
# Expense account, should just be something else than receivable/payable
(invoice_rep_lines | refund_rep_lines).write({'account_id': cls.company_data['default_account_tax_sale'].id})
order = self.env['pos.order'].create({
'amount_total': 0,
'amount_paid': 0,
'amount_tax': 0,
'amount_return': 0,
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
'company_id': self.env.company.id,
'session_id': pos_config.current_session_id.id,
'lines': [
Command.create({
'price_unit': product_by_id[line_data['product_id']].lst_price,
'price_subtotal': product_by_id[line_data['product_id']].lst_price,
'tax_ids': [(6, 0, product_by_id[line_data['product_id']].taxes_id.ids)],
'price_subtotal_incl': 0,
**line_data,
}) for line_data in data.get('line_data', [])
],
**order_data,
})
# Re-trigger prices computation
order.lines._onchange_amount_line_all()
order._compute_prices()
if data.get('payment_data'):
payment_context = {"active_ids": order.ids, "active_id": order.id}
for payment in data['payment_data']:
make_payment = {'payment_method_id': payment['payment_method_id']}
if payment.get('amount'):
make_payment['amount'] = payment['amount']
order_payment = self.env['pos.make.payment'].with_context(**payment_context).create(make_payment)
order_payment.with_context(**payment_context).check()
if data.get('refund_data'):
refund_action = order.refund()
refund = self.env['pos.order'].browse(refund_action['res_id'])
payment_context = {"active_ids": refund.ids, "active_id": refund.id}
if data.get('order_data') and data['order_data'].get('to_invoice', False):
refund.to_invoice = True
for refund_data in data['refund_data']:
make_refund = {'payment_method_id': refund_data['payment_method_id']}
if refund_data.get('amount'):
make_refund['amount'] = refund_data['amount']
refund_payment = self.env['pos.make.payment'].with_context(**payment_context).create(make_refund)
refund_payment.with_context(**payment_context).check()
return order, refund
def compute_tax(self, product, price, qty=1, taxes=None, pos_config=None):
config = pos_config or self.pos_config_usd
if not taxes:
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id)
currency = config.currency_id
res = taxes.compute_all(price, currency, qty, product=product)
untax = res['total_excluded']
return untax, sum(tax.get('amount', 0.0) for tax in res['taxes'])
class TestPoSCommon(ValuationReconciliationTestCommon):
@ -140,8 +369,9 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
"""
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
def setUpClass(cls):
super().setUpClass()
cls.env.user.group_ids |= cls.env.ref('point_of_sale.group_pos_manager')
cls.company_data['company'].write({
'point_of_sale_update_stock_quantities': 'real',
@ -152,14 +382,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
})
# Set basic defaults
cls.company = cls.company_data['company']
cls.pos_sale_journal = cls.env['account.journal'].create({
'type': 'general',
'name': 'Point of Sale Test',
'code': 'POSS',
'company_id': cls.company.id,
'sequence': 20
})
cls.account_tax_return_journal = cls.company_data['default_tax_return_journal']
cls.sales_account = cls.company_data['default_account_revenue']
cls.invoice_journal = cls.company_data['default_journal_sale']
cls.receivable_account = cls.company_data['default_account_receivable']
@ -173,7 +396,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
cls.pos_receivable_account = cls.company.account_default_pos_receivable_account_id
cls.pos_receivable_cash = cls.copy_account(cls.company.account_default_pos_receivable_account_id, {'name': 'POS Receivable Cash'})
cls.pos_receivable_bank = cls.copy_account(cls.company.account_default_pos_receivable_account_id, {'name': 'POS Receivable Bank'})
cls.outstanding_bank = cls.copy_account(cls.company.account_journal_payment_debit_account_id, {'name': 'Outstanding Bank'})
cls.outstanding_bank = cls.copy_account(cls.inbound_payment_method_line.payment_account_id, {'name': 'Outstanding Bank'})
cls.c1_receivable = cls.copy_account(cls.receivable_account, {'name': 'Customer 1 Receivable'})
cls.other_receivable_account = cls.env['account.account'].create({
'name': 'Other Receivable',
@ -187,7 +410,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
cls.company_currency = cls.company.currency_id
# other_currency is a currency different from the company_currency
# sometimes company_currency is different from USD, so handle appropriately.
cls.other_currency = cls.currency_data['currency']
cls.other_currency = cls.setup_other_currency("EUR", rounding=0.001)
cls.currency_pricelist = cls.env['product.pricelist'].create({
'name': 'Public Pricelist',
@ -203,18 +426,18 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
# Set product categories
# categ_basic
# - just the plain 'product.product_category_all'
# - just the plain 'product.product_category_services'
# categ_anglo
# - product category with fifo and real_time valuations
# - used for checking anglo saxon accounting behavior
cls.categ_basic = cls.env.ref('product.product_category_all')
cls.categ_basic = cls.env.ref('product.product_category_services')
cls.env.company.anglo_saxon_accounting = True
cls.categ_anglo = cls._create_categ_anglo()
# other basics
cls.sale_account = cls.categ_basic.property_account_income_categ_id
cls.sale_account = cls.company.income_account_id
cls.other_sale_account = cls.env['account.account'].search([
('company_id', '=', cls.company.id),
('company_ids', '=', cls.company.id),
('account_type', '=', 'income'),
('id', '!=', cls.sale_account.id)
], limit=1)
@ -243,16 +466,20 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
config = cls.env['pos.config'].create({
'name': 'PoS Shop Test',
'invoice_journal_id': cls.invoice_journal.id,
'journal_id': cls.pos_sale_journal.id,
'available_pricelist_ids': cls.currency_pricelist.ids,
'pricelist_id': cls.currency_pricelist.id,
})
cls.cash_pm1 = cls.env['pos.payment.method'].create({
'name': 'Cash',
'journal_id': cls.company_data['default_journal_cash'].id,
'receivable_account_id': cls.pos_receivable_cash.id,
'company_id': cls.env.company.id,
})
cls.company_data['default_journal_cash'].pos_payment_method_ids.unlink()
cls.cash_pm1 = config.payment_method_ids.filtered(lambda c: c.journal_id.type == 'cash')
if cls.cash_pm1:
cls.cash_pm1.write({'receivable_account_id': cls.pos_receivable_cash.id})
else:
cls.cash_pm1 = cls.env['pos.payment.method'].create({
'name': 'Cash',
'journal_id': cls.company_data['default_journal_cash'].id,
'receivable_account_id': cls.pos_receivable_cash.id,
'company_id': cls.env.company.id,
})
cls.bank_pm1 = cls.env['pos.payment.method'].create({
'name': 'Bank',
'journal_id': cls.company_data['default_journal_bank'].id,
@ -263,6 +490,11 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
cls.cash_split_pm1 = cls.cash_pm1.copy(default={
'name': 'Split (Cash) PM',
'split_transactions': True,
'journal_id': cls.env['account.journal'].create({
'name': "Cash",
'code': "CSH %s" % config.id,
'type': 'cash',
}).id
})
cls.bank_split_pm1 = cls.bank_pm1.copy(default={
'name': 'Split (Bank) PM',
@ -346,8 +578,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
'parent_id': False,
'property_cost_method': 'fifo',
'property_valuation': 'real_time',
'property_stock_account_input_categ_id': cls.company_data['default_account_stock_in'].id,
'property_stock_account_output_categ_id': cls.company_data['default_account_stock_out'].id,
'property_stock_valuation_account_id': cls.company_data['default_account_stock_valuation'].copy().id
})
@classmethod
@ -370,11 +601,11 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
cls.tax_tag_refund_base = create_tag('Refund Base tag')
cls.tax_tag_refund_tax = create_tag('Refund Tax tag')
def create_tax(percentage, price_include=False, include_base_amount=False):
def create_tax(percentage, price_include_override='tax_excluded', include_base_amount=False):
return cls.env['account.tax'].create({
'name': f'Tax {percentage}%',
'amount': percentage,
'price_include': price_include,
'price_include_override': price_include_override,
'amount_type': 'percent',
'include_base_amount': include_base_amount,
'invoice_repartition_line_ids': [
@ -400,12 +631,13 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
}),
],
})
def create_tax_fixed(amount, price_include=False):
def create_tax_fixed(amount, price_include_override='tax_excluded', include_base_amount=False):
return cls.env['account.tax'].create({
'name': f'Tax fixed amount {amount}',
'amount': amount,
'price_include': price_include,
'include_base_amount': price_include,
'price_include_override': price_include_override,
'include_base_amount': include_base_amount,
'amount_type': 'fixed',
'invoice_repartition_line_ids': [
(0, 0, {
@ -431,13 +663,13 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
],
})
tax_fixed006 = create_tax_fixed(0.06, price_include=True)
tax_fixed012 = create_tax_fixed(0.12, price_include=True)
tax7 = create_tax(7, price_include=False)
tax7base = create_tax(7, price_include=False, include_base_amount=True)
tax10nobase = create_tax(10)
tax10 = create_tax(10, price_include=True)
tax21 = create_tax(21, price_include=True)
tax_fixed006 = create_tax_fixed(0.06, price_include_override='tax_included', include_base_amount=True)
tax_fixed012 = create_tax_fixed(0.12, price_include_override='tax_included', include_base_amount=True)
tax7 = create_tax(7, price_include_override='tax_excluded')
tax8 = create_tax(8, include_base_amount=True)
tax9 = create_tax(9)
tax10 = create_tax(10, price_include_override='tax_included')
tax21 = create_tax(21, price_include_override='tax_included')
tax_group_7_10 = tax7.copy()
@ -449,8 +681,8 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
return {
'tax7': tax7,
'tax7base': tax7base,
'tax10nobase': tax10nobase,
'tax8': tax8,
'tax9': tax9,
'tax10': tax10,
'tax21': tax21,
'tax_fixed006': tax_fixed006,
@ -465,7 +697,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
def create_random_uid(self):
return ('%05d-%03d-%04d' % (randint(1, 99999), randint(1, 999), randint(1, 9999)))
def create_ui_order_data(self, pos_order_lines_ui_args, customer=False, is_invoiced=False, payments=None, uid=None):
def create_ui_order_data(self, pos_order_lines_ui_args, pos_order_ui_args={}, customer=False, is_invoiced=False, payments=None, uuid=None):
""" Mocks the order_data generated by the pos ui.
This is useful in making orders in an open pos session without making tours.
@ -487,9 +719,21 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
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, discount=0.0):
def normalize_order_line_param(param):
if isinstance(param, dict):
return param
assert len(param) >= 2
return {
'product': param[0],
'quantity': param[1],
'discount': 0.0 if len(param) == 2 else param[2],
}
def create_order_line(product, quantity, **kwargs):
price_unit = self.pricelist._get_product_price(product, quantity)
tax_ids = fiscal_position.map_tax(product.taxes_id)
tax_ids = fiscal_position.map_tax(product.taxes_id.filtered_domain(self.env['account.tax']._check_company_domain(self.env.company)))
discount = kwargs.get('discount', 0.0)
price_unit_after_discount = price_unit * (1 - discount / 100.0)
tax_values = (
tax_ids.compute_all(price_unit_after_discount, self.currency, quantity)
@ -500,7 +744,6 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
}
)
return (0, 0, {
'discount': discount,
'id': randint(1, 1000000),
'pack_lot_ids': [],
'price_unit': price_unit,
@ -508,29 +751,29 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
'price_subtotal': tax_values['total_excluded'],
'price_subtotal_incl': tax_values['total_included'],
'qty': quantity,
'tax_ids': [(6, 0, tax_ids.ids)]
'tax_ids': [(6, 0, tax_ids.ids)],
**kwargs
})
def create_payment(payment_method, amount):
return (0, 0, {
'amount': amount,
'name': fields.Datetime.to_string(fields.Datetime.now()),
'name': fields.Datetime.now(),
'payment_method_id': payment_method.id,
})
uid = uid or self.create_random_uid()
uuid = uuid or self.create_random_uid()
# 1. generate the order lines
order_lines = [
create_order_line(product, quantity, discount and discount[0] or 0.0)
for product, quantity, *discount
in pos_order_lines_ui_args
create_order_line(**normalize_order_line_param(param))
for param in pos_order_lines_ui_args
]
# 2. generate the payments
total_amount_incl = sum(line[2]['price_subtotal_incl'] for line in order_lines)
if payments is None:
default_cash_pm = self.config.payment_method_ids.filtered(lambda pm: pm.is_cash_count)[:1]
default_cash_pm = self.config.payment_method_ids.filtered(lambda pm: pm.is_cash_count and not pm.split_transactions)[:1]
if not default_cash_pm:
raise Exception('There should be a cash payment method set in the pos.config.')
payments = [create_payment(default_cash_pm, total_amount_incl)]
@ -543,38 +786,36 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
# 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.env.user.id,
'to_invoice': is_invoiced,
},
'id': uid,
'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,
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': fiscal_position.id,
'pricelist_id': self.config.pricelist_id.id,
'name': 'Order %s' % uuid,
'last_order_preparation_change': '{}',
'lines': order_lines,
'partner_id': customer and customer.id,
'session_id': self.pos_session.id,
'payment_ids': payments,
'uuid': uuid,
'user_id': self.env.uid,
'to_invoice': is_invoiced,
**pos_order_ui_args,
}
@classmethod
def create_product(cls, name, category, lst_price, standard_price=None, tax_ids=None, sale_account=None):
product = cls.env['product.product'].create({
'type': 'product',
'is_storable': True,
'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,
'company_id': cls.env.company.id,
})
if sale_account:
product.property_account_income_id = sale_account
@ -611,7 +852,7 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
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.pos_session.set_cashbox_pos(opening_cash, None)
self.pos_session.set_opening_control(opening_cash, None)
return self.pos_session
def _run_test(self, args):
@ -643,11 +884,12 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
return pos_session
def _create_orders(self, order_data_params):
'''Returns a dict mapping uid to its created pos.order record.'''
'''Returns a dict mapping uuid to its created pos.order record.'''
result = {}
for params in order_data_params:
order_data = self.create_ui_order_data(**params)
result[params['uid']] = self.env['pos.order'].browse([order['id'] for order in self.env['pos.order'].create_from_ui([order_data])])
order_data = [self.create_ui_order_data(**params) for params in order_data_params]
order_ids = [order['id'] for order in self.env['pos.order'].sync_from_ui(order_data)['pos.order']]
for order_id in self.env["pos.order"].browse(order_ids):
result[order_id.uuid] = order_id
return result
def _check_invoice_journal_entries(self, pos_session, orders_map, expected_values):
@ -713,8 +955,43 @@ class TestPoSCommon(ValuationReconciliationTestCommon):
# We allow partial checks of the lines of the account move if `line_ids_predicate` is specified.
# This means that only those that satisfy the predicate are compared to the expected account move line_ids.
line_ids_predicate = expected_account_move_vals.pop('line_ids_predicate', lambda _: True)
self.assertRecordValues(account_move.line_ids.filtered(line_ids_predicate), expected_account_move_vals.pop('line_ids'))
line_ids = expected_account_move_vals.pop('line_ids')
reconciliation_statuses = []
for line in line_ids:
partially_reconciled = line.pop('partially_reconciled', False)
if partially_reconciled is True:
reconciliation_statuses.append('partially_reconciled')
else:
reconciliation_statuses.append('fully_reconciled' if line.get('reconciled') else 'not_reconciled')
account_move_line_ids = account_move.line_ids.filtered(line_ids_predicate)
self.assertRecordValues(account_move_line_ids, line_ids)
self.assertRecordValues(account_move, [expected_account_move_vals])
# Check reconciliation status
for line, reconciliation_status in zip(account_move_line_ids, reconciliation_statuses):
# See 'account_move_line._compute_amount_residual' for more explanation
if reconciliation_status == 'fully_reconciled':
if line.matching_number:
self.assertTrue(line.full_reconcile_id)
self.assertAlmostEqual(line.amount_residual, 0)
elif reconciliation_status == 'partially_reconciled':
self.assertFalse(line.full_reconcile_id)
if line.reconciled:
self.assertAlmostEqual(line.amount_residual, 0)
else:
self.assertGreater(abs(line.amount_residual), 0)
elif reconciliation_status == 'not_reconciled':
self.assertFalse(line.full_reconcile_id)
self.assertFalse(line.reconciled)
else:
# if the expected_account_move_vals is falsy, the account_move should be falsy.
self.assertFalse(account_move)
def make_payment(self, order, payment_method, amount):
""" Make payment for the order using the given payment method.
"""
payment_context = {"active_id": order.id, "active_ids": order.ids}
return self.env['pos.make.payment'].with_context(**payment_context).create({
'amount': amount,
'payment_method_id': payment_method.id,
}).check()

View file

@ -0,0 +1,235 @@
from odoo.fields import Command
def setup_product_combo_items(self):
tax10 = self.env["account.tax"].create(
{
"name": "10%",
"amount": 10,
"amount_type": "percent",
"type_tax_use": "sale",
}
)
tax20in = self.env["account.tax"].create(
{
"name": "20% incl",
"amount": 20,
"amount_type": "percent",
"type_tax_use": "sale",
"price_include_override": "tax_included",
"include_base_amount": True,
}
)
tax30 = self.env["account.tax"].create(
{
"name": "30%",
"amount": 30,
"amount_type": "percent",
"type_tax_use": "sale",
}
)
pos_category_1 = self.env["pos.category"].create({
"name": "Category 1",
})
pos_category_2 = self.env["pos.category"].create({
"name": "Category 2",
})
pos_category_3 = self.env["pos.category"].create({
"name": "Category 3",
})
combo_product_1 = self.env["product.product"].create(
{
"name": "Combo Product 1",
"is_storable": True,
"available_in_pos": True,
"list_price": 10,
"taxes_id": [(6, 0, [tax10.id])],
"pos_categ_ids": [(6, 0, [pos_category_1.id])],
}
)
combo_product_2 = self.env["product.product"].create(
{
"name": "Combo Product 2",
"is_storable": True,
"available_in_pos": True,
"list_price": 11,
"taxes_id": [(6, 0, [tax20in.id])],
"pos_categ_ids": [(6, 0, [pos_category_1.id])],
}
)
combo_product_3 = self.env["product.product"].create(
{
"name": "Combo Product 3",
"is_storable": True,
"available_in_pos": True,
"list_price": 16,
"taxes_id": [(6, 0, [tax30.id])],
"pos_categ_ids": [(6, 0, [pos_category_1.id])],
}
)
self.desk_accessories_combo = self.env["product.combo"].create(
{
"name": "Desk Accessories Combo",
"combo_item_ids": [
Command.create({
"product_id": combo_product_1.id,
"extra_price": 0,
}),
Command.create({
"product_id": combo_product_2.id,
"extra_price": 0,
}),
Command.create({
"product_id": combo_product_3.id,
"extra_price": 2,
}),
],
}
)
combo_product_4 = self.env["product.product"].create(
{
"name": "Combo Product 4",
"is_storable": True,
"available_in_pos": True,
"list_price": 20,
"taxes_id": [(6, 0, [tax10.id])],
"pos_categ_ids": [(6, 0, [pos_category_2.id])],
}
)
combo_product_5 = self.env["product.product"].create(
{
"name": "Combo Product 5",
"is_storable": True,
"available_in_pos": True,
"list_price": 25,
"taxes_id": [(6, 0, [tax20in.id])],
"pos_categ_ids": [(6, 0, [pos_category_2.id])],
}
)
self.desks_combo = self.env["product.combo"].create(
{
"name": "Desks Combo",
"combo_item_ids": [
Command.create({
"product_id": combo_product_4.id,
"extra_price": 0,
}),
Command.create({
"product_id": combo_product_5.id,
"extra_price": 2,
}),
],
}
)
combo_product_6 = self.env["product.product"].create(
{
"name": "Combo Product 6",
"is_storable": True,
"available_in_pos": True,
"list_price": 30,
"taxes_id": [(6, 0, [tax30.id])],
"pos_categ_ids": [(6, 0, [pos_category_3.id])],
}
)
combo_product_7 = self.env["product.product"].create(
{
"name": "Combo Product 7",
"is_storable": True,
"available_in_pos": True,
"list_price": 32,
"taxes_id": [(6, 0, [tax10.id])],
"pos_categ_ids": [(6, 0, [pos_category_3.id])],
}
)
combo_product_8 = self.env["product.product"].create(
{
"name": "Combo Product 8",
"is_storable": True,
"available_in_pos": True,
"list_price": 40,
"taxes_id": [(6, 0, [tax20in.id])],
"pos_categ_ids": [(6, 0, [pos_category_3.id])],
}
)
combo_product_9 = self.env["product.product"].create(
{
"name": "Combo Product 9",
"is_storable": True,
"available_in_pos": True,
"list_price": 50,
"taxes_id": [(6, 0, [tax20in.id])],
"pos_categ_ids": [(6, 0, [pos_category_3.id])],
}
)
chair_color_attribute = self.env['product.attribute'].create({
'name': 'Color',
'display_type': 'color',
'create_variant': 'no_variant',
})
chair_color_red = self.env['product.attribute.value'].create({
'name': 'Red',
'attribute_id': chair_color_attribute.id,
'html_color': '#ff0000',
})
chair_color_blue = self.env['product.attribute.value'].create({
'name': 'Blue',
'attribute_id': chair_color_attribute.id,
'html_color': '#0000ff',
})
self.env['product.template.attribute.line'].create({
'product_tmpl_id': combo_product_9.product_tmpl_id.id,
'attribute_id': chair_color_attribute.id,
'value_ids': [(6, 0, [chair_color_red.id, chair_color_blue.id])]
})
self.chairs_combo = self.env["product.combo"].create(
{
"name": "Chairs Combo",
"combo_item_ids": [
Command.create({
"product_id": combo_product_6.id,
"extra_price": 0,
}),
Command.create({
"product_id": combo_product_7.id,
"extra_price": 0,
}),
Command.create({
"product_id": combo_product_8.id,
"extra_price": 5,
}),
Command.create({
"product_id": combo_product_9.id,
"extra_price": 0,
}),
],
}
)
# Create Office Combo
self.office_combo = self.env["product.product"].create(
{
"available_in_pos": True,
"list_price": 40,
"name": "Office Combo",
"type": "combo",
"uom_id": self.env.ref("uom.product_uom_unit").id,
"combo_ids": [
(6, 0, [self.desks_combo.id, self.chairs_combo.id, self.desk_accessories_combo.id])
],
"pos_categ_ids": [(6, 0, [pos_category_2.id])],
}
)

View file

@ -1,7 +1,9 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import time
from unittest import skip
from odoo import Command
from odoo.tests import tagged
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
@ -9,36 +11,41 @@ from odoo.addons.account.tests.common import AccountTestInvoicingCommon
class TestAngloSaxonCommon(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
def setUpClass(cls):
super().setUpClass()
cls.env.user.group_ids |= cls.env.ref('point_of_sale.group_pos_manager')
cls.PosMakePayment = cls.env['pos.make.payment']
cls.PosOrder = cls.env['pos.order']
cls.Statement = cls.env['account.bank.statement']
cls.company = cls.env.company
cls.warehouse = cls.env['stock.warehouse'].search([('company_id', '=', cls.env.company.id)], limit=1)
cls.partner = cls.env['res.partner'].create({'name': 'Partner 1'})
cls.category = cls.env.ref('product.product_category_all')
cls.category = cls.category.copy({'name': 'New category', 'property_valuation': 'real_time'})
cls.category = cls.env.ref('product.product_category_services')
cls.category = cls.category.copy({'name': 'New category','property_valuation': 'real_time'})
cls.account = cls.env['account.account'].create({'name': 'Receivable', 'code': 'RCV00', 'account_type': 'asset_receivable', 'reconcile': True})
account_expense = cls.env['account.account'].create({'name': 'Expense', 'code': 'EXP00', 'account_type': 'expense', 'reconcile': True})
account_income = cls.env['account.account'].create({'name': 'Income', 'code': 'INC00', 'account_type': 'income', 'reconcile': True})
account_output = cls.env['account.account'].create({'name': 'Output', 'code': 'OUT00', 'account_type': 'expense', 'reconcile': True})
account_valuation = cls.env['account.account'].create({'name': 'Valuation', 'code': 'STV00', 'account_type': 'expense', 'reconcile': True})
cls.partner.property_account_receivable_id = cls.account
cls.category.property_account_income_categ_id = account_income
cls.category.property_account_expense_categ_id = account_expense
cls.category.property_stock_account_input_categ_id = cls.account
cls.category.property_stock_account_output_categ_id = account_output
cls.category.property_stock_valuation_account_id = account_valuation
cls.category.property_stock_journal = cls.env['account.journal'].create({'name': 'Stock journal', 'type': 'sale', 'code': 'STK00'})
cls.cash_journal = cls.env['account.journal'].create(
{'name': 'CASH journal', 'type': 'cash', 'code': 'CSH02'})
cls.cash_payment_method = cls.env['pos.payment.method'].create({
'name': 'Cash Test',
'journal_id': cls.cash_journal.id,
})
cls.pos_config = cls.env['pos.config'].create({
'name': 'New POS config',
'payment_method_ids': cls.cash_payment_method,
})
cls.product = cls.env['product.product'].create({
'name': 'New product',
'standard_price': 100,
'available_in_pos': True,
'type': 'product',
'is_storable': True,
})
cls.company.anglo_saxon_accounting = True
cls.company.point_of_sale_update_stock_quantities = 'real'
@ -57,16 +64,18 @@ class TestAngloSaxonCommon(AccountTestInvoicingCommon):
})
cls.pos_config.write({'payment_method_ids': [(6, 0, cls.cash_payment_method.ids)]})
@tagged('post_install', '-at_install')
class TestAngloSaxonFlow(TestAngloSaxonCommon):
@skip('Temporary to fast merge new valuation')
def test_create_account_move_line(self):
# 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_ui()
current_session = self.pos_config.current_session_id
self.cash_journal.loss_account_id = self.account
current_session.set_cashbox_pos(0, None)
current_session.set_opening_control(0, None)
# I create a PoS order with 1 unit of New product at 450 EUR
self.pos_order_pos0 = self.PosOrder.create({
@ -87,6 +96,7 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
'amount_tax': 0,
'amount_paid': 0,
'amount_return': 0,
'last_order_preparation_change': '{}'
})
# I make a payment to fully pay the order
@ -115,10 +125,10 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
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
valuation_account = self.category.property_stock_valuation_account_id
aml = current_session.move_id.line_ids
aml_output = aml.filtered(lambda l: l.account_id.id == account_output.id)
aml_output = aml.filtered(lambda l: l.account_id.id == valuation_account.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")
self.assertEqual(aml_expense.debit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching")
@ -141,13 +151,13 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
'inventory_quantity': 10.0,
'location_id': self.warehouse.lot_stock_id.id,
}).action_apply_inventory()
self.assertEqual(self.product.value_svl, 30, "Value should be (5*5 + 5*1) = 30")
self.assertEqual(self.product.quantity_svl, 10)
self.assertEqual(self.product.total_value, 30, "Value should be (5*5 + 5*1) = 30")
self.assertEqual(self.product.virtual_available, 10)
self.pos_config.open_ui()
pos_session = self.pos_config.current_session_id
pos_session.set_cashbox_pos(0, None)
pos_session.set_opening_control(0, None)
pos_order_values = {
'company_id': self.company.id,
@ -167,10 +177,12 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
'amount_tax': 0,
'amount_paid': 0,
'amount_return': 0,
'last_order_preparation_change': '{}'
}
return self.PosOrder.create(pos_order_values)
@skip('Temporary to fast merge new valuation')
def test_fifo_valuation_no_invoice(self):
"""Register a payment and validate a session after selling a fifo
product without making an invoice for the customer"""
@ -220,13 +232,14 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
self.assertEqual(pos_order_pos0.account_move.journal_id, self.pos_config.invoice_journal_id)
self.assertEqual(line.debit, 27, 'As it is a fifo product, the move\'s value should be 5*5 + 2*1')
@skip('Temporary to fast merge new valuation')
def test_cogs_with_ship_later_no_invoicing(self):
# This test will check that the correct journal entries are created when a product in real time valuation
# is sold using the ship later option and no invoice is created in a company using anglo-saxon
self.pos_config.open_ui()
current_session = self.pos_config.current_session_id
self.cash_journal.loss_account_id = self.account
current_session.set_cashbox_pos(0, None)
current_session.set_opening_control(0, None)
# 2 step delivery method
self.warehouse.delivery_steps = 'pick_ship'
@ -238,7 +251,7 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
'pricelist_id': self.company.partner_id.property_product_pricelist.id,
'session_id': self.pos_config.current_session_id.id,
'to_invoice': False,
'to_ship': True,
'shipping_date': '2023-01-01',
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product.id,
@ -252,6 +265,7 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
'amount_tax': 0,
'amount_paid': 0,
'amount_return': 0,
'last_order_preparation_change': '{}'
})
# I make a payment to fully pay the order
@ -271,8 +285,10 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
current_session_id.close_session_from_ui()
self.assertEqual(current_session_id.state, 'closed', 'Check that session is closed')
self.assertEqual(len(current_session.picking_ids), 1, "There should be 2 pickings")
current_session.picking_ids.move_ids.write({'quantity': 1, 'picked': True})
current_session.picking_ids.button_validate()
self.assertEqual(len(current_session.picking_ids), 2, "There should be 2 pickings")
current_session.picking_ids.move_ids_without_package.quantity_done = 1
current_session.picking_ids.button_validate()
# I test that the generated journal entries are correct.
@ -282,24 +298,21 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
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(len(aml_output), 3, "There should be 3 output account move lines")
self.assertEqual(len(aml_output), 2, "There should be 2 output account move lines")
# 2 moves in POS journal (Pos order + manual entry at delivery)
self.assertEqual(len(aml_output.move_id.filtered(lambda l: l.journal_id == self.pos_config.journal_id)), 2)
self.assertEqual(len(aml_output.move_id.filtered(lambda l: l.journal_id == self.pos_config.journal_id)), 1)
# 1 move in stock journal (delivery from stock layers)
self.assertEqual(len(aml_output.move_id.filtered(lambda l: l.journal_id == self.category.property_stock_journal)), 1)
#Check the lines created after the picking validation
self.assertEqual(aml_output[2].credit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_output[2].debit, 0.0, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_output[1].debit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_output[1].credit, 0.0, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_expense[1].debit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_expense[1].credit, 0.0, "Cost of Good Sold entry missing or mismatching")
#Check the lines created by the PoS session
self.assertEqual(aml_output[0].debit, 0.0, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_output[0].credit, 0.0, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_output[1].credit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_output[1].debit, 0.0, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_expense[0].debit, self.product.standard_price, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_expense[0].credit, 0.0, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_expense[0].debit, 0.0, "Cost of Good Sold entry missing or mismatching")
#Check the lines created by the PoS session
self.assertEqual(aml_output[0].debit, 100.0, "Cost of Good Sold entry missing or mismatching")
self.assertEqual(aml_output[0].credit, 0.0, "Cost of Good Sold entry missing or mismatching")
@skip('Temporary to fast merge new valuation')
def test_action_pos_order_invoice(self):
self.company.point_of_sale_update_stock_quantities = 'closing'
@ -347,13 +360,14 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
self.pos_config.open_ui()
pricelist = self.env['product.pricelist'].create({
'name': 'Test Pricelist',
'discount_policy': 'without_discount',
'item_ids': [(0, 0, {
'compute_price': 'percentage',
'percent_price': 5,
'min_quantity': 0,
'applied_on': '3_global',
})]
'item_ids': [
Command.create({
'compute_price': 'percentage',
'percent_price': '5.0',
'min_quantity': 0,
'applied_on': '3_global',
})
],
})
self.product.lst_price = 100
self.pos_order_pos0 = self.PosOrder.create({
@ -365,7 +379,7 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
'product_id': self.product.id,
'price_unit': 95,
'qty': 1.0,
'tax_ids': [(6, 0, self.product.taxes_id.ids)],
'tax_ids': [(6, 0, self.tax_purchase_a.ids)],
'price_subtotal': 90.25,
'price_subtotal_incl': 103.79,
'discount': 5,
@ -386,9 +400,190 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
res = self.pos_order_pos0.action_pos_order_invoice()
invoice = self.env['account.move'].browse(res['res_id'])
self.assertTrue('Price discount from 100.00 -> 95.00' in invoice.invoice_line_ids.filtered(lambda l: l.display_type == "line_note").display_name)
self.assertTrue('Price discount from 100.00 to 95.00' in invoice.invoice_line_ids.filtered(lambda l: l.display_type == "line_note").display_name)
product_line = invoice.invoice_line_ids.filtered(lambda l: l.display_type == "product")
self.assertEqual(product_line.price_unit, 95) # Only fiscal position applies
self.assertEqual(product_line.price_unit, 95) # Only pricelist applies
self.assertEqual(product_line.discount, 5) # Disount is reflected
self.assertEqual(product_line.price_subtotal, 90.25) # Discount applies on price_unit
self.assertEqual(product_line.price_total, 103.79) # Taxes applied with price_total
@skip('Temporary to fast merge new valuation')
def test_cogs_with_ship_later_with_backorder(self):
# This test will check that the correct journal entries are created when 2 products are sold
# using the ship later option and one of them is processed in a backorder
self.pos_config.open_ui()
current_session = self.pos_config.current_session_id
self.cash_journal.loss_account_id = self.account
current_session.set_opening_control(0, None)
# Create 2 product one with no cost and one with a cost of 20 EUR
self.product_2 = self.env['product.product'].create({
'name': 'New product 2',
'standard_price': 20,
'available_in_pos': True,
'is_storable': True,
'categ_id': self.category.id,
})
self.product_1 = self.env['product.product'].create({
'name': 'New product 1',
'standard_price': 0,
'available_in_pos': True,
'is_storable': True,
'categ_id': self.category.id,
})
# I create a PoS order with 1 unit of New product at 450 EUR
self.pos_order_pos0 = self.PosOrder.create({
'company_id': self.company.id,
'partner_id': self.partner.id,
'pricelist_id': self.company.partner_id.property_product_pricelist.id,
'session_id': self.pos_config.current_session_id.id,
'to_invoice': False,
'shipping_date': '2023-01-01',
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product_1.id,
'price_unit': 100,
'discount': 0.0,
'qty': 1.0,
'price_subtotal': 100,
'price_subtotal_incl': 100,
}), (0, 0, {
'name': "OL/0002",
'product_id': self.product_2.id,
'price_unit': 200,
'discount': 0.0,
'qty': 1.0,
'price_subtotal': 200,
'price_subtotal_incl': 200,
})],
'amount_total': 300,
'amount_tax': 0,
'amount_paid': 0,
'amount_return': 0,
'last_order_preparation_change': '{}'
})
# I make a payment to fully pay the order
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': 300.0,
'payment_method_id': self.cash_payment_method.id,
})
# I click on the validate button to register the payment.
context_payment = {'active_id': self.pos_order_pos0.id}
self.pos_make_payment_0.with_context(context_payment).check()
# I close the current session to generate the journal entries
current_session_id = self.pos_config.current_session_id
current_session_id.post_closing_cash_details(300.0)
current_session_id.close_session_from_ui()
self.assertEqual(current_session_id.state, 'closed', 'Check that session is closed')
current_session.picking_ids.move_ids.filtered(lambda m: m.product_id == self.product_2).write({'quantity': 1, 'picked': True})
res_dict = current_session.picking_ids.button_validate()
self.env['stock.backorder.confirmation'].with_context(res_dict['context']).process()
# I test that the generated journal entries are correct.
out = self.product_1.categ_id.property_stock_account_output_categ_id
exp = self.product_1._get_product_accounts()['expense']
aml = current_session._get_related_account_moves().line_ids
aml_output = aml.filtered(lambda l: l.account_id.id == out.id and l.journal_id == self.pos_config.journal_id)
aml_expense = aml.filtered(lambda l: l.account_id.id == exp.id and l.journal_id == self.pos_config.journal_id)
self.assertEqual(len(aml_expense), 1, "There should be 1 output account move lines")
self.assertEqual(aml_expense.debit, 20)
self.assertEqual(aml_expense.credit, 0)
self.assertEqual(len(aml_output), 1, "There should be 1 output account move lines")
self.assertEqual(aml_output.debit, 0)
self.assertEqual(aml_output.credit, 20)
backorder_picking = current_session.picking_ids.filtered(lambda p: p.state == 'confirmed')
backorder_picking.move_ids.write({'quantity': 1, 'picked': True})
backorder_picking.button_validate()
# As the second item has no cost, the account move line should be the same as before
aml = current_session._get_related_account_moves().line_ids
aml_output = aml.filtered(lambda l: l.account_id.id == out.id and l.journal_id == self.pos_config.journal_id)
aml_expense = aml.filtered(lambda l: l.account_id.id == exp.id and l.journal_id == self.pos_config.journal_id)
self.assertEqual(len(aml_expense), 1, "There should be 1 output account move lines")
self.assertEqual(aml_expense.debit, 20)
self.assertEqual(aml_expense.credit, 0)
self.assertEqual(len(aml_output), 1, "There should be 1 output account move lines")
self.assertEqual(aml_output.debit, 0)
self.assertEqual(aml_output.credit, 20)
def test_cogs_multi_products_perpetual(self):
"""
Check that an order with mutliple products that is invoiced
in anglo saxon perpetual posts its stock valuation entries
"""
self.category.property_valuation = 'real_time'
self.product.write({
'categ_id': self.category,
'standard_price': 20,
'list_price': 100
})
product2 = self.env['product.product'].create({
'name': 'P2',
'categ_id': self.category.id,
'standard_price': 100,
'list_price': 200,
'available_in_pos': True,
'is_storable': True,
})
self.pos_config.open_ui()
pos_session = self.pos_config.current_session_id
pos_session.set_opening_control(0, None)
# create order
pos_order_values = {
'company_id': self.company.id,
'partner_id': self.partner.id,
'session_id': self.pos_config.current_session_id.id,
'lines': [Command.create({
'product_id': self.product.id,
'price_unit': 100,
'discount': 0.0,
'qty': 1.0,
'price_subtotal': 100,
'price_subtotal_incl': 100,
}), Command.create({
'product_id': product2.id,
'price_unit': 200,
'discount': 0.0,
'qty': 1.0,
'price_subtotal': 200,
'price_subtotal_incl': 200,
})],
'amount_total': 300,
'amount_tax': 0,
'amount_paid': 0,
'amount_return': 0,
'last_order_preparation_change': '{}',
'to_invoice': True,
}
pos_order = self.PosOrder.create(pos_order_values)
# register payment
context_make_payment = {"active_ids": [pos_order.id], "active_id": pos_order.id}
pos_payment = self.PosMakePayment.with_context(context_make_payment).create({
'amount': 300.0,
'payment_method_id': self.cash_payment_method.id,
})
context_payment = {'active_id': pos_order.id}
pos_payment.with_context(context_payment).check()
valuation_account = self.category.property_stock_valuation_account_id
valuation_lines = pos_order.account_move.line_ids.filtered(lambda line: line.account_id == valuation_account)
self.assertRecordValues(valuation_lines.sorted(lambda aml: aml.product_id.id), [
{'product_id': self.product.id, 'credit': 20.0},
{'product_id': product2.id, 'credit': 100.0},
])

View file

@ -0,0 +1,78 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import tagged
from odoo.addons.point_of_sale.tests.test_anglo_saxon import TestAngloSaxonCommon
class TestContinentalCommon(TestAngloSaxonCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.company.anglo_saxon_accounting = False
@tagged('post_install', '-at_install')
class TestContinentalPerpetualFlow(TestContinentalCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.company.inventory_valuation = 'real_time'
cls.category.property_valuation = 'real_time'
cls.product.write({
'name': "Real time valo product",
'categ_id': cls.category,
'standard_price': 20,
'list_price': 100
})
def test_inventory_valuation_session_closing_no_invoice(self):
""" Tests that closing the session posts the stock valuation
move line entries, even if order was not invoiced. """
self.pos_config.open_ui()
pos_session = self.pos_config.current_session_id
pos_session.set_opening_control(0, None)
# create order
pos_order_values = {
'company_id': self.company.id,
'partner_id': self.partner.id,
'session_id': self.pos_config.current_session_id.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product.id,
'price_unit': 100,
'discount': 0.0,
'qty': 1.0,
'price_subtotal': 100,
'price_subtotal_incl': 100,
})],
'amount_total': 100,
'amount_tax': 0,
'amount_paid': 0,
'amount_return': 0,
'last_order_preparation_change': '{}'
}
pos_order = self.PosOrder.create(pos_order_values)
# register payment
context_make_payment = {"active_ids": [pos_order.id], "active_id": pos_order.id}
pos_payment = self.PosMakePayment.with_context(context_make_payment).create({
'amount': 100.0,
'payment_method_id': self.cash_payment_method.id,
})
context_payment = {'active_id': pos_order.id}
pos_payment.with_context(context_payment).check()
# validate the session
current_session_id = self.pos_config.current_session_id
current_session_id.post_closing_cash_details(100.0)
current_session_id.close_session_from_ui()
valuation_account = self.category.property_stock_valuation_account_id
valuation_lines = current_session_id.move_id.line_ids.filtered(lambda line: line.account_id == valuation_account)
self.assertEqual(len(valuation_lines), 1)
self.assertEqual(valuation_lines.credit, 20.0)

View file

@ -0,0 +1,29 @@
from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon
from odoo.tests import tagged
from odoo.fields import Command
@tagged('post_install', '-at_install', 'post_install_l10n')
class TestGenericLocalization(TestPointOfSaleHttpCommon):
allow_inherited_tests_method = True
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_a.name = "AAAA Generic Partner"
cls.partner_a.vat = "32345678"
cls.whiteboard_pen.write({
'standard_price': 10.0,
'taxes_id': [Command.link(cls.tax_sale_a.id)]
})
cls.wall_shelf.write({
'standard_price': 10.0,
'taxes_id': [Command.link(cls.tax_sale_a.id)]
})
def test_generic_localization(self):
self.main_pos_config.open_ui()
url = "/pos/ui?config_id=%d" % self.main_pos_config.id
url += "&company_name=%s" % self.main_pos_config.company_id.name
self.start_tour(url, "generic_localization_tour", login="accountman")

View file

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo.tests import tagged, HttpCase
_logger = logging.getLogger(__name__)
@tagged("post_install", "-at_install")
class WebSuite(HttpCase):
def setUp(self):
super().setUp()
env = self.env(user=self.env.ref('base.user_admin'))
payment_method = env['pos.payment.method'].create({'name': 'Lets Pay for Tests'})
env['product.product'].create({'name': 'Test Product', 'available_in_pos': True})
self.main_pos_config = self.main_pos_config = env['pos.config'].create({
'name': 'Shop',
'payment_method_ids': [(4, payment_method.id)]
})
def test_pos_js(self):
# open a session, the /pos/ui controller will redirect to it
self.main_pos_config.open_ui()
self.main_pos_config.current_session_id.set_cashbox_pos(0, None)
# point_of_sale desktop test suite
self.browser_js("/pos/ui/tests?mod=web", "", "", login="admin", timeout=1800)

View file

@ -0,0 +1,43 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon
from odoo.cli.populate import Populate
from odoo.tests.common import tagged
from odoo.tools import mute_logger
_logger = logging.getLogger(__name__)
@tagged('-standard', 'pos_performance', '-at_install', 'post_install')
class TestPosPerformance(TestPointOfSaleHttpCommon):
"""
These tests are designed for local performance testing only and will be skipped
unless the 'pos_performance' tag is explicitly included in the test tags.
To execute these tests locally, use the 'pos_performance' tag before the test name.
Example:
--test-tags pos_performance.test_pos_session_open_product_performance
"""
def __populate_model(self, model_name, total_count):
before_count = self.env[model_name].search_count([])
if not before_count:
return False
populate_count = round(total_count / before_count) - 1
Populate.populate(self.env, {model_name: populate_count}, 1)
after_count = self.env[model_name].search_count([])
_logger.info("\n\nBefore %s Count: %s\nAfter %s Count: %s\n\n", model_name, before_count, model_name, after_count)
return True
@mute_logger('odoo.models.unlink', 'odoo.cli.populate', 'odoo.tools.populate', 'odoo.tests.common', 'werkzeug')
def test_pos_session_open_product_performance(self):
self.env['ir.config_parameter'].sudo().set_param('point_of_sale.limited_product_count', 20000)
if not self.__populate_model('product.template', 20000):
_logger.warning("The product.template model must contain at least one record before it can be populated.")
return
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_pos_tour('tourSessionOpenProductPerformance', timeout=2000)

View file

@ -1,36 +1,44 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.fields import Command
from odoo.tests.common import TransactionCase
from odoo.exceptions import UserError
class TestPointOfSale(TransactionCase):
def setUp(self):
super(TestPointOfSale, self).setUp()
@classmethod
def setUpClass(cls):
super().setUpClass()
# ignore pre-existing pricelists for the purpose of this test
self.env["product.pricelist"].search([]).write({"active": False})
cls.env["product.pricelist"].search([]).write({"active": False})
self.currency = self.env.ref("base.USD")
self.company1 = self.env["res.company"].create({
cls.currency = cls.env.ref("base.USD")
cls.company1, cls.company2 = cls.env["res.company"].create([{
"name": "company 1",
"currency_id": self.currency.id
})
self.company2 = self.env["res.company"].create({
"currency_id": cls.currency.id
}, {
"name": "company 2",
"currency_id": self.currency.id
})
self.company2_pricelist = self.env["product.pricelist"].create({
"currency_id": cls.currency.id
}])
cls.company2_pricelist = cls.env["product.pricelist"].create({
"name": "company 2 pricelist",
"currency_id": self.currency.id,
"company_id": self.company2.id,
"currency_id": cls.currency.id,
"company_id": cls.company2.id,
"sequence": 1, # force this pricelist to be first
})
cls.bank_journal = cls.env['account.journal'].create({
'name': 'Bank',
'type': 'bank',
'company_id': cls.company1.id,
'code': 'BNK',
'sequence': 11,
})
self.env.user.company_id = self.company1
cls.env.user.company_id = cls.company1
def test_default_pricelist_with_company(self):
""" Verify that the default pricelist belongs to the same company as the config """
def test_no_default_pricelist(self):
""" Verify that the default pricelist isn't automatically set in the config """
company1_pricelist = self.env["product.pricelist"].create({
"name": "company 1 pricelist",
"currency_id": self.currency.id,
@ -38,26 +46,106 @@ class TestPointOfSale(TransactionCase):
"sequence": 2,
})
# make sure this doesn't pick the company2 pricelist
# make sure this doesn't pick a pricelist as default
new_config = self.env["pos.config"].create({
"name": "usd config"
"name": "usd config", "available_pricelist_ids": [(6, 0, [company1_pricelist.id])]
})
self.assertEqual(new_config.pricelist_id, company1_pricelist,
self.assertEqual(new_config.pricelist_id, self.env['product.pricelist'],
"POS config incorrectly has pricelist %s" % new_config.pricelist_id.display_name)
def test_default_pricelist_without_company(self):
""" Verify that a default pricelist without a company works """
universal_pricelist = self.env["product.pricelist"].create({
"name": "universal pricelist",
"currency_id": self.currency.id,
"sequence": 2,
def test_product_combo_variants(self):
# Create product and combo
product = self.env['product.product'].create({
'name': 'Test Product 1',
'list_price': 100,
'taxes_id': False,
'available_in_pos': True,
})
# make sure this doesn't pick the company2 pricelist
new_config = self.env["pos.config"].create({
"name": "usd config"
product_combo = self.env["product.combo"].create(
{
"name": "Product combo",
"combo_item_ids": [
Command.create({
"product_id": product.id,
"extra_price": 0,
}),
],
}
)
# Add attribute and values, simulating variant creation
size_attribute = self.env['product.attribute'].create({'name': 'Size'})
attribute_value_1 = self.env['product.attribute.value'].create({'name': 'Large', 'attribute_id': size_attribute.id})
attribute_value_2 = self.env['product.attribute.value'].create({'name': 'Small', 'attribute_id': size_attribute.id})
original_product_id = product.id
product_template = product.product_tmpl_id
product.product_tmpl_id.with_context(create_product_product=True).write({
'attribute_line_ids': [(0, 0, {
'attribute_id': size_attribute.id,
'value_ids': [(6, 0, [attribute_value_1.id, attribute_value_2.id])],
})],
})
# Check that original product should not be in combo anymore (replace by variants)
self.assertTrue(original_product_id not in product_combo.combo_item_ids.mapped('product_id').ids, "Original product should not be in combo")
self.assertEqual(new_config.pricelist_id, universal_pricelist,
"POS config incorrectly has pricelist %s" % new_config.pricelist_id.display_name)
def test_pos_bill_digits(self):
coin = self.env["pos.bill"].create({
"name": "0.005 not rounded",
"value": 0.005
})
self.assertEqual(coin.value, 0.005)
def test_pos_config_creates_warehouse(self):
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)])
if warehouse:
warehouse.write({'active': False, 'name': 'Archived ' + warehouse[0].name})
pos_config = self.env['pos.config'].create({
'name': 'Shop',
'module_pos_restaurant': False,
})
self.assertEqual(pos_config.warehouse_id.code, 'Sho')
def test_session_filter_local_data(self):
product1 = self.env['product.template'].create({
'name': 'product1'
})
product2 = self.env['product.template'].create({
'name': 'product2'
})
product3 = self.env['product.template'].create({
'name': 'product3'
})
config = self.env["pos.config"].create({
"name": "shop"
})
session = self.env['pos.session'].create({'name': 'Test Session', 'config_id': config.id})
# Delete one product and archive another one
products_to_display = [product1.id, product2.id, product3.id]
product1.write({'active': False})
product2.unlink()
models_to_filter = {'product.template': products_to_display}
products_to_display = list(set(products_to_display) - set(session.filter_local_data(models_to_filter)['product.template']))
self.assertEqual(products_to_display, [product3.id])
# No change
product4 = self.env['product.template'].create({
'name': 'product4'
})
products_to_display = [product3.id, product4.id]
models_to_filter = {'product.template': products_to_display}
products_to_display = list(set(products_to_display) - set(session.filter_local_data(models_to_filter)['product.template']))
self.assertEqual(sorted(products_to_display), sorted([product3.id, product4.id]))
# Delete all products
products_to_display = [product3.id, product4.id]
product3.unlink()
product4.unlink()
models_to_filter = {'product.template': products_to_display}
products_to_display = list(set(products_to_display) - set(session.filter_local_data(models_to_filter)['product.template']))
self.assertEqual(products_to_display, [])
# Cannot archive config while session is active
with self.assertRaises(UserError):
config.write({'active': False})

View file

@ -13,4 +13,4 @@ class TestUi(HttpCase):
@tools.mute_logger('odoo.http')
def test_01_point_of_sale_tour(self):
self.start_tour("/web", 'point_of_sale_tour', login="admin")
self.start_tour("/odoo", 'point_of_sale_tour', login="admin")

View file

@ -5,8 +5,12 @@ import odoo
from odoo import fields
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
from odoo.exceptions import ValidationError
from freezegun import freeze_time
from dateutil.relativedelta import relativedelta
from datetime import datetime, timedelta
import unittest.mock
from odoo.http import UserError
@odoo.tests.tagged('post_install', '-at_install')
@ -26,8 +30,9 @@ class TestPoSBasicConfig(TestPoSCommon):
self.product3 = self.create_product('Product 3', self.categ_basic, 30.0, 15)
self.product4 = self.create_product('Product_4', self.categ_basic, 9.96, 4.98)
self.product99 = self.create_product('Product_99', self.categ_basic, 99, 50)
self.product_multi_tax = self.create_product('Multi-tax product', self.categ_basic, 100, 100, (self.taxes['tax7base'] | self.taxes['tax10nobase']).ids)
self.product_multi_tax = self.create_product('Multi-tax product', self.categ_basic, 100, 100, (self.taxes['tax8'] | self.taxes['tax9']).ids)
self.adjust_inventory([self.product1, self.product2, self.product3], [100, 50, 50])
self.company_data_2 = self.setup_other_company()
def test_orders_no_invoiced(self):
""" Test for orders without invoice
@ -107,9 +112,9 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 5)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 1)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product3, 5), (self.product2, 3)], 'payments': [(self.bank_pm1, 220)], 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 5)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 1)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product3, 5), (self.product2, 3)], 'payments': [(self.bank_pm1, 220)], 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {},
@ -222,12 +227,12 @@ class TestPoSBasicConfig(TestPoSCommon):
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.')
# check state of orders before validating the session.
self.assertEqual('invoiced', invoiced_order.state, msg="state should be 'invoiced' for invoiced orders.")
# check account_move of orders before validating the session.
self.assertTrue(invoiced_order.account_move, msg="Invoiced orders must have account_move.")
uninvoiced_orders = self.pos_session.order_ids - invoiced_order
self.assertTrue(
all([order.state == 'paid' for order in uninvoiced_orders]),
msg="state should be 'paid' for uninvoiced orders before validating the session."
all(not order.account_move for order in uninvoiced_orders),
msg="Uninvoiced orders do not have account_move."
)
def _after_closing_cb():
@ -241,9 +246,9 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 6), (self.product2, 3), (self.product3, 1), ], 'payments': [(self.cash_pm1, 150)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product2, 20), ], 'payments': [(self.bank_pm1, 410)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product3, 1), ], 'payments': [(self.bank_pm1, 130)], 'is_invoiced': True, 'customer': self.customer, 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 6), (self.product2, 3), (self.product3, 1), ], 'payments': [(self.cash_pm1, 150)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product2, 20), ], 'payments': [(self.bank_pm1, 410)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product3, 1), ], 'payments': [(self.bank_pm1, 130)], 'is_invoiced': True, 'customer': self.customer, 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {
@ -299,7 +304,7 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product0, 1)], 'payments': [(self.bank_pm1, 0)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product0, 1)], 'payments': [(self.bank_pm1, 0)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -324,7 +329,7 @@ class TestPoSBasicConfig(TestPoSCommon):
def test_return_order_invoiced(self):
def _before_closing_cb():
order = self.pos_session.order_ids.filtered(lambda order: '666-666-666' in order.pos_reference)
order = self.pos_session.order_ids.filtered(lambda order: '666-666-666' in order.uuid)
# refund
order.refund()
@ -344,7 +349,7 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments': [(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '666-666-666'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments': [(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '666-666-666'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {
@ -424,7 +429,7 @@ class TestPoSBasicConfig(TestPoSCommon):
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 = self.pos_session.order_ids.filtered(lambda order: '12345-123-1234' in order.uuid)
order_to_return.refund()
refund_order = self.pos_session.order_ids.filtered(lambda order: order.state == 'draft')
@ -473,8 +478,8 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product2, 5)], 'payments': [(self.bank_pm1, 110)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 3), (self.product2, 2), (self.product3, 1)], 'payments': [(self.cash_pm1, 100)], 'uid': '12345-123-1234'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product2, 5)], 'payments': [(self.bank_pm1, 110)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 3), (self.product2, 2), (self.product3, 1)], 'payments': [(self.cash_pm1, 100)], 'uuid': '12345-123-1234'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {},
@ -502,9 +507,9 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 5)], 'payments': [(self.cash_split_pm1, 100), (self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 1)], 'payments': [(self.cash_split_pm1, 70), (self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product3, 5), (self.product2, 3)], 'payments': [(self.cash_split_pm1, 120), (self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 5)], 'payments': [(self.cash_split_pm1, 100), (self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 1)], 'payments': [(self.cash_split_pm1, 70), (self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product3, 5), (self.product2, 3)], 'payments': [(self.cash_split_pm1, 120), (self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0003'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -610,10 +615,10 @@ class TestPoSBasicConfig(TestPoSCommon):
))
# sync orders
order = self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
self.assertEqual(orders[0]['data']['amount_return'], 0, msg='The amount return should be 0')
self.assertEqual(orders[1]['data']['amount_return'], 0, msg='The amount return should be 0')
self.assertEqual(orders[0]['amount_return'], 0, msg='The amount return should be 0')
self.assertEqual(orders[1]['amount_return'], 0, msg='The amount return should be 0')
# close the session
self.pos_session.action_pos_session_validate()
@ -628,17 +633,17 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.cash_split_pm1 | self.bank_pm1 | self.bank_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.cash_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0004'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0005'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0006'},
{'pos_order_lines_ui_args': [(self.product99, 1)], 'payments':[(self.cash_split_pm1, 99)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0007'},
{'pos_order_lines_ui_args': [(self.product99, 1)], 'payments':[(self.bank_split_pm1, 99)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0008'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.other_customer, 'is_invoiced': True, 'uid': '00100-010-0009'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.other_customer, 'is_invoiced': True, 'uid': '00100-010-0010'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0011'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.cash_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0004'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0005'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0006'},
{'pos_order_lines_ui_args': [(self.product99, 1)], 'payments':[(self.cash_split_pm1, 99)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0007'},
{'pos_order_lines_ui_args': [(self.product99, 1)], 'payments':[(self.bank_split_pm1, 99)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0008'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.other_customer, 'is_invoiced': True, 'uuid': '00100-010-0009'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.other_customer, 'is_invoiced': True, 'uuid': '00100-010-0010'},
{'pos_order_lines_ui_args': [(self.product1, 10)], 'payments':[(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0011'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -818,8 +823,21 @@ class TestPoSBasicConfig(TestPoSCommon):
self.open_new_session(0)
session = self.pos_session
order_data = self.create_ui_order_data([(self.product3, 1)])
amount_paid = order_data['data']['amount_paid']
self.env['pos.order'].create_from_ui([order_data])
amount_paid = order_data['amount_paid']
with (
self.assertLogs('odoo.addons.point_of_sale.models.pos_order', level='DEBUG') as cm,
unittest.mock.patch('odoo.addons.point_of_sale.models.pos_order.randrange', return_value=1996)
):
res = self.env['pos.order'].sync_from_ui([order_data])
# Basic check for logs on order synchronization
order_log_str = self.env['pos.order']._get_order_log_representation(order_data)
odoo_order_id = res['pos.order'][0]['id']
self.assertEqual(len(cm.output), 4)
self.assertEqual(cm.output[0], f"INFO:odoo.addons.point_of_sale.models.pos_order:PoS synchronisation #1996 started for PoS orders references: [{order_log_str}]")
self.assertTrue(cm.output[1].startswith(f'DEBUG:odoo.addons.point_of_sale.models.pos_order:PoS synchronisation #1996 processing order {order_log_str} order full data: '))
self.assertEqual(cm.output[2], f'INFO:odoo.addons.point_of_sale.models.pos_order:PoS synchronisation #1996 order {order_log_str} created pos.order #{odoo_order_id}')
self.assertEqual(cm.output[3], 'INFO:odoo.addons.point_of_sale.models.pos_order:PoS synchronisation #1996 finished')
session.post_closing_cash_details(amount_paid)
session.close_session_from_ui()
@ -840,13 +858,22 @@ class TestPoSBasicConfig(TestPoSCommon):
def open_and_check(pos_data):
self.config = pos_data['config']
self.open_new_session()
self.open_new_session(pos_data['amount_paid'])
session = self.pos_session
session.set_cashbox_pos(pos_data['amount_paid'], False)
self.assertEqual(session.cash_register_balance_start, pos_data['amount_paid'])
pos01_config = self.config
pos02_config = pos01_config.copy()
self.cash_journal = self.env['account.journal'].create(
{'name': 'CASH journal', 'type': 'cash', 'code': 'CSH00'})
self.cash_payment_method = self.env['pos.payment.method'].create({
'name': 'Cash Test',
'journal_id': self.cash_journal.id,
'receivable_account_id': pos01_config.payment_method_ids.filtered(lambda s: s.is_cash_count)[
1].receivable_account_id.id
})
pos02_config = pos01_config.copy({
'payment_method_ids': self.cash_payment_method
})
pos01_data = {'config': pos01_config, 'p_qty': 1, 'amount_paid': 0}
pos02_data = {'config': pos02_config, 'p_qty': 3, 'amount_paid': 0}
@ -855,8 +882,8 @@ class TestPoSBasicConfig(TestPoSCommon):
session = self.pos_session
order_data = self.create_ui_order_data([(self.product3, pos_data['p_qty'])])
pos_data['amount_paid'] += order_data['data']['amount_paid']
self.env['pos.order'].create_from_ui([order_data])
pos_data['amount_paid'] += order_data['amount_paid']
self.env['pos.order'].sync_from_ui([order_data])
session.post_closing_cash_details(pos_data['amount_paid'])
session.close_session_from_ui()
@ -864,8 +891,26 @@ class TestPoSBasicConfig(TestPoSCommon):
open_and_check(pos01_data)
open_and_check(pos02_data)
def test_load_pos_data_should_not_fail(self):
"""load_pos_data shouldn't fail
def test_pos_session_name_sequencing(self):
""" This test check if the session name is correctly set according to the sequence """
sequence = self.env['ir.sequence'].search([('code', '=', 'pos.session')])
sequence.prefix = '/'
sequence.write({'number_next_actual': 1000})
name = self.config.name
self.open_new_session(0)
self.assertEqual(self.pos_session.name, name + '/01000')
self.pos_session.close_session_from_ui()
sequence.prefix = 'TEST/'
self.open_new_session(0)
self.assertEqual(self.pos_session.name, 'TEST/01001')
def test_load_data_should_not_fail(self):
"""load_data shouldn't fail
(Include test conditions here if possible)
@ -879,12 +924,23 @@ class TestPoSBasicConfig(TestPoSCommon):
'company_id': company2.id,
})
# activate limited partners loading
self.config.limited_partners_loading = True
self.open_new_session()
# calling load_pos_data should not raise an error
self.pos_session.load_pos_data()
# calling load_data should not raise an error
self.pos_session.load_data([])
def test_load_data_picks_the_company_website_domain(self):
if self.env['ir.module.module']._get('website').state != 'installed':
self.skipTest("website module is required for this test")
company_website = self.config.company_id.website_id
if company_website:
company_website.write({'domain': 'https://custom.test.domain.com'})
self.open_new_session()
response = self.pos_session.load_data([])
self.assertEqual(response['pos.config'][0]['_base_url'], company_website.domain)
def test_invoice_past_refund(self):
""" Test invoicing a past refund
@ -912,7 +968,7 @@ class TestPoSBasicConfig(TestPoSCommon):
"""
def _before_closing_cb():
# Return the order
order_to_return = self.pos_session.order_ids.filtered(lambda order: '12345-123-1234' in order.pos_reference)
order_to_return = self.pos_session.order_ids.filtered(lambda order: '12345-123-1234' in order.uuid)
order_to_return.refund()
refund_order = self.pos_session.order_ids.filtered(lambda order: order.state == 'draft')
@ -930,7 +986,7 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product3, 1)], 'payments': [(self.cash_pm1, 30)], 'uid': '12345-123-1234'},
{'pos_order_lines_ui_args': [(self.product3, 1)], 'payments': [(self.cash_pm1, 30)], 'uuid': '12345-123-1234'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {},
@ -962,7 +1018,7 @@ class TestPoSBasicConfig(TestPoSCommon):
# Check the credit note
self.assertTrue(return_to_invoice.account_move, 'Invoice should be created.')
self.assertEqual(return_to_invoice.account_move.move_type, 'out_refund', 'Invoice should be a credit note.')
self.assertEqual(return_to_invoice.account_move.invoice_date, new_session_date, 'Invoice date should be the same as the session it is created in.')
self.assertEqual(return_to_invoice.account_move.invoice_date, new_session_date.date(), 'Invoice date should be the same as the session it is created in.')
self.assertRecordValues(return_to_invoice.account_move, [{
'amount_untaxed': 30,
'amount_tax': 0,
@ -978,7 +1034,7 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product99, 1)], 'payments': [(self.bank_pm1, 99)], 'customer': False, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product99, 1)], 'payments': [(self.bank_pm1, 99)], 'customer': False, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -1030,24 +1086,24 @@ class TestPoSBasicConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product_multi_tax, 1)], 'payments': [(self.bank_pm1, 117.7)], 'customer': False, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product_multi_tax, 1)], 'payments': [(self.bank_pm1, 117.72)], 'customer': False, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
'session_journal_entry': {
'line_ids': [
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 0, 'credit': 7, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 0, 'credit': 10.7, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 0, 'credit': 8, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 0, 'credit': 9.72, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 100, 'reconciled': False},
{'account_id': self.bank_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 117.7, 'credit': 0, 'reconciled': True},
{'account_id': self.bank_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 117.72, 'credit': 0, 'reconciled': True},
],
},
'cash_statement': [],
'bank_payments': [
((117.7, ), {
((117.72, ), {
'line_ids': [
{'account_id': self.bank_pm1.outstanding_account_id.id, 'partner_id': False, 'debit': 117.7, 'credit': 0, 'reconciled': False},
{'account_id': self.bank_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 0, 'credit': 117.7, 'reconciled': True},
{'account_id': self.bank_pm1.outstanding_account_id.id, 'partner_id': False, 'debit': 117.72, 'credit': 0, 'reconciled': False},
{'account_id': self.bank_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 0, 'credit': 117.72, 'reconciled': True},
]
})
],
@ -1069,7 +1125,376 @@ class TestPoSBasicConfig(TestPoSCommon):
self.assertTrue(order_to_invoice.account_move, 'Invoice should be created.')
self.assertRecordValues(order_to_invoice.account_move.line_ids, [
{'account_id': self.sales_account.id, 'balance': -100, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'balance': -7, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'balance': -10.7, 'reconciled': False},
{'account_id': self.receivable_account.id, 'balance': 117.7, 'reconciled': True},
{'account_id': self.tax_received_account.id, 'balance': -8, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'balance': -9.72, 'reconciled': False},
{'account_id': self.receivable_account.id, 'balance': 117.72, 'reconciled': True},
])
def test_limited_products_loading(self):
self.env['ir.config_parameter'].sudo().set_param('point_of_sale.limited_product_count', 3)
# Make the service products that are available in the pos inactive.
# We don't need them to test the loading of 'consu' products.
self.env['product.template'].search([('available_in_pos', '=', True), ('type', '=', 'service')]).write({'available_in_pos': False})
session = self.open_new_session(0)
self.product1.write({'company_id': False})
self.product2.write({'company_id': False})
self.product3.write({'company_id': False})
def get_top_product_ids(count):
data = session.load_data([])
special_product = session.config_id._get_special_products().ids
available_top_product = [product for product in data['product.template'] if product['product_variant_ids'][0] not in special_product]
return [p['product_variant_ids'][0] for p in available_top_product[:count]]
self.patch(self.env.cr, 'now', lambda: datetime.now() + timedelta(days=1))
self.env['pos.order'].sync_from_ui([self.create_ui_order_data([(self.product1, 1)])])
self.assertEqual(get_top_product_ids(1), [self.product1.id])
self.patch(self.env.cr, 'now', lambda: datetime.now() + timedelta(days=2))
self.env['pos.order'].sync_from_ui([self.create_ui_order_data([(self.product2, 1)])])
self.assertEqual(get_top_product_ids(2), [self.product1.id, self.product2.id])
self.patch(self.env.cr, 'now', lambda: datetime.now() + timedelta(days=3))
self.env['pos.order'].sync_from_ui([self.create_ui_order_data([(self.product3, 1)])])
self.assertEqual(get_top_product_ids(3), [self.product1.id, self.product2.id, self.product3.id])
def test_closing_entry_by_product(self):
# set the Group by Product at Closing Entry
self.config.is_closing_entry_by_product = True
self.open_new_session()
# 4 orders
# Orders
# ======
# +---------+----------+---------------+----------+-----+-------+
# | order | payments | invoiced? | product | qty | total |
# +---------+----------+---------------+----------+-----+-------+
# | order 1 | bank | no | product1 | 2 | 60 |
# | | | | product4 | 3 | 39.84 |
# +---------+----------+---------------+----------+-----+-------+
# | order 2 | bank | yes | product4 | 1 | 29.88 |
# | | | | product2 | 5 | 400 |
# +---------+----------+---------------+----------+-----+-------+
# | order 3 | bank | yes | product1 | 3 | 29.88 |
# | | | | product2 | 10 | 400 |
# +---------+----------+---------------+----------+-----+-------+
# | order 4 | bank | yes | product1 | 5 | 29.88 |
# | | | | product0 | 10| 400 |
# +---------+----------+---------------+----------+-----+-------+
# Expected Output
# +---------------+-----------+
# | invoice_line | Quantity |
# +---------------+-----------+
# | Product 0 | 10 |
# +---------------+-----------+
# | Product 1 | 10 |
# +---------------+-----------+
# | Product 2 | 15 |
# +---------------+-----------+
# | Product 4 | 4 |
# +---------------+-----------+
# create orders
orders = []
# create orders
orders = []
orders.append(self.create_ui_order_data(
[(self.product1, 2), (self.product4, 3)],
payments=[(self.bank_pm1, 49.88)]
))
orders.append(self.create_ui_order_data(
[(self.product4, 1), (self.product2, 5)],
payments=[(self.bank_pm1, 109.96)]
))
orders.append(self.create_ui_order_data(
[(self.product1, 3), (self.product2, 10)],
payments=[(self.bank_pm1, 230)]
))
orders.append(self.create_ui_order_data(
[(self.product1, 5), (self.product0, 10)],
payments=[(self.bank_pm1, 50)]
))
# sync orders
self.env['pos.order'].sync_from_ui(orders)
# close the session
self.pos_session.action_pos_session_validate()
# check values after the session is closed
session_account_move = self.pos_session.move_id
# Define expected quantities for each product
expected_product_quantity = {
self.product0: 10,
self.product1: 10,
self.product2: 15,
self.product4: 4,
}
# Iterate through invoice lines and assert the expected quantities
for i in session_account_move.line_ids:
if i.product_id and expected_product_quantity.get(i.product_id):
self.assertEqual(i.quantity, expected_product_quantity.get(i.product_id), f"Unexpected quantity for {i.product_id.name}")
def test_pos_payment_method_copy(self):
"""
Test POS payment method copy:
- Create two payment methods in which one of the payment method's journal type be cash
- Copy multiple payment methods
- Check the duplicated cash payment method journal should be empty
"""
pm_1 = self.cash_pm1
pm_2 = self.bank_pm1
pm_3, pm_4 = (pm_1 + pm_2).copy()
self.assertTrue(pm_3)
self.assertFalse(pm_3.journal_id)
self.assertTrue(pm_4)
self.assertEqual(pm_4.journal_id.type, "bank")
def test_single_config_global_invoice(self):
"""For a single POS config, create multiple orders and consolidate them into a single invoice"""
self.open_new_session()
# create orders
orders = []
orders.append(self.create_ui_order_data(
[(self.product1, 2), (self.product4, 3)],
payments=[(self.bank_pm1, 49.88)]
))
orders.append(self.create_ui_order_data(
[(self.product4, 1), (self.product2, 5)],
payments=[(self.bank_pm1, 109.96)]
))
# sync orders
self.env['pos.order'].sync_from_ui(orders)
# close the session
self.pos_session.action_pos_session_validate()
pos_orders = self.env['pos.order'].search([])
# set customer for the orders
pos_orders.write({'partner_id': self.customer.id})
# create consolidated invoice
self.env['pos.make.invoice'].create({"consolidated_billing": True}).with_context({"active_ids": pos_orders.ids}).action_create_invoices()
# check if have single invoice
self.assertEqual(len(pos_orders), 2)
self.assertEqual(len(pos_orders.account_move), 1)
self.assertEqual(pos_orders.account_move.partner_id, self.customer)
self.assertEqual(pos_orders.account_move.amount_total, sum(pos_orders.mapped('amount_total')))
self.assertEqual(pos_orders.account_move.payment_state, 'paid')
self.assertEqual(pos_orders.account_move.state, 'posted')
def test_multi_config_global_invoice(self):
self.open_new_session()
orders = []
orders.append(self.create_ui_order_data(
[(self.product1, 3), (self.product2, 10)],
payments=[(self.bank_pm1, 230)]
))
orders.append(self.create_ui_order_data(
[(self.product1, 5), (self.product0, 10)],
payments=[(self.bank_pm1, 50)]
))
self.env['pos.order'].sync_from_ui(orders)
self.pos_session.action_pos_session_validate()
# open new session & create orders
self.open_new_session()
orders2 = []
orders2.append(self.create_ui_order_data(
[(self.product1, 2), (self.product4, 3)],
payments=[(self.bank_pm1, 49.88)]
))
orders2.append(self.create_ui_order_data(
[(self.product4, 1), (self.product2, 5)],
payments=[(self.bank_pm1, 109.96)]
))
self.env['pos.order'].sync_from_ui(orders2)
self.pos_session.action_pos_session_validate()
pos_orders = self.env['pos.order'].search([])
# set customer for the orders
pos_orders.write({'partner_id': self.customer.id})
# create consolidated invoice
self.env['pos.make.invoice'].create({"consolidated_billing": True}).with_context({"active_ids": pos_orders.ids}).action_create_invoices()
# check if have single invoice
self.assertEqual(len(pos_orders), 4)
self.assertEqual(len(pos_orders.account_move), 1)
self.assertEqual(pos_orders.account_move.partner_id, self.customer)
self.assertEqual(pos_orders.account_move.amount_total, round(sum(pos_orders.mapped('amount_total')), 2))
self.assertEqual(pos_orders.account_move.payment_state, 'paid')
self.assertEqual(pos_orders.account_move.state, 'posted')
def test_double_syncing_same_order(self):
""" Test that double syncing the same order doesn't create duplicates records
"""
self.open_new_session()
# Create an order
order_data = self.create_ui_order_data([(self.product1, 1)], payments=[(self.cash_pm1, 10)], customer=self.customer, is_invoiced=True)
order_data['access_token'] = '0123456789'
res = self.env['pos.order'].sync_from_ui([order_data])
order_id = res['pos.order'][0]['id']
# Sync the same order again
res = self.env['pos.order'].sync_from_ui([order_data])
self.assertEqual(res['pos.order'][0]['id'], order_id, 'Syncing the same order should not create a new one')
order = self.env['pos.order'].browse(order_id)
self.assertEqual(order.picking_count, 1, 'Order should have one picking')
self.assertEqual(len(order.payment_ids), 1, 'Order should have one payment')
self.assertEqual(self.env['account.move'].search_count([('pos_order_ids', 'in', order.ids)]), 1, 'Order should have one invoice')
def test_pos_archived_combination(self):
product = self.env['product.template'].create({
'name': 'Product Test',
'available_in_pos': True,
'list_price': 10,
'taxes_id': False,
})
attribute_1, attribute_2, attribute_3 = self.env['product.attribute'].create([{
'name': 'Attribute 1',
'create_variant': 'always',
'value_ids': [(0, 0, {
'name': 'Value 1',
}), (0, 0, {
'name': 'Value 2',
})],
}, {
'name': 'Attribute 2',
'create_variant': 'always',
'value_ids': [(0, 0, {
'name': 'Value 1',
}), (0, 0, {
'name': 'Value 2',
})],
}, {
'name': 'Attribute 3',
'create_variant': 'always',
'value_ids': [(0, 0, {
'name': 'Value 1',
}), (0, 0, {
'name': 'Value 2',
})],
}])
_, _, ptal = self.env['product.template.attribute.line'].create([{
'product_tmpl_id': product.id,
'attribute_id': attribute_1.id,
'value_ids': [(6, 0, attribute_1.value_ids.ids)],
'sequence': 3,
}, {
'product_tmpl_id': product.id,
'attribute_id': attribute_2.id,
'value_ids': [(6, 0, attribute_2.value_ids.ids)],
'sequence': 2,
}, {
'product_tmpl_id': product.id,
'attribute_id': attribute_3.id,
'value_ids': [(6, 0, attribute_3.value_ids.ids)],
'sequence': 1,
}])
product.write({
'attribute_line_ids': [(2, ptal.id)],
})
self.open_new_session()
response = self.pos_session.load_data([])
product_data = next((item for item in response['product.template'] if item['id'] == product.id), None)
self.assertEqual(len(product_data['_archived_combinations']), 0, "There should be no archived combinations for the product")
first_variant = product.product_variant_ids[0]
first_variant.write({'active': False})
response = self.pos_session.load_data([])
product_data = next((item for item in response['product.template'] if item['id'] == product.id), None)
self.assertEqual(len(product_data['_archived_combinations']), 1, "There should be one archived combination for the product")
self.assertEqual(len(product_data['_archived_combinations'][0]), 2, "Archived combination should have two values")
self.assertTrue(all(value in product_data['_archived_combinations'][0] for value in first_variant.product_template_attribute_value_ids.ids), "Archived combination should match the first variant's attribute values")
def test_refunded_order_id(self):
"""
An order containing refunded lines from two different orders is no longer allowed,
but some legacy records of this kind may still exist.
This test ensures that the refunded_order_id is correctly computed in such cases.
"""
current_session = self.open_new_session()
orders = list(self._create_orders([
{'pos_order_lines_ui_args': [(self.product1, 1)]},
{'pos_order_lines_ui_args': [(self.product2, 1)]}
]).values())
refund_order = self.env['pos.order'].create({
'company_id': self.env.company.id,
'session_id': current_session.id,
'lines': [
(0, 0, {
'product_id': self.product1.id,
'price_unit': -10,
'qty': 1,
'tax_ids': [[6, False, []]],
'price_subtotal': -10,
'price_subtotal_incl': -10,
'refunded_orderline_id': orders[0].lines[0].id
}),
(0, 0, {
'product_id': self.product2.id,
'price_unit': -10,
'qty': 1,
'tax_ids': [[6, False, []]],
'price_subtotal': -10,
'price_subtotal_incl': -10,
'refunded_orderline_id': orders[1].lines[0].id
})
],
'amount_paid': -10,
'amount_total': -10,
'amount_tax': 0.0,
'amount_return': 0.0,
})
self.assertEqual(refund_order.refunded_order_id, orders[0])
def test_cannot_archive_journal_linked_to_pos_payment_method(self):
"""Test that archiving a journal linked to a POS payment method is blocked, and allowed when not linked."""
test_journal = self.env['account.journal'].create({
'name': 'Test POS Journal',
'type': 'cash',
'code': 'TPJ',
'company_id': self.env.company.id,
})
test_payment_method = self.env['pos.payment.method'].create({
'name': 'Test PM',
'journal_id': test_journal.id,
'receivable_account_id': self.cash_pm1.receivable_account_id.id,
})
with self.assertRaises(ValidationError):
test_journal.action_archive()
# Unlink the payment method and try again (should succeed)
test_payment_method.journal_id = False
test_journal.action_archive()
self.assertFalse(test_journal.active, "Journal should be archived when not linked to a POS payment method.")
def test_archive_delete_special_product(self):
special_product = self.env.ref('point_of_sale.product_product_tip')
with self.assertRaisesRegex(UserError, "You cannot archive a product that is set as a special product in a Point of Sale configuration. Please change the configuration first."):
special_product.action_archive()
with self.assertRaisesRegex(UserError, "You cannot archive a product that is set as a special product in a Point of Sale configuration. Please change the configuration first."):
special_product.product_variant_ids[0].action_archive()
with self.assertRaisesRegex(UserError, "You cannot archive a product that is set as a special product in a Point of Sale configuration. Please change the configuration first."):
special_product.unlink()
with self.assertRaisesRegex(UserError, "You cannot archive a product that is set as a special product in a Point of Sale configuration. Please change the configuration first."):
special_product.product_variant_ids[0].unlink()

View file

@ -1,173 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from unittest.mock import patch
import odoo
from odoo.addons.point_of_sale.models.pos_order import PosOrder
from odoo.addons.point_of_sale.models.pos_session import PosSession
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
class IntendedException(Exception):
pass
def mocked_process_order(*args):
# We just want the order process to crash (to see if it is captured)
raise IntendedException()
def mocked_handle_order_process_fail(self, order: dict, exception: Exception, draft: bool):
# We DO NOT want to create a new env in the test as the current pos_session does not exist (as it was not committed)
self._process_order_process_fail(order, exception)
@odoo.tests.tagged('post_install', '-at_install')
class TestPosCapture(TestPoSCommon):
"""
Test the capture system of failed to process orders
"""
def setUp(self):
super(TestPosCapture, self).setUp()
self.config = self.basic_config
self.product1 = self.create_product('Product 1', self.categ_basic, 10, 5)
def assert_activity_and_attachment(self, pos_session, number):
pos_attachments_domain = [
['res_model', '=', pos_session._name],
['res_id', '=', pos_session.id]
]
self.assertEqual(len(pos_session.activity_ids), number)
self.assertEqual(len(self.env['ir.attachment'].search(pos_attachments_domain)), number)
def test_capture_one_order(self):
# open a session
session = self.open_new_session()
orders = [self.create_ui_order_data([(self.product1, 1)])]
self.assert_activity_and_attachment(session, 0)
with patch.object(PosOrder, '_process_order', mocked_process_order),\
patch.object(PosSession, '_handle_order_process_fail', mocked_handle_order_process_fail),\
self.assertLogs('odoo.addons.point_of_sale.models.pos_order', level=logging.ERROR) as logger_error_output:
try:
self.env['pos.order'].create_from_ui(orders)
except IntendedException:
self.assertIn("An error occurred when processing the PoS order", logger_error_output.output[0])
self.assert_activity_and_attachment(session, 1)
self.assertEqual(session.activity_ids[0].user_id.id, self.env.user.id)
def test_capture_two_orders(self):
"""Two order even with same content should have distinct captured file"""
# open a session
session = self.open_new_session()
order1 = [self.create_ui_order_data([(self.product1, 1)], uid='12345-678-1996')]
order2 = [self.create_ui_order_data([(self.product1, 1)], uid='12345-678-1999')] # Different order with same content but different uuid
with patch.object(PosOrder, '_process_order', mocked_process_order),\
patch.object(PosSession, '_handle_order_process_fail', mocked_handle_order_process_fail),\
self.assertLogs('odoo.addons.point_of_sale.models.pos_order', level=logging.ERROR):
try:
self.env['pos.order'].create_from_ui(order1)
except IntendedException:
self.assert_activity_and_attachment(session, 1)
try:
self.env['pos.order'].create_from_ui(order2)
except IntendedException:
self.assert_activity_and_attachment(session, 2)
def test_capture_one_order_twice(self):
"""Should have only one attachment as we sync the same order twice"""
# open a session
session = self.open_new_session()
orders = [self.create_ui_order_data([(self.product1, 1)])]
self.assert_activity_and_attachment(session, 0)
with patch.object(PosOrder, '_process_order', mocked_process_order),\
patch.object(PosSession, '_handle_order_process_fail', mocked_handle_order_process_fail),\
self.assertLogs('odoo.addons.point_of_sale.models.pos_order', level=logging.ERROR):
for _ in range(2):
try:
self.env['pos.order'].create_from_ui(orders)
except IntendedException:
self.assert_activity_and_attachment(session, 1)
def test_capture_order_same_uuid(self):
"""Should have 2 attachments as the content is different"""
# open a session
session = self.open_new_session()
order1 = [self.create_ui_order_data([(self.product1, 1)], uid='12345-678-1996')]
order2 = [self.create_ui_order_data([(self.product1, 2)], uid='12345-678-1996')]
self.assert_activity_and_attachment(session, 0)
with patch.object(PosOrder, '_process_order', mocked_process_order),\
patch.object(PosSession, '_handle_order_process_fail', mocked_handle_order_process_fail),\
self.assertLogs('odoo.addons.point_of_sale.models.pos_order', level=logging.ERROR):
try:
self.env['pos.order'].create_from_ui(order1)
except IntendedException:
self.assert_activity_and_attachment(session, 1)
try:
self.env['pos.order'].create_from_ui(order2)
except IntendedException:
self.assert_activity_and_attachment(session, 2)
def test_capture_one_order_and_removed(self):
"""Check if the attachment and activity is automatically remove after the order sync"""
# open a session
session = self.open_new_session()
orders = [self.create_ui_order_data([(self.product1, 1)])]
self.assert_activity_and_attachment(session, 0)
with patch.object(PosOrder, '_process_order', mocked_process_order),\
patch.object(PosSession, '_handle_order_process_fail', mocked_handle_order_process_fail),\
self.assertLogs('odoo.addons.point_of_sale.models.pos_order', level=logging.ERROR):
try:
self.env['pos.order'].create_from_ui(orders)
except IntendedException:
pass
self.assert_activity_and_attachment(session, 1)
# Resync the order, this time it should go through!
self.env['pos.order'].create_from_ui(orders)
# Should automatically remove the attachment for this order after sync
self.assert_activity_and_attachment(session, 0)
def test_capture_two_orders_and_removed(self):
"""Check if the attachment and activity is automatically remove after the order sync (with 2 orders)"""
# open a session
session = self.open_new_session()
order1 = [self.create_ui_order_data([(self.product1, 1)], uid='12345-678-1996')]
order2 = [self.create_ui_order_data([(self.product1, 1)], uid='12345-678-1999')] # Different order with same content but different uuid
with patch.object(PosOrder, '_process_order', mocked_process_order),\
patch.object(PosSession, '_handle_order_process_fail', mocked_handle_order_process_fail),\
self.assertLogs('odoo.addons.point_of_sale.models.pos_order', level=logging.ERROR):
try:
self.env['pos.order'].create_from_ui(order1)
except IntendedException:
self.assert_activity_and_attachment(session, 1)
try:
self.env['pos.order'].create_from_ui(order2)
except IntendedException:
self.assert_activity_and_attachment(session, 2)
self.assert_activity_and_attachment(session, 2)
# Resync the order, this time it should go through!
self.env['pos.order'].create_from_ui(order2)
# Should automatically remove the attachment for this order after sync
self.assert_activity_and_attachment(session, 1)
self.env['pos.order'].create_from_ui(order1)
# Should automatically remove the attachment for this order after sync
self.assert_activity_and_attachment(session, 0)

View file

@ -0,0 +1,184 @@
from odoo import Command
from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon
from odoo.tests import tagged
@tagged('post_install', '-at_install')
class TestPosCashRounding(TestPointOfSaleHttpCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.partner_a.name = "AAAAAA" # The POS only load the first 100 partners
cls.cash_rounding_add_invoice_line = cls.env['account.cash.rounding'].create({
'name': "cash_rounding_add_invoice_line",
'rounding': 0.05,
'rounding_method': 'HALF-UP',
'strategy': 'add_invoice_line',
'profit_account_id': cls.env.company.default_cash_difference_income_account_id.id,
'loss_account_id': cls.env.company.default_cash_difference_expense_account_id.id,
})
cls.cash_rounding_biggest_tax = cls.env['account.cash.rounding'].create({
'name': "cash_rounding_biggest_tax",
'rounding': 0.05,
'rounding_method': 'HALF-UP',
'strategy': 'biggest_tax',
'profit_account_id': cls.env.company.default_cash_difference_income_account_id.id,
'loss_account_id': cls.env.company.default_cash_difference_expense_account_id.id,
})
cls.product = cls.env['product.product'].create({
'name': "random_product",
'available_in_pos': True,
'list_price': 13.67,
'taxes_id': [Command.set(cls.company_data['default_tax_sale'].ids)],
'pos_categ_ids': [Command.set(cls.pos_desk_misc_test.ids)],
})
def test_cash_rounding_halfup_biggest_tax_not_only_round_cash_method(self):
self.skipTest('To re-introduce when feature is ready')
self.main_pos_config.write({
'rounding_method': self.cash_rounding_biggest_tax.id,
'cash_rounding': True,
'only_round_cash_method': False,
})
with self.with_new_session(user=self.pos_user) as session:
self.start_pos_tour('test_cash_rounding_halfup_biggest_tax_not_only_round_cash_method')
refund, order = self.env['pos.order'].search([('session_id', '=', session.id)], limit=2)
self.assertRecordValues(order, [{
'amount_tax': 2.03,
'amount_total': 15.7,
'amount_paid': 15.7,
}])
self.assertRecordValues(order.account_move, [{
'amount_untaxed': 13.67,
'amount_tax': 2.03,
'amount_total': 15.7,
}])
self.assertRecordValues(refund, [{
'amount_tax': -2.03,
'amount_total': -15.7,
'amount_paid': -15.7,
}])
self.assertRecordValues(refund.account_move, [{
'amount_untaxed': 13.67,
'amount_tax': 2.03,
'amount_total': 15.7,
}])
def test_cash_rounding_halfup_biggest_tax_not_only_round_cash_method_pay_by_bank_and_cash(self):
self.skipTest('To re-introduce when feature is ready')
self.main_pos_config.write({
'rounding_method': self.cash_rounding_biggest_tax.id,
'cash_rounding': True,
'only_round_cash_method': False,
})
with self.with_new_session(user=self.pos_user) as session:
self.start_pos_tour('test_cash_rounding_halfup_biggest_tax_not_only_round_cash_method_pay_by_bank_and_cash')
refund, order = self.env['pos.order'].search([('session_id', '=', session.id)], limit=2)
self.assertRecordValues(order, [{
'amount_tax': 2.03,
'amount_total': 15.7,
'amount_paid': 15.72,
}])
self.assertRecordValues(order.account_move, [{
'amount_untaxed': 13.69,
'amount_tax': 2.03,
'amount_total': 15.72,
}])
self.assertRecordValues(refund, [{
'amount_tax': -2.03,
'amount_total': -15.7,
'amount_paid': -15.72,
}])
self.assertRecordValues(refund.account_move, [{
'amount_untaxed': 13.69,
'amount_tax': 2.03,
'amount_total': 15.72,
}])
def test_cash_rounding_halfup_biggest_tax_only_round_cash_method(self):
self.skipTest('To re-introduce when feature is ready')
self.main_pos_config.write({
'rounding_method': self.cash_rounding_biggest_tax.id,
'cash_rounding': True,
'only_round_cash_method': True,
})
with self.with_new_session(user=self.pos_user) as session:
self.start_pos_tour('test_cash_rounding_halfup_biggest_tax_only_round_cash_method')
refund, order = self.env['pos.order'].search([('session_id', '=', session.id)], limit=2)
self.assertRecordValues(order, [{
'amount_tax': 2.05,
'amount_total': 15.72,
'amount_paid': 15.7,
}])
self.assertRecordValues(order.account_move, [{
'amount_untaxed': 13.67,
'amount_tax': 2.03,
'amount_total': 15.7,
}])
self.assertRecordValues(refund, [{
'amount_tax': -2.05,
'amount_total': -15.72,
'amount_paid': -15.7,
}])
self.assertRecordValues(refund.account_move, [{
'amount_untaxed': 13.67,
'amount_tax': 2.03,
'amount_total': 15.7,
}])
def test_cash_rounding_halfup_biggest_tax_only_round_cash_method_pay_by_bank_and_cash(self):
self.skipTest('To re-introduce when feature is ready')
self.main_pos_config.write({
'rounding_method': self.cash_rounding_biggest_tax.id,
'cash_rounding': True,
'only_round_cash_method': True,
})
with self.with_new_session(user=self.pos_user) as session:
self.start_pos_tour('test_cash_rounding_halfup_biggest_tax_only_round_cash_method_pay_by_bank_and_cash')
refund, order = self.env['pos.order'].search([('session_id', '=', session.id)], limit=2)
self.assertRecordValues(order, [{
'amount_tax': 2.05,
'amount_total': 15.72,
'amount_paid': 15.73,
}])
self.assertRecordValues(order.account_move, [{
'amount_untaxed': 13.70,
'amount_tax': 2.03,
'amount_total': 15.73,
}])
self.assertRecordValues(refund, [{
'amount_tax': -2.05,
'amount_total': -15.72,
'amount_paid': -15.73,
}])
self.assertRecordValues(refund.account_move, [{
'amount_untaxed': 13.70,
'amount_tax': 2.03,
'amount_total': 15.73,
}])
def test_archived_product_removed_and_order_is_refunded(self):
"""
Tests that once product is archived after order is created
product is not shown but the order can still be refunded.
"""
self.pos_admin.write({
'group_ids': [
(4, self.env.ref('product.group_product_manager').id),
(4, self.env.ref('account.group_account_manager').id),
]
})
self.env['product.product'].create({
'is_storable': True,
'name': 'A Test Product',
'available_in_pos': True,
'list_price': 1,
})
self.main_pos_config.with_user(self.pos_admin).open_ui()
self.start_tour(
"/pos/ui?config_id=%d" % self.main_pos_config.id,
"test_archived_product_removed_and_order_is_refunded",
login="pos_admin"
)

View file

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime
import odoo
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon
@ -15,11 +15,12 @@ class TestPoSController(TestPointOfSaleHttpCommon):
self.new_partner = self.env['res.partner'].create({
'name': 'AAA Partner',
'zip': '12345',
'state_id': self.env.ref('base.state_us_1').id,
'country_id': self.env.ref('base.us').id,
})
self.product1 = self.env['product.product'].create({
'name': 'Test Product 1',
'type': 'product',
'is_storable': True,
'list_price': 10.0,
'taxes_id': False,
})
@ -55,13 +56,14 @@ class TestPoSController(TestPointOfSaleHttpCommon):
'city': "Test City",
'zipcode': self.new_partner.zip,
'country_id': self.new_partner.country_id.id,
'state_id': self.new_partner.state_id,
'state_id': self.new_partner.state_id.id,
'phone': "123456789",
'csrf_token': odoo.http.Request.csrf_token(self)
}
self.url_open(f'/pos/ticket/validate?access_token={self.pos_order.access_token}', data=get_invoice_data)
self.assertEqual(self.env['res.partner'].sudo().search_count([('name', '=', 'AAA Partner')]), 1)
self.assertTrue(self.pos_order.is_invoiced, "The pos order should have an invoice")
self.assertTrue(len(self.pos_order.pos_reference) >= 12, "The pos reference should not be less than 12 characters")
def test_qr_code_receipt_user_connected(self):
"""This test make sure that when the user is already connected he correctly gets redirected to the invoice."""
@ -80,7 +82,7 @@ class TestPoSController(TestPointOfSaleHttpCommon):
self.product1 = self.env['product.product'].create({
'name': 'Test Product 1',
'type': 'product',
'is_storable': True,
'list_price': 10.0,
'taxes_id': False,
})
@ -108,3 +110,98 @@ class TestPoSController(TestPointOfSaleHttpCommon):
res = self.url_open(f'/pos/ticket/validate?access_token={self.pos_order.access_token}', timeout=30000)
self.assertTrue(self.pos_order.is_invoiced, "The pos order should have an invoice")
self.assertTrue("my/invoices" in res.url)
def test_qr_code_receipt_user_not_connected(self):
"""This test make sure that when the user is not connected (public user). Order should invoiced with public user data."""
self.product1 = self.env['product.product'].create({
'name': 'Test Product 1',
'is_storable': True,
'list_price': 10.0,
'taxes_id': False,
})
self.main_pos_config.open_ui()
self.pos_order = self.env['pos.order'].create({
'session_id': self.main_pos_config.current_session_id.id,
'company_id': self.env.company.id,
'access_token': '1234567890',
'lines': [(0, 0, {
'name': "Test Product 1",
'product_id': self.product1.id,
'price_unit': 10,
'tax_ids': False,
'price_subtotal': 10,
'price_subtotal_incl': 10,
})],
'amount_tax': 10,
'amount_total': 10,
'amount_paid': 10.0,
'amount_return': 10.0,
'pos_reference': '2500-002-00002',
'ticket_code': 'inPoS',
'date_order': datetime.today(),
})
context_make_payment = {"active_ids": [self.pos_order.id], "active_id": self.pos_order.id}
self.pos_make_payment = self.env['pos.make.payment'].with_context(context_make_payment).create({
'amount': 10.0,
'payment_method_id': self.main_pos_config.payment_method_ids[0].id,
})
context_payment = {'active_id': self.pos_order.id}
self.pos_make_payment.with_context(context_payment).check()
self.main_pos_config.current_session_id.close_session_from_ui()
self.start_tour('/pos/ticket', 'invoicePoSOrderWithSelfInvocing', login=None)
self.assertTrue(self.pos_order.account_move, "The pos order should have an invoice after self invoicing")
def test_qr_code_receipt_user_updated(self):
"""This test make sure that when the user is already connected he correctly gets redirected to the invoice."""
self.authenticate(None, None)
self.partner_1 = self.env['res.partner'].create({
'name': 'Valid Lelitre',
'email': 'valid.lelitre@agrolait.com',
})
self.product1 = self.env['product.product'].create({
'name': 'Test Product 1',
'is_storable': True,
'list_price': 10.0,
'taxes_id': False,
})
self.main_pos_config.open_ui()
self.pos_order = self.env['pos.order'].create({
'session_id': self.main_pos_config.current_session_id.id,
'company_id': self.env.company.id,
'partner_id': self.partner_1.id,
'access_token': '1234567890',
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product1.id,
'price_unit': 10,
'discount': 0.0,
'qty': 1.0,
'tax_ids': False,
'price_subtotal': 10,
'price_subtotal_incl': 10,
})],
'amount_tax': 10,
'amount_total': 10,
'amount_paid': 10.0,
'amount_return': 10.0,
})
self.main_pos_config.current_session_id.close_session_from_ui()
get_invoice_data = {
'access_token': self.pos_order.access_token,
'name': 'New Name',
'email': "test@test.com",
'vat': 'VAT_TEST_NUMBER_123',
'street': "Test street",
'city': "Test City",
'zipcode': '12345',
'country_id': self.company.country_id.id,
'phone': "123456789",
'state_id': self.env['res.country.state'].search([], limit=1).id,
'csrf_token': odoo.http.Request.csrf_token(self)
}
self.url_open(f'/pos/ticket/validate?access_token={self.pos_order.access_token}', data=get_invoice_data, timeout=30000)
self.assertEqual(self.partner_1.vat, 'VAT_TEST_NUMBER_123')
self.assertEqual(self.partner_1.name, 'New Name')
self.assertEqual(self.partner_1.zip, '12345')

View file

@ -0,0 +1,107 @@
from odoo import Command
from odoo.addons.point_of_sale.tests.common import CommonPosTest, TestPoSCommon
from odoo.tests.common import tagged
@tagged('post_install', '-at_install')
class TestPosInvoiceConsolidation(TestPoSCommon, CommonPosTest):
def setUp(cls):
super().setUp()
cls.config = cls.basic_config
cls.user1 = cls.env.user
cls.user2 = cls.simple_accountman
cls.user2.group_ids = [Command.link(cls.env.ref('point_of_sale.group_pos_user').id)]
cls.product1 = cls.create_product('Product 1', cls.categ_basic, 10.0)
cls.product2 = cls.create_product('Product 2', cls.categ_basic, 20.0)
def test_ignore_generated_invoices(self):
self.open_new_session()
with self.with_user(self.user1.login):
orders_user1 = self._create_orders([{
'pos_order_lines_ui_args': [(self.product1, 1)],
'customer': self.customer,
'is_invoiced': False,
'uuid': 'u1-order',
}])
# This flattens the dict into the recordset
orders_user1 = sum(orders_user1.values(), self.env['pos.order'])
with self.with_user(self.user2.login):
orders_user2 = self._create_orders([
{
'pos_order_lines_ui_args': [(self.product1, 2)],
'customer': self.customer,
'is_invoiced': False,
}, {
'pos_order_lines_ui_args': [(self.product2, 1)],
'customer': self.customer,
'is_invoiced': False,
}
])
# This flattens the dict into the recordset
orders_user2 = sum(orders_user2.values(), self.env['pos.order'])
self.env['pos.make.invoice'].create({'consolidated_billing': True}).with_context(active_ids=orders_user1.ids).action_create_invoices()
invoice_user1 = orders_user1.account_move
invoice_user2 = orders_user2.account_move
self.assertEqual(len(invoice_user1), 1, "User 1 should have one invoice")
self.assertEqual(orders_user1.amount_total, invoice_user1.amount_total)
self.assertEqual(len(invoice_user2), 0, "User 2 should have no invoices")
all_orders = orders_user1 + orders_user2
self.env['pos.make.invoice'].create({'consolidated_billing': True}).with_context(active_ids=all_orders.ids).action_create_invoices()
invoice_user1 = orders_user1.account_move
invoice_user2 = orders_user2.account_move
self.assertEqual(len(invoice_user1), 1, "User 1 should have one invoice")
self.assertEqual(orders_user1.amount_total, invoice_user1.amount_total)
self.assertEqual(len(invoice_user2), 1, "User 2 should have one invoice")
self.assertEqual(sum(orders_user2.mapped('amount_total')), invoice_user2.amount_total)
def test_invoice_grouped_by_user_id(self):
self.open_new_session()
with self.with_user(self.user1.login):
orders_user1 = self._create_orders([{
'pos_order_lines_ui_args': [(self.product1, 1)],
'customer': self.customer,
'is_invoiced': False,
'uuid': 'u1-order',
}])
# This flattens the dict into the recordset
orders_user1 = sum(orders_user1.values(), self.env['pos.order'])
with self.with_user(self.user2.login):
orders_user2 = self._create_orders([
{
'pos_order_lines_ui_args': [(self.product1, 2)],
'customer': self.customer,
'is_invoiced': False,
}, {
'pos_order_lines_ui_args': [(self.product2, 1)],
'customer': self.customer,
'is_invoiced': False,
}
])
# This flattens the dict into the recordset
orders_user2 = sum(orders_user2.values(), self.env['pos.order'])
all_orders = orders_user1 + orders_user2
# create consolidated invoice
self.env['pos.make.invoice'].create({'consolidated_billing': True}).with_context(active_ids=all_orders.ids).action_create_invoices()
invoice_user1 = orders_user1.account_move
invoice_user2 = orders_user2.account_move
self.assertEqual(len(invoice_user1), 1, "User 1 should have one invoice")
self.assertEqual(orders_user1.amount_total, invoice_user1.amount_total)
self.assertEqual(len(invoice_user2), 1, "User 2 should have one invoice")
self.assertEqual(sum(orders_user2.mapped('amount_total')), invoice_user2.amount_total)

View file

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
from unittest import skip
import odoo
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
@ -42,7 +44,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, 2), (product2, 2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins
self.assertEqual(self.pos_session.order_ids[0].margin, 5)
@ -74,7 +76,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, 2), (product2, 2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins
self.assertEqual(self.pos_session.order_ids[0].margin, -5)
@ -106,7 +108,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, 2), (product2, 2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins
self.assertEqual(self.pos_session.order_ids[0].margin, 10)
@ -140,7 +142,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, 2), (product2, 2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins
self.assertEqual(self.pos_session.order_ids[0].margin, 5)
@ -178,7 +180,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, 2), (product2, 2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins in the config currency
self.assertEqual(self.pos_session.order_ids[0].margin, 2.5)
@ -220,7 +222,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, 2), (product2, 2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins in the config currency
self.assertEqual(self.pos_session.order_ids[0].margin, 2.5)
@ -255,7 +257,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, -2), (product2, -2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins
self.assertEqual(self.pos_session.order_ids[0].margin, -5)
@ -270,6 +272,7 @@ class TestPosMargin(TestPoSCommon):
# close session
self.pos_session.action_pos_session_validate()
@skip('Temporary to fast merge new valuation')
def test_fifo_margin_real_time(self):
"""
Test margin where there is product in FIFO with stock update in real time
@ -279,7 +282,6 @@ class TestPosMargin(TestPoSCommon):
product2 = self.create_product('Product 2', self.categ_basic, 50, 30)
move1 = self.env['stock.move'].create({
'name': 'IN 2 unit @ 3 per unit',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': product1.id,
@ -289,11 +291,11 @@ class TestPosMargin(TestPoSCommon):
}).sudo()
move1._action_confirm()
move1._action_assign()
move1.move_line_ids.qty_done = 2
move1.move_line_ids.quantity = 2
move1.picked = True
move1._action_done()
move2 = self.env['stock.move'].create({
'name': 'IN 1 unit @ 7 per unit',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': product1.id,
@ -303,7 +305,8 @@ class TestPosMargin(TestPoSCommon):
}).sudo()
move2._action_confirm()
move2._action_assign()
move2.move_line_ids.qty_done = 1
move2.move_line_ids.quantity = 1
move2.picked = True
move2._action_done()
# open a session
@ -314,7 +317,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, 2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins
self.assertEqual(self.pos_session.order_ids[0].margin, 27)
@ -327,6 +330,7 @@ class TestPosMargin(TestPoSCommon):
# close session
self.pos_session.action_pos_session_validate()
@skip('Temporary to fast merge new valuation')
def test_avco_margin_closing_time(self):
"""
Test margin where there is product in AVCO with stock update in closing
@ -339,7 +343,6 @@ class TestPosMargin(TestPoSCommon):
move1 = self.env['stock.move'].create({
'name': 'IN 2 unit @ 3 per unit',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': product1.id,
@ -349,11 +352,11 @@ class TestPosMargin(TestPoSCommon):
}).sudo()
move1._action_confirm()
move1._action_assign()
move1.move_line_ids.qty_done = 2
move1.move_line_ids.quantity = 2
move1.picked = True
move1._action_done()
move2 = self.env['stock.move'].create({
'name': 'IN 1 unit @ 6 per unit',
'location_id': self.supplier_location.id,
'location_dest_id': self.stock_location.id,
'product_id': product1.id,
@ -363,7 +366,8 @@ class TestPosMargin(TestPoSCommon):
}).sudo()
move2._action_confirm()
move2._action_assign()
move2.move_line_ids.qty_done = 1
move2.move_line_ids.quantity = 1
move2.picked = True
move2._action_done()
# open a session
@ -374,7 +378,7 @@ class TestPosMargin(TestPoSCommon):
self.create_ui_order_data([(product1, 2)])]
# sync orders
self.env['pos.order'].create_from_ui(orders)
self.env['pos.order'].sync_from_ui(orders)
# check margins which are not really computed so it should be 0
self.assertEqual(self.pos_session.order_ids[0].margin, 0)

View file

@ -90,14 +90,14 @@ class TestPoSMultipleReceivableAccounts(TestPoSCommon):
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.')
self.assertEqual(len(self.pos_session.order_ids.filtered(lambda order: order.account_move)), 1, 'There should only be one invoiced order.')
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'payments': [(self.bank_pm1, 158.75)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm1, 264.76)], 'is_invoiced': True, 'customer': self.other_customer, 'uid': '09876-098-0987'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'payments': [(self.bank_pm1, 158.75)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm1, 264.76)], 'is_invoiced': True, 'customer': self.other_customer, 'uuid': '09876-098-0987'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {
@ -106,7 +106,7 @@ class TestPoSMultipleReceivableAccounts(TestPoSCommon):
'line_ids_predicate': lambda line: line.account_id in self.other_sale_account | self.sales_account | self.other_receivable_account,
'line_ids': [
{'account_id': self.other_sale_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 90.86, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 140.86, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 140.87, 'reconciled': False},
{'account_id': self.other_receivable_account.id, 'partner_id': self.other_customer.id, 'debit': 264.76, 'credit': 0, 'reconciled': True},
]
},
@ -124,10 +124,10 @@ class TestPoSMultipleReceivableAccounts(TestPoSCommon):
'session_journal_entry': {
'line_ids': [
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 0, 'credit': 31.26, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 0, 'credit': 55.43, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 0, 'credit': 55.44, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 164.85, 'reconciled': False},
{'account_id': self.other_sale_account.id, 'partner_id': False, 'debit': 0, 'credit': 272.59, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 281.73, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 281.72, 'reconciled': False},
{'account_id': self.bank_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 423.51, 'credit': 0, 'reconciled': True},
{'account_id': self.cash_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 647.11, 'credit': 0, 'reconciled': True},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 0, 'credit': 264.76, 'reconciled': True},
@ -192,14 +192,14 @@ class TestPoSMultipleReceivableAccounts(TestPoSCommon):
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.')
self.assertEqual(len(self.pos_session.order_ids.filtered(lambda order: order.account_move)), 3, 'All orders should be invoiced.')
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'is_invoiced': True, 'customer': self.other_customer, 'uid': '09876-098-0987'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'payments': [(self.bank_pm1, 158.75)], 'is_invoiced': True, 'customer': self.customer, 'uid': '09876-098-0988'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm1, 264.76)], 'is_invoiced': True, 'customer': self.other_customer, 'uid': '09876-098-0989'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'is_invoiced': True, 'customer': self.other_customer, 'uuid': '09876-098-0987'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'payments': [(self.bank_pm1, 158.75)], 'is_invoiced': True, 'customer': self.customer, 'uuid': '09876-098-0988'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm1, 264.76)], 'is_invoiced': True, 'customer': self.other_customer, 'uuid': '09876-098-0989'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {
@ -209,7 +209,7 @@ class TestPoSMultipleReceivableAccounts(TestPoSCommon):
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 109.90, 'reconciled': False},
{'account_id': self.other_sale_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 181.73, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 281.73, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 281.72, 'reconciled': False},
{'account_id': self.other_receivable_account.id, 'partner_id': self.other_customer.id, 'debit': 647.11, 'credit': 0, 'reconciled': True},
]
},
@ -245,7 +245,7 @@ class TestPoSMultipleReceivableAccounts(TestPoSCommon):
'line_ids_predicate': lambda line: line.account_id in self.other_sale_account | self.sales_account | self.other_receivable_account,
'line_ids': [
{'account_id': self.other_sale_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 90.86, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 140.86, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': self.other_customer.id, 'debit': 0, 'credit': 140.87, 'reconciled': False},
{'account_id': self.other_receivable_account.id, 'partner_id': self.other_customer.id, 'debit': 264.76, 'credit': 0, 'reconciled': True},
]
},

View file

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest import skip
import odoo
from odoo import tools
from odoo.tests.common import Form
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
@odoo.tests.tagged('post_install', '-at_install')
@ -34,7 +35,6 @@ class TestPoSOtherCurrencyConfig(TestPoSCommon):
})
self.config.pricelist_id.write({'item_ids': [(6, 0, (self.config.pricelist_id.item_ids | pricelist_item).ids)]})
self.output_account = self.categ_anglo.property_stock_account_output_categ_id
self.expense_account = self.categ_anglo.property_account_expense_categ_id
def test_01_check_product_cost(self):
@ -91,9 +91,9 @@ class TestPoSOtherCurrencyConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm2 | self.bank_pm2,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm2, 139.95)], 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm2, 139.95)], 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {},
@ -167,9 +167,9 @@ class TestPoSOtherCurrencyConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm2 | self.bank_pm2,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'is_invoiced': True, 'customer': self.customer, 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm2, 139.95)], 'is_invoiced': True, 'customer': self.customer, 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'is_invoiced': True, 'customer': self.customer, 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm2, 139.95)], 'is_invoiced': True, 'customer': self.customer, 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {
@ -223,6 +223,7 @@ class TestPoSOtherCurrencyConfig(TestPoSCommon):
},
})
@skip('Temporary to fast merge new valuation')
def test_04_anglo_saxon_products(self):
"""
======
@ -263,10 +264,10 @@ class TestPoSOtherCurrencyConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm2,
'orders': [
{'pos_order_lines_ui_args': [(self.product4, 7), (self.product5, 7)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product5, 6), (self.product4, 6), (self.product6, 49)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product5, 2), (self.product6, 13)], 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product6, 1)], 'uid': '00100-010-0004'},
{'pos_order_lines_ui_args': [(self.product4, 7), (self.product5, 7)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product5, 6), (self.product4, 6), (self.product6, 49)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product5, 2), (self.product6, 13)], 'uuid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product6, 1)], 'uuid': '00100-010-0004'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -294,7 +295,7 @@ class TestPoSOtherCurrencyConfig(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm2,
'orders': [
{'pos_order_lines_ui_args': [(self.product7, 7)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product7, 7)], 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -363,3 +364,24 @@ class TestPoSOtherCurrencyConfig(TestPoSCommon):
debit += line.debit
credit += line.credit
self.assertEqual(tools.float_compare(debit, credit, precision_rounding=self.other_currency_config.currency_id.rounding), 0) # debit and credit should be equal
def test_with_session_check_product_cost(self):
def find_by(list_of_dicts, key, value):
return next((d for d in list_of_dicts if d.get(key) == value), None)
self.other_currency_config.open_ui()
product = self.other_currency_config.current_session_id.load_data([])['product.product']
self.assertAlmostEqual(find_by(product, 'id', self.product1.id)['lst_price'], 5.00)
self.assertAlmostEqual(find_by(product, 'id', self.product2.id)['lst_price'], 10.00)
self.assertAlmostEqual(find_by(product, 'id', self.product3.id)['lst_price'], 15.00)
self.assertAlmostEqual(find_by(product, 'id', self.product4.id)['lst_price'], 50.00)
self.assertAlmostEqual(find_by(product, 'id', self.product5.id)['lst_price'], 100.00)
self.assertAlmostEqual(find_by(product, 'id', self.product6.id)['lst_price'], 22.65)
self.assertAlmostEqual(find_by(product, 'id', self.product7.id)['lst_price'], 3.50)
def test_pos_data_standard_price_converted(self):
self.other_currency_config.open_ui()
res = self.other_currency_config.current_session_id.load_data({})
product1_data = next(filter(lambda product: product['display_name'] == "Product 1", res['product.product']))
self.assertEqual(product1_data['standard_price'], 2.5) # standard price should be converted

View file

@ -0,0 +1,291 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import Command
from odoo.tests import tagged
from odoo.addons.product.tests.common import ProductVariantsCommon
from odoo.addons.point_of_sale.tests.test_frontend import TestPointOfSaleHttpCommon
@tagged('post_install', '-at_install')
class TestPoSProductVariants(ProductVariantsCommon, TestPointOfSaleHttpCommon):
def test_integration_dynamic_variant_price(self):
"""Tests the price of products with dynamic variant when added to cart"""
self.env['product.attribute.value'].create({
'name': 'dyn3',
'attribute_id': self.dynamic_attribute.id,
'default_extra_price': 10,
})
(dyn1, dyn2, dyn3) = self.dynamic_attribute.value_ids
dyn2.default_extra_price = 5
product_template = self.env['product.template'].create({
'name': 'A dynamic product',
'taxes_id': False,
'available_in_pos': True,
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.env['product.template.attribute.line'].create({
'product_tmpl_id': product_template.id,
'attribute_id': self.dynamic_attribute.id,
'value_ids': [Command.set([dyn1.id, dyn2.id, dyn3.id])],
})
# Create a variant (because of dynamic attribute)
ptav_dyn2 = self.env['product.template.attribute.value'].search([
('attribute_line_id', '=', product_template.attribute_line_ids.id),
('product_attribute_value_id', '=', dyn2.id)
])
self.env['product.product'].create({
'available_in_pos': True,
'product_tmpl_id': product_template.id,
'product_template_attribute_value_ids': [(6, 0, [ptav_dyn2.id])],
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'test_integration_dynamic_variant_price', login="pos_user")
def test_integration_always_variant_price(self):
"""Tests the price of products with always variant when added to cart"""
self.size_attribute_m.default_extra_price = 5
product_template = self.env['product.template'].create({
'name': 'A always product',
'uom_id': self.env.ref('uom.product_uom_unit').id,
'is_storable': True,
'taxes_id': False,
'available_in_pos': True,
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.env['product.template.attribute.line'].create({
'product_tmpl_id': product_template.id,
'attribute_id': self.size_attribute.id,
'value_ids': [Command.set([self.size_attribute_s.id, self.size_attribute_m.id])],
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'test_integration_always_variant_price', login="pos_user")
def test_integration_never_variant_price(self):
"""Tests the price of products with no variant(never) variant when added to cart"""
self.no_variant_attribute_second.default_extra_price = 5
product_template = self.env['product.template'].create({
'name': 'A never product',
'uom_id': self.env.ref('uom.product_uom_unit').id,
'is_storable': True,
'taxes_id': False,
'available_in_pos': True,
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.env['product.template.attribute.line'].create({
'product_tmpl_id': product_template.id,
'attribute_id': self.no_variant_attribute.id,
'value_ids': [Command.set([self.no_variant_attribute_extra.id, self.no_variant_attribute_second.id])],
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'test_integration_never_variant_price', login="pos_user")
def test_integration_dynamic_always_variant_price(self):
"""Tests the price of products with dynamic and always variants when added to cart"""
self.env['product.attribute.value'].create({
'name': 'dyn3',
'attribute_id': self.dynamic_attribute.id,
'default_extra_price': 20,
})
(dyn1, dyn2, dyn3) = self.dynamic_attribute.value_ids
dyn2.default_extra_price = 10
self.size_attribute_m.default_extra_price = 5
product_template = self.env['product.template'].create({
'name': 'A dyn/alw product',
'taxes_id': False,
'available_in_pos': True,
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.env['product.template.attribute.line'].create([{
'product_tmpl_id': product_template.id,
'attribute_id': self.dynamic_attribute.id,
'value_ids': [Command.set([dyn1.id, dyn2.id, dyn3.id])],
}, {
'product_tmpl_id': product_template.id,
'attribute_id': self.size_attribute.id,
'value_ids': [Command.set([self.size_attribute_s.id, self.size_attribute_m.id])],
}])
# Create a variant (because of dynamic attribute)
ptav_dyn2 = self.env['product.template.attribute.value'].search([
('attribute_line_id', '=', product_template.attribute_line_ids[0].id),
('product_attribute_value_id', '=', dyn2.id)
])
ptav_always1 = self.env['product.template.attribute.value'].search([
('attribute_line_id', '=', product_template.attribute_line_ids[1].id),
('product_attribute_value_id', '=', self.size_attribute_s.id)
])
self.env['product.product'].create({
'available_in_pos': True,
'product_tmpl_id': product_template.id,
'product_template_attribute_value_ids': [(6, 0, (ptav_dyn2 + ptav_always1).ids)],
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'test_integration_dynamic_always_variant_price', login="pos_user")
def test_integration_dynamic_never_variant_price(self):
"""Tests the price of products with dynamic and never variants when added to cart"""
self.env['product.attribute.value'].create({
'name': 'dyn3',
'attribute_id': self.dynamic_attribute.id,
'default_extra_price': 20,
})
(dyn1, dyn2, dyn3) = self.dynamic_attribute.value_ids
dyn2.default_extra_price = 10
self.no_variant_attribute_second.default_extra_price = 5
product_template = self.env['product.template'].create({
'name': 'A dyn/nev product',
'taxes_id': False,
'available_in_pos': True,
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.env['product.template.attribute.line'].create([{
'product_tmpl_id': product_template.id,
'attribute_id': self.dynamic_attribute.id,
'value_ids': [Command.set([dyn1.id, dyn2.id, dyn3.id])],
}, {
'product_tmpl_id': product_template.id,
'attribute_id': self.no_variant_attribute.id,
'value_ids': [Command.set([self.no_variant_attribute_extra.id, self.no_variant_attribute_second.id])],
}])
# Create a variant (because of dynamic attribute)
ptav_dyn2 = self.env['product.template.attribute.value'].search([
('attribute_line_id', '=', product_template.attribute_line_ids[0].id),
('product_attribute_value_id', '=', dyn2.id)
])
ptav_never1 = self.env['product.template.attribute.value'].search([
('attribute_line_id', '=', product_template.attribute_line_ids[1].id),
('product_attribute_value_id', '=', self.no_variant_attribute_extra.id)
])
self.env['product.product'].create({
'available_in_pos': True,
'product_tmpl_id': product_template.id,
'product_template_attribute_value_ids': [(6, 0, (ptav_dyn2 + ptav_never1).ids)],
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'test_integration_dynamic_never_variant_price', login="pos_user")
def test_integration_always_never_variant_price(self):
"""Tests the price of products with always and never variants when added to cart"""
self.no_variant_attribute_second.default_extra_price = 5
self.size_attribute_m.default_extra_price = 10
product_template = self.env['product.template'].create({
'name': 'A alw/nev product',
'uom_id': self.env.ref('uom.product_uom_unit').id,
'is_storable': True,
'taxes_id': False,
'available_in_pos': True,
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.env['product.template.attribute.line'].create([{
'product_tmpl_id': product_template.id,
'attribute_id': self.no_variant_attribute.id,
'value_ids': [Command.set([self.no_variant_attribute_extra.id, self.no_variant_attribute_second.id])],
}, {
'product_tmpl_id': product_template.id,
'attribute_id': self.size_attribute.id,
'value_ids': [Command.set([self.size_attribute_s.id, self.size_attribute_m.id])],
}])
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'test_integration_always_never_variant_price', login="pos_user")
def test_integration_dynamic_always_never_variant_price(self):
"""Tests the price of products with all types of variants when added to cart"""
(dyn1, dyn2) = self.dynamic_attribute.value_ids
dyn2.default_extra_price = 10
self.size_attribute_m.default_extra_price = 5
self.no_variant_attribute_second.default_extra_price = 0.5
product_template = self.env['product.template'].create({
'name': 'A dyn/alw/nev product',
'taxes_id': False,
'available_in_pos': True,
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.env['product.template.attribute.line'].create([{
'product_tmpl_id': product_template.id,
'attribute_id': self.dynamic_attribute.id,
'value_ids': [Command.set([dyn1.id, dyn2.id])],
}, {
'product_tmpl_id': product_template.id,
'attribute_id': self.no_variant_attribute.id,
'value_ids': [Command.set([self.no_variant_attribute_extra.id, self.no_variant_attribute_second.id])],
}, {
'product_tmpl_id': product_template.id,
'attribute_id': self.size_attribute.id,
'value_ids': [Command.set([self.size_attribute_s.id, self.size_attribute_m.id])],
}])
# Create a variant (because of dynamic attribute)
ptav_dyn2 = self.env['product.template.attribute.value'].search([
('attribute_line_id', '=', product_template.attribute_line_ids[0].id),
('product_attribute_value_id', '=', dyn2.id)
])
ptav_never1 = self.env['product.template.attribute.value'].search([
('attribute_line_id', '=', product_template.attribute_line_ids[1].id),
('product_attribute_value_id', '=', self.no_variant_attribute_extra.id)
])
ptav_always1 = self.env['product.template.attribute.value'].search([
('attribute_line_id', '=', product_template.attribute_line_ids[1].id),
('product_attribute_value_id', '=', self.size_attribute_s.id)
])
self.env['product.product'].create({
'available_in_pos': True,
'product_tmpl_id': product_template.id,
'product_template_attribute_value_ids': [(6, 0, (ptav_dyn2 + ptav_always1 + ptav_never1).ids)],
'pos_categ_ids': [Command.set(self.pos_desk_misc_test.ids)],
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'test_integration_dynamic_always_never_variant_price', login="pos_user")
def test_image_variants_displayed(self):
"""
Tests that the user can correctly chose variants in the product_configurator_popup
if the variant was set as Image
"""
image_attribute = self.env['product.attribute'].create({
'name': 'Images',
'display_type': 'image',
'create_variant': 'always',
})
images = self.env['product.attribute.value'].create([{
'name': 'First Image',
'attribute_id': image_attribute.id,
}, {
'name': 'Second Image',
'attribute_id': image_attribute.id,
'default_extra_price': 20,
}])
product_template = self.env['product.template'].create({
'name': 'Image Product',
'is_storable': True,
'taxes_id': False,
'available_in_pos': True,
})
self.env['product.template.attribute.line'].create({
'product_tmpl_id': product_template.id,
'attribute_id': image_attribute.id,
'value_ids': [Command.set([images[0].id, images[1].id])],
})
self.main_pos_config.with_user(self.pos_user).open_ui()
self.start_tour("/pos/ui?config_id=%d" % self.main_pos_config.id, 'test_image_variants_displayed', login="pos_user")

View file

@ -1,10 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import tools
from odoo import Command
import odoo
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
from odoo.tests import Form
from odoo.exceptions import UserError
@odoo.tests.tagged('post_install', '-at_install')
class TestPoSProductsWithTax(TestPoSCommon):
@ -82,9 +84,9 @@ class TestPoSProductsWithTax(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 5)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 4)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product3, 5), (self.product2, 3)], 'payments': [(self.bank_pm1, 230.25)], 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 5)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 4)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 1), (self.product3, 5), (self.product2, 3)], 'payments': [(self.bank_pm1, 230.25)], 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {},
@ -96,8 +98,8 @@ class TestPoSProductsWithTax(TestPoSCommon):
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 110, 'reconciled': False, 'display_type': 'product'},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 272.73, 'reconciled': False, 'display_type': 'product'},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 245.45, 'reconciled': False, 'display_type': 'product'},
{'account_id': self.bank_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 230.25, 'credit': 0, 'reconciled': True, 'display_type': 'product'},
{'account_id': self.cash_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 474.64, 'credit': 0, 'reconciled': True, 'display_type': 'product'},
{'account_id': self.bank_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 230.25, 'credit': 0, 'reconciled': True, 'display_type': 'payment_term'},
{'account_id': self.cash_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 474.64, 'credit': 0, 'reconciled': True, 'display_type': 'payment_term'},
],
},
'cash_statement': [
@ -175,10 +177,10 @@ class TestPoSProductsWithTax(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product3, 1), (self.product1, 6), (self.product2, 3)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 20), (self.product1, 1)], 'payments': [(self.bank_pm1, 410.7)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product3, 10)], 'payments': [(self.bank_pm1, 426.09)], 'customer': self.customer, 'is_invoiced': True, 'uid': '09876-098-0987'},
{'pos_order_lines_ui_args': [(self.product4, 1)], 'payments': [(self.bank_pm1, 54.99)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0004'},
{'pos_order_lines_ui_args': [(self.product3, 1), (self.product1, 6), (self.product2, 3)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 20), (self.product1, 1)], 'payments': [(self.bank_pm1, 410.7)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product3, 10)], 'payments': [(self.bank_pm1, 426.09)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '09876-098-0987'},
{'pos_order_lines_ui_args': [(self.product4, 1)], 'payments': [(self.bank_pm1, 54.99)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0004'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {
@ -263,7 +265,7 @@ class TestPoSProductsWithTax(TestPoSCommon):
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 = self.pos_session.order_ids.filtered(lambda order: '12345-123-1234' in order.uuid)
order_to_return.refund()
refund_order = self.pos_session.order_ids.filtered(lambda order: order.state == 'draft')
@ -277,7 +279,7 @@ class TestPoSProductsWithTax(TestPoSCommon):
self.assertAlmostEqual(refund_order.amount_paid, -104.01, msg='Amount paid for return order should be negative.')
def _after_closing_cb():
manually_calculated_taxes = (4.01, 6.37) # should be positive since it is return order
manually_calculated_taxes = (4.01, 6.36) # should be positive since it is return order
tax_lines = self.pos_session.move_id.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'))):
@ -286,7 +288,7 @@ class TestPoSProductsWithTax(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 3), (self.product2, 2), (self.product3, 1)], 'payments': [(self.cash_pm1, 104.01)], 'customer': self.customer, 'is_invoiced': True, 'uid': '12345-123-1234'},
{'pos_order_lines_ui_args': [(self.product1, 3), (self.product2, 2), (self.product3, 1)], 'payments': [(self.cash_pm1, 104.01)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '12345-123-1234'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {
@ -306,9 +308,9 @@ class TestPoSProductsWithTax(TestPoSCommon):
'session_journal_entry': {
'line_ids': [
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 4.01, 'credit': 0, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 6.37, 'credit': 0, 'reconciled': False},
{'account_id': self.tax_received_account.id, 'partner_id': False, 'debit': 6.36, 'credit': 0, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 30, 'credit': 0, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 36.36, 'credit': 0, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 36.37, 'credit': 0, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 27.27, 'credit': 0, 'reconciled': False},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 0, 'credit': 104.01, 'reconciled': True},
],
@ -344,7 +346,7 @@ class TestPoSProductsWithTax(TestPoSCommon):
tax_ids=tax_21_incl.ids,
)
self.open_new_session()
self.env['pos.order'].create_from_ui([self.create_ui_order_data([
self.env['pos.order'].sync_from_ui([self.create_ui_order_data([
(product1, 1),
(product2, -1),
])])
@ -390,7 +392,7 @@ class TestPoSProductsWithTax(TestPoSCommon):
tax_ids=tax_21_incl.ids,
)
self.open_new_session()
self.env['pos.order'].create_from_ui([self.create_ui_order_data([
self.env['pos.order'].sync_from_ui([self.create_ui_order_data([
(product1, 1),
(product2, -1),
])])
@ -436,7 +438,7 @@ class TestPoSProductsWithTax(TestPoSCommon):
tax_ids=tax_21_incl.ids,
)
self.open_new_session()
self.env['pos.order'].create_from_ui([self.create_ui_order_data([
self.env['pos.order'].sync_from_ui([self.create_ui_order_data([
(product1, 1, 10),
(product2, -1, 10),
])])
@ -483,7 +485,7 @@ class TestPoSProductsWithTax(TestPoSCommon):
tax_ids=tax_21_incl.ids,
)
self.open_new_session()
self.env['pos.order'].create_from_ui([self.create_ui_order_data([
self.env['pos.order'].sync_from_ui([self.create_ui_order_data([
(product1, 6, 5),
(product2, -6, 5),
])])
@ -529,7 +531,7 @@ class TestPoSProductsWithTax(TestPoSCommon):
})
self.open_new_session()
self.env['pos.order'].create_from_ui([self.create_ui_order_data([
self.env['pos.order'].sync_from_ui([self.create_ui_order_data([
(zero_amount_product, 1),
])])
self.pos_session.action_pos_session_validate()
@ -541,3 +543,194 @@ class TestPoSProductsWithTax(TestPoSCommon):
{'account_id': self.sale_account.id, 'balance': 0},
{'account_id': self.cash_pm1.receivable_account_id.id, 'balance': 1},
])
def test_tax_is_used_when_in_transactions(self):
''' Ensures that a tax is set to used when it is part of some transactions '''
# Call another test that uses product_1
tax_pos = self.product1.taxes_id
self.assertFalse(tax_pos.is_used)
self.test_orders_no_invoiced()
tax_pos.invalidate_model(fnames=['is_used'])
self.assertTrue(tax_pos.is_used)
def test_pos_loaded_product_taxes_on_branch(self):
""" Check loaded product taxes on branch company """
# create the following branch hierarchy:
# Parent company
# |----> Branch X
# |----> Branch XX
company = self.config.company_id
branch_x = self.env['res.company'].create({
'name': 'Parent Company',
'country_id': company.country_id.id,
'parent_id': company.id,
})
branch_xx = self.env['res.company'].create({
'name': 'Branch XX',
'country_id': company.country_id.id,
'parent_id': branch_x.id,
})
self.cr.precommit.run() # load the CoA
# create taxes for the parent company and its branches
tax_groups = self.env['account.tax.group'].create([{
'name': 'Tax Group',
'company_id': company.id,
}, {
'name': 'Tax Group X',
'company_id': branch_x.id,
}, {
'name': 'Tax Group XX',
'company_id': branch_xx.id,
}])
tax_a = self.env['account.tax'].create({
'name': 'Tax A',
'type_tax_use': 'sale',
'amount_type': 'percent',
'amount': 10,
'tax_group_id': tax_groups[0].id,
'company_id': company.id,
})
tax_b = self.env['account.tax'].create({
'name': 'Tax B',
'type_tax_use': 'sale',
'amount_type': 'percent',
'amount': 15,
'tax_group_id': tax_groups[0].id,
'company_id': company.id,
})
tax_x = self.env['account.tax'].create({
'name': 'Tax X',
'type_tax_use': 'sale',
'amount_type': 'percent',
'amount': 20,
'tax_group_id': tax_groups[1].id,
'company_id': branch_x.id,
})
tax_xx = self.env['account.tax'].create({
'name': 'Tax XX',
'type_tax_use': 'sale',
'amount_type': 'percent',
'amount': 25,
'tax_group_id': tax_groups[2].id,
'company_id': branch_xx.id,
})
# create several products with different taxes combination
product_all_taxes = self.env['product.product'].create({
'name': 'Product all taxes',
'available_in_pos': True,
'taxes_id': [odoo.Command.set((tax_a + tax_b + tax_x + tax_xx).ids)],
})
product_no_xx_tax = self.env['product.product'].create({
'name': 'Product no tax from XX',
'available_in_pos': True,
'taxes_id': [odoo.Command.set((tax_a + tax_b + tax_x).ids)],
})
product_no_branch_tax = self.env['product.product'].create({
'name': 'Product no tax from branch',
'available_in_pos': True,
'taxes_id': [odoo.Command.set((tax_a + tax_b).ids)],
})
product_no_tax = self.env['product.product'].create({
'name': 'Product no tax',
'available_in_pos': True,
'taxes_id': [],
})
# configure a session on Branch XX
self.xx_bank_journal = self.env['account.journal'].with_company(branch_xx).create({
'name': 'Bank',
'type': 'bank',
'company_id': branch_xx.id,
'code': 'BNK',
'sequence': 15,
})
xx_config = self.env['pos.config'].with_company(branch_xx).create({
'name': 'Branch XX config',
'company_id': branch_xx.id,
})
xx_account_receivable = self.company_data['default_account_receivable'].copy({'company_ids': [Command.set(branch_xx.ids)]})
xx_cash_journal = self.company_data['default_journal_cash'].copy({'company_id': branch_xx.id})
xx_cash_payment_method = self.env['pos.payment.method'].create({
'name': 'XX Cash Payment',
'receivable_account_id': xx_account_receivable.id,
'journal_id': xx_cash_journal.id,
'company_id': branch_xx.id,
})
xx_config.write({'payment_method_ids': [
odoo.Command.set(xx_cash_payment_method.ids),
]})
self.config = xx_config
pos_session = self.open_new_session()
# load the session data from Branch XX:
# - Product all taxes => tax from Branch XX should be set
# - Product no tax from XX => tax from Branch X should be set
# - Product no tax from branch => 2 taxes from parent company should be set
# - Product no tax => no tax should be set
pos_data = pos_session.load_data([])
self.assertEqual(
next(iter(filter(lambda p: p['id'] == product_all_taxes.product_tmpl_id.id, pos_data['product.template'])))['taxes_id'],
tax_xx.ids
)
self.assertEqual(
next(iter(filter(lambda p: p['id'] == product_no_xx_tax.product_tmpl_id.id, pos_data['product.template'])))['taxes_id'],
tax_x.ids
)
tax_data_no_branch = next(iter(filter(lambda p: p['id'] == product_no_branch_tax.product_tmpl_id.id, pos_data['product.template'])))['taxes_id']
tax_data_no_branch.sort()
self.assertEqual(
tax_data_no_branch,
(tax_a + tax_b).ids
)
self.assertEqual(
next(iter(filter(lambda p: p['id'] == product_no_tax.product_tmpl_id.id, pos_data['product.template'])))['taxes_id'],
[]
)
pos_user = self.env['res.users'].create({
'name': 'Joe Odoo',
'login': 'pos_user',
'password': 'pos_user',
'group_ids': [
(4, self.env.ref('base.group_user').id),
(4, self.env.ref('point_of_sale.group_pos_user').id),
(4, self.env.ref('stock.group_stock_user').id),
],
'tz': 'America/New_York',
'company_id': branch_xx.id,
'company_ids': [Command.set([company.id, branch_x.id, branch_xx.id])],
})
def get_taxes_name_popup(product):
product = product.product_tmpl_id
# In order to simulate the state of the cache when we run this
# function over RPC, we need to fetch the below data first,
# invalidate our cache, and then enter `get_product_info_pos`
# with the arguments already loaded. This is necessary to test
# an access rights issue when trying to load product info.
branch_xx_id = branch_xx.id
xx_config_id = xx_config.id
product_all_taxes_lst_price = product_all_taxes.lst_price
self.env.invalidate_all()
return [tax['name'] for tax in product.with_user(pos_user).with_context(allowed_company_ids=[branch_xx_id]).get_product_info_pos(product_all_taxes_lst_price, 1, xx_config_id)['all_prices']['tax_details']]
self.assertEqual(get_taxes_name_popup(product_all_taxes), ["Tax XX"])
self.assertEqual(get_taxes_name_popup(product_no_xx_tax), ["Tax X"])
self.assertEqual(get_taxes_name_popup(product_no_branch_tax), ["Tax A", "Tax B"])
self.assertEqual(get_taxes_name_popup(product_no_tax), [])
def test_combo_product_variant_error(self):
"""This tests make sure that product containing variants cannot change type to combo"""
size_attribute = self.env['product.attribute'].create({'name': 'Size'})
a1 = self.env['product.attribute.value'].create({'name': 'V0hFCg==', 'attribute_id': size_attribute.id})
self.variant_product = self.env["product.product"].create(
{
"name": "Test product",
"attribute_line_ids": [(0, 0, {
"attribute_id": size_attribute.id,
"value_ids": [(6, 0, [a1.id])]
})],
})
with self.assertRaises(UserError):
with Form(self.variant_product.product_tmpl_id) as product:
product.type = "combo"

View file

@ -38,7 +38,7 @@ class TestPoSSetup(TestPoSCommon):
# 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')
self.assertEqual(self.categ_basic.property_valuation, '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')
@ -77,7 +77,14 @@ class TestPoSSetup(TestPoSCommon):
self.assertEqual(sorted(tax_group_7_10.children_tax_ids.ids), sorted((tax7 | tax10).ids))
def test_archive_used_journal(self):
journal = self.cash_pm1.journal_id
journal = self.env['account.journal'].create({
'name': 'BANKOS',
'company_id': self.company.id,
'code': 'BANKOS',
'type': 'bank',
'invoice_reference_type': 'invoice',
'invoice_reference_model': 'odoo'
})
payment_method = self.env['pos.payment.method'].create({'name': 'Lets Pay for Tests', 'journal_id': journal.id})
self.basic_config.write({'payment_method_ids': [payment_method.id]})
journal.write({'pos_payment_method_ids': [payment_method.id]})
@ -111,3 +118,11 @@ class TestPoSSetup(TestPoSCommon):
)
with self.assertRaises(ValidationError):
journal.action_archive()
def test_card_payment_method_initialization(self):
"""Test that the 'Card' payment method created by default has an outstanding account."""
card_pm = self.env['pos.payment.method'].search([
('name', '=', 'Card'), ('company_id', '=', self.env.company.id),
], limit=1)
self.assertTrue(card_pm)
self.assertTrue(card_pm.outstanding_account_id)

View file

@ -22,7 +22,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -65,7 +65,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -112,7 +112,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.pay_later_pm, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.pay_later_pm, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -137,7 +137,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -184,7 +184,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -227,21 +227,20 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
'invoice': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 100, 'reconciled': False},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': True},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': True, 'partially_reconciled': True},
]
},
'payments': [
((self.cash_pm1, 200), {
'line_ids': [
# needs to check the residual because it's supposed to be partial reconciled
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 200, 'reconciled': False, 'amount_residual': -100},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 200, 'reconciled': False, 'partially_reconciled': True, 'amount_residual': -100},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 200, 'credit': 0, 'reconciled': False, 'amount_residual': 200},
]
}),
@ -271,21 +270,20 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
'invoice': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 100, 'reconciled': False},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': True},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': True, 'partially_reconciled': True},
]
},
'payments': [
((self.bank_pm1, 200), {
'line_ids': [
# needs to check the residual because it's supposed to be partial reconciled
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 200, 'reconciled': False, 'amount_residual': -100},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 200, 'reconciled': False, 'partially_reconciled': True, 'amount_residual': -100},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 200, 'credit': 0, 'reconciled': False, 'amount_residual': 200},
]
}),
@ -315,21 +313,20 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
'invoice': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 100, 'reconciled': False},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': True},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': True, 'partially_reconciled': True},
]
},
'payments': [
((self.cash_split_pm1, 200), {
'line_ids': [
# needs to check the residual because it's supposed to be partial reconciled
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 200, 'reconciled': False, 'amount_residual': -100},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 200, 'reconciled': False, 'partially_reconciled': True, 'amount_residual': -100},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 200, 'credit': 0, 'reconciled': False, 'amount_residual': 200},
]
}),
@ -359,21 +356,20 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
'invoice': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 100, 'reconciled': False},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': True},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': True, 'partially_reconciled': True},
]
},
'payments': [
((self.bank_split_pm1, 200), {
'line_ids': [
# needs to check the residual because it's supposed to be partial reconciled
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 200, 'reconciled': False, 'amount_residual': -100},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 200, 'reconciled': False, 'partially_reconciled': True, 'amount_residual': -100},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 200, 'credit': 0, 'reconciled': False, 'amount_residual': 200},
]
}),
@ -403,7 +399,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -452,7 +448,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -510,7 +506,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -568,7 +564,7 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 200), (self.cash_split_pm1, -100)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 200), (self.cash_split_pm1, -100)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
@ -625,21 +621,20 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
'invoice': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 100, 'reconciled': False, 'amount_residual': 0},
# needs to check the residual because it's supposed to be partial reconciled
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': False, 'amount_residual': 50},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': False, 'partially_reconciled': True, 'amount_residual': 50},
]
},
'payments': [
((self.cash_pm1, 50), {
'line_ids': [
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 50, 'reconciled': True},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 50, 'reconciled': True, 'partially_reconciled': True},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 50, 'credit': 0, 'reconciled': False},
]
}),
@ -669,21 +664,20 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
'invoice': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 100, 'reconciled': False, 'amount_residual': 0},
# needs to check the residual because it's supposed to be partial reconciled
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': False, 'amount_residual': 50},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': False, 'partially_reconciled': True, 'amount_residual': 50},
]
},
'payments': [
((self.bank_pm1, 50), {
'line_ids': [
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 50, 'reconciled': True},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 50, 'reconciled': True, 'partially_reconciled': True},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 50, 'credit': 0, 'reconciled': False},
]
}),
@ -713,21 +707,20 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
'invoice': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 100, 'reconciled': False, 'amount_residual': 0},
# needs to check the residual because it's supposed to be partial reconciled
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': False, 'amount_residual': 50},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': False, 'partially_reconciled': True, 'amount_residual': 50},
]
},
'payments': [
((self.bank_split_pm1, 50), {
'line_ids': [
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 50, 'reconciled': True},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 50, 'reconciled': True, 'partially_reconciled': True},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 50, 'credit': 0, 'reconciled': False},
]
}),
@ -757,21 +750,20 @@ class TestPosSimpleInvoicedOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {
'00100-010-0001': {
'invoice': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 100, 'reconciled': False, 'amount_residual': 0},
# needs to check the residual because it's supposed to be partial reconciled
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': False, 'amount_residual': 50},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 100, 'credit': 0, 'reconciled': False, 'partially_reconciled': True, 'amount_residual': 50},
]
},
'payments': [
((self.cash_split_pm1, 50), {
'line_ids': [
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 50, 'reconciled': True},
{'account_id': self.c1_receivable.id, 'partner_id': self.customer.id, 'debit': 0, 'credit': 50, 'reconciled': True, 'partially_reconciled': True},
{'account_id': self.pos_receivable_account.id, 'partner_id': False, 'debit': 50, 'credit': 0, 'reconciled': False},
]
}),

View file

@ -22,7 +22,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -48,7 +48,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -74,7 +74,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.pay_later_pm, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.pay_later_pm, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -93,7 +93,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -119,7 +119,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -145,7 +145,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [], 'payments': [(self.cash_pm1, 100), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [], 'payments': [(self.cash_pm1, 100), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -171,7 +171,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [], 'payments': [(self.bank_pm1, 100), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [], 'payments': [(self.bank_pm1, 100), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -197,7 +197,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [], 'payments': [(self.bank_split_pm1, 100), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [], 'payments': [(self.bank_split_pm1, 100), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -223,7 +223,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [], 'payments': [(self.cash_split_pm1, 100), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [], 'payments': [(self.cash_split_pm1, 100), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -249,7 +249,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -276,7 +276,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -303,7 +303,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -330,7 +330,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 200), (self.pay_later_pm, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -357,7 +357,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -383,7 +383,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -417,7 +417,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 200), (self.cash_pm1, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -451,7 +451,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 200), (self.cash_split_pm1, -100)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 200), (self.cash_split_pm1, -100)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -484,7 +484,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -511,7 +511,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -538,7 +538,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.bank_split_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
@ -565,7 +565,7 @@ class TestPosSimpleOrders(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_split_pm1 | self.pay_later_pm,
'orders': [
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': False, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product100, 1)], 'payments': [(self.cash_split_pm1, 50), (self.pay_later_pm, 50)], 'customer': self.customer, 'is_invoiced': False, 'uuid': '00100-010-0001'},
],
'journal_entries_before_closing': {},
'journal_entries_after_closing': {

View file

@ -1,11 +1,15 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from unittest import skip
from odoo import tools
import odoo
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
@odoo.tests.tagged('post_install', '-at_install')
@skip('Temporary to fast merge new valuation')
class TestPoSStock(TestPoSCommon):
""" Tests for anglo saxon accounting scenario.
"""
@ -16,6 +20,9 @@ class TestPoSStock(TestPoSCommon):
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)
self.product4 = self.create_product('Product 4', self.categ_anglo, 10.0, 5.0)
self.product4.type = 'consu'
self.product4.is_storable = False
# start inventory with 10 items for each product
self.adjust_inventory([self.product1, self.product2, self.product3], [10, 10, 10])
@ -53,14 +60,16 @@ class TestPoSStock(TestPoSCommon):
| | 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 |
+---------+----------+-----+-------------+------------+
| order 4 | product4 | 6 | 60.0 | 0.0 | -> consumable product cost = 0
+---------+----------+-----+-------------+------------+
Expected Result
===============
+---------------------+---------+
| account | balance |
+---------------------+---------+
| sale_account | -1010.0 |
| pos_receivable-cash | 1010.0 |
| sale_account | -1070.0 |
| pos_receivable-cash | 1070.0 |
| expense_account | 327.0 |
| output_account | -327.0 |
+---------------------+---------+
@ -70,10 +79,10 @@ class TestPoSStock(TestPoSCommon):
def _before_closing_cb():
# check values before closing the session
self.assertEqual(3, self.pos_session.order_count)
self.assertEqual(4, 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.')
self.assertAlmostEqual(orders_total, 1070.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)
@ -88,26 +97,27 @@ class TestPoSStock(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 7)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 6), (self.product2, 6), (self.product3, 6)], 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 7)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 6), (self.product2, 6), (self.product3, 6)], 'uuid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product4, 6)], 'uuid': '00100-010-0004'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {},
'journal_entries_after_closing': {
'session_journal_entry': {
'line_ids': [
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 1010.0, 'reconciled': False},
{'account_id': self.sales_account.id, 'partner_id': False, 'debit': 0, 'credit': 1070.0, 'reconciled': False},
{'account_id': self.expense_account.id, 'partner_id': False, 'debit': 327, 'credit': 0, 'reconciled': False},
{'account_id': self.cash_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 1010.0, 'credit': 0, 'reconciled': True},
{'account_id': self.cash_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 1070.0, 'credit': 0, 'reconciled': True},
{'account_id': self.output_account.id, 'partner_id': False, 'debit': 0, 'credit': 327, 'reconciled': True},
],
},
'cash_statement': [
((1010.0, ), {
((1070.0, ), {
'line_ids': [
{'account_id': self.cash_pm1.journal_id.default_account_id.id, 'partner_id': False, 'debit': 1010.0, 'credit': 0, 'reconciled': False},
{'account_id': self.cash_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 0, 'credit': 1010.0, 'reconciled': True},
{'account_id': self.cash_pm1.journal_id.default_account_id.id, 'partner_id': False, 'debit': 1070.0, 'credit': 0, 'reconciled': False},
{'account_id': self.cash_pm1.receivable_account_id.id, 'partner_id': False, 'debit': 0, 'credit': 1070.0, 'reconciled': True},
]
}),
],
@ -157,9 +167,9 @@ class TestPoSStock(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10)], 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 7)], 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 6), (self.product2, 6), (self.product3, 6)], 'is_invoiced': True, 'customer': self.customer, 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10)], 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product2, 7), (self.product3, 7)], 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product1, 6), (self.product2, 6), (self.product3, 6)], 'is_invoiced': True, 'customer': self.customer, 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {
@ -203,7 +213,7 @@ class TestPoSStock(TestPoSCommon):
"""
group_owner = self.env.ref('stock.group_tracking_owner')
self.env.user.write({'groups_id': [(4, group_owner.id)]})
self.env.user.write({'group_ids': [(4, group_owner.id)]})
self.product4 = self.create_product('Product 3', self.categ_basic, 30.0, 15.0)
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': self.product4.id,
@ -219,7 +229,7 @@ class TestPoSStock(TestPoSCommon):
orders.append(self.create_ui_order_data([(self.product4, 1)]))
# sync orders
order = self.env['pos.order'].create_from_ui(orders)
order = self.env['pos.order'].sync_from_ui(orders)
# check values before closing the session
self.assertEqual(1, self.pos_session.order_count)
@ -247,9 +257,9 @@ class TestPoSStock(TestPoSCommon):
self.open_new_session()
orders = []
orders.append(self.create_ui_order_data([(self.product4, 1)]))
order = self.env['pos.order'].create_from_ui(orders)
order = self.env['pos.order'].sync_from_ui(orders)
refund_action = self.env['pos.order'].browse(order[0]['id']).refund()
refund_action = self.env['pos.order'].browse(order['pos.order'][0]['id']).refund()
refund = self.env['pos.order'].browse(refund_action['res_id'])
payment_context = {"active_ids": refund.ids, "active_id": refund.id}
@ -260,5 +270,15 @@ class TestPoSStock(TestPoSCommon):
refund_payment.with_context(**payment_context).check()
self.pos_session.action_pos_session_validate()
expense_account_move_line = self.env['account.move.line'].search([('account_id', '=', self.expense_account.id)])
expense_account_move_line = self.env['account.move.line'].search([('account_id', '=', self.expense_account.id), ('product_id', '=', False)])
self.assertEqual(expense_account_move_line.balance, 0.0, "Expense account should be 0.0")
def test_stock_duplicate_warehouse_with_PoS_operation_type(self):
wh = self.env['stock.warehouse'].create({
'name': 'WH1',
'code': 'WH1',
'company_id': self.env.company.id,
})
wh_copy = wh.copy()
self.assertTrue(wh_copy.pos_type_id)
self.assertNotEqual(wh.pos_type_id, wh_copy.pos_type_id)

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import tools
from odoo import Command
import odoo
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
@ -17,6 +17,7 @@ class TestPoSWithFiscalPosition(TestPoSCommon):
super(TestPoSWithFiscalPosition, cls).setUpClass()
cls.config = cls.basic_config
cls.company.tax_calculation_rounding_method = 'round_per_line'
cls.new_tax_17 = cls.env['account.tax'].create({'name': 'New Tax 17%', 'amount': 17})
cls.new_tax_17.invoice_repartition_line_ids.write({'account_id': cls.tax_received_account.id})
@ -56,14 +57,12 @@ class TestPoSWithFiscalPosition(TestPoSCommon):
'account_src_id': cls.sale_account.id,
'account_dest_id': cls.other_sale_account.id,
})
tax_fpos = cls.env['account.fiscal.position.tax'].create({
'position_id': fpos.id,
'tax_src_id': cls.taxes['tax7'].id,
'tax_dest_id': cls.new_tax_17.id,
})
fpos.write({
'account_ids': [(6, 0, account_fpos.ids)],
'tax_ids': [(6, 0, tax_fpos.ids)],
})
cls.new_tax_17.write({
'fiscal_position_ids': [Command.link(fpos.id)],
'original_tax_ids': [Command.link(cls.taxes['tax7'].id)],
})
return fpos
@ -75,13 +74,14 @@ class TestPoSWithFiscalPosition(TestPoSCommon):
'account_src_id': cls.sale_account.id,
'account_dest_id': cls.other_sale_account.id,
})
tax_fpos = cls.env['account.fiscal.position.tax'].create({
'position_id': fpos_no_tax_dest.id,
'tax_src_id': cls.taxes['tax7'].id,
})
fpos_no_tax_dest.write({
'account_ids': [(6, 0, account_fpos.ids)],
'tax_ids': [(6, 0, tax_fpos.ids)],
})
cls.env['account.tax'].create({
'name': 'Exempt',
'amount': 0,
'fiscal_position_ids': [Command.link(fpos_no_tax_dest.id)],
'original_tax_ids': [Command.link(cls.taxes['tax7'].id)],
})
return fpos_no_tax_dest
@ -134,9 +134,9 @@ class TestPoSWithFiscalPosition(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'customer': self.customer, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'customer': self.customer, 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm1, 265.75)], 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'customer': self.customer, 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'customer': self.customer, 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm1, 265.75)], 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {},
@ -221,9 +221,9 @@ class TestPoSWithFiscalPosition(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'payments': [(self.bank_pm1, 619.7)], 'customer': self.customer, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'customer': self.customer, 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm1, 265.75)], 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'payments': [(self.bank_pm1, 619.7)], 'customer': self.customer, 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'customer': self.customer, 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'payments': [(self.bank_pm1, 265.75)], 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {},
@ -304,8 +304,8 @@ class TestPoSWithFiscalPosition(TestPoSCommon):
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: '00100-010-0001' in order.pos_reference)
invoiced_order_2 = self.pos_session.order_ids.filtered(lambda order: '00100-010-0003' in order.pos_reference)
invoiced_order_1 = self.pos_session.order_ids.filtered(lambda order: '00100-010-0001' in order.uuid)
invoiced_order_2 = self.pos_session.order_ids.filtered(lambda order: '00100-010-0003' in order.uuid)
self.assertTrue(invoiced_order_1, msg='Invoiced order 1 should exist.')
self.assertTrue(invoiced_order_2, msg='Invoiced order 2 should exist.')
@ -315,9 +315,9 @@ class TestPoSWithFiscalPosition(TestPoSCommon):
self._run_test({
'payment_methods': self.cash_pm1 | self.bank_pm1,
'orders': [
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'payments': [(self.bank_pm1, 691.06)], 'customer': self.customer, 'is_invoiced': True, 'uid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'customer': self.customer, 'uid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'customer': self.other_customer, 'is_invoiced': True, 'uid': '00100-010-0003'},
{'pos_order_lines_ui_args': [(self.product1, 10), (self.product2, 10), (self.product3, 10)], 'payments': [(self.bank_pm1, 691.06)], 'customer': self.customer, 'is_invoiced': True, 'uuid': '00100-010-0001'},
{'pos_order_lines_ui_args': [(self.product1, 5), (self.product2, 5)], 'customer': self.customer, 'uuid': '00100-010-0002'},
{'pos_order_lines_ui_args': [(self.product2, 5), (self.product3, 5)], 'customer': self.other_customer, 'is_invoiced': True, 'uuid': '00100-010-0003'},
],
'before_closing_cb': _before_closing_cb,
'journal_entries_before_closing': {

View file

@ -14,6 +14,8 @@ class TestReportPoSOrder(TestPoSCommon):
def test_report_pos_order_0(self):
"""Test the margin and price_total of a PoS Order with no taxes."""
product1 = self.create_product('Product 1', self.categ_basic, 150)
self.categ_all = self.env['pos.category'].search([])
product1.write({'pos_categ_ids': [odoo.Command.set(self.categ_all.ids)]})
self.open_new_session()
session = self.pos_session
@ -38,6 +40,7 @@ class TestReportPoSOrder(TestPoSCommon):
# PoS Orders have negative IDs to avoid conflict, so reports[0] will correspond to the newest order
reports = self.env['report.pos.order'].sudo().search([('product_id', '=', product1.id)], order='id')
self.assertEqual(len(reports.ids), 1)
self.assertEqual(reports[0].margin, 150)
self.assertEqual(reports[0].price_total, 150)

View file

@ -0,0 +1,392 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
from odoo.exceptions import UserError
@odoo.tests.tagged('post_install', '-at_install')
class TestReportSession(TestPoSCommon):
def setUp(self):
super(TestReportSession, self).setUp()
self.config = self.basic_config
def test_report_session(self):
self.tax1 = self.env['account.tax'].create({
'name': 'Tax 1',
'amount': 10,
'price_include_override': 'tax_included',
})
self.product1 = self.create_product('Product A', self.categ_basic, 110, self.tax1.id)
product_to_archive = self.create_product('Product to archive', self.categ_basic, 100, self.tax1.id)
self.config.open_ui()
# check that an unsed product can be archived by any user with archive rights
self.res_users_stock_user.group_ids |= self.env.ref('product.group_product_manager')
product_to_archive.with_user(self.res_users_stock_user).action_archive()
session_id = self.config.current_session_id.id
order = self.env['pos.order'].create({
'company_id': self.env.company.id,
'session_id': session_id,
'partner_id': self.partner_a.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product1.id,
'price_unit': 110,
'discount': 0,
'qty': 1,
'tax_ids': [[6, False, [self.tax1.id]]],
'price_subtotal': 100,
'price_subtotal_incl': 110,
})],
'pricelist_id': self.config.pricelist_id.id,
'amount_paid': 110.0,
'amount_total': 110.0,
'amount_tax': 10.0,
'amount_return': 0.0,
'last_order_preparation_change': '{}',
'to_invoice': False,
})
# check that an used product can not be archived
with self.assertRaisesRegex(UserError, "Hold up! Archiving products while POS sessions are active is like pulling a plate mid-meal.\nMake sure to close all sessions first to avoid any issues."):
self.product1.with_user(self.res_users_stock_user).action_archive()
self.make_payment(order, self.bank_split_pm1, 60)
self.make_payment(order, self.bank_pm1, 50)
self.config.current_session_id.action_pos_session_closing_control(bank_payment_method_diffs={self.bank_split_pm1.id: 50, self.bank_pm1.id: 40})
# PoS Orders have negative IDs to avoid conflict, so reports[0] will correspond to the newest order
report = self.env['report.point_of_sale.report_saledetails'].get_sale_details(session_ids=[session_id])
split_payment_bank = [p for p in report['payments'] if p.get('id', 0) == self.bank_split_pm1.id]
self.assertEqual(split_payment_bank[0]['cash_moves'][0]['amount'], 50)
bank_payment = [p for p in report['payments'] if p.get('id', 0) == self.bank_pm1.id]
# self.assertEqual(bank_payment[0]['cash_moves'][0]['amount'], 40) TODO WAN
self.assertEqual(report['products_info']['total'], 100, "Total amount of products should be 100, as we want total without tax")
self.assertEqual(report['products'][0]['products'][0]['base_amount'], 100, "Base amount of product should be 100, as we want price without tax")
def test_report_session_2(self):
self.product1 = self.create_product('Product A', self.categ_basic, 100)
self.config.open_ui()
session_id_1 = self.config.current_session_id.id
order_info = {'company_id': self.env.company.id,
'session_id': session_id_1,
'partner_id': self.partner_a.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product1.id,
'price_unit': 100,
'discount': 0,
'qty': 1,
'tax_ids': [],
'price_subtotal': 100,
'price_subtotal_incl': 100,
})],
'pricelist_id': self.config.pricelist_id.id,
'amount_paid': 100.0,
'amount_total': 100.0,
'amount_tax': 0.0,
'amount_return': 0.0,
'to_invoice': False,
}
order = self.env['pos.order'].create(order_info)
self.make_payment(order, self.bank_pm1, 100)
order = self.env['pos.order'].create(order_info)
self.make_payment(order, self.cash_pm1, 100)
self.config.current_session_id.action_pos_session_closing_control()
self.config.open_ui()
session_id_2 = self.config.current_session_id.id
order_info['session_id'] = session_id_2
order = self.env['pos.order'].create(order_info)
self.make_payment(order, self.bank_pm1, 100)
order = self.env['pos.order'].create(order_info)
self.make_payment(order, self.cash_pm1, 100)
self.config.current_session_id.action_pos_session_closing_control()
report = self.env['report.point_of_sale.report_saledetails'].get_sale_details()
for payment in report['payments']:
session_name = self.env['pos.session'].browse(payment['session']).name
payment_method_name = self.env['pos.payment.method'].browse(payment['id']).name
self.assertEqual(payment['name'], payment_method_name + " " + session_name)
pdf = self.env['ir.actions.report']._render_qweb_pdf('point_of_sale.sale_details_report', res_ids=session_id_2)
self.assertTrue(pdf)
def test_report_listing(self):
product1 = self.create_product('Product 1', self.categ_basic, 150)
product2 = self.create_product('Product 2', self.categ_basic, 150)
cash_payment_method = self.env['pos.payment.method'].create({
'name': 'Cash',
'receivable_account_id': self.company_data['default_account_receivable'].id,
'journal_id': self.company_data['default_journal_cash'].id,
'company_id': self.env.company.id,
})
bank_payment_method = self.env['pos.payment.method'].create({
'name': 'Bank',
'journal_id': self.company_data['default_journal_bank'].id,
'receivable_account_id': self.company_data['default_account_receivable'].id,
'company_id': self.env.company.id,
})
self.config.write({'payment_method_ids': [(4, bank_payment_method.id), (4, cash_payment_method.id)]})
self.open_new_session()
session = self.pos_session
self.tax_sale_a['amount'] = 10
order = self.env['pos.order'].create({
'session_id': session.id,
'lines': [(0, 0, {
'name': "TR/0001",
'product_id': product1.id,
'price_unit': 150,
'discount': 0,
'qty': 1.0,
'price_subtotal': 150,
'tax_ids': [(6, 0, self.tax_sale_a.ids)],
'price_subtotal_incl': 165,
}), (0, 0, {
'name': "TR/0001",
'product_id': product2.id,
'price_unit': 150,
'discount': 0,
'qty': 1.0,
'price_subtotal': 150,
'tax_ids': [(6, 0, self.tax_sale_a.ids)],
'price_subtotal_incl': 165,
})],
'amount_total': 330.0,
'amount_tax': 30.0,
'amount_paid': 0.0,
'amount_return': 0.0,
})
payment_context = {"active_ids": order.ids, "active_id": order.id}
order_payment = self.env['pos.make.payment'].with_context(**payment_context).create([{
'amount': am,
'payment_method_id': pm
} for am in [65, 100] for pm in [cash_payment_method.id, bank_payment_method.id]])
for payment in order_payment:
payment.with_context(**payment_context).check()
order_report_lines = self.env['report.pos.order'].sudo().search([('order_id', '=', order.id)])
self.assertEqual(len(order_report_lines), 2)
self.assertEqual(order_report_lines[0].payment_method_id.id, order_report_lines[1].payment_method_id.id)
for order in order_report_lines:
self.assertEqual(order.price_total, 165.0)
self.assertEqual(order.nbr_lines, 1)
self.assertEqual(order.product_qty, 1)
order_report_lines_count_product1 = self.env['report.pos.order'].sudo().search_count([('product_id', '=', product1.id)])
order_report_lines_count_product2 = self.env['report.pos.order'].sudo().search_count([('product_id', '=', product2.id)])
self.assertEqual(order_report_lines_count_product1, 1)
self.assertEqual(order_report_lines_count_product2, 1)
def test_report_session_3(self):
self.product1 = self.create_product('Product A', self.categ_basic, 100)
self.config.open_ui()
session_id = self.config.current_session_id.id
order_info = {'company_id': self.env.company.id,
'session_id': session_id,
'partner_id': self.partner_a.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product1.id,
'price_unit': 0,
'discount': 0,
'qty': 14.9,
'tax_ids': [],
'price_subtotal': 0,
'price_subtotal_incl': 0,
})],
'pricelist_id': self.config.pricelist_id.id,
'amount_paid': 0.0,
'amount_total': 0.0,
'amount_tax': 0.0,
'amount_return': 0.0,
'to_invoice': False,
}
order = self.env['pos.order'].create(order_info)
self.make_payment(order, self.bank_pm1, 0)
order_info['lines'][0][2]['qty'] = 59.7
order = self.env['pos.order'].create(order_info)
self.make_payment(order, self.bank_pm1, 0)
self.config.current_session_id.action_pos_session_closing_control()
report = self.env['report.point_of_sale.report_saledetails'].get_sale_details()
self.assertEqual(report['products'][0]['products'][0]['quantity'], 74.6, "Quantity of product should be 74.6, as we want the sum of the quantity of the two orders")
def test_report_bank_expected_different_than_counted(self):
"""
Test that in the pos session report, the difference between the expected and counted bank payment is correct.
Test both with a default outstanding account on the payment and without.
"""
self.tax1 = self.env['account.tax'].create({
'name': 'Tax 1',
'amount': 10,
'price_include_override': 'tax_included',
})
self.product1 = self.create_product('Product A', self.categ_basic, 100, self.tax1.id)
self.bank_pm1.outstanding_account_id = self.outstanding_bank.id
self.config.open_ui()
session1_id = self.config.current_session_id.id
order1 = self.env['pos.order'].create({
'company_id': self.env.company.id,
'session_id': session1_id,
'partner_id': self.partner_a.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product1.id,
'price_unit': 100,
'discount': 0,
'qty': 1,
'tax_ids': [[6, False, [self.tax1.id]]],
'price_subtotal': 100,
'price_subtotal_incl': 100,
})],
'pricelist_id': self.config.pricelist_id.id,
'amount_paid': 100.0,
'amount_total': 100.0,
'amount_tax': 10.0,
'amount_return': 0.0,
'last_order_preparation_change': '{}',
'to_invoice': False,
})
self.make_payment(order1, self.bank_pm1, 100)
self.config.current_session_id.action_pos_session_closing_control(
bank_payment_method_diffs={self.bank_pm1.id: -20})
report = self.env['report.point_of_sale.report_saledetails'].get_sale_details(session_ids=[session1_id])
self.assertEqual(report['payments'][1]['money_difference'], -20)
self.bank_pm1.outstanding_account_id = False
self.config.open_ui()
session2_id = self.config.current_session_id.id
order2 = self.env['pos.order'].create({
'company_id': self.env.company.id,
'session_id': session2_id,
'partner_id': self.partner_a.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product1.id,
'price_unit': 100,
'discount': 0,
'qty': 1,
'tax_ids': [[6, False, [self.tax1.id]]],
'price_subtotal': 100,
'price_subtotal_incl': 100,
})],
'pricelist_id': self.config.pricelist_id.id,
'amount_paid': 100.0,
'amount_total': 100.0,
'amount_tax': 10.0,
'amount_return': 0.0,
'last_order_preparation_change': '{}',
'to_invoice': False,
})
self.make_payment(order2, self.bank_pm1, 100)
self.config.current_session_id.action_pos_session_closing_control(bank_payment_method_diffs={self.bank_pm1.id: -20})
report = self.env['report.point_of_sale.report_saledetails'].get_sale_details(session_ids=[session2_id])
self.assertEqual(report['payments'][1]['money_difference'], -20)
def test_report_session_4(self):
self.tax1 = self.env['account.tax'].create({
'name': 'Tax 1',
'amount': 10,
'price_include': True,
})
self.tax2 = self.env['account.tax'].create({
'name': 'Tax 2',
'amount': 15,
'price_include': True,
})
self.product1 = self.create_product('Product A', self.categ_basic, 125, self.tax1.id)
self.config.open_ui()
session_id = self.config.current_session_id.id
order_info = {
'company_id': self.env.company.id,
'session_id': session_id,
'partner_id': self.partner_a.id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': self.product1.id,
'price_unit': 125,
'discount': 0,
'qty': 1,
'tax_ids': [[6, False, [self.tax1.id, self.tax2.id]]],
'price_subtotal': 100,
'price_subtotal_incl': 125,
})],
'pricelist_id': self.config.pricelist_id.id,
'amount_paid': 125.0,
'amount_total': 156.25,
'amount_tax': 25.0,
'amount_return': 0.0,
'last_order_preparation_change': '{}',
'to_invoice': False,
}
order = self.env['pos.order'].create(order_info)
self.make_payment(order, self.bank_pm1, 156.25)
self.config.current_session_id.action_pos_session_closing_control()
report = self.env['report.point_of_sale.report_saledetails'].get_sale_details()
self.assertEqual(report["taxes_info"]["base_amount"], 100, "Base amount should be equal to 100")
def test_report_sum_taxes_base_amounts(self):
tax_included = self.env['account.tax'].create({
'name': 'Tax Included',
'amount': 5,
'price_include_override': 'tax_included',
})
product = self.create_product('Product A', self.categ_basic, 110, tax_included.id)
self.config.open_ui()
session_id = self.config.current_session_id.id
order_info = [{
'company_id': self.env.company.id,
'session_id': session_id,
'lines': [(0, 0, {
'name': "OL/0001",
'product_id': product.id,
'price_unit': 100,
'discount': 0,
'qty': 1,
'tax_ids': [[6, False, [tax_included.id]]],
'price_subtotal': 100,
'price_subtotal_incl': 100,
})],
'pricelist_id': self.config.pricelist_id.id,
'amount_paid': 100.0,
'amount_total': 100.0,
'amount_tax': 4.76,
'amount_return': 0.0,
'last_order_preparation_change': '{}',
'to_invoice': False,
} for _ in range(5)]
for order_data in order_info:
order = self.env['pos.order'].create(order_data)
self.make_payment(order, self.bank_pm1, 100.0)
self.config.current_session_id.action_pos_session_closing_control()
report = self.env['report.point_of_sale.report_saledetails'].get_sale_details()
self.assertAlmostEqual(report["taxes"][0]["base_amount"], 95.24 * 5, msg="Base amount should be equal to 476.20")

View file

@ -2,8 +2,9 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import odoo
from odoo import Command
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
from odoo.tests.common import Form
@odoo.tests.tagged('post_install', '-at_install')
@ -31,47 +32,61 @@ class TestConfigureShops(TestPoSCommon):
('company_id', '=', self.env.company.id), ('tax_exigibility', '=', 'on_payment')
]).unlink()
def test_should_not_affect_other_pos_config(self):
""" Change in one pos.config should not reflect to the other.
"""
def test_properly_set_pos_config_x2many_fields(self):
"""Simulate what is done from the res.config.settings view when editing x2 many fields."""
self._remove_on_payment_taxes()
pos_config1 = self.env['pos.config'].create({'name': 'Shop 1'})
pos_config2 = self.env['pos.config'].create({'name': 'Shop 2'})
self.assertEqual(pos_config1.receipt_header, False)
self.assertEqual(pos_config2.receipt_header, False)
# Modify Shop 1.
with Form(self.env['res.config.settings']) as form:
form.pos_config_id = pos_config1
form.pos_is_header_or_footer = True
form.pos_receipt_header = 'xxxxx'
self.assertEqual(pos_config1.receipt_header, 'xxxxx')
self.assertEqual(pos_config2.receipt_header, False)
# Modify Shop 2.
with Form(self.env['res.config.settings']) as form:
form.pos_config_id = pos_config2
form.pos_is_header_or_footer = True
form.pos_receipt_header = 'yyyyy'
self.assertEqual(pos_config1.receipt_header, 'xxxxx')
self.assertEqual(pos_config2.receipt_header, 'yyyyy')
def test_is_header_or_footer_to_false(self):
self._remove_on_payment_taxes()
pos_config = self.env['pos.config'].create({
'name': 'Shop',
'is_header_or_footer': True,
'receipt_header': 'header val',
'receipt_footer': 'footer val',
'name': 'Shop 1',
'module_pos_restaurant': False,
'payment_method_ids': [
Command.create({
'name': 'Bank 1',
'receivable_account_id': self.env.company.account_default_pos_receivable_account_id.id,
'is_cash_count': False,
'split_transactions': False,
'company_id': self.env.company.id,
}),
Command.create({
'name': 'Bank 2',
'receivable_account_id': self.env.company.account_default_pos_receivable_account_id.id,
'is_cash_count': False,
'split_transactions': False,
'company_id': self.env.company.id,
}),
Command.create({
'name': 'Cash',
'receivable_account_id': self.env.company.account_default_pos_receivable_account_id.id,
'is_cash_count': True,
'company_id': self.env.company.id,
})
]
})
with Form(self.env['res.config.settings']) as form:
form.pos_config_id = pos_config
form.pos_is_header_or_footer = False
# Manually simulate the unlinking of the second record and then save the settings.
# It will be a set of link commands except the one we want to delete.
linked_ids = pos_config.payment_method_ids.ids
second_id = linked_ids[1]
commands = [Command.link(_id) for _id in linked_ids if _id != second_id]
self.assertEqual(pos_config.receipt_header, False)
self.assertEqual(pos_config.receipt_footer, False)
pos_config.with_context(from_settings_view=True).write({
'payment_method_ids': commands
})
self.assertTrue(second_id not in pos_config.payment_method_ids.ids)
self.assertTrue(len(pos_config.payment_method_ids) == 2)
def test_write_default_and_available_presets_on_multiple_pos_configs(self):
preset = self.env['pos.preset'].create({'name': 'Preset 1'})
pos_config1 = self.env['pos.config'].create({'name': 'Shop 1', 'module_pos_restaurant': False})
pos_config2 = self.env['pos.config'].create({'name': 'Shop 2', 'module_pos_restaurant': False})
pos_config3 = self.env['pos.config'].create({'name': 'Shop 3', 'module_pos_restaurant': False})
pos_configs = pos_config1 | pos_config2 | pos_config3
pos_configs.write({
'use_presets': True,
'available_preset_ids': [(6, 0, [preset.id])],
'default_preset_id': preset.id,
})