mirror of
https://github.com/bringout/oca-ocb-sale.git
synced 2026-04-28 01:52:04 +02:00
19.0 vanilla
This commit is contained in:
parent
79f83631d5
commit
73afc09215
6267 changed files with 1534193 additions and 1130106 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import common
|
||||
from . import test_anglo_saxon_valuation
|
||||
from . import test_anglo_saxon_valuation_reconciliation
|
||||
from . import test_anglosaxon_account
|
||||
|
|
@ -8,7 +9,9 @@ from . import test_sale_stock
|
|||
from . import test_sale_stock_lead_time
|
||||
from . import test_sale_stock_report
|
||||
from . import test_sale_order_dates
|
||||
from . import test_sale_stock_multi_warehouse
|
||||
from . import test_sale_stock_multicompany
|
||||
from . import test_sale_stock_accrued_entries
|
||||
from . import test_sale_stock_access_rights
|
||||
from . import test_create_perf
|
||||
from . import test_packaging_tours
|
||||
|
|
|
|||
57
odoo-bringout-oca-ocb-sale_stock/sale_stock/tests/common.py
Normal file
57
odoo-bringout-oca-ocb-sale_stock/sale_stock/tests/common.py
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.fields import Command
|
||||
from odoo.addons.product.tests.common import ProductVariantsCommon
|
||||
from odoo.addons.sale.tests.common import TestSaleCommon
|
||||
|
||||
|
||||
class TestSaleStockCommon(TestSaleCommon, ProductVariantsCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.warehouse_3_steps_pull = cls.env['stock.warehouse'].create({
|
||||
'name': 'Warehouse 3 steps',
|
||||
'code': '3S',
|
||||
'delivery_steps': 'pick_pack_ship',
|
||||
})
|
||||
delivery_route_3 = cls.warehouse_3_steps_pull.delivery_route_id
|
||||
delivery_route_3.rule_ids[0].write({
|
||||
'location_dest_id': delivery_route_3.rule_ids[1].location_src_id.id,
|
||||
})
|
||||
delivery_route_3.rule_ids[1].write({'action': 'pull'})
|
||||
delivery_route_3.rule_ids[2].write({'action': 'pull'})
|
||||
cls.account_income = cls.company.income_account_id
|
||||
|
||||
def _inv_adj_two_units(self, product):
|
||||
self.env['stock.quant'].with_context(inventory_mode=True).create({
|
||||
'product_id': product.id, # tracking serial
|
||||
'inventory_quantity': 2,
|
||||
'location_id': self.stock_location.id,
|
||||
}).action_apply_inventory()
|
||||
|
||||
def _so_deliver(self, product, quantity=1, price=1, picking=True, partner=None, date_order=None, currency=None):
|
||||
if partner is None:
|
||||
partner = self.owner
|
||||
vals = {
|
||||
'partner_id': partner.id,
|
||||
'warehouse_id': self.warehouse.id,
|
||||
'order_line': [Command.create({
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': quantity,
|
||||
'price_unit': price,
|
||||
'tax_ids': False,
|
||||
})],
|
||||
}
|
||||
if date_order:
|
||||
vals['date_order'] = date_order
|
||||
if currency:
|
||||
vals['currency_id'] = currency.id
|
||||
|
||||
so = self.env['sale.order'].sudo().create(vals)
|
||||
so.action_confirm()
|
||||
if picking:
|
||||
so.picking_ids.move_ids.write({'quantity': quantity, 'picked': True})
|
||||
so.picking_ids.button_validate()
|
||||
return so
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,205 +1,183 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
|
||||
|
||||
from odoo.addons.stock_account.tests.common import TestStockValuationCommon
|
||||
from odoo.addons.sale_stock.tests.common import TestSaleStockCommon
|
||||
from odoo.tests import Form, tagged
|
||||
|
||||
|
||||
class TestValuationReconciliationCommon(ValuationReconciliationTestCommon):
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestValuationReconciliationCommon(TestStockValuationCommon, TestSaleStockCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
# Set the invoice_policy to delivery to have an accurate COGS entry.
|
||||
cls.test_product_delivery.invoice_policy = 'delivery'
|
||||
|
||||
def _create_sale(self, product, date, quantity=1.0):
|
||||
rslt = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': quantity,
|
||||
'product_uom': product.uom_po_id.id,
|
||||
'price_unit': 66.0,
|
||||
})],
|
||||
'date_order': date,
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.other_currency = cls.setup_other_currency('EUR')
|
||||
cls.product_standard_auto = cls.env['product.product'].create({
|
||||
'name': 'Test product template invoiced on delivery',
|
||||
'standard_price': 42.0,
|
||||
'is_storable': True,
|
||||
'categ_id': cls.category_standard_auto.id,
|
||||
'uom_id': cls.uom.id,
|
||||
'invoice_policy': 'delivery',
|
||||
})
|
||||
rslt.action_confirm()
|
||||
return rslt
|
||||
|
||||
def _create_invoice_for_so(self, sale_order, product, date, quantity=1.0):
|
||||
rslt = self.env['account.move'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'move_type': 'out_invoice',
|
||||
'invoice_date': date,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': 'test line',
|
||||
'account_id': self.company_data['default_account_revenue'].id,
|
||||
'price_unit': 66.0,
|
||||
'quantity': quantity,
|
||||
'discount': 0.0,
|
||||
'product_uom_id': product.uom_id.id,
|
||||
'product_id': product.id,
|
||||
'sale_line_ids': [(6, 0, sale_order.order_line.ids)],
|
||||
})],
|
||||
cls.product_standard_auto_2 = cls.env['product.product'].create({
|
||||
'name': 'Test product template invoiced on delivery 2',
|
||||
'standard_price': 42.0,
|
||||
'is_storable': True,
|
||||
'categ_id': cls.category_standard_auto.id,
|
||||
'uom_id': cls.uom.id,
|
||||
'invoice_policy': 'delivery',
|
||||
})
|
||||
|
||||
sale_order.invoice_ids += rslt
|
||||
return rslt
|
||||
def _process_pickings(self, pickings, quantity=None):
|
||||
for move in pickings.move_ids:
|
||||
move._action_assign()
|
||||
if quantity is not None:
|
||||
move.write({'quantity': quantity, 'picked': True})
|
||||
else:
|
||||
move.write({'quantity': move.product_uom_qty, 'picked': True})
|
||||
pickings.button_validate()
|
||||
|
||||
def _set_initial_stock_for_product(self, product):
|
||||
move1 = self.env['stock.move'].create({
|
||||
'name': 'Initial stock',
|
||||
'location_id': self.env.ref('stock.stock_location_suppliers').id,
|
||||
'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id,
|
||||
'product_id': product.id,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': 11,
|
||||
'price_unit': 13,
|
||||
})
|
||||
move1._action_confirm()
|
||||
move1._action_assign()
|
||||
move1.move_line_ids.qty_done = 11
|
||||
move1._action_done()
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestValuationReconciliation(TestValuationReconciliationCommon):
|
||||
def test_shipment_invoice(self):
|
||||
""" Tests the case into which we send the goods to the customer before
|
||||
making the invoice
|
||||
"""
|
||||
test_product = self.test_product_delivery
|
||||
self._set_initial_stock_for_product(test_product)
|
||||
test_product = self.product_standard_auto
|
||||
self._make_in_move(test_product, 11, 13)
|
||||
|
||||
sale_order = self._create_sale(test_product, '2108-01-01')
|
||||
sale_order = self._so_deliver(test_product, quantity=1, price=66.0, picking=False, partner=self.partner_b, date_order='2108-01-01', currency=self.other_currency)
|
||||
self._process_pickings(sale_order.picking_ids)
|
||||
|
||||
invoice = self._create_invoice_for_so(sale_order, test_product, '2018-02-12')
|
||||
invoice.action_post()
|
||||
picking = self.env['stock.picking'].search([('sale_id', '=', sale_order.id)])
|
||||
self.check_reconciliation(invoice, picking, operation='sale')
|
||||
self._create_invoice(test_product, quantity=1, price_unit=66.0, invoice_date='2018-02-12', currency_id=self.other_currency.id, account_id=self.account_income.id)
|
||||
|
||||
amls = self.env['account.move.line'].search([('product_id', '=', test_product.id)])
|
||||
self.assertRecordValues(amls, [
|
||||
{'debit': 0.0, 'credit': 66.0, 'account_id': self.account_income.id},
|
||||
{'debit': 0.0, 'credit': 42.0, 'account_id': self.account_stock_valuation.id},
|
||||
{'debit': 42.0, 'credit': 0.0, 'account_id': self.account_expense.id},
|
||||
])
|
||||
|
||||
def test_invoice_shipment(self):
|
||||
""" Tests the case into which we make the invoice first, and then send
|
||||
the goods to our customer.
|
||||
"""
|
||||
test_product = self.test_product_delivery
|
||||
#since the invoice come first, the COGS will use the standard price on product
|
||||
self.test_product_delivery.standard_price = 13
|
||||
self._set_initial_stock_for_product(test_product)
|
||||
test_product = self.product_standard_auto
|
||||
# since the invoice come first, the COGS will use the standard price on product
|
||||
self.product_standard_auto.standard_price = 13
|
||||
self._make_in_move(test_product, 11, 13)
|
||||
|
||||
sale_order = self._create_sale(test_product, '2018-01-01')
|
||||
sale_order = self._so_deliver(test_product, quantity=1, price=66.0, picking=False, partner=self.partner_b, date_order='2018-01-01', currency=self.other_currency)
|
||||
|
||||
invoice = self._create_invoice_for_so(sale_order, test_product, '2018-02-03')
|
||||
invoice.action_post()
|
||||
invoice = self._create_invoice(test_product, quantity=1, price_unit=66.0, invoice_date='2018-02-03', currency_id=self.other_currency.id, account_id=self.account_income.id)
|
||||
|
||||
self._process_pickings(sale_order.picking_ids)
|
||||
|
||||
picking = self.env['stock.picking'].search([('sale_id', '=', sale_order.id)])
|
||||
self.check_reconciliation(invoice, picking, operation='sale')
|
||||
amls = self.env['account.move.line'].search([('product_id', '=', test_product.id)])
|
||||
self.assertRecordValues(amls, [
|
||||
{'debit': 0.0, 'credit': 66.0, 'account_id': self.account_income.id},
|
||||
{'debit': 0.0, 'credit': 13.0, 'account_id': self.account_stock_valuation.id},
|
||||
{'debit': 13.0, 'credit': 0.0, 'account_id': self.account_expense.id},
|
||||
])
|
||||
|
||||
#return the goods and refund the invoice
|
||||
stock_return_picking_form = Form(self.env['stock.return.picking']
|
||||
.with_context(active_ids=picking.ids, active_id=picking.ids[0],
|
||||
.with_context(active_ids=sale_order.picking_ids.ids, active_id=sale_order.picking_ids.ids[0],
|
||||
active_model='stock.picking'))
|
||||
stock_return_picking = stock_return_picking_form.save()
|
||||
stock_return_picking.product_return_moves.quantity = 1.0
|
||||
stock_return_picking_action = stock_return_picking.create_returns()
|
||||
stock_return_picking_action = stock_return_picking.action_create_returns()
|
||||
return_pick = self.env['stock.picking'].browse(stock_return_picking_action['res_id'])
|
||||
return_pick.action_assign()
|
||||
return_pick.move_ids.quantity_done = 1
|
||||
return_pick.move_ids.write({'quantity': 1, 'picked': True})
|
||||
return_pick._action_done()
|
||||
refund_invoice_wiz = self.env['account.move.reversal'].with_context(active_model='account.move', active_ids=[invoice.id]).create({
|
||||
'reason': 'test_invoice_shipment_refund',
|
||||
'refund_method': 'cancel',
|
||||
'journal_id': invoice.journal_id.id,
|
||||
})
|
||||
refund_invoice = self.env['account.move'].browse(refund_invoice_wiz.reverse_moves()['res_id'])
|
||||
new_invoice = self.env['account.move'].browse(refund_invoice_wiz.modify_moves()['res_id'])
|
||||
self.assertEqual(invoice.payment_state, 'reversed', "Invoice should be in 'reversed' state.")
|
||||
self.assertEqual(refund_invoice.payment_state, 'paid', "Refund should be in 'paid' state.")
|
||||
self.check_reconciliation(refund_invoice, return_pick, operation='sale')
|
||||
self.assertEqual(invoice.reversal_move_ids.payment_state, 'paid', "Refund should be in 'paid' state.")
|
||||
self.assertEqual(new_invoice.state, 'draft', "New invoice should be in 'draft' state.")
|
||||
|
||||
def test_multiple_shipments_invoices(self):
|
||||
""" Tests the case into which we deliver part of the goods first, then 2 invoices at different rates, and finally the remaining quantities
|
||||
"""
|
||||
test_product = self.test_product_delivery
|
||||
self._set_initial_stock_for_product(test_product)
|
||||
test_product = self.product_standard_auto
|
||||
self._make_in_move(test_product, 11, 13)
|
||||
|
||||
sale_order = self._create_sale(test_product, '2018-01-01', quantity=5)
|
||||
sale_order = self._so_deliver(test_product, quantity=5, price=66.0, picking=False, partner=self.partner_b, date_order='2018-01-01', currency=self.other_currency)
|
||||
|
||||
self._process_pickings(sale_order.picking_ids, quantity=2.0)
|
||||
picking = self.env['stock.picking'].search([('sale_id', '=', sale_order.id)], order="id asc", limit=1)
|
||||
|
||||
invoice = self._create_invoice_for_so(sale_order, test_product, '2018-02-03', quantity=3)
|
||||
invoice.action_post()
|
||||
self.check_reconciliation(invoice, picking, full_reconcile=False, operation='sale')
|
||||
|
||||
invoice2 = self._create_invoice_for_so(sale_order, test_product, '2018-03-12', quantity=2)
|
||||
invoice2.action_post()
|
||||
self.check_reconciliation(invoice2, picking, full_reconcile=False, operation='sale')
|
||||
self._create_invoice(test_product, quantity=3, price_unit=66.0, invoice_date='2018-02-03', currency_id=self.other_currency.id, account_id=self.account_income.id)
|
||||
self._create_invoice(test_product, quantity=2, price_unit=66.0, invoice_date='2018-03-12', currency_id=self.other_currency.id, account_id=self.account_income.id)
|
||||
|
||||
self._process_pickings(sale_order.picking_ids.filtered(lambda x: x.state != 'done'), quantity=3.0)
|
||||
picking = self.env['stock.picking'].search([('sale_id', '=', sale_order.id)], order='id desc', limit=1)
|
||||
self.check_reconciliation(invoice2, picking, operation='sale')
|
||||
|
||||
# Final check, everything should be reconciled
|
||||
amls = self.env['account.move.line'].search([('product_id', '=', test_product.id)])
|
||||
self.assertRecordValues(amls, [
|
||||
{'debit': 0.0, 'credit': 132.0, 'account_id': self.account_income.id},
|
||||
{'debit': 0.0, 'credit': 84.0, 'account_id': self.account_stock_valuation.id},
|
||||
{'debit': 84.0, 'credit': 0.0, 'account_id': self.account_expense.id},
|
||||
{'debit': 0.0, 'credit': 198.0, 'account_id': self.account_income.id},
|
||||
{'debit': 0.0, 'credit': 126.0, 'account_id': self.account_stock_valuation.id},
|
||||
{'debit': 126.0, 'credit': 0.0, 'account_id': self.account_expense.id},
|
||||
])
|
||||
|
||||
def test_fifo_multiple_products(self):
|
||||
""" Test Automatic Inventory Valuation with FIFO costs method, 3 products,
|
||||
2,3,4 out svls and 2 in moves by product. This tests a more complex use case with anglo-saxon accounting.
|
||||
"""
|
||||
wh = self.env['stock.warehouse'].search([
|
||||
('company_id', '=', self.env.company.id),
|
||||
])
|
||||
wh = self.warehouse
|
||||
stock_loc = wh.lot_stock_id
|
||||
in_type = wh.in_type_id
|
||||
product_1, product_2, = tuple(self.env['product.product'].create([{
|
||||
'name': f'P{i}',
|
||||
# 'categ_id': fifo_categ.id,
|
||||
'list_price': 10 * i,
|
||||
'standard_price': 10 * i,
|
||||
'type': 'product'
|
||||
} for i in range(1, 3)]))
|
||||
product_1.categ_id.property_valuation = 'real_time'
|
||||
product_1.categ_id.property_cost_method = 'fifo'
|
||||
# give another output account to product_2
|
||||
|
||||
product_1 = self.product_fifo_auto
|
||||
product_1.standard_price = 10
|
||||
product_1.list_price = 10
|
||||
|
||||
# product_2 similar to product_1 but with different output account
|
||||
product_2 = product_1.copy({'name': 'P2', 'standard_price': 20, 'list_price': 20})
|
||||
categ_2 = product_1.categ_id.copy()
|
||||
account_2 = categ_2.property_stock_account_output_categ_id.copy()
|
||||
categ_2.property_stock_account_output_categ_id = account_2
|
||||
account_2 = self.env['account.account'].create({
|
||||
'name': 'Stock Valuation 2',
|
||||
'code': '100105',
|
||||
'account_type': 'asset_current',
|
||||
})
|
||||
categ_2.property_stock_valuation_account_id = account_2
|
||||
product_2.categ_id = categ_2
|
||||
|
||||
# Create out_svls
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
so = self.env['sale.order'].sudo().create({
|
||||
'partner_id': self.partner_b.id,
|
||||
'currency_id': self.other_currency.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': product.uom_po_id.id,
|
||||
'product_uom_id': product.uom_id.id,
|
||||
'price_unit': 10.0,
|
||||
}) for product in 2 * [product_1] + [product_2]],
|
||||
'date_order': '2021-01-01',
|
||||
})
|
||||
so.action_confirm()
|
||||
so.picking_ids.move_ids.quantity_done = 2
|
||||
so.picking_ids._action_done()
|
||||
|
||||
self._process_pickings(so.picking_ids)
|
||||
self.assertEqual(so.picking_ids.state, 'done')
|
||||
|
||||
inv = self.env['account.move'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'currency_id': self.currency_data['currency'].id,
|
||||
'partner_id': self.partner_b.id,
|
||||
'currency_id': self.other_currency.id,
|
||||
'move_type': 'out_invoice',
|
||||
'invoice_date': '2021-01-10',
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': 'test line',
|
||||
'account_id': self.company_data['default_account_revenue'].id,
|
||||
'account_id': self.account_income.id,
|
||||
'price_unit': 10.0,
|
||||
'quantity': 2,
|
||||
'discount': 0.0,
|
||||
'product_uom_id': line.product_id.uom_id.id,
|
||||
'product_id': line.product_id.id,
|
||||
'sale_line_ids': [(6, 0, line.ids)],
|
||||
}) for line in so.order_line],
|
||||
|
|
@ -207,10 +185,10 @@ class TestValuationReconciliation(TestValuationReconciliationCommon):
|
|||
|
||||
so.invoice_ids += inv
|
||||
inv.action_post()
|
||||
# Create in_moves for P1/P2 such that the first move compensates the out_svls
|
||||
|
||||
# Create in_moves for P1/P2
|
||||
in_moves = self.env['stock.move'].create([{
|
||||
'name': 'in %s units @ %s per unit' % (str(quantity), str(product.standard_price)),
|
||||
'description_picking': '%s-%s' % (str(quantity), str(product)), # to not merge the moves
|
||||
'description_picking': '%s-%s' % (str(quantity), str(product)),
|
||||
'product_id': product.id,
|
||||
'location_id': self.env.ref('stock.stock_location_suppliers').id,
|
||||
'location_dest_id': stock_loc.id,
|
||||
|
|
@ -224,27 +202,19 @@ class TestValuationReconciliation(TestValuationReconciliationCommon):
|
|||
)])
|
||||
in_moves._action_confirm()
|
||||
for move in in_moves:
|
||||
move.quantity_done = move.product_uom_qty
|
||||
move.quantity = move.product_uom_qty
|
||||
move.picked = True
|
||||
in_moves._action_done()
|
||||
|
||||
self.assertEqual(product_1.value_svl, -20)
|
||||
self.assertEqual(product_2.value_svl, 0)
|
||||
# Check that the correct number of amls have been created and posted
|
||||
input_aml = self.env['account.move.line'].search([
|
||||
('account_id', '=', product_1.categ_id.property_stock_account_input_categ_id.id),
|
||||
], order='date, id')
|
||||
output1_aml = self.env['account.move.line'].search([
|
||||
('account_id', '=', product_1.categ_id.property_stock_account_output_categ_id.id),
|
||||
], order='date, id')
|
||||
output2_aml = self.env['account.move.line'].search([
|
||||
('account_id', '=', product_2.categ_id.property_stock_account_output_categ_id.id),
|
||||
], order='date, id')
|
||||
valo_aml = self.env['account.move.line'].search([
|
||||
('account_id', '=', product_1.categ_id.property_stock_valuation_account_id.id),
|
||||
], order='date, id')
|
||||
self.assertEqual(len(input_aml), 2)
|
||||
self.assertEqual(len(output1_aml), 6)
|
||||
self.assertEqual(len(output2_aml), 4)
|
||||
self.assertEqual(len(valo_aml), 7)
|
||||
# All amls should be reconciled
|
||||
self.assertTrue(all(aml.reconciled for aml in output1_aml + output2_aml))
|
||||
amls = self.env['account.move.line'].search([('product_id', 'in', [product_1.id, product_2.id])])
|
||||
self.assertRecordValues(amls, [
|
||||
{'debit': 0.0, 'credit': 10.0, 'account_id': self.account_income.id},
|
||||
{'debit': 0.0, 'credit': 10.0, 'account_id': self.account_income.id},
|
||||
{'debit': 0.0, 'credit': 10.0, 'account_id': self.account_income.id},
|
||||
{'debit': 0.0, 'credit': 20.0, 'account_id': self.account_stock_valuation.id},
|
||||
{'debit': 20.0, 'credit': 0.0, 'account_id': self.account_expense.id},
|
||||
{'debit': 0.0, 'credit': 20.0, 'account_id': self.account_stock_valuation.id},
|
||||
{'debit': 20.0, 'credit': 0.0, 'account_id': self.account_expense.id},
|
||||
{'debit': 0.0, 'credit': 40.0, 'account_id': account_2.id},
|
||||
{'debit': 40.0, 'credit': 0.0, 'account_id': self.account_expense.id},
|
||||
])
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
from odoo.addons.sale_stock.tests.test_anglo_saxon_valuation_reconciliation import TestValuationReconciliationCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAngloSaxonAccounting(TestValuationReconciliationCommon):
|
||||
|
||||
|
|
@ -10,20 +11,23 @@ class TestAngloSaxonAccounting(TestValuationReconciliationCommon):
|
|||
Reproduce the flow of creating an invoice from a sale order with company A
|
||||
and posting the invoice with both companies selected and company B as the main.
|
||||
"""
|
||||
company_a_data = self.company_data
|
||||
company_b_data = self.company_data_2
|
||||
companies_with_b_first = company_b_data['company'] + company_a_data['company']
|
||||
product = self.test_product_delivery
|
||||
# Conflict between account common and stock common file. We should remove the depends on account common.
|
||||
self.env.user.company_id = self.company
|
||||
company_a_data = self.company
|
||||
company_b_data = self._create_company()
|
||||
companies_with_b_first = company_b_data + company_a_data
|
||||
self.env.user.company_ids = companies_with_b_first
|
||||
product = self.product_standard_auto
|
||||
|
||||
# set different cost price for the same product in the 2 companies
|
||||
company_a_standard_price = 20.0
|
||||
product.with_company(company_a_data['company']).standard_price = company_a_standard_price
|
||||
product.with_company(company_a_data).standard_price = company_a_standard_price
|
||||
company_b_standard_price = 10.0
|
||||
product.with_company(company_b_data['company']).standard_price = company_b_standard_price
|
||||
product.with_company(company_b_data).standard_price = company_b_standard_price
|
||||
|
||||
# create sale order with company A in draft (by default, self.env.user.company_id is company A)
|
||||
company_a_order = self._create_sale(product, '2021-01-01')
|
||||
company_a_invoice = self._create_invoice_for_so(company_a_order, product, '2021-01-10')
|
||||
self._so_deliver(product, quantity=1, price=66.0, picking=False, partner=self.vendor, date_order='2021-01-01')
|
||||
company_a_invoice = self._create_invoice(product, quantity=1, price_unit=66.0, invoice_date='2021-01-10', post=False)
|
||||
|
||||
# Post the invoice from company A with company B
|
||||
company_a_invoice.with_context(allowed_company_ids=companies_with_b_first.ids).action_post()
|
||||
|
|
|
|||
|
|
@ -1,19 +1,32 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import functools
|
||||
import logging
|
||||
import random
|
||||
import time
|
||||
|
||||
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
|
||||
from odoo.fields import Command
|
||||
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests.common import users, warmup
|
||||
|
||||
from odoo.addons.base.tests.common import TransactionCaseWithUserDemo
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def prepare(func, /):
|
||||
"""Prepare data to remove common queries from the count.
|
||||
|
||||
Must be run after `warmup` because of the invalidations"""
|
||||
# prefetch the data linked to the company and its country code to avoid changing
|
||||
# the query count during l10n tests
|
||||
@functools.wraps(func)
|
||||
def test_func(self):
|
||||
self.env.company.country_id.code
|
||||
return func(self)
|
||||
return test_func
|
||||
|
||||
|
||||
@tagged('so_batch_perf')
|
||||
class TestPERF(TransactionCaseWithUserDemo):
|
||||
|
||||
|
|
@ -38,6 +51,7 @@ class TestPERF(TransactionCaseWithUserDemo):
|
|||
|
||||
@users('admin')
|
||||
@warmup
|
||||
@prepare
|
||||
def test_empty_sale_order_creation_perf(self):
|
||||
with self.assertQueryCount(admin=34):
|
||||
self.env['sale.order'].create({
|
||||
|
|
@ -47,6 +61,7 @@ class TestPERF(TransactionCaseWithUserDemo):
|
|||
|
||||
@users('admin')
|
||||
@warmup
|
||||
@prepare
|
||||
def test_empty_sales_orders_batch_creation_perf(self):
|
||||
# + 1 SO insert
|
||||
# + 1 SO sequence fetch
|
||||
|
|
@ -61,6 +76,7 @@ class TestPERF(TransactionCaseWithUserDemo):
|
|||
|
||||
@users('admin')
|
||||
@warmup
|
||||
@prepare
|
||||
def test_dummy_sales_orders_batch_creation_perf(self):
|
||||
""" Dummy SOlines (notes/sections) should not add any custom queries other than their insert"""
|
||||
# + 2 SOL (batched) insert
|
||||
|
|
@ -76,12 +92,14 @@ class TestPERF(TransactionCaseWithUserDemo):
|
|||
|
||||
@users('admin')
|
||||
@warmup
|
||||
@prepare
|
||||
def test_light_sales_orders_batch_creation_perf_without_taxes(self):
|
||||
self.env['res.country'].search([]).mapped('code')
|
||||
self.products[0].taxes_id = [Command.set([])]
|
||||
# + 2 SQL insert
|
||||
# + 2 queries to get analytic default tags
|
||||
# + 9 follower queries ?
|
||||
with self.assertQueryCount(admin=57):
|
||||
with self.assertQueryCount(admin=53): # com 46
|
||||
self.env['sale.order'].create([{
|
||||
'partner_id': self.partners[0].id,
|
||||
'user_id': self.salesmans[0].id,
|
||||
|
|
@ -118,16 +136,6 @@ class TestPERF(TransactionCaseWithUserDemo):
|
|||
# (Seems to be a time-based problem, everytime happening around 10PM)
|
||||
self._test_complex_sales_orders_batch_creation_perf(1504)
|
||||
|
||||
@users('admin')
|
||||
@warmup
|
||||
def ___test_complex_sales_orders_batch_creation_perf_with_discount_computation(self):
|
||||
"""Cover the "complex" logic triggered inside the `_compute_discount`"""
|
||||
self.env['product.pricelist'].search([]).discount_policy = 'without_discount'
|
||||
self.env.user.groups_id += self.env.ref('product.group_discount_per_so_line')
|
||||
|
||||
# Verify any modification to this count on nightly runbot builds
|
||||
self._test_complex_sales_orders_batch_creation_perf(1546)
|
||||
|
||||
def _test_complex_sales_orders_batch_creation_perf(self, query_count):
|
||||
MSG = "Model %s, %i records, %s, time %.2f"
|
||||
|
||||
|
|
@ -155,9 +163,6 @@ class TestPERF(TransactionCaseWithUserDemo):
|
|||
"""Make sure the price and discounts computation are complexified
|
||||
and do not gain from any prefetch/batch gains during the price computation
|
||||
"""
|
||||
# Enable discounts
|
||||
self.env['product.pricelist'].search([]).discount_policy = 'without_discount'
|
||||
self.env.user.groups_id += self.env.ref('product.group_discount_per_so_line')
|
||||
|
||||
vals_list = [{
|
||||
"partner_id": self.partners[i].id,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
from odoo import Command
|
||||
from odoo.tests import HttpCase, tagged
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestPackagingTours(HttpCase):
|
||||
|
||||
def _get_product_url(self, product_id):
|
||||
return '/odoo/action-stock.product_template_action_product/%s' % (product_id)
|
||||
|
||||
def test_barcode_duplication_error(self):
|
||||
""" Test the barcode duplication error when creating a new product with an existing barcode """
|
||||
product_a = self.env['product.product'].create({
|
||||
'name': 'Product A',
|
||||
'is_storable': True,
|
||||
'tracking': 'none',
|
||||
'uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'uom_ids': [Command.link(self.env.ref('uom.product_uom_pack_6').id)],
|
||||
'product_uom_ids': [Command.create({
|
||||
'barcode': 'test-1234',
|
||||
'uom_id': self.env.ref('uom.product_uom_pack_6').id,
|
||||
})]
|
||||
})
|
||||
url = self._get_product_url(product_a.product_tmpl_id.id)
|
||||
self.env['res.config.settings'].create({
|
||||
'group_uom': True,
|
||||
}).execute()
|
||||
with mute_logger('odoo.sql_db', 'odoo.http'):
|
||||
self.start_tour(url, 'test_barcode_duplication_error', login='admin', timeout=60)
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
|
||||
from datetime import timedelta
|
||||
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import fields
|
||||
from odoo.tests import common, tagged
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import (
|
||||
ValuationReconciliationTestCommon,
|
||||
)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
|
|
@ -16,19 +22,19 @@ class TestSaleExpectedDate(ValuationReconciliationTestCommon):
|
|||
|
||||
product_A = Product.create({
|
||||
'name': 'Product A',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'sale_delay': 5,
|
||||
'uom_id': 1,
|
||||
})
|
||||
product_B = Product.create({
|
||||
'name': 'Product B',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'sale_delay': 10,
|
||||
'uom_id': 1,
|
||||
})
|
||||
product_C = Product.create({
|
||||
'name': 'Product C',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'sale_delay': 15,
|
||||
'uom_id': 1,
|
||||
})
|
||||
|
|
@ -37,13 +43,13 @@ class TestSaleExpectedDate(ValuationReconciliationTestCommon):
|
|||
self.env['stock.quant']._update_available_quantity(product_B, self.company_data['default_warehouse'].lot_stock_id, 10)
|
||||
self.env['stock.quant']._update_available_quantity(product_C, self.company_data['default_warehouse'].lot_stock_id, 10)
|
||||
|
||||
sale_order = self.env['sale.order'].create({
|
||||
sale_order = self.env['sale.order'].sudo().create({
|
||||
'partner_id': self.env['res.partner'].create({'name': 'A Customer'}).id,
|
||||
'picking_policy': 'direct',
|
||||
'order_line': [
|
||||
(0, 0, {'name': product_A.name, 'product_id': product_A.id, 'customer_lead': product_A.sale_delay, 'product_uom_qty': 5}),
|
||||
(0, 0, {'name': product_B.name, 'product_id': product_B.id, 'customer_lead': product_B.sale_delay, 'product_uom_qty': 5}),
|
||||
(0, 0, {'name': product_C.name, 'product_id': product_C.id, 'customer_lead': product_C.sale_delay, 'product_uom_qty': 5})
|
||||
Command.create({'product_id': product_A.id, 'product_uom_qty': 5}),
|
||||
Command.create({'product_id': product_B.id, 'product_uom_qty': 5}),
|
||||
Command.create({'product_id': product_C.id, 'product_uom_qty': 5})
|
||||
],
|
||||
})
|
||||
|
||||
|
|
@ -82,8 +88,7 @@ class TestSaleExpectedDate(ValuationReconciliationTestCommon):
|
|||
|
||||
# Check effective date, it should be date on which the first shipment successfully delivered to customer
|
||||
picking = sale_order.picking_ids[0]
|
||||
for ml in picking.move_line_ids:
|
||||
ml.qty_done = ml.reserved_uom_qty
|
||||
picking.move_ids.picked = True
|
||||
picking._action_done()
|
||||
self.assertEqual(picking.state, 'done', "Picking not processed correctly!")
|
||||
self.assertEqual(fields.Date.today(), sale_order.effective_date.date(), "Wrong effective date on sale order!")
|
||||
|
|
@ -92,17 +97,17 @@ class TestSaleExpectedDate(ValuationReconciliationTestCommon):
|
|||
|
||||
# In order to test the Commitment Date feature in Sales Orders in Odoo,
|
||||
# I copy a demo Sales Order with committed Date on 2010-07-12
|
||||
new_order = self.env['sale.order'].create({
|
||||
new_order = self.env['sale.order'].sudo().create({
|
||||
'partner_id': self.env['res.partner'].create({'name': 'A Partner'}).id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': "A product",
|
||||
'product_id': self.env['product.product'].create({
|
||||
'name': 'A product',
|
||||
'type': 'product',
|
||||
}).id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': 750,
|
||||
})],
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': self.env['product.product'].create({
|
||||
'name': 'A product',
|
||||
'is_storable': True,
|
||||
}).id,
|
||||
'price_unit': 750,
|
||||
})
|
||||
],
|
||||
'commitment_date': '2010-07-12',
|
||||
})
|
||||
# I confirm the Sales Order.
|
||||
|
|
@ -113,3 +118,94 @@ class TestSaleExpectedDate(ValuationReconciliationTestCommon):
|
|||
right_date = commitment_date - security_delay
|
||||
for line in new_order.order_line:
|
||||
self.assertEqual(line.move_ids[0].date, right_date, "The expected date for the Stock Move is wrong")
|
||||
|
||||
@freeze_time('2025-10-10')
|
||||
def test_expected_date_with_storable_product(self):
|
||||
''' This test ensures the expected date is computed based on only goods(consu) products.
|
||||
It's avoiding computation for non-goods products.
|
||||
'''
|
||||
sale_delay = 10.0
|
||||
self.product.sale_delay = sale_delay
|
||||
|
||||
# Create a sale order with a consu product.
|
||||
sale_order = self.env['sale.order'].sudo().create({
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_qty': 1000,
|
||||
})],
|
||||
})
|
||||
|
||||
# Ensure that expected date is correctly computed based on the consu product's sale delay.
|
||||
self.assertEqual(sale_order.expected_date, fields.Datetime.now() + timedelta(days=sale_delay))
|
||||
|
||||
# Add a service product and ensure the expected date remains unchanged.
|
||||
sale_order.write({
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.service_product.id,
|
||||
'product_uom_qty': 1000,
|
||||
})],
|
||||
})
|
||||
self.assertEqual(sale_order.expected_date, fields.Datetime.now() + timedelta(days=sale_delay))
|
||||
|
||||
def test_invoice_delivery_date(self):
|
||||
self.env['stock.quant']._update_available_quantity(
|
||||
self.test_product_order,
|
||||
self.company_data['default_warehouse'].lot_stock_id,
|
||||
75.0,
|
||||
)
|
||||
order = self.env['sale.order'].sudo().create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'picking_policy': 'one',
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.test_product_order.id,
|
||||
'product_uom_qty': 100.0,
|
||||
})],
|
||||
})
|
||||
order.action_confirm()
|
||||
picking_1 = order.picking_ids
|
||||
picking_1.move_ids.picked = True
|
||||
invoice = order._create_invoices()
|
||||
self.assertFalse(invoice.delivery_date)
|
||||
picking_1._action_done()
|
||||
self.assertTrue(order.effective_date, "Effective date should exist after done picking")
|
||||
effective_date = order.effective_date.date()
|
||||
self.assertEqual(
|
||||
invoice.delivery_date, effective_date,
|
||||
"Default invoice delivery date should equal effective date",
|
||||
)
|
||||
|
||||
self.env['stock.quant']._update_available_quantity(
|
||||
self.test_product_order,
|
||||
self.company_data['default_warehouse'].lot_stock_id,
|
||||
25.0,
|
||||
)
|
||||
with freeze_time(effective_date + timedelta(days=3)):
|
||||
custom_delivery_date = fields.Date.today()
|
||||
picking_2 = (order.picking_ids - picking_1).ensure_one()
|
||||
picking_2.move_ids.write({'quantity': 25.0, 'picked': True})
|
||||
picking_2._action_done()
|
||||
self.assertEqual(
|
||||
invoice.delivery_date, effective_date,
|
||||
"Invoice delivery date should default to earliest picking date",
|
||||
)
|
||||
product_line = invoice.line_ids[0]
|
||||
invoice.write({
|
||||
'delivery_date': custom_delivery_date,
|
||||
'line_ids': [Command.update(product_line.id, {'quantity': 0.0})],
|
||||
})
|
||||
product_line.quantity += 75.0
|
||||
self.assertEqual(
|
||||
invoice.delivery_date, custom_delivery_date,
|
||||
"Custom invoice delivery shouldn't change after line change",
|
||||
)
|
||||
invoice.action_post()
|
||||
self.assertEqual(
|
||||
invoice.delivery_date, custom_delivery_date,
|
||||
"Custom invoice delivery shouldn't change posting invoice",
|
||||
)
|
||||
invoice.button_draft()
|
||||
self.assertEqual(
|
||||
invoice.delivery_date, custom_delivery_date,
|
||||
"Custom invoice delivery shouldn't change resetting to draft invoice",
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,35 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from odoo import fields, Command
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.tests import tagged, Form
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import timedelta
|
||||
from freezegun.api import freeze_time
|
||||
|
||||
from odoo import fields
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import Form, tagged
|
||||
|
||||
from odoo.addons.sale.tests.common import TestSaleCommon
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestAccruedStockSaleOrders(AccountTestInvoicingCommon):
|
||||
class TestAccruedStockSaleOrders(TestSaleCommon):
|
||||
def _make_in_move(self,
|
||||
product,
|
||||
quantity,
|
||||
unit_cost=None,
|
||||
):
|
||||
""" Helper to create and validate a receipt move.
|
||||
|
||||
:param product: Product to move
|
||||
:param quantity: Quantity to move
|
||||
:param unit_cost: Price unit
|
||||
"""
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
||||
receipt_type = warehouse.in_type_id
|
||||
product_qty = quantity
|
||||
move_vals = {
|
||||
'product_id': product.id,
|
||||
'location_id': receipt_type.default_location_src_id.id,
|
||||
'location_dest_id': receipt_type.default_location_dest_id.id,
|
||||
'product_uom': self.uom_unit.id,
|
||||
'product_uom_qty': quantity,
|
||||
'picking_type_id': receipt_type.id,
|
||||
}
|
||||
if unit_cost:
|
||||
move_vals['value_manual'] = unit_cost * product_qty
|
||||
move_vals['price_unit'] = unit_cost
|
||||
else:
|
||||
move_vals['value_manual'] = product.standard_price * product_qty
|
||||
|
||||
in_move = self.env['stock.move'].create(move_vals)
|
||||
in_move._action_confirm()
|
||||
in_move._action_assign()
|
||||
in_move.picked = True
|
||||
in_move._action_done()
|
||||
|
||||
return in_move
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
uom_unit = cls.env.ref('uom.product_uom_unit')
|
||||
cls.product_order = cls.env['product.product'].create({
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
product = cls.env['product.product'].create({
|
||||
'name': "Product",
|
||||
'list_price': 30.0,
|
||||
'type': 'consu',
|
||||
'uom_id': uom_unit.id,
|
||||
'uom_po_id': uom_unit.id,
|
||||
'uom_id': cls.uom_unit.id,
|
||||
'invoice_policy': 'delivery',
|
||||
})
|
||||
cls.sale_order = cls.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
'partner_id': cls.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'name': cls.product_order.name,
|
||||
'product_id': cls.product_order.id,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 10.0,
|
||||
'product_uom': cls.product_order.uom_id.id,
|
||||
'price_unit': cls.product_order.list_price,
|
||||
'tax_id': False,
|
||||
'tax_ids': False,
|
||||
})
|
||||
]
|
||||
})
|
||||
|
|
@ -40,17 +77,16 @@ class TestAccruedStockSaleOrders(AccountTestInvoicingCommon):
|
|||
def test_sale_stock_accruals(self):
|
||||
# deliver 2 on 2020-01-02
|
||||
pick = self.sale_order.picking_ids
|
||||
pick.move_ids.write({'quantity_done': 2})
|
||||
pick.move_ids.write({'quantity': 2, 'picked': True})
|
||||
pick.button_validate()
|
||||
wiz_act = pick.button_validate()
|
||||
wiz = Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save()
|
||||
wiz.process()
|
||||
Form.from_action(self.env, wiz_act).save().process()
|
||||
pick.move_ids.write({'date': fields.Date.to_date('2020-01-02')})
|
||||
|
||||
# deliver 3 on 2020-01-06
|
||||
pick = pick.copy()
|
||||
pick.move_ids.write({'quantity_done': 3})
|
||||
wiz_act = pick.button_validate()
|
||||
pick.move_ids.write({'quantity': 3, 'picked': True})
|
||||
pick.button_validate()
|
||||
pick.move_ids.write({'date': fields.Date.to_date('2020-01-06')})
|
||||
|
||||
wizard = self.env['account.accrued.orders.wizard'].with_context({
|
||||
|
|
@ -89,11 +125,9 @@ class TestAccruedStockSaleOrders(AccountTestInvoicingCommon):
|
|||
def test_sale_stock_invoiced_accrued_entries(self):
|
||||
# deliver 2 on 2020-01-02
|
||||
pick = self.sale_order.picking_ids
|
||||
pick.move_ids.write({'quantity_done': 2})
|
||||
pick.move_ids.write({'quantity': 2, 'picked': True})
|
||||
pick.button_validate()
|
||||
wiz_act = pick.button_validate()
|
||||
wiz = Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save()
|
||||
wiz.process()
|
||||
Form.from_action(self.env, pick.button_validate()).save().process()
|
||||
pick.move_ids.write({'date': fields.Date.to_date('2020-01-02')})
|
||||
|
||||
# invoice on 2020-01-04
|
||||
|
|
@ -103,8 +137,8 @@ class TestAccruedStockSaleOrders(AccountTestInvoicingCommon):
|
|||
|
||||
# deliver 3 on 2020-01-06
|
||||
pick = pick.copy()
|
||||
pick.move_ids.write({'quantity_done': 3})
|
||||
wiz_act = pick.button_validate()
|
||||
pick.move_ids.write({'quantity': 3, 'picked': True})
|
||||
pick.button_validate()
|
||||
pick.move_ids.write({'date': fields.Date.to_date('2020-01-06')})
|
||||
|
||||
# invoice on 2020-01-08
|
||||
|
|
@ -149,3 +183,265 @@ class TestAccruedStockSaleOrders(AccountTestInvoicingCommon):
|
|||
wizard.date = fields.Date.to_date('2020-01-09')
|
||||
with self.assertRaises(UserError):
|
||||
wizard.create_entries()
|
||||
|
||||
def test_accrued_order_in_anglo_saxon_standard_perpetual(self):
|
||||
""" Ensure the COGS accrual lines are correctly computed."""
|
||||
# Create a product using anglox-saxon valuation.
|
||||
product_category = self.env['product.category'].create({
|
||||
'name': 'Test Category',
|
||||
'property_account_income_categ_id': self.account_revenue.id,
|
||||
'property_account_expense_categ_id': self.account_expense.id,
|
||||
'property_valuation': 'real_time',
|
||||
})
|
||||
account_variation = product_category.property_stock_valuation_account_id.account_stock_variation_id
|
||||
anglo_saxon_product = self.env['product.product'].create({
|
||||
'name': "Saxy Product",
|
||||
'categ_id': product_category.id,
|
||||
'invoice_policy': 'order',
|
||||
'is_storable': True,
|
||||
'list_price': 120,
|
||||
'standard_price': 80,
|
||||
'uom_id': self.uom_unit.id,
|
||||
})
|
||||
|
||||
# Case 1.: SO with more delivered quantities than invoiced quantities.
|
||||
sale_order_1 = self.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': anglo_saxon_product.id,
|
||||
'product_uom_qty': 1,
|
||||
'price_unit': 135, # Must be different than standard cost.
|
||||
'tax_ids': False,
|
||||
})
|
||||
]
|
||||
})
|
||||
sale_order_1.action_confirm()
|
||||
sale_order_1.picking_ids.move_ids.write({'quantity': 1, 'picked': True})
|
||||
sale_order_1.picking_ids.button_validate()
|
||||
# Use accrued order wizard and check generated values.
|
||||
wizard = self.env['account.accrued.orders.wizard'].with_context({
|
||||
'active_model': 'sale.order',
|
||||
'active_ids': [sale_order_1.id],
|
||||
}).create({
|
||||
'account_id': self.account_expense.id,
|
||||
'date': fields.Date.today(),
|
||||
})
|
||||
account_move_domain = wizard.create_entries()['domain']
|
||||
account_move = self.env['account.move'].search(account_move_domain)
|
||||
self.assertRecordValues(account_move.line_ids.sorted('id'), [
|
||||
# Accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 0, 'credit': 135},
|
||||
{'account_id': self.account_expense.id, 'debit': 135, 'credit': 0},
|
||||
{'account_id': account_variation.id, 'debit': 0, 'credit': 80},
|
||||
{'account_id': self.account_expense.id, 'debit': 80, 'credit': 0},
|
||||
# Reversal of accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 135, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 135},
|
||||
{'account_id': account_variation.id, 'debit': 80, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 80},
|
||||
])
|
||||
|
||||
# Case 2.: SO with more invoiced quantities than delivered quantities.
|
||||
sale_order_2 = self.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': anglo_saxon_product.id,
|
||||
'product_uom_qty': 1,
|
||||
'tax_ids': False,
|
||||
})
|
||||
]
|
||||
})
|
||||
sale_order_2.action_confirm()
|
||||
invoice = sale_order_2._create_invoices()
|
||||
invoice.line_ids.price_unit = 140 # Must be different than SO line unit cost.
|
||||
invoice.action_post()
|
||||
# Use accrued order wizard and check generated values.
|
||||
wizard = self.env['account.accrued.orders.wizard'].with_context({
|
||||
'active_model': 'sale.order',
|
||||
'active_ids': [sale_order_2.id],
|
||||
}).create({
|
||||
'account_id': self.account_expense.id,
|
||||
'date': fields.Date.today(),
|
||||
})
|
||||
account_move_domain = wizard.create_entries()['domain']
|
||||
account_move = self.env['account.move'].search(account_move_domain)
|
||||
self.assertRecordValues(account_move.line_ids.sorted('id'), [
|
||||
# Accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 140, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 140},
|
||||
{'account_id': account_variation.id, 'debit': 80, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 80},
|
||||
# Reversal of accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 0, 'credit': 140},
|
||||
{'account_id': self.account_expense.id, 'debit': 140, 'credit': 0},
|
||||
{'account_id': account_variation.id, 'debit': 0, 'credit': 80},
|
||||
{'account_id': self.account_expense.id, 'debit': 80, 'credit': 0},
|
||||
])
|
||||
|
||||
def test_accrued_order_in_anglo_saxon_avco_perpetual(self):
|
||||
""" Ensure the COGS accrual lines are correctly computed for AVCO costing method product."""
|
||||
# Create a product using anglox-saxon valuation.
|
||||
product_category = self.env['product.category'].create({
|
||||
'name': 'Test AVCO Category',
|
||||
'property_account_income_categ_id': self.account_revenue.id,
|
||||
'property_account_expense_categ_id': self.account_expense.id,
|
||||
'property_valuation': 'real_time',
|
||||
'property_cost_method': 'average',
|
||||
})
|
||||
account_variation = product_category.property_stock_valuation_account_id.account_stock_variation_id
|
||||
# Set the product in the past so its `product.value` won't be considered as the most recent one.
|
||||
with freeze_time(fields.Datetime.now() - timedelta(seconds=10)):
|
||||
avco_product = self.env['product.product'].create({
|
||||
'name': "AVCO Product",
|
||||
'categ_id': product_category.id,
|
||||
'invoice_policy': 'order',
|
||||
'is_storable': True,
|
||||
'standard_price': 0,
|
||||
'uom_id': self.uom_unit.id,
|
||||
})
|
||||
self._make_in_move(avco_product, 10, 10)
|
||||
self._make_in_move(avco_product, 10, 20)
|
||||
|
||||
# Create a SO for 10 units, deliver 7 units and invoice 5 units.
|
||||
sale_order_1 = self.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': avco_product.id,
|
||||
'product_uom_qty': 10,
|
||||
'price_unit': 35,
|
||||
'tax_ids': False,
|
||||
})
|
||||
]
|
||||
})
|
||||
sale_order_1.action_confirm()
|
||||
# Deliver 7 / 10 units.
|
||||
sale_order_1.picking_ids.move_ids.write({'quantity': 5, 'picked': True})
|
||||
backorder_wizard = Form.from_action(self.env, sale_order_1.picking_ids.button_validate())
|
||||
backorder_wizard.save().process()
|
||||
backorder_delivery = sale_order_1.picking_ids.filtered(lambda pick: pick.state == 'assigned')
|
||||
backorder_delivery.move_ids.write({'quantity': 2, 'picked': True})
|
||||
backorder_wizard = Form.from_action(self.env, sale_order_1.picking_ids.button_validate())
|
||||
backorder_wizard.save().process()
|
||||
# Invoice 5 / 10 units.
|
||||
invoice = sale_order_1._create_invoices()
|
||||
invoice.line_ids.quantity = 5
|
||||
invoice.action_post()
|
||||
# Use accrued order wizard and check generated values.
|
||||
wizard = self.env['account.accrued.orders.wizard'].with_context({
|
||||
'active_model': 'sale.order',
|
||||
'active_ids': [sale_order_1.id],
|
||||
}).create({
|
||||
'account_id': self.account_expense.id,
|
||||
'date': fields.Date.today(),
|
||||
})
|
||||
account_move_domain = wizard.create_entries()['domain']
|
||||
account_move = self.env['account.move'].search(account_move_domain)
|
||||
self.assertRecordValues(account_move.line_ids.sorted('id'), [
|
||||
# Accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 0, 'credit': 70},
|
||||
{'account_id': self.account_expense.id, 'debit': 70, 'credit': 0},
|
||||
{'account_id': account_variation.id, 'debit': 0, 'credit': 30},
|
||||
{'account_id': self.account_expense.id, 'debit': 30, 'credit': 0},
|
||||
# Reversal of accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 70, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 70},
|
||||
{'account_id': account_variation.id, 'debit': 30, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 30},
|
||||
])
|
||||
|
||||
def test_accrued_order_in_anglo_saxon_fifo_perpetual(self):
|
||||
""" Ensure the COGS accrual lines are correctly computed for FIFO costing method product."""
|
||||
# Create a product using anglox-saxon valuation.
|
||||
product_category = self.env['product.category'].create({
|
||||
'name': 'Test FIFO Category',
|
||||
'property_account_income_categ_id': self.account_revenue.id,
|
||||
'property_account_expense_categ_id': self.account_expense.id,
|
||||
'property_valuation': 'real_time',
|
||||
'property_cost_method': 'fifo',
|
||||
})
|
||||
account_variation = product_category.property_stock_valuation_account_id.account_stock_variation_id
|
||||
fifo_product = self.env['product.product'].create({
|
||||
'name': "FIFO Product",
|
||||
'categ_id': product_category.id,
|
||||
'invoice_policy': 'order',
|
||||
'is_storable': True,
|
||||
'standard_price': 15,
|
||||
'uom_id': self.uom_unit.id,
|
||||
})
|
||||
self._make_in_move(fifo_product, 10, 10)
|
||||
self._make_in_move(fifo_product, 10, 20)
|
||||
|
||||
# Create a SO for 10 units, deliver 7 units and invoice 5 units.
|
||||
sale_order_1 = self.env['sale.order'].with_context(tracking_disable=True).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({
|
||||
'product_id': fifo_product.id,
|
||||
'product_uom_qty': 20,
|
||||
'price_unit': 30,
|
||||
'tax_ids': False,
|
||||
})
|
||||
]
|
||||
})
|
||||
sale_order_1.action_confirm()
|
||||
# Deliver 17 / 20 units.
|
||||
sale_order_1.picking_ids.move_ids.write({'quantity': 17, 'picked': True})
|
||||
backorder_wizard = Form.from_action(self.env, sale_order_1.picking_ids.button_validate())
|
||||
backorder_wizard.save().process()
|
||||
# Invoice 5 / 20 units.
|
||||
invoice = sale_order_1._create_invoices()
|
||||
invoice.line_ids.quantity = 5
|
||||
invoice.line_ids.price_unit = 30
|
||||
invoice.action_post()
|
||||
# Use accrued order wizard and check generated values.
|
||||
wizard = self.env['account.accrued.orders.wizard'].with_context({
|
||||
'active_model': 'sale.order',
|
||||
'active_ids': [sale_order_1.id],
|
||||
}).create({
|
||||
'account_id': self.account_expense.id,
|
||||
'date': fields.Date.today(),
|
||||
})
|
||||
account_move_domain = wizard.create_entries()['domain']
|
||||
account_move = self.env['account.move'].search(account_move_domain)
|
||||
self.assertRecordValues(account_move.line_ids.sorted('id'), [
|
||||
# Accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 0, 'credit': 360},
|
||||
{'account_id': self.account_expense.id, 'debit': 360, 'credit': 0},
|
||||
{'account_id': account_variation.id, 'debit': 0, 'credit': 169.41},
|
||||
{'account_id': self.account_expense.id, 'debit': 169.41, 'credit': 0},
|
||||
# Reversal of accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 360, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 360},
|
||||
{'account_id': account_variation.id, 'debit': 169.41, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 169.41},
|
||||
])
|
||||
|
||||
# Delivery 3 more units (20 / 20 units.)
|
||||
backorder_delivery = sale_order_1.picking_ids.filtered(lambda pick: pick.state == 'assigned')
|
||||
backorder_delivery.button_validate()
|
||||
|
||||
# Use accrued order wizard and check generated values.
|
||||
wizard = self.env['account.accrued.orders.wizard'].with_context({
|
||||
'active_model': 'sale.order',
|
||||
'active_ids': [sale_order_1.id],
|
||||
}).create({
|
||||
'account_id': self.account_expense.id,
|
||||
'date': fields.Date.today(),
|
||||
})
|
||||
account_move_domain = wizard.create_entries()['domain']
|
||||
account_move = self.env['account.move'].search(account_move_domain)
|
||||
self.assertRecordValues(account_move.line_ids.sorted('id'), [
|
||||
# Accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 0, 'credit': 450},
|
||||
{'account_id': self.account_expense.id, 'debit': 450, 'credit': 0},
|
||||
{'account_id': account_variation.id, 'debit': 0, 'credit': 229.41},
|
||||
{'account_id': self.account_expense.id, 'debit': 229.41, 'credit': 0},
|
||||
# Reversal of accrued revenues entries.
|
||||
{'account_id': self.account_revenue.id, 'debit': 450, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 450},
|
||||
{'account_id': account_variation.id, 'debit': 229.41, 'credit': 0},
|
||||
{'account_id': self.account_expense.id, 'debit': 0, 'credit': 229.41},
|
||||
])
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
|
||||
from odoo.addons.sale.tests.common import TestSaleCommon
|
||||
from odoo.addons.sale_stock.tests.common import TestSaleStockCommon
|
||||
from odoo import fields
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
|
@ -9,11 +9,11 @@ from datetime import timedelta
|
|||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
||||
class TestSaleStockLeadTime(TestSaleStockCommon, ValuationReconciliationTestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# Update the product_1 with type and Customer Lead Time
|
||||
cls.test_product_order.sale_delay = 5.0
|
||||
|
|
@ -28,13 +28,11 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
# Create sale order of product_1
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
'picking_policy': 'direct',
|
||||
'warehouse_id': self.company_data['default_warehouse'].id,
|
||||
'order_line': [(0, 0, {
|
||||
'product_id': self.test_product_order.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': self.env.ref('uom.product_uom_unit').id,
|
||||
})]
|
||||
})
|
||||
|
||||
|
|
@ -54,12 +52,11 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
def test_01_product_route_level_delays(self):
|
||||
""" In order to check schedule dates, set product's Customer Lead Time
|
||||
and warehouse route's delay."""
|
||||
|
||||
# Update warehouse_1 with Outgoing Shippings pick + pack + ship
|
||||
self.company_data['default_warehouse'].write({'delivery_steps': 'pick_pack_ship'})
|
||||
# FIXME QUWO: This test no longer works with the current push flow, yet still works with old pull rules.
|
||||
warehouse = self.warehouse_3_steps_pull
|
||||
|
||||
# Set delay on pull rule
|
||||
for pull_rule in self.company_data['default_warehouse'].delivery_route_id.rule_ids:
|
||||
for pull_rule in warehouse.delivery_route_id.rule_ids:
|
||||
pull_rule.write({'delay': 2})
|
||||
|
||||
# Create sale order of product_1
|
||||
|
|
@ -67,13 +64,11 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
'picking_policy': 'direct',
|
||||
'warehouse_id': self.company_data['default_warehouse'].id,
|
||||
'warehouse_id': warehouse.id,
|
||||
'order_line': [(0, 0, {'name': self.test_product_order.name,
|
||||
'product_id': self.test_product_order.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.env.ref('uom.product_uom_unit').id,
|
||||
'customer_lead': self.test_product_order.sale_delay})]})
|
||||
|
||||
# Confirm our standard sale order
|
||||
|
|
@ -83,19 +78,19 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
self.assertTrue(order.picking_ids, "Pickings should be created.")
|
||||
|
||||
# Check schedule date of ship type picking
|
||||
out = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].out_type_id)
|
||||
out = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.out_type_id)
|
||||
out_min_date = fields.Datetime.from_string(out.scheduled_date)
|
||||
out_date = fields.Datetime.from_string(order.date_order) + timedelta(days=self.test_product_order.sale_delay) - timedelta(days=out.move_ids[0].rule_id.delay)
|
||||
self.assertTrue(abs(out_min_date - out_date) <= timedelta(seconds=1), 'Schedule date of ship type picking should be equal to: order date + Customer Lead Time - pull rule delay.')
|
||||
|
||||
# Check schedule date of pack type picking
|
||||
pack = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].pack_type_id)
|
||||
pack = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.pack_type_id)
|
||||
pack_min_date = fields.Datetime.from_string(pack.scheduled_date)
|
||||
pack_date = out_date - timedelta(days=pack.move_ids[0].rule_id.delay)
|
||||
self.assertTrue(abs(pack_min_date - pack_date) <= timedelta(seconds=1), 'Schedule date of pack type picking should be equal to: Schedule date of ship type picking - pull rule delay.')
|
||||
|
||||
# Check schedule date of pick type picking
|
||||
pick = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].pick_type_id)
|
||||
pick = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.pick_type_id)
|
||||
pick_min_date = fields.Datetime.from_string(pick.scheduled_date)
|
||||
pick_date = pack_date - timedelta(days=pick.move_ids[0].rule_id.delay)
|
||||
self.assertTrue(abs(pick_min_date - pick_date) <= timedelta(seconds=1), 'Schedule date of pick type picking should be equal to: Schedule date of pack type picking - pull rule delay.')
|
||||
|
|
@ -104,6 +99,7 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
""" In order to check deadline date propagation, set product's Customer Lead Time
|
||||
and warehouse route's delay in stock rules"""
|
||||
|
||||
# FIXME QUWO: This test no longer works with the current push flow, yet still works with old pull rules.
|
||||
# Example :
|
||||
# -> Set Warehouse with Outgoing Shipments : pick + pack + ship
|
||||
# -> Set Delay : 5 days on stock rules
|
||||
|
|
@ -124,38 +120,34 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
|
||||
# Update company with Sales Safety Days
|
||||
self.env.company.security_lead = 2.00
|
||||
|
||||
# Update warehouse_1 with Outgoing Shippings pick + pack + ship
|
||||
self.company_data['default_warehouse'].write({'delivery_steps': 'pick_pack_ship'})
|
||||
warehouse = self.warehouse_3_steps_pull
|
||||
|
||||
# Set delay on pull rule
|
||||
self.company_data['default_warehouse'].delivery_route_id.rule_ids.write({'delay': 5})
|
||||
warehouse.delivery_route_id.rule_ids.write({'delay': 5})
|
||||
|
||||
# Update the product_1 with type and Customer Lead Time
|
||||
self.test_product_order.write({'type': 'product', 'sale_delay': 30.0})
|
||||
self.test_product_order.write({'is_storable': True, 'sale_delay': 30.0})
|
||||
|
||||
# Now, create sale order of product_1 with customer_lead set on product
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
'picking_policy': 'direct',
|
||||
'warehouse_id': self.company_data['default_warehouse'].id,
|
||||
'warehouse_id': warehouse.id,
|
||||
'order_line': [(0, 0, {'name': self.test_product_order.name,
|
||||
'product_id': self.test_product_order.id,
|
||||
'product_uom_qty': 5,
|
||||
'product_uom': self.env.ref('uom.product_uom_unit').id,
|
||||
'customer_lead': self.test_product_order.sale_delay})]})
|
||||
|
||||
# Confirm our standard sale order
|
||||
order.action_confirm()
|
||||
|
||||
# Check the picking crated or not
|
||||
self.assertTrue(order.picking_ids, "Pickings should be created.")
|
||||
# Check the pickings creation
|
||||
self.assertEqual(len(order.picking_ids), 3)
|
||||
|
||||
# Check schedule/deadline date of ship type picking
|
||||
out = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].out_type_id)
|
||||
out = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.out_type_id)
|
||||
deadline_date = order.date_order + timedelta(days=self.test_product_order.sale_delay) - timedelta(days=out.move_ids[0].rule_id.delay)
|
||||
self.assertAlmostEqual(
|
||||
out.date_deadline, deadline_date, delta=timedelta(seconds=1),
|
||||
|
|
@ -166,7 +158,7 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
msg='Schedule date of ship type picking should be equal to: order date + Customer Lead Time - pull rule delay - security_lead')
|
||||
|
||||
# Check schedule/deadline date of pack type picking
|
||||
pack = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].pack_type_id)
|
||||
pack = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.pack_type_id)
|
||||
pack_scheduled_date = out_scheduled_date - timedelta(days=pack.move_ids[0].rule_id.delay)
|
||||
self.assertAlmostEqual(
|
||||
pack.scheduled_date, pack_scheduled_date, delta=timedelta(seconds=1),
|
||||
|
|
@ -177,7 +169,7 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
msg='Deadline date of pack type picking should be equal to: Deadline date of ship type picking - pull rule delay.')
|
||||
|
||||
# Check schedule/deadline date of pick type picking
|
||||
pick = order.picking_ids.filtered(lambda r: r.picking_type_id == self.company_data['default_warehouse'].pick_type_id)
|
||||
pick = order.picking_ids.filtered(lambda r: r.picking_type_id == warehouse.pick_type_id)
|
||||
pick_scheduled_date = pack_scheduled_date - timedelta(days=pick.move_ids[0].rule_id.delay)
|
||||
self.assertAlmostEqual(
|
||||
pick.scheduled_date, pick_scheduled_date, delta=timedelta(seconds=1),
|
||||
|
|
@ -214,7 +206,6 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
"""
|
||||
order = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
'picking_policy': 'direct',
|
||||
'warehouse_id': self.company_data['default_warehouse'].id,
|
||||
})
|
||||
|
|
@ -222,7 +213,7 @@ class TestSaleStockLeadTime(TestSaleCommon, ValuationReconciliationTestCommon):
|
|||
order_line = self.env['sale.order.line'].create({
|
||||
'product_id': self.test_product_order.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': self.env.ref('uom.product_uom_unit').id,
|
||||
'product_uom_id': self.env.ref('uom.product_uom_unit').id,
|
||||
'order_id': order.id,
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
|
||||
from odoo.addons.sale_stock.tests.common import TestSaleStockCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestSaleStockMultiWarehouse(TestSaleStockCommon, ValuationReconciliationTestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.product_a.is_storable = True
|
||||
|
||||
cls.warehouse_A = cls.company_data['default_warehouse']
|
||||
cls.env['stock.quant']._update_available_quantity(cls.product_a, cls.warehouse_A.lot_stock_id, 10)
|
||||
|
||||
cls.warehouse_B = cls.env['stock.warehouse'].create({
|
||||
'name': 'WH B',
|
||||
'code': 'WHB',
|
||||
'company_id': cls.env.company.id,
|
||||
'partner_id': cls.env.company.partner_id.id,
|
||||
})
|
||||
cls.env['stock.quant']._update_available_quantity(cls.product_a, cls.warehouse_B.lot_stock_id, 10)
|
||||
|
||||
cls.env.user.group_ids |= cls.env.ref('stock.group_stock_user')
|
||||
cls.env.user.group_ids |= cls.env.ref('stock.group_stock_multi_locations')
|
||||
cls.env.user.group_ids |= cls.env.ref('sales_team.group_sale_salesman')
|
||||
|
||||
def test_multiple_warehouses_generate_multiple_pickings(self):
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'warehouse_id': self.warehouse_A.id,
|
||||
'order_line': [
|
||||
(0, 0, {
|
||||
'name': self.product_a.name,
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 9,
|
||||
'price_unit': 1,
|
||||
'route_ids': [self.warehouse_A.delivery_route_id.id],
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': self.product_a.name,
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 10,
|
||||
'price_unit': 1,
|
||||
'route_ids': [self.warehouse_B.delivery_route_id.id],
|
||||
}),
|
||||
],
|
||||
})
|
||||
so.action_confirm()
|
||||
|
||||
# 2 pickings: 1 per warehouse
|
||||
self.assertEqual(len(so.picking_ids), 2)
|
||||
# single move per picking
|
||||
self.assertEqual(len(so.picking_ids[0].move_ids), 1)
|
||||
self.assertEqual(len(so.picking_ids[1].move_ids), 1)
|
||||
# pickings comes from the right warehouse
|
||||
self.assertEqual(so.picking_ids[0].move_ids[0].location_id.warehouse_id, self.warehouse_A)
|
||||
self.assertEqual(so.picking_ids[1].move_ids[0].location_id.warehouse_id, self.warehouse_B)
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.addons.stock_account.tests.test_anglo_saxon_valuation_reconciliation_common import ValuationReconciliationTestCommon
|
||||
from odoo.addons.sale.tests.common import TestSaleCommon
|
||||
from odoo.fields import Command
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
|
|
@ -9,8 +10,9 @@ from odoo.tests import tagged
|
|||
class TestSaleStockMultiCompany(TestSaleCommon, ValuationReconciliationTestCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.company_data_2 = cls.setup_other_company()
|
||||
|
||||
cls.warehouse_A = cls.company_data['default_warehouse']
|
||||
cls.warehouse_A2 = cls.env['stock.warehouse'].create({
|
||||
|
|
@ -21,9 +23,9 @@ class TestSaleStockMultiCompany(TestSaleCommon, ValuationReconciliationTestCommo
|
|||
})
|
||||
cls.warehouse_B = cls.company_data_2['default_warehouse']
|
||||
|
||||
cls.env.user.groups_id |= cls.env.ref('stock.group_stock_user')
|
||||
cls.env.user.groups_id |= cls.env.ref('stock.group_stock_multi_locations')
|
||||
cls.env.user.groups_id |= cls.env.ref('sales_team.group_sale_salesman')
|
||||
cls.env.user.group_ids |= cls.env.ref('stock.group_stock_user')
|
||||
cls.env.user.group_ids |= cls.env.ref('stock.group_stock_multi_locations')
|
||||
cls.env.user.group_ids |= cls.env.ref('sales_team.group_sale_salesman')
|
||||
|
||||
cls.env.user.with_company(cls.company_data['company']).property_warehouse_id = cls.warehouse_A.id
|
||||
cls.env.user.with_company(cls.company_data_2['company']).property_warehouse_id = cls.warehouse_B.id
|
||||
|
|
@ -43,9 +45,7 @@ class TestSaleStockMultiCompany(TestSaleCommon, ValuationReconciliationTestCommo
|
|||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': product.uom_id.id,
|
||||
'price_unit': product.list_price})],
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
}
|
||||
sale_order = self.env['sale.order']
|
||||
|
||||
|
|
@ -62,9 +62,7 @@ class TestSaleStockMultiCompany(TestSaleCommon, ValuationReconciliationTestCommo
|
|||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': product.uom_id.id,
|
||||
'price_unit': product.list_price})],
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
}
|
||||
so_company_A = sale_order.with_company(self.env.company).create(sale_order_vals2)
|
||||
self.assertEqual(so_company_A.warehouse_id.id, self.warehouse_A.id)
|
||||
|
|
@ -78,9 +76,103 @@ class TestSaleStockMultiCompany(TestSaleCommon, ValuationReconciliationTestCommo
|
|||
'name': product.name,
|
||||
'product_id': product.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': product.uom_id.id,
|
||||
'price_unit': product.list_price})],
|
||||
'pricelist_id': self.company_data['default_pricelist'].id,
|
||||
}
|
||||
so_company_B = sale_order.with_company(self.company_data_2['company']).create(sale_order_vals3)
|
||||
self.assertEqual(so_company_B.warehouse_id.id, self.warehouse_B.id)
|
||||
|
||||
def test_sale_product_from_parent_company(self):
|
||||
"""
|
||||
Check that a product from a company can be sold by a branch
|
||||
and that the resulting move can be created.
|
||||
"""
|
||||
parent_company = self.env.company
|
||||
branch_company = self.env['res.company'].create({
|
||||
'name': 'Branch Company',
|
||||
'parent_id': parent_company.id,
|
||||
})
|
||||
|
||||
self.product_a.company_id = parent_company
|
||||
|
||||
sale_order = self.env['sale.order'].with_company(branch_company).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': self.product_a.name,
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 1,
|
||||
})],
|
||||
})
|
||||
|
||||
sale_order.action_confirm()
|
||||
|
||||
self.assertTrue(sale_order.picking_ids.move_ids)
|
||||
|
||||
def test_intercompany_transfer_sale_order_workflow(self):
|
||||
company2 = self.company_data_2['company']
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': company2.partner_id.id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': self.product_a.name,
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 5.0,
|
||||
'product_uom_id': self.product_a.uom_id.id,
|
||||
'price_unit': self.product_a.list_price})],
|
||||
})
|
||||
so.action_confirm()
|
||||
|
||||
picking = so.picking_ids
|
||||
|
||||
# create another move
|
||||
self.env['stock.move'].create({
|
||||
'picking_id': picking.id,
|
||||
'location_id': picking.location_id.id,
|
||||
'location_dest_id': picking.location_dest_id.id,
|
||||
'product_id': self.product_b.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': self.product_b.uom_id.id,
|
||||
'quantity': 1,
|
||||
})
|
||||
|
||||
# ensure we have to moves in the picking
|
||||
self.assertEqual(len(picking.move_ids), 2)
|
||||
|
||||
# make the moves as picked
|
||||
picking.move_ids.picked = True
|
||||
|
||||
picking.button_validate()
|
||||
|
||||
# make sure an order line is created for the new stock move
|
||||
self.assertEqual(len(picking.sale_id.order_line), 2)
|
||||
|
||||
def test_intercompany_show_lot_on_invoice(self):
|
||||
"""
|
||||
Check that lots and serial numbers are displayed on inter-companies invoices.
|
||||
"""
|
||||
self.env.user.group_ids |= self.env.ref('stock_account.group_lot_on_invoice')
|
||||
company2 = self.company_data_2['company']
|
||||
self.product_a.write({
|
||||
'is_storable': 'True',
|
||||
'tracking': 'serial',
|
||||
'invoice_policy': 'delivery',
|
||||
})
|
||||
self.product_a.tracking = 'serial'
|
||||
sn = self.env['stock.lot'].create({'name': 'SN0012', 'product_id': self.product_a.id})
|
||||
self.env['stock.quant']._update_available_quantity(self.product_a, self.warehouse_A.lot_stock_id, 1.0, lot_id=sn)
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': company2.partner_id.id,
|
||||
'order_line': [Command.create({
|
||||
'name': self.product_a.name,
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 1.0,
|
||||
'price_unit': self.product_a.list_price})],
|
||||
})
|
||||
so.action_confirm()
|
||||
delivery = so.picking_ids
|
||||
delivery.button_validate()
|
||||
invoice = so._create_invoices()
|
||||
invoice.action_post()
|
||||
self.assertEqual(
|
||||
[(rec['product_name'], rec['lot_id']) for rec in invoice._get_invoiced_lot_values()],
|
||||
[(self.product_a.name, sn.id)]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@ from datetime import datetime, timedelta
|
|||
from odoo.tools import html2plaintext
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import Form, tagged
|
||||
from odoo.exceptions import AccessError
|
||||
from odoo.tests.common import Form, tagged
|
||||
from odoo.addons.stock.tests.test_report import TestReportsCommon
|
||||
from odoo.addons.sale.tests.common import TestSaleCommon
|
||||
|
||||
|
|
@ -58,10 +58,10 @@ class TestSaleStockReports(TestReportsCommon):
|
|||
line_2 = lines[1]
|
||||
self.assertEqual(line_1['quantity'], 5)
|
||||
self.assertTrue(line_1['replenishment_filled'])
|
||||
self.assertEqual(line_1['document_out'].id, so_2.id)
|
||||
self.assertEqual(line_1['document_out']['id'], so_2.id)
|
||||
self.assertEqual(line_2['quantity'], 5)
|
||||
self.assertEqual(line_2['replenishment_filled'], False)
|
||||
self.assertEqual(line_2['document_out'].id, so_1.id)
|
||||
self.assertEqual(line_2['document_out']['id'], so_1.id)
|
||||
|
||||
def test_report_forecast_2_report_line_corresponding_to_so_line_highlighted(self):
|
||||
""" When accessing the report from a SO line, checks if the correct SO line is highlighted in the report
|
||||
|
|
@ -82,7 +82,7 @@ class TestSaleStockReports(TestReportsCommon):
|
|||
context = {"move_to_match_ids": so.order_line.move_ids.ids}
|
||||
_, _, lines = self.get_report_forecast(product_template_ids=self.product_template.ids, context=context)
|
||||
for line in lines:
|
||||
if line['document_out'] == so:
|
||||
if line['document_out']['id'] == so.id:
|
||||
self.assertTrue(line['is_matched'], "The corresponding SO line should be matched in the forecast report.")
|
||||
else:
|
||||
self.assertFalse(line['is_matched'], "A line of the forecast report not linked to the SO shoud not be matched.")
|
||||
|
|
@ -112,8 +112,8 @@ class TestSaleStockReports(TestReportsCommon):
|
|||
_, _, lines = self.get_report_forecast(product_template_ids=product.product_tmpl_id.ids)
|
||||
outgoing_line = next(filter(lambda line: line.get('document_out'), lines))
|
||||
self.assertEqual(
|
||||
(outgoing_line['document_out'], outgoing_line['quantity'], outgoing_line['replenishment_filled'], outgoing_line['reservation']),
|
||||
(so, 3.0, True, True)
|
||||
(outgoing_line['document_out']['id'], outgoing_line['quantity'], outgoing_line['replenishment_filled'], outgoing_line['reservation']['id']),
|
||||
(so.id, 3.0, True, so.picking_ids.filtered(lambda p: p.picking_type_id == warehouse.pick_type_id).id)
|
||||
)
|
||||
stock_line = next(filter(lambda line: not line.get('document_out'), lines))
|
||||
self.assertEqual(
|
||||
|
|
@ -126,8 +126,8 @@ class TestSaleStockReports(TestReportsCommon):
|
|||
_, _, lines = self.get_report_forecast(product_template_ids=product.product_tmpl_id.ids)
|
||||
outgoing_line = next(filter(lambda line: line.get('document_out'), lines))
|
||||
self.assertEqual(
|
||||
(outgoing_line['document_out'], outgoing_line['quantity'], outgoing_line['replenishment_filled'], outgoing_line['reservation']),
|
||||
(so, 3.0, True, False)
|
||||
(outgoing_line['document_out']['id'], outgoing_line['quantity'], outgoing_line['replenishment_filled'], outgoing_line['reservation']),
|
||||
(so.id, 3.0, True, False)
|
||||
)
|
||||
stock_line = next(filter(lambda line: not line.get('document_out'), lines))
|
||||
self.assertEqual(
|
||||
|
|
@ -163,7 +163,7 @@ class TestSaleStockReports(TestReportsCommon):
|
|||
other = self.env['res.users'].create({
|
||||
'name': 'Other Salesman',
|
||||
'login': 'other',
|
||||
'groups_id': [
|
||||
'group_ids': [
|
||||
Command.link(self.env.ref('sales_team.group_sale_salesman').id),
|
||||
Command.link(self.env.ref('stock.group_stock_user').id),
|
||||
],
|
||||
|
|
@ -171,16 +171,52 @@ class TestSaleStockReports(TestReportsCommon):
|
|||
|
||||
# Need to reset the cache otherwise it wouldn't trigger an Access Error anyway as the Sale Order is already there.
|
||||
sale_order.env.invalidate_all()
|
||||
report_values = self.env['report.stock.report_product_product_replenishment'].with_user(other).get_report_values(docids=self.product.ids)
|
||||
report_values = self.env['stock.forecasted_product_product'].with_user(other).get_report_values(docids=self.product.ids)
|
||||
self.assertEqual(len(report_values['docs']['lines']), 1)
|
||||
self.assertEqual(report_values['docs']['lines'][0]['document_out'], sale_order)
|
||||
self.assertEqual(report_values['docs']['draft_sale_orders'], draft)
|
||||
self.assertEqual(report_values['docs']['lines'][0]['document_out']['name'], sale_order.name)
|
||||
self.assertEqual(len(report_values['docs']['product'][self.product.id]['draft_sale_orders']), 1)
|
||||
self.assertEqual(report_values['docs']['product'][self.product.id]['draft_sale_orders'][0]['name'], draft.name)
|
||||
|
||||
# While 'other' can see these SO on the report, they shouldn't be able to access them.
|
||||
with self.assertRaises(AccessError):
|
||||
report_values['docs']['draft_sale_orders'].with_user(other).check_access_rule('read')
|
||||
sale_order.with_user(other).check_access('read')
|
||||
with self.assertRaises(AccessError):
|
||||
report_values['docs']['lines'][0]['document_out'].with_user(other).check_access_rule('read')
|
||||
draft.with_user(other).check_access('read')
|
||||
|
||||
def test_add_reference_remove_reference_works_with_multiple_records(self):
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner.id,
|
||||
'order_line': [Command.create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_qty': 5,
|
||||
})],
|
||||
})
|
||||
so.action_confirm()
|
||||
so_delivery = so.picking_ids
|
||||
|
||||
so_delivery.reference_ids.copy()
|
||||
|
||||
picking_receipt = self.env['stock.picking'].create({
|
||||
'picking_type_id': self.env.ref('stock.picking_type_in').id,
|
||||
'partner_id': self.partner.id,
|
||||
'move_ids': [Command.create({
|
||||
'product_id': self.product.id,
|
||||
'product_uom_qty': 18,
|
||||
})],
|
||||
})
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
self.env['report.stock.report_reception']._action_assign(
|
||||
picking_receipt.move_ids,
|
||||
so_delivery.move_ids,
|
||||
)
|
||||
self.assertEqual(picking_receipt.move_ids.reference_ids, so_delivery.move_ids.reference_ids)
|
||||
|
||||
self.env['report.stock.report_reception']._action_unassign(
|
||||
picking_receipt.move_ids,
|
||||
so_delivery.move_ids,
|
||||
)
|
||||
self.assertNotIn(picking_receipt.move_ids.reference_ids, so_delivery.move_ids.reference_ids)
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
|
|
@ -191,12 +227,12 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
self.env.ref('base.group_user').write({'implied_ids': [(4, self.env.ref('stock.group_production_lot').id)]})
|
||||
self.product_by_lot = self.env['product.product'].create({
|
||||
'name': 'Product By Lot',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'tracking': 'lot',
|
||||
})
|
||||
self.product_by_usn = self.env['product.product'].create({
|
||||
'name': 'Product By USN',
|
||||
'type': 'product',
|
||||
'is_storable': True,
|
||||
'tracking': 'serial',
|
||||
})
|
||||
self.warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
||||
|
|
@ -204,17 +240,14 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
lot = self.env['stock.lot'].create({
|
||||
'name': 'LOT0001',
|
||||
'product_id': self.product_by_lot.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.usn01 = self.env['stock.lot'].create({
|
||||
'name': 'USN0001',
|
||||
'product_id': self.product_by_usn.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.usn02 = self.env['stock.lot'].create({
|
||||
'name': 'USN0002',
|
||||
'product_id': self.product_by_usn.id,
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
self.env['stock.quant']._update_available_quantity(self.product_by_lot, self.stock_location, 10, lot_id=lot)
|
||||
self.env['stock.quant']._update_available_quantity(self.product_by_usn, self.stock_location, 1, lot_id=self.usn01)
|
||||
|
|
@ -228,7 +261,7 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
"""
|
||||
display_lots = self.env.ref('stock_account.group_lot_on_invoice')
|
||||
display_uom = self.env.ref('uom.group_uom')
|
||||
self.env.user.write({'groups_id': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
self.env.user.write({'group_ids': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
|
|
@ -239,7 +272,7 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
so.action_confirm()
|
||||
|
||||
picking = so.picking_ids
|
||||
picking.move_ids.quantity_done = 5
|
||||
picking.move_ids.write({'quantity': 5, 'picked': True})
|
||||
picking.button_validate()
|
||||
|
||||
invoice = so._create_invoices()
|
||||
|
|
@ -262,7 +295,7 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
"""
|
||||
display_lots = self.env.ref('stock_account.group_lot_on_invoice')
|
||||
display_uom = self.env.ref('uom.group_uom')
|
||||
self.env.user.write({'groups_id': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
self.env.user.write({'group_ids': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
|
||||
self.product_by_lot.invoice_policy = "order"
|
||||
|
||||
|
|
@ -278,7 +311,7 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
invoice.action_post()
|
||||
|
||||
picking = so.picking_ids
|
||||
picking.move_ids.quantity_done = 4
|
||||
picking.move_ids.write({'quantity': 4, 'picked': True})
|
||||
picking.button_validate()
|
||||
|
||||
html = self.env['ir.actions.report']._render_qweb_html(
|
||||
|
|
@ -286,6 +319,40 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
text = html2plaintext(html)
|
||||
self.assertRegex(text, r'Product By Lot\n4.00Units\nLOT0001', "There should be a line that specifies 4 x LOT0001")
|
||||
|
||||
def test_picking_description(self):
|
||||
"""
|
||||
Verify that for a no-variant product, the product name is not included as the first element in the picking description,
|
||||
as this avoids repeating the name on the delivery slip.
|
||||
"""
|
||||
|
||||
product_attr = self.env['product.attribute'].create({'name': 'Color', 'create_variant': 'no_variant'})
|
||||
product_attrv1, product_attrv2 = self.env['product.attribute.value'].create([
|
||||
{'name': 'Value1', 'attribute_id': product_attr.id},
|
||||
{'name': 'Value2', 'attribute_id': product_attr.id},
|
||||
])
|
||||
product_template_no_variant = self.env['product.template'].create({
|
||||
'name': 'product name',
|
||||
'attribute_line_ids': [
|
||||
Command.create({
|
||||
'attribute_id': product_attr.id,
|
||||
'value_ids': [Command.set([product_attrv1.id, product_attrv2.id])],
|
||||
})]
|
||||
})
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [
|
||||
Command.create({'name': product_template_no_variant.name,
|
||||
'product_id': product_template_no_variant.product_variant_id.id,
|
||||
'product_uom_qty': 4,
|
||||
'product_no_variant_attribute_value_ids': product_template_no_variant.product_variant_id.attribute_line_ids.product_template_value_ids
|
||||
}),
|
||||
],
|
||||
})
|
||||
so.action_confirm()
|
||||
picking = so.picking_ids[0]
|
||||
picking_description = picking.move_ids._get_report_description_picking()
|
||||
self.assertEqual(picking_description, 'Color: Value1\nColor: Value2')
|
||||
|
||||
def test_backorder_and_several_invoices(self):
|
||||
"""
|
||||
Suppose the lots are printed on the invoices.
|
||||
|
|
@ -295,7 +362,7 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
"""
|
||||
display_lots = self.env.ref('stock_account.group_lot_on_invoice')
|
||||
display_uom = self.env.ref('uom.group_uom')
|
||||
self.env.user.write({'groups_id': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
self.env.user.write({'group_ids': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
|
|
@ -306,11 +373,8 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
so.action_confirm()
|
||||
|
||||
picking = so.picking_ids
|
||||
picking.move_ids.move_line_ids[0].qty_done = 1
|
||||
picking.move_ids.move_line_ids[0].quantity = 1
|
||||
picking.button_validate()
|
||||
action = picking.button_validate()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
|
||||
invoice01 = so._create_invoices()
|
||||
with Form(invoice01) as form:
|
||||
|
|
@ -319,7 +383,7 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
invoice01.action_post()
|
||||
|
||||
backorder = picking.backorder_ids
|
||||
backorder.move_ids.move_line_ids.qty_done = 1
|
||||
backorder.move_ids.move_line_ids.quantity = 1
|
||||
backorder.button_validate()
|
||||
|
||||
IrActionsReport = self.env['ir.actions.report']
|
||||
|
|
@ -367,13 +431,12 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
"""
|
||||
display_lots = self.env.ref('stock_account.group_lot_on_invoice')
|
||||
display_uom = self.env.ref('uom.group_uom')
|
||||
self.env.user.write({'groups_id': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
self.env.user.write({'group_ids': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
|
||||
lot01 = self.env['stock.lot'].search([('name', '=', 'LOT0001')])
|
||||
lot02, lot03 = self.env['stock.lot'].create([{
|
||||
'name': name,
|
||||
'product_id': self.product_by_lot.id,
|
||||
'company_id': self.env.company.id,
|
||||
} for name in ['LOT0002', 'LOT0003']])
|
||||
self.env['stock.quant']._update_available_quantity(self.product_by_lot, self.stock_location, 8, lot_id=lot02)
|
||||
self.env['stock.quant']._update_available_quantity(self.product_by_lot, self.stock_location, 2, lot_id=lot03)
|
||||
|
|
@ -388,39 +451,41 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
|
||||
# Deliver 10 x LOT0001
|
||||
delivery01 = so.picking_ids
|
||||
delivery01.move_ids.quantity_done = 10
|
||||
delivery01.move_ids.write({'quantity': 10, 'picked': True})
|
||||
delivery01.button_validate()
|
||||
self.assertEqual(delivery01.move_line_ids.lot_id.name, 'LOT0001')
|
||||
|
||||
# Return delivery01 (-> 10 x LOT0001)
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_ids=[delivery01.id], active_id=delivery01.id, active_model='stock.picking'))
|
||||
return_wizard = return_form.save()
|
||||
action = return_wizard.create_returns()
|
||||
return_wizard.product_return_moves.quantity = 10
|
||||
action = return_wizard.action_create_returns()
|
||||
pick_return = self.env['stock.picking'].browse(action['res_id'])
|
||||
|
||||
move_form = Form(pick_return.move_ids, view='stock.view_stock_move_nosuggest_operations')
|
||||
with move_form.move_line_nosuggest_ids.new() as line:
|
||||
move_form = Form(pick_return.move_ids, view='stock.view_stock_move_operations')
|
||||
with move_form.move_line_ids.edit(0) as line:
|
||||
line.lot_id = lot01
|
||||
line.qty_done = 10
|
||||
line.quantity = 10
|
||||
move_form.save()
|
||||
pick_return.move_ids.picked = True
|
||||
pick_return.button_validate()
|
||||
|
||||
# Return pick_return
|
||||
return_form = Form(self.env['stock.return.picking'].with_context(active_ids=[pick_return.id], active_id=pick_return.id, active_model='stock.picking'))
|
||||
return_wizard = return_form.save()
|
||||
action = return_wizard.create_returns()
|
||||
return_wizard.product_return_moves.quantity = 10
|
||||
action = return_wizard.action_create_returns()
|
||||
delivery02 = self.env['stock.picking'].browse(action['res_id'])
|
||||
|
||||
# Deliver 3 x LOT0002
|
||||
delivery02.do_unreserve()
|
||||
move_form = Form(delivery02.move_ids, view='stock.view_stock_move_nosuggest_operations')
|
||||
with move_form.move_line_nosuggest_ids.new() as line:
|
||||
move_form = Form(delivery02.move_ids, view='stock.view_stock_move_operations')
|
||||
with move_form.move_line_ids.new() as line:
|
||||
line.lot_id = lot02
|
||||
line.qty_done = 3
|
||||
line.quantity = 3
|
||||
move_form.save()
|
||||
action = delivery02.button_validate()
|
||||
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
|
||||
wizard.process()
|
||||
delivery02.move_ids.picked = True
|
||||
Form.from_action(self.env, delivery02.button_validate()).save().process()
|
||||
|
||||
# Invoice 2 x P
|
||||
invoice01 = so._create_invoices()
|
||||
|
|
@ -438,14 +503,15 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
# Deliver 5 x LOT0002 + 2 x LOT0003
|
||||
delivery03 = delivery02.backorder_ids
|
||||
delivery03.do_unreserve()
|
||||
move_form = Form(delivery03.move_ids, view='stock.view_stock_move_nosuggest_operations')
|
||||
with move_form.move_line_nosuggest_ids.new() as line:
|
||||
move_form = Form(delivery03.move_ids, view='stock.view_stock_move_operations')
|
||||
with move_form.move_line_ids.new() as line:
|
||||
line.lot_id = lot02
|
||||
line.qty_done = 5
|
||||
with move_form.move_line_nosuggest_ids.new() as line:
|
||||
line.quantity = 5
|
||||
with move_form.move_line_ids.new() as line:
|
||||
line.lot_id = lot03
|
||||
line.qty_done = 2
|
||||
line.quantity = 2
|
||||
move_form.save()
|
||||
delivery03.move_ids.picked = True
|
||||
delivery03.button_validate()
|
||||
|
||||
# Invoice 8 x P
|
||||
|
|
@ -468,7 +534,7 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
"""
|
||||
display_lots = self.env.ref('stock_account.group_lot_on_invoice')
|
||||
display_uom = self.env.ref('uom.group_uom')
|
||||
self.env.user.write({'groups_id': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
self.env.user.write({'group_ids': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
|
|
@ -479,8 +545,9 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
so.action_confirm()
|
||||
|
||||
picking = so.picking_ids
|
||||
picking.move_ids.move_line_ids[0].qty_done = 1
|
||||
picking.move_ids.move_line_ids[1].qty_done = 1
|
||||
picking.move_ids.move_line_ids[0].quantity = 1
|
||||
picking.move_ids.move_line_ids[1].quantity = 1
|
||||
picking.move_ids.picked = True
|
||||
picking.button_validate()
|
||||
|
||||
invoice01 = so._create_invoices()
|
||||
|
|
@ -493,26 +560,28 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
|
||||
# Refund the invoice
|
||||
refund_wizard = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=invoice01.ids).create({
|
||||
'refund_method': 'cancel',
|
||||
'journal_id': invoice01.journal_id.id,
|
||||
})
|
||||
res = refund_wizard.reverse_moves()
|
||||
res = refund_wizard.refund_moves()
|
||||
refund_invoice = self.env['account.move'].browse(res['res_id'])
|
||||
refund_invoice.action_post()
|
||||
|
||||
# recieve the returned product
|
||||
stock_return_picking_form = Form(self.env['stock.return.picking'].with_context(active_ids=picking.ids, active_id=picking.sorted().ids[0], active_model='stock.picking'))
|
||||
return_wiz = stock_return_picking_form.save()
|
||||
res = return_wiz.create_returns()
|
||||
return_wiz.product_return_moves.quantity = 2
|
||||
res = return_wiz.action_create_returns()
|
||||
pick_return = self.env['stock.picking'].browse(res['res_id'])
|
||||
|
||||
move_form = Form(pick_return.move_ids, view='stock.view_stock_move_nosuggest_operations')
|
||||
with move_form.move_line_nosuggest_ids.new() as line:
|
||||
move_form = Form(pick_return.move_ids, view='stock.view_stock_move_operations')
|
||||
with move_form.move_line_ids.edit(0) as line:
|
||||
line.lot_id = self.usn01
|
||||
line.qty_done = 1
|
||||
with move_form.move_line_nosuggest_ids.new() as line:
|
||||
line.quantity = 1
|
||||
with move_form.move_line_ids.edit(1) as line:
|
||||
line.lot_id = self.usn02
|
||||
line.qty_done = 1
|
||||
line.quantity = 1
|
||||
move_form.save()
|
||||
pick_return.move_ids.picked = True
|
||||
pick_return.button_validate()
|
||||
|
||||
# reversed invoice
|
||||
|
|
@ -530,7 +599,7 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
"""
|
||||
display_lots = self.env.ref('stock_account.group_lot_on_invoice')
|
||||
display_uom = self.env.ref('uom.group_uom')
|
||||
self.env.user.write({'groups_id': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
self.env.user.write({'group_ids': [(4, display_lots.id), (4, display_uom.id)]})
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
|
|
@ -541,7 +610,8 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
so.action_confirm()
|
||||
|
||||
picking = so.picking_ids
|
||||
picking.move_ids.move_line_ids[0].qty_done = 1
|
||||
picking.move_ids.move_line_ids[0].quantity = 1
|
||||
picking.move_ids.picked = True
|
||||
picking.button_validate()
|
||||
|
||||
invoice01 = so._create_invoices()
|
||||
|
|
@ -553,10 +623,9 @@ class TestSaleStockInvoices(TestSaleCommon):
|
|||
|
||||
# Refund the invoice with full refund and new draft invoice
|
||||
refund_wizard = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=invoice01.ids).create({
|
||||
'refund_method': 'modify',
|
||||
'journal_id': invoice01.journal_id.id,
|
||||
})
|
||||
res = refund_wizard.reverse_moves()
|
||||
res = refund_wizard.modify_moves()
|
||||
invoice02 = self.env['account.move'].browse(res['res_id'])
|
||||
invoice02.action_post()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue