[IMP]: point_of_sale: enable margin on orders
Margin has been added to both Order and OrderLine allowing the user to have a better view on his profitability. Margin also has been added to the order report as a measure. Related task id: 2455794 closes odoo/odoo#66689 Signed-off-by: pimodoo <pimodoo@users.noreply.github.com>
This commit is contained in:
@@ -142,6 +142,7 @@ class PosOrder(models.Model):
|
||||
except Exception as e:
|
||||
_logger.error('Could not fully process the POS Order: %s', tools.ustr(e))
|
||||
pos_order._create_order_picking()
|
||||
pos_order._compute_total_cost_in_real_time()
|
||||
|
||||
if pos_order.to_invoice and pos_order.state == 'paid':
|
||||
pos_order.action_pos_order_invoice()
|
||||
@@ -230,6 +231,10 @@ class PosOrder(models.Model):
|
||||
amount_paid = fields.Float(string='Paid', states={'draft': [('readonly', False)]},
|
||||
readonly=True, digits=0, required=True)
|
||||
amount_return = fields.Float(string='Returned', digits=0, required=True, readonly=True)
|
||||
margin = fields.Monetary(string="Margin", compute='_compute_margin')
|
||||
margin_percent = fields.Float(string="Margin (%)", compute='_compute_margin', digits=(12, 4))
|
||||
is_total_cost_computed = fields.Boolean(compute='_compute_is_total_cost_computed',
|
||||
help="Allows to know if all the total cost of the order lines have already been computed")
|
||||
lines = fields.One2many('pos.order.line', 'order_id', string='Order Lines', states={'draft': [('readonly', False)]}, readonly=True, copy=True)
|
||||
company_id = fields.Many2one('res.company', string='Company', required=True, readonly=True)
|
||||
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, states={
|
||||
@@ -291,6 +296,45 @@ class PosOrder(models.Model):
|
||||
for order in self:
|
||||
order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order)
|
||||
|
||||
@api.depends('lines.is_total_cost_computed')
|
||||
def _compute_is_total_cost_computed(self):
|
||||
for order in self:
|
||||
order.is_total_cost_computed = not False in order.lines.mapped('is_total_cost_computed')
|
||||
|
||||
def _compute_total_cost_in_real_time(self):
|
||||
"""
|
||||
Compute the total cost of the order when it's processed by the server. It will compute the total cost of all the lines
|
||||
if it's possible. If a margin of one of the order's lines cannot be computed (because of session_id.update_stock_at_closing),
|
||||
then the margin of said order is not computed (it will be computed when closing the session).
|
||||
"""
|
||||
for order in self:
|
||||
lines = order.lines
|
||||
if not order._should_create_picking_real_time():
|
||||
storable_fifo_avco_lines = lines.filtered(lambda l: l._is_product_storable_fifo_avco())
|
||||
lines -= storable_fifo_avco_lines
|
||||
stock_moves = order.picking_ids.move_lines
|
||||
lines._compute_total_cost(stock_moves)
|
||||
|
||||
def _compute_total_cost_at_session_closing(self, stock_moves):
|
||||
"""
|
||||
Compute the margin at the end of the session. This method should be called to compute the remaining lines margin
|
||||
containing a storable product with a fifo/avco cost method and then compute the order margin
|
||||
"""
|
||||
for order in self:
|
||||
storable_fifo_avco_lines = order.lines.filtered(lambda l: l._is_product_storable_fifo_avco())
|
||||
storable_fifo_avco_lines._compute_total_cost(stock_moves)
|
||||
|
||||
@api.depends('lines.margin', 'is_total_cost_computed')
|
||||
def _compute_margin(self):
|
||||
for order in self:
|
||||
if order.is_total_cost_computed:
|
||||
order.margin = sum(order.lines.mapped('margin'))
|
||||
amount_untaxed = order.currency_id.round(sum(line.price_subtotal for line in order.lines))
|
||||
order.margin_percent = not float_is_zero(amount_untaxed, order.currency_id.rounding) and order.margin / amount_untaxed or 0
|
||||
else:
|
||||
order.margin = 0
|
||||
order.margin_percent = 0
|
||||
|
||||
@api.onchange('payment_ids', 'lines')
|
||||
def _onchange_amount_all(self):
|
||||
for order in self:
|
||||
@@ -566,12 +610,15 @@ class PosOrder(models.Model):
|
||||
|
||||
return self.env['pos.order'].search_read(domain = [('id', 'in', order_ids)], fields = ['id', 'pos_reference'])
|
||||
|
||||
def _should_create_picking_real_time(self):
|
||||
return not self.session_id.update_stock_at_closing or (self.company_id.anglo_saxon_accounting and self.to_invoice)
|
||||
|
||||
def _create_order_picking(self):
|
||||
self.ensure_one()
|
||||
if self.to_ship:
|
||||
self.lines._launch_stock_rule_from_pos_order_lines()
|
||||
else:
|
||||
if not self.session_id.update_stock_at_closing or (self.company_id.anglo_saxon_accounting and self.to_invoice):
|
||||
if self._should_create_picking_real_time():
|
||||
picking_type = self.config_id.picking_type_id
|
||||
if self.partner_id.property_stock_customer:
|
||||
destination_id = self.partner_id.property_stock_customer.id
|
||||
@@ -600,6 +647,7 @@ class PosOrder(models.Model):
|
||||
'amount_tax': -self.amount_tax,
|
||||
'amount_total': -self.amount_total,
|
||||
'amount_paid': 0,
|
||||
'is_total_cost_computed': False
|
||||
}
|
||||
|
||||
def refund(self):
|
||||
@@ -770,6 +818,10 @@ class PosOrderLine(models.Model):
|
||||
readonly=True, required=True)
|
||||
price_subtotal_incl = fields.Float(string='Subtotal', digits=0,
|
||||
readonly=True, required=True)
|
||||
margin = fields.Monetary(string="Margin", compute='_compute_margin')
|
||||
margin_percent = fields.Float(string="Margin (%)", compute='_compute_margin', digits=(12, 4))
|
||||
total_cost = fields.Float(string='Total cost', digits='Product Price', readonly=True)
|
||||
is_total_cost_computed = fields.Boolean(help="Allows to know if the total cost has already been computed or not")
|
||||
discount = fields.Float(string='Discount (%)', digits=0, default=0.0)
|
||||
order_id = fields.Many2one('pos.order', string='Order Ref', ondelete='cascade', required=True)
|
||||
tax_ids = fields.Many2many('account.tax', string='Taxes', readonly=True)
|
||||
@@ -801,6 +853,7 @@ class PosOrderLine(models.Model):
|
||||
'price_subtotal': -self.price_subtotal,
|
||||
'price_subtotal_incl': -self.price_subtotal_incl,
|
||||
'pack_lot_ids': PosOrderLineLot,
|
||||
'is_total_cost_computed': False
|
||||
}
|
||||
|
||||
@api.model
|
||||
@@ -952,6 +1005,36 @@ class PosOrderLine(models.Model):
|
||||
pickings_to_confirm.action_confirm()
|
||||
return True
|
||||
|
||||
def _is_product_storable_fifo_avco(self):
|
||||
self.ensure_one()
|
||||
return self.product_id.type == 'product' and self.product_id.cost_method in ['fifo', 'average']
|
||||
|
||||
def _compute_total_cost(self, stock_moves):
|
||||
"""
|
||||
Compute the total cost of the order lines.
|
||||
:param stock_moves: recordset of `stock.move`, used for fifo/avco lines
|
||||
"""
|
||||
for line in self.filtered(lambda l: not l.is_total_cost_computed):
|
||||
product = line.product_id
|
||||
if line._is_product_storable_fifo_avco() and stock_moves:
|
||||
product_cost = product._compute_average_price(0, line.qty, stock_moves.filtered(lambda ml: ml.product_id == product))
|
||||
else:
|
||||
product_cost = product.standard_price
|
||||
line.total_cost = line.qty * product.cost_currency_id._convert(
|
||||
from_amount=product_cost,
|
||||
to_currency=line.currency_id,
|
||||
company=line.company_id or self.env.company,
|
||||
date=line.order_id.date_order or fields.Date.today(),
|
||||
round=False,
|
||||
)
|
||||
line.is_total_cost_computed = True
|
||||
|
||||
@api.depends('price_subtotal', 'total_cost')
|
||||
def _compute_margin(self):
|
||||
for line in self:
|
||||
line.margin = line.price_subtotal - line.total_cost
|
||||
line.margin_percent = not float_is_zero(line.price_subtotal, line.currency_id.rounding) and line.margin / line.price_subtotal or 0
|
||||
|
||||
|
||||
class PosOrderLineLot(models.Model):
|
||||
_name = "pos.pack.operation.lot"
|
||||
|
||||
@@ -319,6 +319,7 @@ class PosSession(models.Model):
|
||||
self._check_if_no_draft_orders()
|
||||
if self.update_stock_at_closing:
|
||||
self._create_picking_at_end_of_session()
|
||||
self.order_ids.filtered(lambda o: not o.is_total_cost_computed)._compute_total_cost_at_session_closing(self.picking_ids.move_lines)
|
||||
try:
|
||||
data = self.with_company(self.company_id)._create_account_move(balancing_account, amount_to_balance)
|
||||
except AccessError as e:
|
||||
|
||||
@@ -35,6 +35,7 @@ class PosOrderReport(models.Model):
|
||||
pos_categ_id = fields.Many2one('pos.category', string='PoS Category', readonly=True)
|
||||
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', readonly=True)
|
||||
session_id = fields.Many2one('pos.session', string='Session', readonly=True)
|
||||
margin = fields.Float(string='Margin', readonly=True)
|
||||
|
||||
def _select(self):
|
||||
return """
|
||||
@@ -64,7 +65,8 @@ class PosOrderReport(models.Model):
|
||||
pt.pos_categ_id,
|
||||
s.pricelist_id,
|
||||
s.session_id,
|
||||
s.account_move IS NOT NULL AS invoiced
|
||||
s.account_move IS NOT NULL AS invoiced,
|
||||
SUM(l.price_subtotal - l.total_cost / CASE COALESCE(s.currency_rate, 0) WHEN 0 THEN 1.0 ELSE s.currency_rate END) AS margin
|
||||
"""
|
||||
|
||||
def _from(self):
|
||||
|
||||
@@ -9,6 +9,7 @@ from . import test_point_of_sale
|
||||
from . import test_pos_setup
|
||||
from . import test_pos_basic_config
|
||||
from . import test_pos_products_with_tax
|
||||
from . import test_pos_margin
|
||||
from . import test_pos_multiple_sale_accounts
|
||||
from . import test_pos_multiple_receivable_accounts
|
||||
from . import test_pos_other_currency_config
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import odoo
|
||||
from odoo.addons.point_of_sale.tests.common import TestPoSCommon
|
||||
|
||||
|
||||
@odoo.tests.tagged('post_install', '-at_install')
|
||||
class TestPosMargin(TestPoSCommon):
|
||||
"""
|
||||
Test the margin computation on orders with basic configuration
|
||||
The tests contain the base scenarios.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestPosMargin, self).setUp()
|
||||
self.config = self.basic_config
|
||||
|
||||
self.stock_location = self.env['stock.warehouse'].create({
|
||||
'partner_id': self.env.user.partner_id.id,
|
||||
'name': 'Stock location',
|
||||
'code': 'WH'
|
||||
}).lot_stock_id
|
||||
self.customer_location = self.env.ref('stock.stock_location_customers')
|
||||
self.supplier_location = self.env.ref('stock.stock_location_suppliers')
|
||||
self.uom_unit = self.env.ref('uom.product_uom_unit')
|
||||
|
||||
|
||||
def test_positive_margin(self):
|
||||
"""
|
||||
Test margin where it should be more than zero
|
||||
"""
|
||||
|
||||
product1 = self.create_product('Product 1', self.categ_basic, 10, 5)
|
||||
product2 = self.create_product('Product 2', self.categ_basic, 50, 30)
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, 1)]),
|
||||
self.create_ui_order_data([(product2, 1)]),
|
||||
self.create_ui_order_data([(product1, 2), (product2, 2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_from_ui(orders)
|
||||
|
||||
# check margins
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, 5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, 20)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin, 50)
|
||||
|
||||
# check margins percent
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, 0.5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0.4)
|
||||
self.assertEqual(round(self.pos_session.order_ids[2].margin_percent, 2), 0.42)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
def test_negative_margin(self):
|
||||
"""
|
||||
Test margin where it should be less than zero
|
||||
"""
|
||||
|
||||
product1 = self.create_product('Product 1', self.categ_basic, 10, 15)
|
||||
product2 = self.create_product('Product 2', self.categ_basic, 50, 100)
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, 1)]),
|
||||
self.create_ui_order_data([(product2, 1)]),
|
||||
self.create_ui_order_data([(product1, 2), (product2, 2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_from_ui(orders)
|
||||
|
||||
# check margins
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, -5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, -50)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin, -110)
|
||||
|
||||
# check margins percent
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, -0.5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, -1)
|
||||
self.assertEqual(round(self.pos_session.order_ids[2].margin_percent, 2), -0.92)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
def test_full_margin(self):
|
||||
"""
|
||||
Test margin where the product cost is always 0
|
||||
"""
|
||||
|
||||
product1 = self.create_product('Product 1', self.categ_basic, 10)
|
||||
product2 = self.create_product('Product 2', self.categ_basic, 50)
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, 1)]),
|
||||
self.create_ui_order_data([(product2, 1)]),
|
||||
self.create_ui_order_data([(product1, 2), (product2, 2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_from_ui(orders)
|
||||
|
||||
# check margins
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, 10)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, 50)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin, 120)
|
||||
|
||||
# check margins percent
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, 1)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 1)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin_percent, 1)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
def test_tax_margin(self):
|
||||
"""
|
||||
Test margin with tax on products
|
||||
Product 1 price without tax = 10
|
||||
Product 2 price without tax = 50
|
||||
"""
|
||||
|
||||
product1 = self.create_product('Product 1', self.categ_basic, 10, 5, self.taxes['tax7'].ids)
|
||||
product2 = self.create_product('Product 2', self.categ_basic, 55, 30, self.taxes['tax10'].ids)
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, 1)]),
|
||||
self.create_ui_order_data([(product2, 1)]),
|
||||
self.create_ui_order_data([(product1, 2), (product2, 2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_from_ui(orders)
|
||||
|
||||
# check margins
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, 5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, 20)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin, 50)
|
||||
|
||||
# check margins percent
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, 0.5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0.4)
|
||||
self.assertEqual(round(self.pos_session.order_ids[2].margin_percent, 2), 0.42)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
def test_other_currency_margin(self):
|
||||
"""
|
||||
Test margin with tax on products and with different currency
|
||||
The currency rate is 0.5 so the product price is halved in this currency.
|
||||
"""
|
||||
|
||||
# change the config
|
||||
current_config = self.config
|
||||
self.config = self.other_currency_config
|
||||
|
||||
# same parameters as test_positive_margin
|
||||
product1 = self.create_product('Product 1', self.categ_basic, 10, 5)
|
||||
product2 = self.create_product('Product 2', self.categ_basic, 50, 30)
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, 1)]),
|
||||
self.create_ui_order_data([(product2, 1)]),
|
||||
self.create_ui_order_data([(product1, 2), (product2, 2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_from_ui(orders)
|
||||
|
||||
# check margins in the config currency
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, 2.5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, 10)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin, 25)
|
||||
|
||||
# check margins percent which should be the same as test_positive_margin
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, 0.5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0.4)
|
||||
self.assertEqual(round(self.pos_session.order_ids[2].margin_percent, 2), 0.42)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
# set the config back
|
||||
self.config = current_config
|
||||
|
||||
def test_tax_and_other_currency_margin(self):
|
||||
"""
|
||||
Test margin with different currency between products and config with taxes.
|
||||
Product 1 price without tax = 10
|
||||
Product 2 price without tax = 50
|
||||
The currency rate is 0.5 so the product price is halved in this currency.
|
||||
"""
|
||||
|
||||
# change the config
|
||||
current_config = self.config
|
||||
self.config = self.other_currency_config
|
||||
|
||||
product1 = self.create_product('Product 1', self.categ_basic, 10, 5, self.taxes['tax7'].ids)
|
||||
product2 = self.create_product('Product 2', self.categ_basic, 55, 30, self.taxes['tax10'].ids)
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, 1)]),
|
||||
self.create_ui_order_data([(product2, 1)]),
|
||||
self.create_ui_order_data([(product1, 2), (product2, 2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_from_ui(orders)
|
||||
|
||||
# check margins in the config currency
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, 2.5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, 10)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin, 25)
|
||||
|
||||
# check margins percent which should be the same as test_tax_margin
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, 0.5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0.4)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin_percent, 0.4167)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
# set the config back
|
||||
self.config = current_config
|
||||
|
||||
def test_return_margin(self):
|
||||
"""
|
||||
Test margin where we return product (negative line quantity)
|
||||
"""
|
||||
|
||||
product1 = self.create_product('Product 1', self.categ_basic, 10, 5)
|
||||
product2 = self.create_product('Product 2', self.categ_basic, 50, 30)
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, -1)]),
|
||||
self.create_ui_order_data([(product2, -1)]),
|
||||
self.create_ui_order_data([(product1, -2), (product2, -2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_from_ui(orders)
|
||||
|
||||
# check margins
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, -5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, -20)
|
||||
self.assertEqual(self.pos_session.order_ids[2].margin, -50)
|
||||
|
||||
# check margins percent
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, 0.5)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0.4)
|
||||
self.assertEqual(round(self.pos_session.order_ids[2].margin_percent, 2), 0.42)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
def test_fifo_margin_real_time(self):
|
||||
"""
|
||||
Test margin where there is product in FIFO with stock update in real time
|
||||
"""
|
||||
|
||||
product1 = self.create_product('Product 1', self.categ_anglo, 10, 5)
|
||||
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,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 2,
|
||||
'price_unit': 3,
|
||||
}).sudo()
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
move1.move_line_ids.qty_done = 2
|
||||
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,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': 7,
|
||||
}).sudo()
|
||||
move2._action_confirm()
|
||||
move2._action_assign()
|
||||
move2.move_line_ids.qty_done = 1
|
||||
move2._action_done()
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, 1), (product2, 1)]),
|
||||
self.create_ui_order_data([(product1, 2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_from_ui(orders)
|
||||
|
||||
# check margins
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, 27)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, 10)
|
||||
|
||||
# check margins percent
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, 0.45)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0.5)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
def test_avco_margin_closing_time(self):
|
||||
"""
|
||||
Test margin where there is product in AVCO with stock update in closing
|
||||
"""
|
||||
|
||||
self.categ_anglo.property_cost_method = 'average'
|
||||
product1 = self.create_product('Product 1', self.categ_anglo, 10, 5)
|
||||
product2 = self.create_product('Product 2', self.categ_basic, 50, 30)
|
||||
self.env.company.point_of_sale_update_stock_quantities = 'closing'
|
||||
|
||||
|
||||
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,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 2,
|
||||
'price_unit': 3,
|
||||
}).sudo()
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
move1.move_line_ids.qty_done = 2
|
||||
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,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': 6,
|
||||
}).sudo()
|
||||
move2._action_confirm()
|
||||
move2._action_assign()
|
||||
move2.move_line_ids.qty_done = 1
|
||||
move2._action_done()
|
||||
|
||||
# open a session
|
||||
self.open_new_session()
|
||||
|
||||
# create orders
|
||||
orders = [self.create_ui_order_data([(product1, 1), (product2, 1)]),
|
||||
self.create_ui_order_data([(product1, 2)])]
|
||||
|
||||
# sync orders
|
||||
self.env['pos.order'].create_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)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, 0)
|
||||
|
||||
# check margins percent (same as above)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0)
|
||||
|
||||
# close session
|
||||
self.pos_session.action_pos_session_validate()
|
||||
|
||||
# check margins
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin, 26)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin, 12)
|
||||
|
||||
# check margins percent
|
||||
self.assertEqual(self.pos_session.order_ids[0].margin_percent, 0.4333)
|
||||
self.assertEqual(self.pos_session.order_ids[1].margin_percent, 0.6)
|
||||
|
||||
self.env.company.point_of_sale_update_stock_quantities = 'real'
|
||||
@@ -51,6 +51,10 @@
|
||||
<field name="customer_note" optional="hide"/>
|
||||
<field name="product_uom_id" string="UoM" groups="uom.group_uom"/>
|
||||
<field name="price_unit" widget="monetary"/>
|
||||
<field name="is_total_cost_computed" invisible="1"/>
|
||||
<field name="total_cost" attrs="{'invisible': [('is_total_cost_computed','=', False)]}" optional="hide" widget="monetary"/>
|
||||
<field name="margin" attrs="{'invisible': [('is_total_cost_computed','=', False)]}" optional="hide" widget="monetary"/>
|
||||
<field name="margin_percent" attrs="{'invisible': [('is_total_cost_computed','=', False)]}" optional="hide" widget="percentage"/>
|
||||
<field name="discount" string="Disc.%" widget="monetary"/>
|
||||
<field name="tax_ids_after_fiscal_position" widget="many2many_tags" string="Taxes"/>
|
||||
<field name="tax_ids" widget="many2many_tags" invisible="1"/>
|
||||
@@ -93,6 +97,15 @@
|
||||
class="oe_subtotal_footer_separator"
|
||||
widget="monetary"
|
||||
attrs="{'invisible': [('amount_paid','=', 'amount_total')]}"/>
|
||||
<label for="margin"/>
|
||||
<div class="text-nowrap">
|
||||
<field name="margin" class="oe_inline" attrs="{'invisible': [('is_total_cost_computed','=', False)]}"/>
|
||||
<span class="oe_inline" attrs="{'invisible': [('is_total_cost_computed','=', False)]}">
|
||||
(<field name="margin_percent" nolabel="1" class="oe_inline" widget="percentage"/>)
|
||||
</span>
|
||||
<span attrs="{'invisible': [('is_total_cost_computed','=', True)]}">TBD</span>
|
||||
</div>
|
||||
<field name="is_total_cost_computed" invisible="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
</group>
|
||||
<div class="oe_clear"/>
|
||||
@@ -189,6 +202,8 @@
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="PoS Orders" sample="1">
|
||||
<field name="date_order" type="row"/>
|
||||
<field name="margin"/>
|
||||
<field name="margin_percent" invisible="1"/>
|
||||
<field name="amount_total" type="measure"/>
|
||||
</pivot>
|
||||
</field>
|
||||
|
||||
@@ -57,6 +57,7 @@ class PosMakePayment(models.TransientModel):
|
||||
if order._is_pos_order_paid():
|
||||
order.action_pos_order_paid()
|
||||
order._create_order_picking()
|
||||
order._compute_total_cost_in_real_time()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
return self.launch_payment()
|
||||
|
||||
Reference in New Issue
Block a user