[IMP] point_of_sale: trust client totals

The amounts sent by the point of sale should be trusted instead of recomputing
it server side.
The previous compution could introduce differences in amounts.

The pos.orders should only be created from the pos interface.

Turn the compute methods into onchange to still be possible to do refund but
disable edition from the backend.

Change amount_paid computation with the one server side
The way amount_paid is computed was not coherant

Remove pos.discount model as never used from the server interface

Adapt tests to use a computed amount_total and amount_paid
Adapt tests to be closer to real use flows (no more manual creation of account
move lines or pickings, it should be automatic)
Remove tests that no longer make sense (create + payment from backend is only
relevant for refund, rest should be with create_from_ui)

Add force_create in the view to make sure the onchange values are used
This commit is contained in:
Martin Trigaux
2018-08-13 11:08:03 +02:00
parent 68616ea746
commit 0222fdfdc7
9 changed files with 158 additions and 224 deletions
-1
View File
@@ -16,7 +16,6 @@
'data/digest_data.xml',
'wizard/pos_box.xml',
'wizard/pos_details.xml',
'wizard/pos_discount.xml',
'wizard/pos_open_statement.xml',
'wizard/pos_payment.xml',
'views/pos_templates.xml',
+37 -19
View File
@@ -43,6 +43,10 @@ class PosOrder(models.Model):
'date_order': ui_order['creation_date'],
'fiscal_position_id': ui_order['fiscal_position_id'],
'pricelist_id': ui_order['pricelist_id'],
'amount_paid': ui_order['amount_paid'],
'amount_total': ui_order['amount_total'],
'amount_tax': ui_order['amount_tax'],
'amount_return': ui_order['amount_return'],
}
def _payment_fields(self, ui_paymentline):
@@ -463,10 +467,11 @@ class PosOrder(models.Model):
default=lambda self: self.env.uid,
states={'done': [('readonly', True)], 'invoiced': [('readonly', True)]},
)
amount_tax = fields.Float(compute='_compute_amount_all', string='Taxes', digits=0)
amount_total = fields.Float(compute='_compute_amount_all', string='Total', digits=0)
amount_paid = fields.Float(compute='_compute_amount_all', string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits=0)
amount_return = fields.Float(compute='_compute_amount_all', string='Returned', digits=0)
amount_tax = fields.Float(string='Taxes', digits=0, readonly=True, required=True)
amount_total = fields.Float(string='Total', digits=0, readonly=True, required=True)
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)
lines = fields.One2many('pos.order.line', 'order_id', string='Order Lines', states={'draft': [('readonly', False)]}, readonly=True, copy=True)
statement_ids = fields.One2many('account.bank.statement.line', 'pos_statement_id', string='Payments', states={'draft': [('readonly', False)]}, readonly=True)
pricelist_id = fields.Many2one('product.pricelist', string='Pricelist', required=True, states={
@@ -505,10 +510,9 @@ class PosOrder(models.Model):
states={'draft': [('readonly', False)]},
)
@api.depends('statement_ids', 'lines.price_subtotal_incl', 'lines.discount')
def _compute_amount_all(self):
@api.onchange('statement_ids', 'lines', 'lines')
def _onchange_amount_all(self):
for order in self:
order.amount_paid = order.amount_return = order.amount_tax = 0.0
currency = order.pricelist_id.currency_id
order.amount_paid = sum(payment.amount for payment in order.statement_ids)
order.amount_return = sum(payment.amount < 0 and payment.amount or 0 for payment in order.statement_ids)
@@ -861,10 +865,12 @@ class PosOrder(models.Model):
def add_payment(self, data):
"""Create a new payment for the order"""
self.ensure_one()
args = self._prepare_bank_statement_line_payment_values(data)
context = dict(self.env.context)
context.pop('pos_session_id', False)
self.env['account.bank.statement.line'].with_context(context).create(args)
self.amount_paid = sum(payment.amount for payment in self.statement_ids)
return args.get('statement_id', False)
@api.multi
@@ -882,6 +888,9 @@ class PosOrder(models.Model):
'date_order': fields.Datetime.now(),
'pos_reference': order.pos_reference,
'lines': False,
'amount_tax': -order.amount_tax,
'amount_total': -order.amount_total,
'amount_paid': 0,
})
for line in order.lines:
clone_line = line.copy({
@@ -889,6 +898,8 @@ class PosOrder(models.Model):
'name': line.name + _(' REFUND'),
'order_id': clone.id,
'qty': -line.qty,
'price_subtotal': -line.price_subtotal,
'price_subtotal_incl': -line.price_subtotal_incl,
})
PosOrder += clone
@@ -931,8 +942,10 @@ class PosOrderLine(models.Model):
product_id = fields.Many2one('product.product', string='Product', domain=[('sale_ok', '=', True)], required=True, change_default=True)
price_unit = fields.Float(string='Unit Price', digits=0)
qty = fields.Float('Quantity', digits=dp.get_precision('Product Unit of Measure'), default=1)
price_subtotal = fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal w/o Tax')
price_subtotal_incl = fields.Float(compute='_compute_amount_line_all', digits=0, string='Subtotal')
price_subtotal = fields.Float(string='Subtotal w/o Tax', digits=0,
readonly=True, required=True)
price_subtotal_incl = fields.Float(string='Subtotal', digits=0,
readonly=True, required=True)
discount = fields.Float(string='Discount (%)', digits=0, default=0.0)
order_id = fields.Many2one('pos.order', string='Order Ref', ondelete='cascade')
create_date = fields.Datetime(string='Creation Date', readonly=True)
@@ -965,17 +978,22 @@ class PosOrderLine(models.Model):
values['name'] = self.env['ir.sequence'].next_by_code('pos.order.line')
return super(PosOrderLine, self).create(values)
@api.depends('price_unit', 'tax_ids', 'qty', 'discount', 'product_id')
def _compute_amount_line_all(self):
@api.onchange('price_unit', 'tax_ids', 'qty', 'discount', 'product_id')
def _onchange_amount_line_all(self):
for line in self:
fpos = line.order_id.fiscal_position_id
tax_ids_after_fiscal_position = fpos.map_tax(line.tax_ids, line.product_id, line.order_id.partner_id) if fpos else line.tax_ids
price = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
taxes = tax_ids_after_fiscal_position.compute_all(price, line.order_id.pricelist_id.currency_id, line.qty, product=line.product_id, partner=line.order_id.partner_id)
line.update({
'price_subtotal_incl': taxes['total_included'],
'price_subtotal': taxes['total_excluded'],
})
res = line._compute_amount_line_all()
line.update(res)
def _compute_amount_line_all(self):
self.ensure_one()
fpos = self.order_id.fiscal_position_id
tax_ids_after_fiscal_position = fpos.map_tax(self.tax_ids, self.product_id, self.order_id.partner_id) if fpos else self.tax_ids
price = self.price_unit * (1 - (self.discount or 0.0) / 100.0)
taxes = tax_ids_after_fiscal_position.compute_all(price, self.order_id.pricelist_id.currency_id, self.qty, product=self.product_id, partner=self.order_id.partner_id)
return {
'price_subtotal_incl': taxes['total_included'],
'price_subtotal': taxes['total_excluded'],
}
@api.onchange('product_id')
def _onchange_product_id(self):
+3 -1
View File
@@ -1530,6 +1530,8 @@ exports.Orderline = Backbone.Model.extend({
return {
qty: this.get_quantity(),
price_unit: this.get_unit_price(),
price_subtotal: this.get_price_without_tax(),
price_subtotal_incl: this.get_price_with_tax(),
discount: this.get_discount(),
product_id: this.get_product().id,
tax_ids: [[6, false, _.map(this.get_applicable_taxes(), function(tax){ return tax.id; })]],
@@ -2049,7 +2051,7 @@ exports.Order = Backbone.Model.extend({
}, this));
return {
name: this.get_name(),
amount_paid: this.get_total_paid(),
amount_paid: this.get_total_paid() - this.get_change(),
amount_total: this.get_total_with_tax(),
amount_tax: this.get_total_tax(),
amount_return: this.get_change(),
@@ -70,8 +70,14 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
'product_id': self.product.id,
'price_unit': 450,
'discount': 0.0,
'qty': 1.0
})]
'qty': 1.0,
'price_subtotal': 450,
'price_subtotal_incl': 450,
})],
'amount_total': 450,
'amount_tax': 0,
'amount_paid': 0,
'amount_return': 0,
})
# I make a payment to fully pay the order
@@ -87,6 +93,7 @@ class TestAngloSaxonFlow(TestAngloSaxonCommon):
# I check that the order is marked as paid
self.assertEqual(self.pos_order_pos0.state, 'paid', 'Order should be in paid state.')
self.assertEqual(self.pos_order_pos0.amount_paid, 450, 'Amount paid for the order should be updated.')
# I close the current session to generate the journal entries
self.pos_config.current_session_id.action_pos_session_close()
@@ -17,104 +17,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
# open all statements/cash registers
self.env['pos.open.statement'].create({}).open_statement()
def test_order_to_payment(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.
"""
# I click on create a new session button
self.pos_config.open_session_cb()
# 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)
self.pos_order_pos0 = self.PosOrder.create({
'company_id': self.company_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.ids)],
}), (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.ids)],
})]
})
# I check that the total of the order is equal to 450*2 + 300*3*1.05
# and the tax of the order is equal to 900 -(450 * 2 / 1.1) +
# 300*0.05*3
self.assertLess(
abs(self.pos_order_pos0.amount_total - (450 * 2 + 300 * 3 * 1.05)),
0.01, 'The order has a wrong amount, tax included.')
self.assertLess(
abs(self.pos_order_pos0.amount_tax - (900 - (450 * 2 / 1.1) + 300 * 0.05 * 3)),
0.01, 'The order has a wrong tax amount.')
# I want to add a global discount of 5 percent using the wizard
self.pos_discount_0 = self.env['pos.discount'].create({'discount': 5.0})
context = {"active_model": "pos.order", "active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id}
# I click the apply button to set the discount on all lines
self.pos_discount_0.with_context(context).apply_discount()
# I check that the total of the order is now equal to (450*2 +
# 300*3*1.05)*0.95
self.assertLess(
abs(self.pos_order_pos0.amount_total - (450 * 2 + 300 * 3 * 1.05) * 0.95),
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
})
# 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) * 0.95 - 100.0)), 0.01, "The remaining balance is incorrect.")
#'I pay the remaining balance.
context_make_payment = {
"active_ids": [self.pos_order_pos0.id], "active_id": self.pos_order_pos0.id}
self.pos_make_payment_1 = self.PosMakePayment.with_context(context_make_payment).create({
'amount': (450 * 2 + 300 * 3 * 1.05) * 0.95 - 100.0
})
# 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
self.pos_order_pos0._create_account_move_line()
# I test that the generated journal entry is attached to the PoS order
self.assertTrue(self.pos_order_pos0.account_move, "Journal entry has not been attached to Pos order.")
def test_order_refund(self):
# I create a new PoS order with 2 lines
order = self.PosOrder.create({
@@ -128,6 +30,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'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,
}), (0, 0, {
'name': "OL/0002",
'product_id': self.product4.id,
@@ -135,7 +39,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 5.0,
'qty': 3.0,
'tax_ids': [(6, 0, self.product4.taxes_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': 1710.0,
'amount_return': 0.0,
})
# I create a refund
@@ -163,11 +73,22 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
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
"""
def compute_tax(product, price, qty=1, taxes=None):
if taxes is None:
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.user.id)
currency = self.pos_config.pricelist_id.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'])
# I click on create a new session button
self.pos_config.open_session_cb()
# I create a PoS order with 2 units of PCSC234 at 450 EUR
# and 3 units of PCSC349 at 300 EUR.
untax1, atax1 = compute_tax(self.product3, 450, 2)
untax2, atax2 = compute_tax(self.product4, 300, 3)
self.pos_order_pos1 = self.PosOrder.create({
'company_id': self.company_id,
'pricelist_id': self.partner1.property_product_pricelist.id,
@@ -179,6 +100,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 0.0,
'qty': 2.0,
'tax_ids': [(6, 0, self.product3.taxes_id.ids)],
'price_subtotal': untax1,
'price_subtotal_incl': untax1 + atax1,
}), (0, 0, {
'name': "OL/0002",
'product_id': self.product4.id,
@@ -186,7 +109,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 0.0,
'qty': 3.0,
'tax_ids': [(6, 0, self.product4.taxes_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,
})
context_make_payment = {
@@ -194,13 +123,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
"active_id": self.pos_order_pos1.id
}
self.pos_make_payment_2 = self.PosMakePayment.with_context(context_make_payment).create({
'amount': 1845
'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()
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,
@@ -208,18 +137,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'Order should be in paid state.'
)
# I generate the journal entries
self.pos_order_pos1._create_account_move_line()
# I test that the generated journal entry is attached to the PoS order
self.assertTrue(
self.pos_order_pos1.account_move,
"Journal entry has not been attached to Pos order."
)
# I test that the pickings are created as expected
# 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.pos_order_pos1.create_picking()
self.assertEqual(
self.pos_order_pos1.picking_id.state,
'done',
@@ -231,6 +150,9 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'Move Lines should be in done state.'
)
# I create a second order
untax1, atax1 = compute_tax(self.product3, 450, -2)
untax2, atax2 = compute_tax(self.product4, 300, -3)
self.pos_order_pos2 = self.PosOrder.create({
'company_id': self.company_id,
'pricelist_id': self.partner1.property_product_pricelist.id,
@@ -242,6 +164,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 0.0,
'qty': (-2.0),
'tax_ids': [(6, 0, self.product3.taxes_id.ids)],
'price_subtotal': untax1,
'price_subtotal_incl': untax1 + atax1,
}), (0, 0, {
'name': "OL/0004",
'product_id': self.product4.id,
@@ -249,7 +173,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 0.0,
'qty': (-3.0),
'tax_ids': [(6, 0, self.product4.taxes_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,
})
context_make_payment = {
@@ -257,7 +187,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
"active_id": self.pos_order_pos2.id
}
self.pos_make_payment_3 = self.PosMakePayment.with_context(context_make_payment).create({
'amount': (-1845)
'amount': untax1 + untax2 + atax1 + atax2
})
# I click on the validate button to register the payment.
@@ -271,15 +201,6 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'Order should be in paid state.'
)
# I generate the journal entries
self.pos_order_pos2._create_account_move_line()
# I test that the generated journal entry is attached to the PoS order
self.assertTrue(
self.pos_order_pos2.account_move,
"Journal entry has not been attached to PoS order."
)
# I test that the pickings are created as expected
# One picking attached and having all the positive move lines in the correct state
self.pos_order_pos2.create_picking()
@@ -294,6 +215,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'Move Lines should be in done state.'
)
untax1, atax1 = compute_tax(self.product3, 450, -2)
untax2, atax2 = compute_tax(self.product4, 300, 3)
self.pos_order_pos3 = self.PosOrder.create({
'company_id': self.company_id,
'pricelist_id': self.partner1.property_product_pricelist.id,
@@ -305,6 +228,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 0.0,
'qty': (-2.0),
'tax_ids': [(6, 0, self.product3.taxes_id.ids)],
'price_subtotal': untax1,
'price_subtotal_incl': untax1 + atax1,
}), (0, 0, {
'name': "OL/0006",
'product_id': self.product4.id,
@@ -312,7 +237,13 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 0.0,
'qty': 3.0,
'tax_ids': [(6, 0, self.product4.taxes_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,
})
context_make_payment = {
@@ -320,7 +251,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
"active_id": self.pos_order_pos3.id
}
self.pos_make_payment_4 = self.PosMakePayment.with_context(context_make_payment).create({
'amount': 45,
'amount': untax1 + untax2 + atax1 + atax2,
})
# I click on the validate button to register the payment.
@@ -334,18 +265,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'Order should be in paid state.'
)
# I generate the journal entries
self.pos_order_pos3._create_account_move_line()
# I test that the generated journal entry is attached to the PoS order
self.assertTrue(
self.pos_order_pos3.account_move,
"Journal entry has not been attached to PoS order."
)
# I test that the pickings are created as expected
# One picking attached and having all the positive move lines in the correct state
self.pos_order_pos3.create_picking()
self.assertEqual(
self.pos_order_pos3.picking_id.state,
'done',
@@ -356,9 +277,22 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
['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_invoice(self):
def compute_tax(product, price, qty=1, taxes=None):
if taxes is None:
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.user.id)
currency = self.pos_config.pricelist_id.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'])
untax1, atax1 = compute_tax(self.product3, 450*0.95, 2)
untax2, atax2 = 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.company_id,
@@ -371,6 +305,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 5.0,
'qty': 2.0,
'tax_ids': [(6, 0, self.product3.taxes_id.ids)],
'price_subtotal': untax1,
'price_subtotal_incl': untax1 + atax1,
}), (0, 0, {
'name': "OL/0002",
'product_id': self.product4.id,
@@ -378,13 +314,19 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'discount': 5.0,
'qty': 3.0,
'tax_ids': [(6, 0, self.product4.taxes_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,
})
# 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': (450 * 2 + 300 * 3 * 1.05) * 0.95,
'amount': untax1 + untax2 + atax1 + atax2,
})
# I click on the validate button to register the payment.
context_payment = {'active_id': self.pos_order_pos1.id}
@@ -401,8 +343,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
# I test that the total of the attached invoice is correct
invoice = self.env['account.invoice'].browse(res['res_id'])
self.assertEqual(
float_compare(invoice.amount_total, 1752.75, precision_digits=2), 0, "Invoice not correct")
self.assertAlmostEqual(
invoice.amount_total, self.pos_order_pos1.amount_total, places=2, msg="Invoice not correct")
"""In order to test the reports on Bank Statement defined in point_of_sale module, I create a bank statement line, confirm it and print the reports"""
@@ -456,15 +398,14 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
"""
Simulation of sales coming from the interface, even after closing the session
"""
FROMPRODUCT = object()
def compute_tax(product, price, taxes=FROMPRODUCT, qty=1):
if taxes is FROMPRODUCT:
taxes = product.taxes_id
def compute_tax(product, price, qty=1, taxes=None):
if not taxes:
taxes = product.taxes_id.filtered(lambda t: t.company_id.id == self.env.user.id)
currency = self.pos_config.pricelist_id.currency_id
taxes = taxes.compute_all(price, currency, qty, product=product)['taxes']
untax = price * qty
return untax, sum(tax.get('amount', 0.0) for tax in taxes)
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'])
# I click on create a new session button
self.pos_config.open_session_cb()
@@ -478,7 +419,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_return': 0,
'amount_tax': atax,
'amount_total': untax + atax,
'creation_date': fields.Datetime.now(),
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0,
@@ -488,6 +429,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'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.ids)]}]],
'name': 'Order 00042-003-0014',
@@ -512,7 +455,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_return': 0,
'amount_tax': atax,
'amount_total': untax + atax,
'creation_date': fields.Datetime.now(),
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0,
@@ -522,6 +465,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'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.ids)]}]],
'name': 'Order 00043-003-0014',
@@ -546,7 +491,7 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'amount_return': 0,
'amount_tax': atax,
'amount_total': untax + atax,
'creation_date': fields.Datetime.now(),
'creation_date': fields.Datetime.to_string(fields.Datetime.now()),
'fiscal_position_id': False,
'pricelist_id': self.pos_config.available_pricelist_ids[0].id,
'lines': [[0,
@@ -556,6 +501,8 @@ class TestPointOfSaleFlow(TestPointOfSaleCommon):
'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.ids]]}]],
'name': 'Order 00044-003-0014',
+12 -6
View File
@@ -41,8 +41,8 @@
<field name="discount" widget="monetary"/>
<field name="tax_ids_after_fiscal_position" widget="many2many_tags" string="Taxes"/>
<field name="tax_ids" widget="many2many_tags" invisible="1"/>
<field name="price_subtotal" widget="monetary"/>
<field name="price_subtotal_incl" widget="monetary"/>
<field name="price_subtotal" widget="monetary" force_save="1"/>
<field name="price_subtotal_incl" widget="monetary" force_save="1"/>
</tree>
<form string="Order lines">
<group col="4">
@@ -50,8 +50,8 @@
<field name="qty"/>
<field name="discount" widget="monetary"/>
<field name="price_unit" widget="monetary"/>
<field name="price_subtotal" invisible="1" widget="monetary"/>
<field name="price_subtotal_incl" invisible="1" widget="monetary"/>
<field name="price_subtotal" invisible="1" widget="monetary" force_save="1"/>
<field name="price_subtotal_incl" invisible="1" widget="monetary" force_save="1"/>
<field name="tax_ids_after_fiscal_position" widget="many2many_tags" string="Taxes"/>
<field name="tax_ids" widget="many2many_tags" invisible="1"/>
<field name="notice"/>
@@ -59,13 +59,19 @@
</form>
</field>
<group class="oe_subtotal_footer oe_right" colspan="2" name="order_total">
<field name="amount_tax" widget="monetary"/>
<field name="amount_tax"
force_save="1"
widget="monetary"/>
<div class="oe_subtotal_footer_separator oe_inline">
<label for="amount_total" />
<button name="button_dummy"
states="draft" string="(update)" class="oe_edit_only oe_link"/>
</div>
<field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator" widget="monetary"/>
<field name="amount_total"
force_save="1"
nolabel="1"
class="oe_subtotal_footer_separator"
widget="monetary"/>
</group>
<div class="oe_clear"/>
</page>
-1
View File
@@ -3,6 +3,5 @@
from . import pos_box
from . import pos_details
from . import pos_discount
from . import pos_open_statement
from . import pos_payment
@@ -1,17 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class PosDiscount(models.TransientModel):
_name = 'pos.discount'
_description = 'Add a Global Discount'
discount = fields.Float(string='Discount (%)', required=True, digits=(16, 2), default=5)
@api.multi
def apply_discount(self):
self.ensure_one()
for order in self.env['pos.order'].browse(self.env.context.get('active_id', False)):
order.lines.write({'discount': self.discount})
@@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_pos_discount" model="ir.ui.view">
<field name="name">pos.discount.form</field>
<field name="model">pos.discount</field>
<field name="arch" type="xml">
<form string="Apply Discount">
<group>
<field name="discount"/>
</group>
<footer>
<button name="apply_discount" string="Apply Discount" type="object" class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel" />
</footer>
</form>
</field>
</record>
<record id="action_pos_discount" model="ir.actions.act_window">
<field name="name">Apply Discount</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">pos.discount</field>
<field name="view_type">form</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</odoo>