2003 lines
87 KiB
Python
2003 lines
87 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import time
|
|
from freezegun import freeze_time
|
|
from datetime import datetime
|
|
|
|
import odoo
|
|
from odoo import fields, tools
|
|
from odoo.tools import float_compare, mute_logger, test_reports
|
|
from odoo.tests.common import Form
|
|
from odoo.addons.point_of_sale.tests.common import TestPointOfSaleCommon
|
|
|
|
|
|
@odoo.tests.tagged('post_install', '-at_install')
|
|
class TestPointOfSaleFlow(TestPointOfSaleCommon):
|
|
|
|
def compute_tax(self, product, price, qty=1, taxes=None):
|
|
if not taxes:
|
|
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id)
|
|
currency = self.pos_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'])
|
|
|
|
def _create_pos_order_for_postponed_invoicing(self):
|
|
# Create the order on the first of january.
|
|
with freeze_time('2020-01-01'):
|
|
product = self.env['product.product'].create({
|
|
'name': 'Dummy product',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'taxes_id': self.tax_sale_a.ids,
|
|
})
|
|
self.pos_config.open_ui()
|
|
pos_session = self.pos_config.current_session_id
|
|
untax, atax = self.compute_tax(product, 500, 1)
|
|
pos_order_data = {
|
|
'data': {
|
|
'amount_paid': untax + atax,
|
|
'amount_return': 0,
|
|
'amount_tax': atax,
|
|
'amount_total': untax + atax,
|
|
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
|
|
'fiscal_position_id': False,
|
|
'lines': [(0, 0, {
|
|
'discount': 0,
|
|
'id': 42,
|
|
'pack_lot_ids': [],
|
|
'price_unit': 500.0,
|
|
'product_id': product.id,
|
|
'price_subtotal': 500.0,
|
|
'price_subtotal_incl': 575.0,
|
|
'qty': 1,
|
|
'tax_ids': [(6, 0, product.taxes_id.ids)]
|
|
})],
|
|
'name': 'Order 12345-123-1234',
|
|
'partner_id': False,
|
|
'pos_session_id': pos_session.id,
|
|
'sequence_number': 2,
|
|
'statement_ids': [(0, 0, {
|
|
'amount': untax + atax,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})],
|
|
'uid': '12345-123-1234',
|
|
'user_id': self.env.uid
|
|
},
|
|
'id': '12345-123-1234',
|
|
'to_invoice': False
|
|
}
|
|
pos_order_id = self.PosOrder.create_from_ui([pos_order_data])[0]['id']
|
|
pos_order = self.env['pos.order'].browse(pos_order_id)
|
|
# End the session. The order has been created without any invoice.
|
|
self.pos_config.current_session_id.action_pos_session_closing_control()
|
|
return pos_order
|
|
|
|
def test_order_refund(self):
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
# I create a new PoS order with 2 lines
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product3.id,
|
|
'price_unit': 450,
|
|
'discount': 5.0,
|
|
'qty': 2.0,
|
|
'tax_ids': [(6, 0, self.product3.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': 450 * (1 - 5/100.0) * 2,
|
|
'price_subtotal_incl': 450 * (1 - 5/100.0) * 2,
|
|
}), (0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': self.product4.id,
|
|
'price_unit': 300,
|
|
'discount': 5.0,
|
|
'qty': 3.0,
|
|
'tax_ids': [(6, 0, self.product4.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': 300 * (1 - 5/100.0) * 3,
|
|
'price_subtotal_incl': 300 * (1 - 5/100.0) * 3,
|
|
})],
|
|
'amount_total': 1710.0,
|
|
'amount_tax': 0.0,
|
|
'amount_paid': 0.0,
|
|
'amount_return': 0.0,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
self.assertAlmostEqual(order.amount_total, order.amount_paid, msg='Order should be fully paid.')
|
|
|
|
# I create a refund
|
|
refund_action = order.refund()
|
|
refund = self.PosOrder.browse(refund_action['res_id'])
|
|
|
|
self.assertEqual(order.amount_total, -1*refund.amount_total,
|
|
"The refund does not cancel the order (%s and %s)" % (order.amount_total, refund.amount_total))
|
|
|
|
payment_context = {"active_ids": refund.ids, "active_id": refund.id}
|
|
refund_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': refund.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id,
|
|
})
|
|
|
|
# I click on the validate button to register the payment.
|
|
refund_payment.with_context(**payment_context).check()
|
|
|
|
self.assertEqual(refund.state, 'paid', "The refund is not marked as paid")
|
|
self.assertTrue(refund.payment_ids.payment_method_id.is_cash_count, msg='There should only be one payment and paid in cash.')
|
|
|
|
total_cash_payment = sum(current_session.mapped('order_ids.payment_ids').filtered(lambda payment: payment.payment_method_id.type == 'cash').mapped('amount'))
|
|
current_session.post_closing_cash_details(total_cash_payment)
|
|
current_session.close_session_from_ui()
|
|
self.assertEqual(current_session.state, 'closed', msg='State of current session should be closed.')
|
|
|
|
def test_order_refund_lots(self):
|
|
# open pos session
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
|
|
# set up product iwith SN tracing and create two lots (1001, 1002)
|
|
self.stock_location = self.company_data['default_warehouse'].lot_stock_id
|
|
self.product2 = self.env['product.product'].create({
|
|
'name': 'Product A',
|
|
'type': 'product',
|
|
'tracking': 'serial',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
|
|
lot1 = self.env['stock.lot'].create({
|
|
'name': '1001',
|
|
'product_id': self.product2.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
lot2 = self.env['stock.lot'].create({
|
|
'name': '1002',
|
|
'product_id': self.product2.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product2.id,
|
|
'inventory_quantity': 1,
|
|
'location_id': self.stock_location.id,
|
|
'lot_id': lot1.id
|
|
}).action_apply_inventory()
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product2.id,
|
|
'inventory_quantity': 1,
|
|
'location_id': self.stock_location.id,
|
|
'lot_id': lot2.id
|
|
}).action_apply_inventory()
|
|
|
|
# create pos order with the two SN created before
|
|
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product2.id,
|
|
'price_unit': 6,
|
|
'discount': 0,
|
|
'qty': 2,
|
|
'tax_ids': [[6, False, []]],
|
|
'price_subtotal': 12,
|
|
'price_subtotal_incl': 12,
|
|
'pack_lot_ids': [
|
|
[0, 0, {'lot_name': '1001'}],
|
|
[0, 0, {'lot_name': '1002'}],
|
|
]
|
|
})],
|
|
'pricelist_id': self.pos_config.pricelist_id.id,
|
|
'amount_paid': 12.0,
|
|
'amount_total': 12.0,
|
|
'amount_tax': 0.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
|
|
# I create a refund
|
|
refund_action = order.refund()
|
|
refund = self.PosOrder.browse(refund_action['res_id'])
|
|
|
|
order_lot_id = [lot_id.lot_name for lot_id in order.lines.pack_lot_ids]
|
|
refund_lot_id = [lot_id.lot_name for lot_id in refund.lines.pack_lot_ids]
|
|
self.assertEqual(
|
|
order_lot_id,
|
|
refund_lot_id,
|
|
"In the refund we should find the same lot as in the original order")
|
|
|
|
payment_context = {"active_ids": refund.ids, "active_id": refund.id}
|
|
refund_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': refund.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id,
|
|
})
|
|
|
|
# I click on the validate button to register the payment.
|
|
refund_payment.with_context(**payment_context).check()
|
|
|
|
self.assertEqual(refund.state, 'paid', "The refund is not marked as paid")
|
|
current_session.action_pos_session_closing_control()
|
|
|
|
def test_order_to_picking(self):
|
|
"""
|
|
In order to test the Point of Sale in module, I will do three orders from the sale to the payment,
|
|
invoicing + picking, but will only check the picking consistency in the end.
|
|
|
|
TODO: Check the negative picking after changing the picking relation to One2many (also for a mixed use case),
|
|
check the quantity, the locations and return picking logic
|
|
"""
|
|
|
|
# I click on create a new session button
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
|
|
# I create a PoS order with 2 units of PCSC234 at 450 EUR
|
|
# and 3 units of PCSC349 at 300 EUR.
|
|
untax1, atax1 = self.compute_tax(self.product3, 450, 2)
|
|
untax2, atax2 = self.compute_tax(self.product4, 300, 3)
|
|
self.pos_order_pos1 = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product3.id,
|
|
'price_unit': 450,
|
|
'discount': 0.0,
|
|
'qty': 2.0,
|
|
'tax_ids': [(6, 0, self.product3.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax1,
|
|
'price_subtotal_incl': untax1 + atax1,
|
|
}), (0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': self.product4.id,
|
|
'price_unit': 300,
|
|
'discount': 0.0,
|
|
'qty': 3.0,
|
|
'tax_ids': [(6, 0, self.product4.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax2,
|
|
'price_subtotal_incl': untax2 + atax2,
|
|
})],
|
|
'amount_tax': atax1 + atax2,
|
|
'amount_total': untax1 + untax2 + atax1 + atax2,
|
|
'amount_paid': 0,
|
|
'amount_return': 0,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
context_make_payment = {
|
|
"active_ids": [self.pos_order_pos1.id],
|
|
"active_id": self.pos_order_pos1.id
|
|
}
|
|
self.pos_make_payment_2 = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': untax1 + untax2 + atax1 + atax2
|
|
})
|
|
|
|
# I click on the validate button to register the payment.
|
|
context_payment = {'active_id': self.pos_order_pos1.id}
|
|
|
|
self.pos_make_payment_2.with_context(context_payment).check()
|
|
# I check that the order is marked as paid
|
|
self.assertEqual(
|
|
self.pos_order_pos1.state,
|
|
'paid',
|
|
'Order should be in paid state.'
|
|
)
|
|
|
|
# I test that the pickings are created as expected during payment
|
|
# One picking attached and having all the positive move lines in the correct state
|
|
self.assertEqual(
|
|
self.pos_order_pos1.picking_ids[0].state,
|
|
'done',
|
|
'Picking should be in done state.'
|
|
)
|
|
self.assertEqual(
|
|
self.pos_order_pos1.picking_ids[0].move_ids.mapped('state'),
|
|
['done', 'done'],
|
|
'Move Lines should be in done state.'
|
|
)
|
|
|
|
# I create a second order
|
|
untax1, atax1 = self.compute_tax(self.product3, 450, -2)
|
|
untax2, atax2 = self.compute_tax(self.product4, 300, -3)
|
|
self.pos_order_pos2 = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0003",
|
|
'product_id': self.product3.id,
|
|
'price_unit': 450,
|
|
'discount': 0.0,
|
|
'qty': (-2.0),
|
|
'tax_ids': [(6, 0, self.product3.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax1,
|
|
'price_subtotal_incl': untax1 + atax1,
|
|
}), (0, 0, {
|
|
'name': "OL/0004",
|
|
'product_id': self.product4.id,
|
|
'price_unit': 300,
|
|
'discount': 0.0,
|
|
'qty': (-3.0),
|
|
'tax_ids': [(6, 0, self.product4.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax2,
|
|
'price_subtotal_incl': untax2 + atax2,
|
|
})],
|
|
'amount_tax': atax1 + atax2,
|
|
'amount_total': untax1 + untax2 + atax1 + atax2,
|
|
'amount_paid': 0,
|
|
'amount_return': 0,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
context_make_payment = {
|
|
"active_ids": [self.pos_order_pos2.id],
|
|
"active_id": self.pos_order_pos2.id
|
|
}
|
|
self.pos_make_payment_3 = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': untax1 + untax2 + atax1 + atax2
|
|
})
|
|
|
|
# I click on the validate button to register the payment.
|
|
context_payment = {'active_id': self.pos_order_pos2.id}
|
|
self.pos_make_payment_3.with_context(context_payment).check()
|
|
|
|
# I check that the order is marked as paid
|
|
self.assertEqual(
|
|
self.pos_order_pos2.state,
|
|
'paid',
|
|
'Order should be in paid state.'
|
|
)
|
|
|
|
# I test that the pickings are created as expected
|
|
# One picking attached and having all the positive move lines in the correct state
|
|
self.assertEqual(
|
|
self.pos_order_pos2.picking_ids[0].state,
|
|
'done',
|
|
'Picking should be in done state.'
|
|
)
|
|
self.assertEqual(
|
|
self.pos_order_pos2.picking_ids[0].move_ids.mapped('state'),
|
|
['done', 'done'],
|
|
'Move Lines should be in done state.'
|
|
)
|
|
|
|
untax1, atax1 = self.compute_tax(self.product3, 450, -2)
|
|
untax2, atax2 = self.compute_tax(self.product4, 300, 3)
|
|
self.pos_order_pos3 = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0005",
|
|
'product_id': self.product3.id,
|
|
'price_unit': 450,
|
|
'discount': 0.0,
|
|
'qty': (-2.0),
|
|
'tax_ids': [(6, 0, self.product3.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax1,
|
|
'price_subtotal_incl': untax1 + atax1,
|
|
}), (0, 0, {
|
|
'name': "OL/0006",
|
|
'product_id': self.product4.id,
|
|
'price_unit': 300,
|
|
'discount': 0.0,
|
|
'qty': 3.0,
|
|
'tax_ids': [(6, 0, self.product4.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax2,
|
|
'price_subtotal_incl': untax2 + atax2,
|
|
})],
|
|
'amount_tax': atax1 + atax2,
|
|
'amount_total': untax1 + untax2 + atax1 + atax2,
|
|
'amount_paid': 0,
|
|
'amount_return': 0,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
context_make_payment = {
|
|
"active_ids": [self.pos_order_pos3.id],
|
|
"active_id": self.pos_order_pos3.id
|
|
}
|
|
self.pos_make_payment_4 = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': untax1 + untax2 + atax1 + atax2,
|
|
})
|
|
|
|
# I click on the validate button to register the payment.
|
|
context_payment = {'active_id': self.pos_order_pos3.id}
|
|
self.pos_make_payment_4.with_context(context_payment).check()
|
|
|
|
# I check that the order is marked as paid
|
|
self.assertEqual(
|
|
self.pos_order_pos3.state,
|
|
'paid',
|
|
'Order should be in paid state.'
|
|
)
|
|
|
|
# I test that the pickings are created as expected
|
|
# One picking attached and having all the positive move lines in the correct state
|
|
self.assertEqual(
|
|
self.pos_order_pos3.picking_ids[0].state,
|
|
'done',
|
|
'Picking should be in done state.'
|
|
)
|
|
self.assertEqual(
|
|
self.pos_order_pos3.picking_ids[0].move_ids.mapped('state'),
|
|
['done'],
|
|
'Move Lines should be in done state.'
|
|
)
|
|
# I close the session to generate the journal entries
|
|
self.pos_config.current_session_id.action_pos_session_closing_control()
|
|
|
|
def test_order_to_picking02(self):
|
|
""" This test is similar to test_order_to_picking except that this time, there are two products:
|
|
- One tracked by lot
|
|
- One untracked
|
|
- Both are in a sublocation of the main warehouse
|
|
"""
|
|
tracked_product, untracked_product = self.env['product.product'].create([{
|
|
'name': 'SuperProduct Tracked',
|
|
'type': 'product',
|
|
'tracking': 'lot',
|
|
'available_in_pos': True,
|
|
}, {
|
|
'name': 'SuperProduct Untracked',
|
|
'type': 'product',
|
|
'available_in_pos': True,
|
|
}])
|
|
wh_location = self.company_data['default_warehouse'].lot_stock_id
|
|
shelf1_location = self.env['stock.location'].create({
|
|
'name': 'shelf1',
|
|
'usage': 'internal',
|
|
'location_id': wh_location.id,
|
|
})
|
|
lot = self.env['stock.lot'].create({
|
|
'name': 'SuperLot',
|
|
'product_id': tracked_product.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
qty = 2
|
|
self.env['stock.quant']._update_available_quantity(tracked_product, shelf1_location, qty, lot_id=lot)
|
|
self.env['stock.quant']._update_available_quantity(untracked_product, shelf1_location, qty)
|
|
|
|
self.pos_config.open_ui()
|
|
self.pos_config.current_session_id.update_stock_at_closing = False
|
|
|
|
untax, atax = self.compute_tax(tracked_product, 1.15, 1)
|
|
|
|
for dummy in range(qty):
|
|
pos_order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': self.pos_config.current_session_id.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': tracked_product.id,
|
|
'price_unit': 1.15,
|
|
'discount': 0.0,
|
|
'qty': 1.0,
|
|
'tax_ids': [(6, 0, tracked_product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax,
|
|
'price_subtotal_incl': untax + atax,
|
|
'pack_lot_ids': [[0, 0, {'lot_name': lot.name}]],
|
|
}), (0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': untracked_product.id,
|
|
'price_unit': 1.15,
|
|
'discount': 0.0,
|
|
'qty': 1.0,
|
|
'tax_ids': [(6, 0, untracked_product.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax,
|
|
'price_subtotal_incl': untax + atax,
|
|
})],
|
|
'amount_tax': 2 * atax,
|
|
'amount_total': 2 * (untax + atax),
|
|
'amount_paid': 0,
|
|
'amount_return': 0,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
context_make_payment = {
|
|
"active_ids": [pos_order.id],
|
|
"active_id": pos_order.id,
|
|
}
|
|
pos_make_payment = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': 2 * (untax + atax),
|
|
})
|
|
context_payment = {'active_id': pos_order.id}
|
|
pos_make_payment.with_context(context_payment).check()
|
|
|
|
self.assertEqual(pos_order.state, 'paid')
|
|
tracked_line = pos_order.picking_ids.move_line_ids.filtered(lambda ml: ml.product_id.id == tracked_product.id)
|
|
untracked_line = pos_order.picking_ids.move_line_ids - tracked_line
|
|
self.assertEqual(tracked_line.lot_id, lot)
|
|
self.assertFalse(untracked_line.lot_id)
|
|
self.assertEqual(tracked_line.location_id, shelf1_location)
|
|
self.assertEqual(untracked_line.location_id, shelf1_location)
|
|
|
|
self.pos_config.current_session_id.action_pos_session_closing_control()
|
|
|
|
def test_order_to_invoice(self):
|
|
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
|
|
untax1, atax1 = self.compute_tax(self.product3, 450*0.95, 2)
|
|
untax2, atax2 = self.compute_tax(self.product4, 300*0.95, 3)
|
|
# I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
|
|
self.pos_order_pos1 = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product3.id,
|
|
'price_unit': 450,
|
|
'discount': 5.0,
|
|
'qty': 2.0,
|
|
'tax_ids': [(6, 0, self.product3.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax1,
|
|
'price_subtotal_incl': untax1 + atax1,
|
|
}), (0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': self.product4.id,
|
|
'price_unit': 300,
|
|
'discount': 5.0,
|
|
'qty': 3.0,
|
|
'tax_ids': [(6, 0, self.product4.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)],
|
|
'price_subtotal': untax2,
|
|
'price_subtotal_incl': untax2 + atax2,
|
|
})],
|
|
'amount_tax': atax1 + atax2,
|
|
'amount_total': untax1 + untax2 + atax1 + atax2,
|
|
'amount_paid': 0.0,
|
|
'amount_return': 0.0,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
# I click on the "Make Payment" wizard to pay the PoS order
|
|
context_make_payment = {"active_ids": [self.pos_order_pos1.id], "active_id": self.pos_order_pos1.id}
|
|
self.pos_make_payment = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': untax1 + untax2 + atax1 + atax2,
|
|
})
|
|
# I click on the validate button to register the payment.
|
|
context_payment = {'active_id': self.pos_order_pos1.id}
|
|
self.pos_make_payment.with_context(context_payment).check()
|
|
|
|
# I check that the order is marked as paid and there is no invoice
|
|
# attached to it
|
|
self.assertEqual(self.pos_order_pos1.state, 'paid', "Order should be in paid state.")
|
|
self.assertFalse(self.pos_order_pos1.account_move, 'Invoice should not be attached to order.')
|
|
|
|
# I generate an invoice from the order
|
|
res = self.pos_order_pos1.action_pos_order_invoice()
|
|
self.assertIn('res_id', res, "Invoice should be created")
|
|
|
|
# I test that the total of the attached invoice is correct
|
|
invoice = self.env['account.move'].browse(res['res_id'])
|
|
if invoice.state != 'posted':
|
|
invoice.action_post()
|
|
self.assertAlmostEqual(
|
|
invoice.amount_total, self.pos_order_pos1.amount_total, places=2, msg="Invoice not correct")
|
|
|
|
# I close the session to generate the journal entries
|
|
current_session.action_pos_session_closing_control()
|
|
|
|
def test_create_from_ui(self):
|
|
"""
|
|
Simulation of sales coming from the interface, even after closing the session
|
|
"""
|
|
|
|
# I click on create a new session button
|
|
self.pos_config.open_ui()
|
|
|
|
current_session = self.pos_config.current_session_id
|
|
num_starting_orders = len(current_session.order_ids)
|
|
|
|
current_session.set_cashbox_pos(0, None)
|
|
|
|
untax, atax = self.compute_tax(self.led_lamp, 0.9)
|
|
carrot_order = {'data':
|
|
{'amount_paid': untax + atax,
|
|
'amount_return': 0,
|
|
'amount_tax': atax,
|
|
'amount_total': untax + atax,
|
|
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
|
|
'fiscal_position_id': False,
|
|
'lines': [[0,
|
|
0,
|
|
{'discount': 0,
|
|
'pack_lot_ids': [],
|
|
'price_unit': 0.9,
|
|
'product_id': self.led_lamp.id,
|
|
'price_subtotal': 0.9,
|
|
'price_subtotal_incl': 1.04,
|
|
'qty': 1,
|
|
'tax_ids': [(6, 0, self.led_lamp.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)]}]],
|
|
'name': 'Order 00042-003-0014',
|
|
'partner_id': False,
|
|
'pos_session_id': current_session.id,
|
|
'sequence_number': 2,
|
|
'statement_ids': [[0,
|
|
0,
|
|
{'amount': untax + atax,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.cash_payment_method.id}]],
|
|
'uid': '00042-003-0014',
|
|
'user_id': self.env.uid},
|
|
'to_invoice': False}
|
|
|
|
untax, atax = self.compute_tax(self.whiteboard_pen, 1.2)
|
|
zucchini_order = {'data':
|
|
{'amount_paid': untax + atax,
|
|
'amount_return': 0,
|
|
'amount_tax': atax,
|
|
'amount_total': untax + atax,
|
|
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
|
|
'fiscal_position_id': False,
|
|
'lines': [[0,
|
|
0,
|
|
{'discount': 0,
|
|
'pack_lot_ids': [],
|
|
'price_unit': 1.2,
|
|
'product_id': self.whiteboard_pen.id,
|
|
'price_subtotal': 1.2,
|
|
'price_subtotal_incl': 1.38,
|
|
'qty': 1,
|
|
'tax_ids': [(6, 0, self.whiteboard_pen.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids)]}]],
|
|
'name': 'Order 00043-003-0014',
|
|
'partner_id': self.partner1.id,
|
|
'pos_session_id': current_session.id,
|
|
'sequence_number': self.pos_config.journal_id.id,
|
|
'statement_ids': [[0,
|
|
0,
|
|
{'amount': untax + atax,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.credit_payment_method.id}]],
|
|
'uid': '00043-003-0014',
|
|
'user_id': self.env.uid},
|
|
'to_invoice': False}
|
|
|
|
untax, atax = self.compute_tax(self.newspaper_rack, 1.28)
|
|
newspaper_rack_order = {'data':
|
|
{'amount_paid': untax + atax,
|
|
'amount_return': 0,
|
|
'amount_tax': atax,
|
|
'amount_total': untax + atax,
|
|
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
|
|
'fiscal_position_id': False,
|
|
'lines': [[0,
|
|
0,
|
|
{'discount': 0,
|
|
'pack_lot_ids': [],
|
|
'price_unit': 1.28,
|
|
'product_id': self.newspaper_rack.id,
|
|
'price_subtotal': 1.28,
|
|
'price_subtotal_incl': 1.47,
|
|
'qty': 1,
|
|
'tax_ids': [[6, False, self.newspaper_rack.taxes_id.filtered(lambda t: t.company_id.id == self.env.company.id).ids]]}]],
|
|
'name': 'Order 00044-003-0014',
|
|
'partner_id': False,
|
|
'pos_session_id': current_session.id,
|
|
'sequence_number': self.pos_config.journal_id.id,
|
|
'statement_ids': [[0,
|
|
0,
|
|
{'amount': untax + atax,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.bank_payment_method.id}]],
|
|
'uid': '00044-003-0014',
|
|
'user_id': self.env.uid},
|
|
'to_invoice': False}
|
|
|
|
# I create an order on an open session
|
|
self.PosOrder.create_from_ui([carrot_order])
|
|
self.assertEqual(num_starting_orders + 1, len(current_session.order_ids), "Submitted order not encoded")
|
|
|
|
# I close the session
|
|
total_cash_payment = sum(current_session.mapped('order_ids.payment_ids').filtered(lambda payment: payment.payment_method_id.type == 'cash').mapped('amount'))
|
|
current_session.post_closing_cash_details(total_cash_payment)
|
|
current_session.close_session_from_ui()
|
|
self.assertEqual(current_session.state, 'closed', "Session was not properly closed")
|
|
self.assertFalse(self.pos_config.current_session_id, "Current session not properly recomputed")
|
|
|
|
# I keep selling after the session is closed
|
|
with mute_logger('odoo.addons.point_of_sale.models.pos_order'):
|
|
self.PosOrder.create_from_ui([zucchini_order, newspaper_rack_order])
|
|
rescue_session = self.PosSession.search([
|
|
('config_id', '=', self.pos_config.id),
|
|
('state', '=', 'opened'),
|
|
('rescue', '=', True)
|
|
])
|
|
self.assertEqual(len(rescue_session), 1, "One (and only one) rescue session should be created for orphan orders")
|
|
self.assertIn("(RESCUE FOR %s)" % current_session.name, rescue_session.name, "Rescue session is not linked to the previous one")
|
|
self.assertEqual(len(rescue_session.order_ids), 2, "Rescue session does not contain both orders")
|
|
|
|
# I close the rescue session
|
|
total_cash_payment = sum(rescue_session.mapped('order_ids.payment_ids').filtered(lambda payment: payment.payment_method_id.type == 'cash').mapped('amount'))
|
|
rescue_session.post_closing_cash_details(total_cash_payment)
|
|
rescue_session.close_session_from_ui()
|
|
self.assertEqual(rescue_session.state, 'closed', "Rescue session was not properly closed")
|
|
self.assertEqual(rescue_session.cash_register_balance_start, current_session.cash_register_balance_end_real, "Rescue session does not start with the same amount as the previous session")
|
|
|
|
def test_order_to_payment_currency(self):
|
|
"""
|
|
In order to test the Point of Sale in module, I will do a full flow from the sale to the payment and invoicing.
|
|
I will use two products, one with price including a 10% tax, the other one with 5% tax excluded from the price.
|
|
The order will be in a different currency than the company currency.
|
|
"""
|
|
# Make sure the company is in USD
|
|
self.env.ref('base.USD').active = True
|
|
self.env.ref('base.EUR').active = True
|
|
self.env.cr.execute(
|
|
"UPDATE res_company SET currency_id = %s WHERE id = %s",
|
|
[self.env.ref('base.USD').id, self.env.company.id])
|
|
|
|
# Demo data are crappy, clean-up the rates
|
|
self.env['res.currency.rate'].search([]).unlink()
|
|
self.env['res.currency.rate'].create({
|
|
'name': '2010-01-01',
|
|
'rate': 2.0,
|
|
'currency_id': self.env.ref('base.EUR').id,
|
|
})
|
|
|
|
# make a config that has currency different from the company
|
|
eur_pricelist = self.env['product.pricelist'].create({'name': 'Test EUR Pricelist', 'currency_id': self.env.ref('base.EUR').id})
|
|
sale_journal = self.env['account.journal'].create({
|
|
'name': 'PoS Sale EUR',
|
|
'type': 'sale',
|
|
'code': 'POSE',
|
|
'company_id': self.company.id,
|
|
'sequence': 12,
|
|
'currency_id': self.env.ref('base.EUR').id
|
|
})
|
|
eur_config = self.pos_config.create({
|
|
'name': 'Shop EUR Test',
|
|
'journal_id': sale_journal.id,
|
|
'use_pricelist': True,
|
|
'available_pricelist_ids': [(6, 0, eur_pricelist.ids)],
|
|
'pricelist_id': eur_pricelist.id,
|
|
'payment_method_ids': [(6, 0, self.bank_payment_method.ids)]
|
|
})
|
|
|
|
# I click on create a new session button
|
|
eur_config.open_ui()
|
|
current_session = eur_config.current_session_id
|
|
|
|
# I create a PoS order with 2 units of PCSC234 at 450 EUR (Tax Incl)
|
|
# and 3 units of PCSC349 at 300 EUR. (Tax Excl)
|
|
|
|
untax1, atax1 = self.compute_tax(self.product3, 450, 2)
|
|
untax2, atax2 = self.compute_tax(self.product4, 300, 3)
|
|
self.pos_order_pos0 = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'pricelist_id': eur_pricelist.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product3.id,
|
|
'price_unit': 450,
|
|
'discount': 0.0,
|
|
'qty': 2.0,
|
|
'tax_ids': [(6, 0, self.product3.taxes_id.filtered(lambda t: t.company_id == self.env.company).ids)],
|
|
'price_subtotal': untax1,
|
|
'price_subtotal_incl': untax1 + atax1,
|
|
}), (0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': self.product4.id,
|
|
'price_unit': 300,
|
|
'discount': 0.0,
|
|
'qty': 3.0,
|
|
'tax_ids': [(6, 0, self.product4.taxes_id.filtered(lambda t: t.company_id == self.env.company).ids)],
|
|
'price_subtotal': untax2,
|
|
'price_subtotal_incl': untax2 + atax2,
|
|
})],
|
|
'amount_tax': atax1 + atax2,
|
|
'amount_total': untax1 + untax2 + atax1 + atax2,
|
|
'amount_paid': 0.0,
|
|
'amount_return': 0.0,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
# I check that the total of the order is now equal to (450*2 +
|
|
# 300*3*1.05)*0.95
|
|
self.assertLess(
|
|
abs(self.pos_order_pos0.amount_total - (450 * 2 + 300 * 3 * 1.05)),
|
|
0.01, 'The order has a wrong total including tax and discounts')
|
|
|
|
# I click on the "Make Payment" wizard to pay the PoS order with a
|
|
# partial amount of 100.0 EUR
|
|
context_make_payment = {"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id}
|
|
self.pos_make_payment_0 = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': 100.0,
|
|
'payment_method_id': self.bank_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 check that the order is not marked as paid yet
|
|
self.assertEqual(self.pos_order_pos0.state, 'draft', 'Order should be in draft state.')
|
|
|
|
# On the second payment proposition, I check that it proposes me the
|
|
# remaining balance which is 1790.0 EUR
|
|
defs = self.pos_make_payment_0.with_context({'active_id': self.pos_order_pos0.id}).default_get(['amount'])
|
|
|
|
self.assertLess(
|
|
abs(defs['amount'] - ((450 * 2 + 300 * 3 * 1.05) - 100.0)), 0.01, "The remaining balance is incorrect.")
|
|
|
|
#'I pay the remaining balance.
|
|
context_make_payment = {
|
|
"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id}
|
|
|
|
self.pos_make_payment_1 = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': (450 * 2 + 300 * 3 * 1.05) - 100.0,
|
|
'payment_method_id': self.bank_payment_method.id,
|
|
})
|
|
|
|
# I click on the validate button to register the payment.
|
|
self.pos_make_payment_1.with_context(context_make_payment).check()
|
|
|
|
# I check that the order is marked as paid
|
|
self.assertEqual(self.pos_order_pos0.state, 'paid', 'Order should be in paid state.')
|
|
|
|
# I generate the journal entries
|
|
current_session.action_pos_session_validate()
|
|
|
|
# I test that the generated journal entry is attached to the PoS order
|
|
self.assertTrue(current_session.move_id, "Journal entry should have been attached to the session.")
|
|
|
|
# Check the amounts
|
|
debit_lines = current_session.move_id.mapped('line_ids.debit')
|
|
credit_lines = current_session.move_id.mapped('line_ids.credit')
|
|
amount_currency_lines = current_session.move_id.mapped('line_ids.amount_currency')
|
|
for a, b in zip(sorted(debit_lines), [0.0, 0.0, 0.0, 0.0, 922.5]):
|
|
self.assertAlmostEqual(a, b)
|
|
for a, b in zip(sorted(credit_lines), [0.0, 22.5, 40.91, 409.09, 450]):
|
|
self.assertAlmostEqual(a, b)
|
|
for a, b in zip(sorted(amount_currency_lines), [-900, -818.18, -81.82, -45, 1845]):
|
|
self.assertAlmostEqual(a, b)
|
|
|
|
def test_order_to_invoice_no_tax(self):
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
|
|
# I create a new PoS order with 2 units of PC1 at 450 EUR (Tax Incl) and 3 units of PCSC349 at 300 EUR. (Tax Excl)
|
|
self.pos_order_pos1 = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product3.id,
|
|
'price_unit': 450,
|
|
'discount': 5.0,
|
|
'qty': 2.0,
|
|
'price_subtotal': 855,
|
|
'price_subtotal_incl': 855,
|
|
}), (0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': self.product4.id,
|
|
'price_unit': 300,
|
|
'discount': 5.0,
|
|
'qty': 3.0,
|
|
'price_subtotal': 855,
|
|
'price_subtotal_incl': 855,
|
|
})],
|
|
'amount_tax': 855 * 2,
|
|
'amount_total': 855 * 2,
|
|
'amount_paid': 0.0,
|
|
'amount_return': 0.0,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
# I click on the "Make Payment" wizard to pay the PoS order
|
|
context_make_payment = {"active_ids": [self.pos_order_pos1.id], "active_id": self.pos_order_pos1.id}
|
|
self.pos_make_payment = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': 855 * 2,
|
|
})
|
|
# I click on the validate button to register the payment.
|
|
context_payment = {'active_id': self.pos_order_pos1.id}
|
|
self.pos_make_payment.with_context(context_payment).check()
|
|
|
|
# I check that the order is marked as paid and there is no invoice
|
|
# attached to it
|
|
self.assertEqual(self.pos_order_pos1.state, 'paid', "Order should be in paid state.")
|
|
self.assertFalse(self.pos_order_pos1.account_move, 'Invoice should not be attached to order yet.')
|
|
|
|
# I generate an invoice from the order
|
|
res = self.pos_order_pos1.action_pos_order_invoice()
|
|
self.assertIn('res_id', res, "No invoice created")
|
|
|
|
# I test that the total of the attached invoice is correct
|
|
invoice = self.env['account.move'].browse(res['res_id'])
|
|
if invoice.state != 'posted':
|
|
invoice.action_post()
|
|
self.assertAlmostEqual(
|
|
invoice.amount_total, self.pos_order_pos1.amount_total, places=2, msg="Invoice not correct")
|
|
|
|
for iline in invoice.invoice_line_ids:
|
|
self.assertFalse(iline.tax_ids)
|
|
|
|
self.pos_config.current_session_id.action_pos_session_closing_control()
|
|
|
|
def test_order_with_deleted_tax(self):
|
|
# create tax
|
|
dummy_50_perc_tax = self.env['account.tax'].create({
|
|
'name': 'Tax 50%',
|
|
'amount_type': 'percent',
|
|
'amount': 50.0,
|
|
'price_include': 0
|
|
})
|
|
|
|
# set tax to product
|
|
product5 = self.env['product.product'].create({
|
|
'name': 'product5',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'taxes_id': dummy_50_perc_tax.ids
|
|
})
|
|
|
|
# sell product thru pos
|
|
self.pos_config.open_ui()
|
|
pos_session = self.pos_config.current_session_id
|
|
untax, atax = self.compute_tax(product5, 10.0)
|
|
product5_order = {'data':
|
|
{'amount_paid': untax + atax,
|
|
'amount_return': 0,
|
|
'amount_tax': atax,
|
|
'amount_total': untax + atax,
|
|
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
|
|
'fiscal_position_id': False,
|
|
'lines': [[0,
|
|
0,
|
|
{'discount': 0,
|
|
'pack_lot_ids': [],
|
|
'price_unit': 10.0,
|
|
'product_id': product5.id,
|
|
'price_subtotal': 10.0,
|
|
'price_subtotal_incl': 15.0,
|
|
'qty': 1,
|
|
'tax_ids': [(6, 0, product5.taxes_id.ids)]}]],
|
|
'name': 'Order 12345-123-1234',
|
|
'partner_id': False,
|
|
'pos_session_id': pos_session.id,
|
|
'sequence_number': 2,
|
|
'statement_ids': [[0,
|
|
0,
|
|
{'amount': untax + atax,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.cash_payment_method.id}]],
|
|
'uid': '12345-123-1234',
|
|
'user_id': self.env.uid},
|
|
'to_invoice': False}
|
|
self.PosOrder.create_from_ui([product5_order])
|
|
|
|
# delete tax
|
|
dummy_50_perc_tax.unlink()
|
|
|
|
total_cash_payment = sum(pos_session.mapped('order_ids.payment_ids').filtered(lambda payment: payment.payment_method_id.type == 'cash').mapped('amount'))
|
|
pos_session.post_closing_cash_details(total_cash_payment)
|
|
|
|
# close session (should not fail here)
|
|
# We don't call `action_pos_session_closing_control` to force the failed
|
|
# closing which will return the action because the internal rollback call messes
|
|
# with the rollback of the test runner. So instead, we directly call the method
|
|
# that returns the action by specifying the imbalance amount.
|
|
action = pos_session._close_session_action(5.0)
|
|
wizard = self.env['pos.close.session.wizard'].browse(action['res_id'])
|
|
wizard.with_context(action['context']).close_session()
|
|
|
|
# check the difference line
|
|
diff_line = pos_session.move_id.line_ids.filtered(lambda line: line.name == 'Difference at closing PoS session')
|
|
self.assertAlmostEqual(diff_line.credit, 5.0, msg="Missing amount of 5.0")
|
|
|
|
def test_order_multi_step_route(self):
|
|
""" Test that orders in sessions with "Ship Later" enabled and "Specific Route" set to a
|
|
multi-step (2/3) route can be validated. This config implies multiple picking types
|
|
and multiple move_lines.
|
|
"""
|
|
tracked_product = self.env['product.product'].create({
|
|
'name': 'SuperProduct Tracked',
|
|
'type': 'product',
|
|
'tracking': 'lot',
|
|
'available_in_pos': True
|
|
})
|
|
tracked_product_2 = self.env['product.product'].create({
|
|
'name': 'SuperProduct Tracked 2',
|
|
'type': 'product',
|
|
'tracking': 'lot',
|
|
'available_in_pos': True
|
|
})
|
|
tracked_product_2_lot = self.env['stock.lot'].create({
|
|
'name': '80085',
|
|
'product_id': tracked_product_2.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
stock_location = self.company_data['default_warehouse'].lot_stock_id
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': tracked_product_2.id,
|
|
'inventory_quantity': 1,
|
|
'location_id': stock_location.id,
|
|
'lot_id': tracked_product_2_lot.id
|
|
}).action_apply_inventory()
|
|
warehouse_id = self.company_data['default_warehouse']
|
|
warehouse_id.delivery_steps = 'pick_ship'
|
|
|
|
self.pos_config.ship_later = True
|
|
self.pos_config.warehouse_id = warehouse_id
|
|
self.pos_config.route_id = warehouse_id.route_ids[-1]
|
|
self.pos_config.open_ui()
|
|
self.pos_config.current_session_id.update_stock_at_closing = False
|
|
|
|
untax, tax = self.compute_tax(tracked_product, 1.15, 1)
|
|
|
|
pos_order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': self.pos_config.current_session_id.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': tracked_product.id,
|
|
'price_unit': 1.15,
|
|
'qty': 1.0,
|
|
'price_subtotal': untax,
|
|
'price_subtotal_incl': untax + tax,
|
|
'pack_lot_ids': [
|
|
[0, 0, {'lot_name': '80085'}],
|
|
]
|
|
}),
|
|
(0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': tracked_product_2.id,
|
|
'price_unit': 1.15,
|
|
'qty': 1.0,
|
|
'price_subtotal': untax,
|
|
'price_subtotal_incl': untax + tax,
|
|
'pack_lot_ids': [
|
|
[0, 0, {'lot_name': '80085'}],
|
|
]
|
|
})],
|
|
'amount_tax': tax,
|
|
'amount_total': untax+tax,
|
|
'amount_paid': 0,
|
|
'amount_return': 0,
|
|
'shipping_date': fields.Date.today(),
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
context_make_payment = {
|
|
"active_ids": [pos_order.id],
|
|
"active_id": pos_order.id,
|
|
}
|
|
pos_make_payment = self.PosMakePayment.with_context(context_make_payment).create({
|
|
'amount': untax+tax,
|
|
})
|
|
context_payment = {'active_id': pos_order.id}
|
|
pos_make_payment.with_context(context_payment).check()
|
|
pickings = pos_order.picking_ids
|
|
picking_mls_no_stock = pickings.move_line_ids.filtered(lambda l: l.product_id.id == tracked_product.id)
|
|
picking_mls_stock = pickings.move_line_ids.filtered(lambda l: l.product_id.id == tracked_product_2.id)
|
|
self.assertEqual(pos_order.state, 'paid')
|
|
self.assertEqual(len(picking_mls_no_stock), 0)
|
|
self.assertEqual(len(picking_mls_stock), 1)
|
|
self.assertEqual(len(pickings.picking_type_id), 2)
|
|
|
|
def test_order_refund_picking(self):
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
current_session.update_stock_at_closing = True
|
|
# I create a new PoS order with 1 line
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product3.id,
|
|
'price_unit': 450,
|
|
'discount': 5.0,
|
|
'qty': 2.0,
|
|
'tax_ids': [(6, 0, self.product3.taxes_id.ids)],
|
|
'price_subtotal': 450 * (1 - 5/100.0) * 2,
|
|
'price_subtotal_incl': 450 * (1 - 5/100.0) * 2,
|
|
})],
|
|
'amount_total': 1710.0,
|
|
'amount_tax': 0.0,
|
|
'amount_paid': 0.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': True,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
|
|
# I create a refund
|
|
refund_action = order.refund()
|
|
refund = self.PosOrder.browse(refund_action['res_id'])
|
|
|
|
payment_context = {"active_ids": refund.ids, "active_id": refund.id}
|
|
refund_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': refund.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id,
|
|
})
|
|
|
|
# I click on the validate button to register the payment.
|
|
refund_payment.with_context(**payment_context).check()
|
|
|
|
refund.action_pos_order_invoice()
|
|
self.assertEqual(refund.picking_count, 1)
|
|
|
|
def test_order_with_different_payments_and_refund(self):
|
|
"""
|
|
Test that all the payments are correctly taken into account when the order contains multiple payments and money refund.
|
|
In this example, we create an order with two payments for a product of 750$:
|
|
- one payment of $300 with customer account
|
|
- one payment of $460 with cash
|
|
Then, we refund the order with $10, and check that the amount still due is 300$.
|
|
"""
|
|
|
|
product5 = self.env['product.product'].create({
|
|
'name': 'product5',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
|
|
# sell product thru pos
|
|
self.pos_config.open_ui()
|
|
pos_session = self.pos_config.current_session_id
|
|
product5_order = {'data':
|
|
{'amount_paid': 750,
|
|
'amount_return': 10,
|
|
'amount_tax': 0,
|
|
'amount_total': 750,
|
|
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
|
|
'fiscal_position_id': False,
|
|
'lines': [[0, 0, {
|
|
'discount': 0,
|
|
'pack_lot_ids': [],
|
|
'price_unit': 750.0,
|
|
'product_id': product5.id,
|
|
'price_subtotal': 750.0,
|
|
'price_subtotal_incl': 750.0,
|
|
'tax_ids': [[6, False, []]],
|
|
'qty': 1,
|
|
}]],
|
|
'name': 'Order 12345-123-1234',
|
|
'partner_id': self.partner1.id,
|
|
'pos_session_id': pos_session.id,
|
|
'sequence_number': 2,
|
|
'statement_ids': [[0, 0, {
|
|
'amount': 460,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.cash_payment_method.id
|
|
}], [0, 0, {
|
|
'amount': 300,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.credit_payment_method.id
|
|
}]],
|
|
'uid': '12345-123-1234',
|
|
'user_id': self.env.uid,
|
|
'to_invoice': True, }
|
|
}
|
|
pos_order_id = self.PosOrder.create_from_ui([product5_order])[0]['id']
|
|
pos_order = self.PosOrder.search([('id', '=', pos_order_id)])
|
|
#assert account_move amount_residual is 300
|
|
self.assertEqual(pos_order.account_move.amount_residual, 300)
|
|
|
|
def test_sale_order_postponed_invoicing(self):
|
|
""" Test the flow of creating an invoice later, after the POS session has been closed and everything has been processed.
|
|
The process should:
|
|
- Create a new misc entry, that will revert part of the POS closing entry.
|
|
- Create the move and associating payment(s) entry, as it would do when closing with invoice.
|
|
- Reconcile the receivable lines from the created misc entry with the ones from the created payment(s)
|
|
"""
|
|
# Create the order on the first of january.
|
|
pos_order = self._create_pos_order_for_postponed_invoicing()
|
|
self.assertFalse(pos_order.account_move.exists())
|
|
|
|
# Client is back on the 3rd, asks for an invoice.
|
|
with freeze_time('2020-01-03'):
|
|
# We set the partner on the order
|
|
pos_order.partner_id = self.partner1.id
|
|
pos_order.action_pos_order_invoice()
|
|
# We should now have: an invoice, a payment, and a misc entry reconciled with the payment that reverse the original POS closing entry.
|
|
invoice = pos_order.account_move
|
|
closing_entry = pos_order.session_move_id
|
|
# This search isn't the best, but we don't have any references to this move stored on other models.
|
|
misc_reversal_entry = self.env['account.move'].search([('ref', '=', f'Reversal of POS closing entry {closing_entry.name} for order {pos_order.name} from session {pos_order.session_id.name}')])
|
|
# In this case we will have only one, for cash payment
|
|
payment = self.env['account.move'].search([('ref', '=like', f'Invoice payment for {pos_order.name} ({pos_order.account_move.name}) using {self.cash_payment_method.name}')])
|
|
# And thus only one bank statement for it
|
|
statement = self.env['account.move'].search([('journal_id', '=', self.company_data['default_journal_cash'].id)])
|
|
self.assertTrue(invoice.exists() and closing_entry.exists() and misc_reversal_entry.exists() and payment.exists())
|
|
# Check 1: Check that we have reversed every credit line on the closing entry.
|
|
for closing_entry_line, misc_reversal_entry_line in zip(closing_entry.line_ids, misc_reversal_entry.line_ids):
|
|
if closing_entry_line.balance < 0:
|
|
self.assertEqual(closing_entry_line.balance, -misc_reversal_entry_line.balance)
|
|
self.assertEqual(closing_entry_line.account_id, misc_reversal_entry_line.account_id)
|
|
|
|
# Check 2: Reconciliation
|
|
# The invoice receivable should be reconciled with the payment receivable of the same account.
|
|
invoice_receivable_line = invoice.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'])
|
|
payment_receivable_line = payment.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'])
|
|
self.assertEqual(invoice_receivable_line.matching_number, payment_receivable_line.matching_number)
|
|
# The payment receivable (POS) is reconciled with the closing entry receivable (POS)
|
|
payment_receivable_pos_line = payment.line_ids.filtered(lambda line: line.account_id == self.company_data['company'].account_default_pos_receivable_account_id)
|
|
misc_receivable_pos_line = misc_reversal_entry.line_ids.filtered(lambda line: line.account_id == self.company_data['company'].account_default_pos_receivable_account_id)
|
|
self.assertEqual(misc_receivable_pos_line.matching_number, payment_receivable_pos_line.matching_number)
|
|
# The closing entry receivable is reconciled with the bank statement
|
|
closing_entry_receivable_line = closing_entry.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable']) # Because the payment method use the default receivable
|
|
statement_receivable_line = statement.line_ids.filtered(lambda line: line.account_id == self.company_data['default_account_receivable'] and line.name == pos_order.session_id.name) # Because the payment method use the default receivable
|
|
self.assertEqual(closing_entry_receivable_line.matching_number, statement_receivable_line.matching_number)
|
|
|
|
def test_sale_order_postponed_invoicing_anglosaxon(self):
|
|
""" Test the flow of creating an invoice later, after the POS session has been closed and everything has been processed
|
|
in the case of anglo-saxon accounting.
|
|
"""
|
|
self.env.company.anglo_saxon_accounting = True
|
|
self.env.company.point_of_sale_update_stock_quantities = 'closing'
|
|
pos_order = self._create_pos_order_for_postponed_invoicing()
|
|
|
|
with freeze_time('2020-01-03'):
|
|
# We set the partner on the order
|
|
pos_order.partner_id = self.partner1.id
|
|
pos_order.action_pos_order_invoice()
|
|
|
|
picking_ids = pos_order.session_id.picking_ids
|
|
# only one product is leaving stock
|
|
self.assertEqual(sum(picking_ids.move_line_ids.mapped('quantity')), 1)
|
|
|
|
def test_order_pos_tax_same_as_company(self):
|
|
"""Test that when the default_pos_receivable_account and the partner account_receivable are the same,
|
|
payment are correctly reconciled and the invoice is correctly marked as paid.
|
|
"""
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
current_session.company_id.account_default_pos_receivable_account_id = self.partner1.property_account_receivable_id
|
|
|
|
product5_order = {'data':
|
|
{'amount_paid': 750,
|
|
'amount_tax': 0,
|
|
'amount_return':0,
|
|
'amount_total': 750,
|
|
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
|
|
'fiscal_position_id': False,
|
|
'lines': [[0, 0, {
|
|
'discount': 0,
|
|
'pack_lot_ids': [],
|
|
'price_unit': 750.0,
|
|
'product_id': self.product3.id,
|
|
'price_subtotal': 750.0,
|
|
'price_subtotal_incl': 750.0,
|
|
'tax_ids': [[6, False, []]],
|
|
'qty': 1,
|
|
}]],
|
|
'name': 'Order 12345-123-1234',
|
|
'partner_id': self.partner1.id,
|
|
'pos_session_id': current_session.id,
|
|
'sequence_number': 2,
|
|
'statement_ids': [[0, 0, {
|
|
'amount': 450,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.cash_payment_method.id
|
|
}], [0, 0, {
|
|
'amount': 300,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.bank_payment_method.id
|
|
}]],
|
|
'uid': '12345-123-1234',
|
|
'user_id': self.env.uid,
|
|
'to_invoice': True, }
|
|
}
|
|
|
|
pos_order_id = self.PosOrder.create_from_ui([product5_order])[0]['id']
|
|
pos_order = self.PosOrder.search([('id', '=', pos_order_id)])
|
|
self.assertEqual(pos_order.account_move.amount_residual, 0)
|
|
|
|
def test_order_refund_with_owner(self):
|
|
# open pos session
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
|
|
# set up product iwith SN tracing and create two lots (1001, 1002)
|
|
self.stock_location = self.company_data['default_warehouse'].lot_stock_id
|
|
self.product2 = self.env['product.product'].create({
|
|
'name': 'Product A',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
|
|
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product2.id,
|
|
'inventory_quantity': 1,
|
|
'location_id': self.stock_location.id,
|
|
'owner_id': self.partner1.id
|
|
}).action_apply_inventory()
|
|
|
|
# create pos order with the two SN created before
|
|
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product2.id,
|
|
'price_unit': 6,
|
|
'discount': 0,
|
|
'qty': 1,
|
|
'tax_ids': [[6, False, []]],
|
|
'price_subtotal': 6,
|
|
'price_subtotal_incl': 6,
|
|
})],
|
|
'pricelist_id': self.pos_config.pricelist_id.id,
|
|
'amount_paid': 6.0,
|
|
'amount_total': 6.0,
|
|
'amount_tax': 0.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
|
|
# I create a refund
|
|
refund_action = order.refund()
|
|
refund = self.PosOrder.browse(refund_action['res_id'])
|
|
|
|
payment_context = {"active_ids": refund.ids, "active_id": refund.id}
|
|
refund_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': refund.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id,
|
|
})
|
|
|
|
# I click on the validate button to register the payment.
|
|
refund_payment.with_context(**payment_context).check()
|
|
current_session.action_pos_session_closing_control()
|
|
self.assertEqual(refund.picking_ids.move_line_ids_without_package.owner_id.id, order.picking_ids.move_line_ids_without_package.owner_id.id, "The owner of the refund is not the same as the owner of the original order")
|
|
|
|
def test_journal_entries_category_without_account(self):
|
|
#create a new product category without account
|
|
category = self.env['product.category'].create({
|
|
'name': 'Category without account',
|
|
'property_account_income_categ_id': False,
|
|
'property_account_expense_categ_id': False,
|
|
})
|
|
product = self.env['product.product'].create({
|
|
'name': 'Product with category without account',
|
|
'type': 'product',
|
|
'categ_id': category.id,
|
|
'property_account_income_id': False,
|
|
'property_account_expense_id': False,
|
|
})
|
|
account = self.env['account.account'].create({
|
|
'name': 'Account for category without account',
|
|
'code': 'X1111',
|
|
})
|
|
|
|
self.pos_config.journal_id.default_account_id = account.id
|
|
#create a new pos order with the product
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'pricelist_id': self.partner1.property_product_pricelist.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': product.id,
|
|
'price_unit': 10,
|
|
'discount': 0.0,
|
|
'qty': 1,
|
|
'tax_ids': [],
|
|
'price_subtotal': 10,
|
|
'price_subtotal_incl': 10,
|
|
})],
|
|
'amount_total': 10,
|
|
'amount_tax': 0.0,
|
|
'amount_paid': 10,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
#create a payment
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
current_session.action_pos_session_closing_control()
|
|
self.assertEqual(current_session.move_id.line_ids[0].account_id.id, account.id)
|
|
|
|
def test_tracked_product_with_owner(self):
|
|
# open pos session
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
|
|
# set up product iwith SN tracing and create two lots (1001, 1002)
|
|
self.stock_location = self.company_data['default_warehouse'].lot_stock_id
|
|
self.product2 = self.env['product.product'].create({
|
|
'name': 'Product A',
|
|
'type': 'product',
|
|
'tracking': 'serial',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
|
|
lot1 = self.env['stock.lot'].create({
|
|
'name': '1001',
|
|
'product_id': self.product2.id,
|
|
'company_id': self.env.company.id,
|
|
})
|
|
|
|
self.env['stock.quant']._update_available_quantity(self.product2, self.stock_location, 1, lot_id=lot1, owner_id=self.partner1)
|
|
|
|
|
|
# create pos order with the two SN created before
|
|
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'id': 1,
|
|
'product_id': self.product2.id,
|
|
'price_unit': 6,
|
|
'discount': 0,
|
|
'qty': 1,
|
|
'tax_ids': [[6, False, []]],
|
|
'price_subtotal': 6,
|
|
'price_subtotal_incl': 6,
|
|
'pack_lot_ids': [
|
|
[0, 0, {'lot_name': '1001'}],
|
|
]
|
|
})],
|
|
'pricelist_id': self.pos_config.pricelist_id.id,
|
|
'amount_paid': 6.0,
|
|
'amount_total': 6.0,
|
|
'amount_tax': 0.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
current_session.action_pos_session_closing_control()
|
|
self.assertEqual(current_session.picking_ids.move_line_ids.owner_id.id, self.partner1.id)
|
|
|
|
def test_order_refund_with_invoice(self):
|
|
"""This test make sure that credit notes of pos orders are correctly
|
|
linked to the original invoice."""
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
|
|
order_data = {'data':
|
|
{'amount_paid': 450,
|
|
'amount_tax': 0,
|
|
'amount_return': 0,
|
|
'amount_total': 450,
|
|
'date_order': fields.Datetime.to_string(fields.Datetime.now()),
|
|
'fiscal_position_id': False,
|
|
'lines': [[0, 0, {
|
|
'discount': 0,
|
|
'pack_lot_ids': [],
|
|
'price_unit': 450.0,
|
|
'product_id': self.product3.id,
|
|
'price_subtotal': 450.0,
|
|
'price_subtotal_incl': 450.0,
|
|
'tax_ids': [[6, False, []]],
|
|
'qty': 1,
|
|
}]],
|
|
'name': 'Order 12345-123-1234',
|
|
'partner_id': self.partner1.id,
|
|
'pos_session_id': current_session.id,
|
|
'sequence_number': 2,
|
|
'statement_ids': [[0, 0, {
|
|
'amount': 450,
|
|
'name': fields.Datetime.now(),
|
|
'payment_method_id': self.cash_payment_method.id
|
|
}]],
|
|
'uid': '12345-123-1234',
|
|
'user_id': self.env.uid,
|
|
'to_invoice': True, }
|
|
}
|
|
order = self.PosOrder.create_from_ui([order_data])
|
|
order = self.PosOrder.browse(order[0]['id'])
|
|
|
|
refund_id = order.refund()['res_id']
|
|
refund = self.PosOrder.browse(refund_id)
|
|
context_payment = {"active_ids": refund.ids, "active_id": refund.id}
|
|
refund_payment = self.PosMakePayment.with_context(**context_payment).create({
|
|
'amount': refund.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
refund_payment.with_context(**context_payment).check()
|
|
refund.action_pos_order_invoice()
|
|
#get last invoice created
|
|
current_session.action_pos_session_closing_control()
|
|
invoices = self.env['account.move'].search([('move_type', '=', 'out_invoice')], order='id desc', limit=1)
|
|
credit_notes = self.env['account.move'].search([('move_type', '=', 'out_refund')], order='id desc', limit=1)
|
|
self.assertEqual(credit_notes.ref, "Reversal of: "+invoices.name)
|
|
self.assertEqual(credit_notes.reversed_entry_id.id, invoices.id)
|
|
|
|
def test_invoicing_after_closing_session(self):
|
|
""" Test that an invoice can be created after the session is closed """
|
|
#create customer account payment method
|
|
self.customer_account_payment_method = self.env['pos.payment.method'].create({
|
|
'name': 'Customer Account',
|
|
'split_transactions': True,
|
|
})
|
|
|
|
self.product1 = self.env['product.product'].create({
|
|
'name': 'Product A',
|
|
'type': 'product',
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
})
|
|
self.partner1.write({'parent_id': self.env['res.partner'].create({'name': 'Parent'}).id})
|
|
|
|
#add customer account payment method to pos config
|
|
self.pos_config.write({
|
|
'payment_method_ids': [(4, self.customer_account_payment_method.id, 0)],
|
|
})
|
|
# change the currency of PoS config
|
|
(self.currency_data['currency'].rate_ids | self.company.currency_id.rate_ids).unlink()
|
|
self.env['res.currency.rate'].create({
|
|
'rate': 0.5,
|
|
'currency_id': self.currency_data['currency'].id,
|
|
'name': datetime.today().date(),
|
|
})
|
|
self.pos_config.journal_id.write({
|
|
'currency_id': self.currency_data['currency'].id
|
|
})
|
|
other_pricelist = self.env['product.pricelist'].create({
|
|
'name': 'Public Pricelist Other',
|
|
'currency_id': self.currency_data['currency'].id,
|
|
})
|
|
self.pos_config.write({
|
|
'pricelist_id': other_pricelist.id,
|
|
'available_pricelist_ids': [(6, 0, other_pricelist.ids)],
|
|
})
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
|
|
# create pos order
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product1.id,
|
|
'price_unit': 6,
|
|
'discount': 0,
|
|
'qty': 1,
|
|
'tax_ids': [[6, False, []]],
|
|
'price_subtotal': 6,
|
|
'price_subtotal_incl': 6,
|
|
})],
|
|
'pricelist_id': self.pos_config.pricelist_id.id,
|
|
'amount_paid': 6.0,
|
|
'amount_total': 6.0,
|
|
'amount_tax': 0.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
#pay for the order with customer account
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': 2.0,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': 4.0,
|
|
'payment_method_id': self.customer_account_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
|
|
# close session
|
|
current_session.action_pos_session_closing_control()
|
|
|
|
# create invoice
|
|
order.action_pos_order_invoice()
|
|
#get journal entry that does the reverse payment, it the ref must contains Reversal
|
|
reverse_payment = self.env['account.move'].search([('ref', 'ilike', "Reversal")])
|
|
original_payment = self.env['account.move'].search([('ref', '=', current_session.display_name)])
|
|
original_customer_payment_entry = original_payment.line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable')
|
|
reverser_customer_payment_entry = reverse_payment.line_ids.filtered(lambda l: l.account_id.account_type == 'asset_receivable')
|
|
|
|
#check that both use the same account
|
|
self.assertEqual(len(reverser_customer_payment_entry), 2)
|
|
self.assertTrue(order.account_move.line_ids.partner_id == self.partner1.commercial_partner_id)
|
|
self.assertEqual(reverser_customer_payment_entry[0].balance, -4.0)
|
|
self.assertEqual(reverser_customer_payment_entry[1].balance, -8.0)
|
|
self.assertEqual(reverser_customer_payment_entry[0].amount_currency, -2.0)
|
|
self.assertEqual(reverser_customer_payment_entry[1].amount_currency, -4.0)
|
|
self.assertEqual(original_customer_payment_entry.account_id.id, reverser_customer_payment_entry.account_id.id)
|
|
self.assertEqual(reverser_customer_payment_entry.partner_id, original_customer_payment_entry.partner_id)
|
|
|
|
def test_order_total_subtotal_account_line_values(self):
|
|
self.tax1 = self.env['account.tax'].create({
|
|
'name': 'Tax 1',
|
|
'amount': 10,
|
|
'amount_type': 'percent',
|
|
'type_tax_use': 'sale',
|
|
})
|
|
#create an account to be used as income account
|
|
self.account1 = self.env['account.account'].create({
|
|
'name': 'Account 1',
|
|
'code': 'AC1',
|
|
'account_type': 'income',
|
|
'reconcile': True,
|
|
})
|
|
|
|
self.product1 = self.env['product.product'].create({
|
|
'name': 'Product A',
|
|
'type': 'product',
|
|
'taxes_id': [(6, 0, self.tax1.ids)],
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'property_account_income_id': self.account1.id,
|
|
})
|
|
self.product2 = self.env['product.product'].create({
|
|
'name': 'Product B',
|
|
'type': 'product',
|
|
'taxes_id': [(6, 0, self.tax1.ids)],
|
|
'categ_id': self.env.ref('product.product_category_all').id,
|
|
'property_account_income_id': self.account1.id,
|
|
})
|
|
self.pos_config.open_ui()
|
|
#create an order with product1
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': self.pos_config.current_session_id.id,
|
|
'partner_id': self.partner1.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': 110,
|
|
}), (0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': self.product2.id,
|
|
'price_unit': 100,
|
|
'discount': 0,
|
|
'qty': 1,
|
|
'tax_ids': [[6, False, [self.tax1.id]]],
|
|
'price_subtotal': 100,
|
|
'price_subtotal_incl': 110,
|
|
})],
|
|
'pricelist_id': self.pos_config.pricelist_id.id,
|
|
'amount_paid': 220.0,
|
|
'amount_total': 220.0,
|
|
'amount_tax': 20.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
#make payment
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
session_id = self.pos_config.current_session_id
|
|
|
|
# closing the session with basic pos access
|
|
pos_user = self.env['res.users'].create({
|
|
'name': "PoS user",
|
|
'login': "pos_user",
|
|
'email': "pos_user@yourcompany.com",
|
|
'groups_id': [(6, 0, [self.ref('base.group_user'), self.ref('point_of_sale.group_pos_user')])],
|
|
})
|
|
|
|
self.pos_config.current_session_id.with_user(pos_user).action_pos_session_closing_control()
|
|
|
|
#get journal entries created
|
|
aml = session_id.move_id.line_ids.filtered(lambda x: x.account_id == self.account1 and x.tax_ids == self.tax1)
|
|
self.assertEqual(aml.price_total, 220)
|
|
self.assertEqual(aml.price_subtotal, 200)
|
|
|
|
def test_multi_exp_account_real_time(self):
|
|
|
|
#Create a real time valuation product category
|
|
self.real_time_categ = self.env['product.category'].create({
|
|
'name': 'test category',
|
|
'parent_id': False,
|
|
'property_cost_method': 'fifo',
|
|
'property_valuation': 'real_time',
|
|
})
|
|
|
|
#Create 2 accounts to be used for each product
|
|
self.account1 = self.env['account.account'].create({
|
|
'name': 'Account 1',
|
|
'code': 'AC1',
|
|
'reconcile': True,
|
|
'account_type': 'expense',
|
|
})
|
|
self.account2 = self.env['account.account'].create({
|
|
'name': 'Account 1',
|
|
'code': 'AC2',
|
|
'reconcile': True,
|
|
'account_type': 'expense',
|
|
})
|
|
|
|
self.product_a = self.env['product.product'].create({
|
|
'name': 'Product A',
|
|
'type': 'product',
|
|
'categ_id': self.real_time_categ.id,
|
|
'property_account_expense_id': self.account1.id,
|
|
'property_account_income_id': self.account1.id,
|
|
})
|
|
self.product_b = self.env['product.product'].create({
|
|
'name': 'Product B',
|
|
'type': 'product',
|
|
'categ_id': self.real_time_categ.id,
|
|
'property_account_expense_id': self.account2.id,
|
|
'property_account_income_id': self.account2.id,
|
|
})
|
|
|
|
#Create an order with the 2 products
|
|
self.pos_config.open_ui()
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': self.pos_config.current_session_id.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product_a.id,
|
|
'price_unit': 100,
|
|
'discount': 0,
|
|
'qty': 1,
|
|
'tax_ids': [],
|
|
'price_subtotal': 100,
|
|
'price_subtotal_incl': 100,
|
|
}), (0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': self.product_b.id,
|
|
'price_unit': 100,
|
|
'discount': 0,
|
|
'qty': 1,
|
|
'tax_ids': [],
|
|
'price_subtotal': 100,
|
|
'price_subtotal_incl': 100,
|
|
})],
|
|
'pricelist_id': self.pos_config.pricelist_id.id,
|
|
'amount_paid': 200.0,
|
|
'amount_total': 200.0,
|
|
'amount_tax': 0.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': '{}',
|
|
'shipping_date': fields.Date.today(),
|
|
})
|
|
#make payment
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
self.pos_config.current_session_id.action_pos_session_closing_control()
|
|
order.picking_ids._action_done()
|
|
|
|
moves = self.env['account.move'].search([('ref', '=', f'pos_order_{order.id}')])
|
|
self.assertEqual(len(moves), 2)
|
|
|
|
def test_no_default_pricelist(self):
|
|
"""Should not have default_pricelist if use_pricelist is false."""
|
|
|
|
pricelist = self.env['product.pricelist'].create({
|
|
'name': 'Test Pricelist',
|
|
})
|
|
self.pos_config.write({
|
|
'pricelist_id': pricelist.id,
|
|
'use_pricelist': False,
|
|
})
|
|
self.pos_config.open_ui()
|
|
loaded_data = self.pos_config.current_session_id.load_pos_data()
|
|
|
|
self.assertFalse(loaded_data.get('default_pricelist', False))
|
|
|
|
def test_refund_rounding_backend(self):
|
|
rouding_method = self.env['account.cash.rounding'].create({
|
|
'name': 'Rounding up',
|
|
'rounding': 0.05,
|
|
'rounding_method': 'UP',
|
|
})
|
|
|
|
self.env['product.product'].create({
|
|
'name': 'Product Test',
|
|
'available_in_pos': True,
|
|
'list_price': 49.99,
|
|
'taxes_id': False,
|
|
})
|
|
|
|
self.pos_config.write({
|
|
'rounding_method': rouding_method.id,
|
|
'cash_rounding': True,
|
|
'only_round_cash_method': True,
|
|
})
|
|
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': False,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.env['product.product'].search([('available_in_pos', '=', True)], limit=1).id,
|
|
'price_unit': 49.99,
|
|
'discount': 0,
|
|
'qty': 1,
|
|
'tax_ids': [],
|
|
'price_subtotal': 49.99,
|
|
'price_subtotal_incl': 49.99,
|
|
})],
|
|
'pricelist_id': False,
|
|
'amount_paid': 50.0,
|
|
'amount_total': 49.99,
|
|
'amount_tax': 0.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': {}
|
|
})
|
|
#make payment
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
|
|
refund = order.refund()
|
|
refund = self.PosOrder.browse(refund['res_id'])
|
|
payment_context = {"active_ids": refund.ids, "active_id": refund.id}
|
|
refund_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'payment_method_id': self.cash_payment_method.id
|
|
})
|
|
self.assertEqual(refund_payment.amount, -50.0)
|
|
refund_payment.with_context(**payment_context).check()
|
|
current_session.action_pos_session_closing_control()
|
|
self.assertEqual(refund.amount_total, -49.99)
|
|
self.assertEqual(refund.amount_paid, -50.0)
|
|
self.assertEqual(current_session.state, 'closed')
|
|
def test_order_different_lots(self):
|
|
self.pos_config.open_ui()
|
|
current_session = self.pos_config.current_session_id
|
|
self.stock_location = self.company_data['default_warehouse'].lot_stock_id
|
|
self.product2 = self.env['product.product'].create({
|
|
'name': 'Product A',
|
|
'type': 'product',
|
|
'tracking': 'lot',
|
|
})
|
|
|
|
lot1 = self.env['stock.lot'].create({
|
|
'name': '1001',
|
|
'product_id': self.product2.id,
|
|
})
|
|
lot2 = self.env['stock.lot'].create({
|
|
'name': '1002',
|
|
'product_id': self.product2.id,
|
|
})
|
|
|
|
quant1 = self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product2.id,
|
|
'inventory_quantity': 5,
|
|
'location_id': self.stock_location.id,
|
|
'lot_id': lot1.id
|
|
})
|
|
quant1.action_apply_inventory()
|
|
quant2 = self.env['stock.quant'].with_context(inventory_mode=True).create({
|
|
'product_id': self.product2.id,
|
|
'inventory_quantity': 5,
|
|
'location_id': self.stock_location.id,
|
|
'lot_id': lot2.id
|
|
})
|
|
quant2.action_apply_inventory()
|
|
|
|
order = self.PosOrder.create({
|
|
'company_id': self.env.company.id,
|
|
'session_id': current_session.id,
|
|
'partner_id': self.partner1.id,
|
|
'lines': [(0, 0, {
|
|
'name': "OL/0001",
|
|
'product_id': self.product2.id,
|
|
'price_unit': 6,
|
|
'discount': 0,
|
|
'qty': 2,
|
|
'tax_ids': [[6, False, []]],
|
|
'price_subtotal': 12,
|
|
'price_subtotal_incl': 12,
|
|
'pack_lot_ids': [
|
|
[0, 0, {'lot_name': '1001'}],
|
|
]
|
|
}),
|
|
(0, 0, {
|
|
'name': "OL/0002",
|
|
'product_id': self.product2.id,
|
|
'price_unit': 6,
|
|
'discount': 0,
|
|
'qty': 1,
|
|
'tax_ids': [[6, False, []]],
|
|
'price_subtotal': 6,
|
|
'price_subtotal_incl': 6,
|
|
'pack_lot_ids': [
|
|
[0, 0, {'lot_name': '1002'}],
|
|
]
|
|
})],
|
|
'pricelist_id': self.pos_config.pricelist_id.id,
|
|
'amount_paid': 18.0,
|
|
'amount_total': 18.0,
|
|
'amount_tax': 0.0,
|
|
'amount_return': 0.0,
|
|
'to_invoice': False,
|
|
'last_order_preparation_change': '{}'
|
|
})
|
|
|
|
payment_context = {"active_ids": order.ids, "active_id": order.id}
|
|
order_payment = self.PosMakePayment.with_context(**payment_context).create({
|
|
'amount': order.amount_total,
|
|
'payment_method_id': self.bank_payment_method.id
|
|
})
|
|
order_payment.with_context(**payment_context).check()
|
|
self.pos_config.current_session_id.action_pos_session_closing_control()
|
|
self.assertEqual(quant2.quantity, 4)
|
|
self.assertEqual(quant1.quantity, 3)
|