567 lines
26 KiB
Python
567 lines
26 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
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.addons.stock.tests.test_report import TestReportsCommon
|
|
from odoo.addons.sale.tests.common import TestSaleCommon
|
|
|
|
|
|
class TestSaleStockReports(TestReportsCommon):
|
|
def test_report_forecast_1_sale_order_replenishment(self):
|
|
""" Create and confirm two sale orders: one for the next week and one
|
|
for tomorrow. Then check in the report it's the most urgent who is
|
|
linked to the qty. on stock.
|
|
"""
|
|
# make sure first picking doesn't auto-assign
|
|
self.picking_type_out.reservation_method = 'manual'
|
|
|
|
today = datetime.today()
|
|
# Put some quantity in stock.
|
|
quant_vals = {
|
|
'product_id': self.product.id,
|
|
'product_uom_id': self.product.uom_id.id,
|
|
'location_id': self.stock_location.id,
|
|
'quantity': 5,
|
|
'reserved_quantity': 0,
|
|
}
|
|
self.env['stock.quant'].create(quant_vals)
|
|
# Create a first SO for the next week.
|
|
so_form = Form(self.env['sale.order'])
|
|
so_form.partner_id = self.partner
|
|
# so_form.validity_date = today + timedelta(days=7)
|
|
with so_form.order_line.new() as so_line:
|
|
so_line.product_id = self.product
|
|
so_line.product_uom_qty = 5
|
|
so_1 = so_form.save()
|
|
so_1.action_confirm()
|
|
so_1.picking_ids.scheduled_date = today + timedelta(days=7)
|
|
|
|
# Create a second SO for tomorrow.
|
|
so_form = Form(self.env['sale.order'])
|
|
so_form.partner_id = self.partner
|
|
# so_form.validity_date = today + timedelta(days=1)
|
|
with so_form.order_line.new() as so_line:
|
|
so_line.product_id = self.product
|
|
so_line.product_uom_qty = 5
|
|
so_2 = so_form.save()
|
|
so_2.action_confirm()
|
|
so_2.picking_ids.scheduled_date = today + timedelta(days=1)
|
|
|
|
report_values, docs, lines = self.get_report_forecast(product_template_ids=self.product_template.ids)
|
|
self.assertEqual(len(lines), 2)
|
|
line_1 = lines[0]
|
|
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_2['quantity'], 5)
|
|
self.assertEqual(line_2['replenishment_filled'], False)
|
|
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
|
|
"""
|
|
# We create 2 identical SO
|
|
so_form = Form(self.env['sale.order'])
|
|
so_form.partner_id = self.partner
|
|
with so_form.order_line.new() as line:
|
|
line.product_id = self.product
|
|
line.product_uom_qty = 5
|
|
so1 = so_form.save()
|
|
so1.action_confirm()
|
|
so2 = so1.copy()
|
|
so2.action_confirm()
|
|
|
|
# Check for both SO if the highlight (is_matched) corresponds to the correct SO
|
|
for so in [so1, so2]:
|
|
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']['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.")
|
|
|
|
def test_report_forecast_3_unreserve_2_step_delivery(self):
|
|
"""
|
|
Check that the forecast correctly reconciles the outgoing moves
|
|
that are part of a chain with stock availability when unreserved.
|
|
"""
|
|
warehouse = self.env.ref("stock.warehouse0")
|
|
warehouse.delivery_steps = 'pick_ship'
|
|
product = self.product
|
|
# Put 5 units in stock
|
|
self.env['stock.quant']._update_available_quantity(product, warehouse.lot_stock_id, 5)
|
|
# Create and confirm an SO for 3 units
|
|
so = self.env['sale.order'].create({
|
|
'partner_id': self.partner.id,
|
|
'order_line': [
|
|
Command.create({
|
|
'name': product.name,
|
|
'product_id': product.id,
|
|
'product_uom_qty': 3,
|
|
}),
|
|
],
|
|
})
|
|
so.action_confirm()
|
|
_, _, 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']['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(
|
|
(stock_line['quantity'], stock_line['replenishment_filled'], stock_line['reservation']),
|
|
(2.0, True, False)
|
|
)
|
|
# unrerseve the PICK delivery
|
|
pick_delivery = so.picking_ids.filtered(lambda p: p.picking_type_id == warehouse.pick_type_id)
|
|
pick_delivery.do_unreserve()
|
|
_, _, 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']['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(
|
|
(stock_line['quantity'], stock_line['replenishment_filled'], stock_line['reservation']),
|
|
(2.0, True, False)
|
|
)
|
|
|
|
def test_report_forecast_4_so_from_another_salesman(self):
|
|
""" Try accessing the forecast with a user that has only access to his SO while another user has created:
|
|
- A draft Sale Order
|
|
- A confirmed Sale Order
|
|
The report shoud be usable by that user, and while he cannot open those SO, he should still see them to have the correct
|
|
informations in the report.
|
|
"""
|
|
# Create the SO & confirm it with first user
|
|
with Form(self.env['sale.order']) as so_form:
|
|
so_form.partner_id = self.partner
|
|
with so_form.order_line.new() as line:
|
|
line.product_id = self.product
|
|
line.product_uom_qty = 3
|
|
sale_order = so_form.save()
|
|
sale_order.action_confirm()
|
|
|
|
# Create a draft SO with the same user for the same product
|
|
with Form(self.env['sale.order']) as so_form:
|
|
so_form.partner_id = self.partner
|
|
with so_form.order_line.new() as line:
|
|
line.product_id = self.product
|
|
line.product_uom_qty = 2
|
|
draft = so_form.save()
|
|
|
|
# Create second user which only has access to its own documents
|
|
other = self.env['res.users'].create({
|
|
'name': 'Other Salesman',
|
|
'login': 'other',
|
|
'groups_id': [
|
|
Command.link(self.env.ref('sales_team.group_sale_salesman').id),
|
|
Command.link(self.env.ref('stock.group_stock_user').id),
|
|
],
|
|
})
|
|
|
|
# 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['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']['name'], sale_order.name)
|
|
self.assertEqual(len(report_values['docs']['draft_sale_orders']), 1)
|
|
self.assertEqual(report_values['docs']['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):
|
|
sale_order.with_user(other).check_access('read')
|
|
with self.assertRaises(AccessError):
|
|
draft.with_user(other).check_access('read')
|
|
|
|
|
|
@tagged('post_install', '-at_install')
|
|
class TestSaleStockInvoices(TestSaleCommon):
|
|
|
|
def setUp(self):
|
|
super(TestSaleStockInvoices, self).setUp()
|
|
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',
|
|
'is_storable': True,
|
|
'tracking': 'lot',
|
|
})
|
|
self.product_by_usn = self.env['product.product'].create({
|
|
'name': 'Product By USN',
|
|
'is_storable': True,
|
|
'tracking': 'serial',
|
|
})
|
|
self.warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
|
self.stock_location = self.warehouse.lot_stock_id
|
|
lot = self.env['stock.lot'].create({
|
|
'name': 'LOT0001',
|
|
'product_id': self.product_by_lot.id,
|
|
})
|
|
self.usn01 = self.env['stock.lot'].create({
|
|
'name': 'USN0001',
|
|
'product_id': self.product_by_usn.id,
|
|
})
|
|
self.usn02 = self.env['stock.lot'].create({
|
|
'name': 'USN0002',
|
|
'product_id': self.product_by_usn.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)
|
|
self.env['stock.quant']._update_available_quantity(self.product_by_usn, self.stock_location, 1, lot_id=self.usn02)
|
|
|
|
def test_invoice_less_than_delivered(self):
|
|
"""
|
|
Suppose the lots are printed on the invoices.
|
|
A user invoice a tracked product with a smaller quantity than delivered.
|
|
On the invoice, the quantity of the used lot should be the invoiced one.
|
|
"""
|
|
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)]})
|
|
|
|
so = self.env['sale.order'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'order_line': [
|
|
(0, 0, {'name': self.product_by_lot.name, 'product_id': self.product_by_lot.id, 'product_uom_qty': 5}),
|
|
],
|
|
})
|
|
so.action_confirm()
|
|
|
|
picking = so.picking_ids
|
|
picking.move_ids.write({'quantity': 5, 'picked': True})
|
|
picking.button_validate()
|
|
|
|
invoice = so._create_invoices()
|
|
with Form(invoice) as form:
|
|
with form.invoice_line_ids.edit(0) as line:
|
|
line.quantity = 2
|
|
invoice.action_post()
|
|
|
|
html = self.env['ir.actions.report']._render_qweb_html(
|
|
'account.report_invoice_with_payments', invoice.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By Lot\n2.00Units\nLOT0001', "There should be a line that specifies 2 x LOT0001")
|
|
|
|
def test_invoice_before_delivery(self):
|
|
"""
|
|
Suppose the lots are printed on the invoices.
|
|
The user sells a tracked product, its invoicing policy is "Ordered quantities"
|
|
A user invoice a tracked product with a smaller quantity than delivered.
|
|
On the invoice, the quantity of the used lot should be the invoiced one.
|
|
"""
|
|
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.product_by_lot.invoice_policy = "order"
|
|
|
|
so = self.env['sale.order'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'order_line': [
|
|
(0, 0, {'name': self.product_by_lot.name, 'product_id': self.product_by_lot.id, 'product_uom_qty': 4}),
|
|
],
|
|
})
|
|
so.action_confirm()
|
|
|
|
invoice = so._create_invoices()
|
|
invoice.action_post()
|
|
|
|
picking = so.picking_ids
|
|
picking.move_ids.write({'quantity': 4, 'picked': True})
|
|
picking.button_validate()
|
|
|
|
html = self.env['ir.actions.report']._render_qweb_html(
|
|
'account.report_invoice_with_payments', invoice.ids)[0]
|
|
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_backorder_and_several_invoices(self):
|
|
"""
|
|
Suppose the lots are printed on the invoices.
|
|
The user sells 2 tracked-by-usn products, he delivers 1 product and invoices it
|
|
Then, he delivers the other one and invoices it too. Each invoice should have the
|
|
correct USN
|
|
"""
|
|
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)]})
|
|
|
|
so = self.env['sale.order'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'order_line': [
|
|
(0, 0, {'name': self.product_by_usn.name, 'product_id': self.product_by_usn.id, 'product_uom_qty': 2}),
|
|
],
|
|
})
|
|
so.action_confirm()
|
|
|
|
picking = so.picking_ids
|
|
picking.move_ids.move_line_ids[0].quantity = 1
|
|
picking.button_validate()
|
|
|
|
invoice01 = so._create_invoices()
|
|
with Form(invoice01) as form:
|
|
with form.invoice_line_ids.edit(0) as line:
|
|
line.quantity = 1
|
|
invoice01.action_post()
|
|
|
|
backorder = picking.backorder_ids
|
|
backorder.move_ids.move_line_ids.quantity = 1
|
|
backorder.button_validate()
|
|
|
|
IrActionsReport = self.env['ir.actions.report']
|
|
html = IrActionsReport._render_qweb_html('account.report_invoice_with_payments', invoice01.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0001', "There should be a line that specifies 1 x USN0001")
|
|
self.assertNotIn('USN0002', text)
|
|
|
|
invoice02 = so._create_invoices()
|
|
invoice02.action_post()
|
|
html = IrActionsReport._render_qweb_html('account.report_invoice_with_payments', invoice02.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0002', "There should be a line that specifies 1 x USN0002")
|
|
self.assertNotIn('USN0001', text)
|
|
|
|
# Posting the second invoice shouldn't change the result of the first one
|
|
html = IrActionsReport._render_qweb_html('account.report_invoice_with_payments', invoice01.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0001', "There should still be a line that specifies 1 x USN0001")
|
|
self.assertNotIn('USN0002', text)
|
|
|
|
# Resetting and posting again the first invoice shouldn't change the results
|
|
invoice01.button_draft()
|
|
invoice01.action_post()
|
|
html = IrActionsReport._render_qweb_html('account.report_invoice_with_payments', invoice01.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0001', "There should still be a line that specifies 1 x USN0001")
|
|
self.assertNotIn('USN0002', text)
|
|
html = IrActionsReport._render_qweb_html('account.report_invoice_with_payments', invoice02.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0002', "There should be a line that specifies 1 x USN0002")
|
|
self.assertNotIn('USN0001', text)
|
|
|
|
def test_invoice_with_several_returns(self):
|
|
"""
|
|
Mix of returns and partial invoice
|
|
- Product P tracked by lot
|
|
- SO with 10 x P
|
|
- Deliver 10 x Lot01
|
|
- Return 10 x Lot01
|
|
- Deliver 03 x Lot02
|
|
- Invoice 02 x P
|
|
- Deliver 05 x Lot02 + 02 x Lot03
|
|
- Invoice 08 x P
|
|
"""
|
|
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)]})
|
|
|
|
lot01 = self.env['stock.lot'].search([('name', '=', 'LOT0001')])
|
|
lot02, lot03 = self.env['stock.lot'].create([{
|
|
'name': name,
|
|
'product_id': self.product_by_lot.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)
|
|
|
|
so = self.env['sale.order'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'order_line': [
|
|
(0, 0, {'name': self.product_by_lot.name, 'product_id': self.product_by_lot.id, 'product_uom_qty': 10}),
|
|
],
|
|
})
|
|
so.action_confirm()
|
|
|
|
# Deliver 10 x LOT0001
|
|
delivery01 = so.picking_ids
|
|
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()
|
|
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_operations')
|
|
with move_form.move_line_ids.edit(0) as line:
|
|
line.lot_id = lot01
|
|
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()
|
|
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_operations')
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_id = lot02
|
|
line.quantity = 3
|
|
move_form.save()
|
|
delivery02.move_ids.picked = True
|
|
Form.from_action(self.env, delivery02.button_validate()).save().process()
|
|
|
|
# Invoice 2 x P
|
|
invoice01 = so._create_invoices()
|
|
with Form(invoice01) as form:
|
|
with form.invoice_line_ids.edit(0) as line:
|
|
line.quantity = 2
|
|
invoice01.action_post()
|
|
|
|
html = self.env['ir.actions.report']._render_qweb_html(
|
|
'account.report_invoice_with_payments', invoice01.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By Lot\n2.00Units\nLOT0002', "There should be a line that specifies 2 x LOT0002")
|
|
self.assertNotIn('LOT0001', text)
|
|
|
|
# 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_operations')
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_id = lot02
|
|
line.quantity = 5
|
|
with move_form.move_line_ids.new() as line:
|
|
line.lot_id = lot03
|
|
line.quantity = 2
|
|
move_form.save()
|
|
delivery03.move_ids.picked = True
|
|
delivery03.button_validate()
|
|
|
|
# Invoice 8 x P
|
|
invoice02 = so._create_invoices()
|
|
invoice02.action_post()
|
|
|
|
html = self.env['ir.actions.report']._render_qweb_html(
|
|
'account.report_invoice_with_payments', invoice02.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By Lot\n6.00Units\nLOT0002', "There should be a line that specifies 6 x LOT0002")
|
|
self.assertRegex(text, r'Product By Lot\n2.00Units\nLOT0003', "There should be a line that specifies 2 x LOT0003")
|
|
self.assertNotIn('LOT0001', text)
|
|
|
|
def test_refund_cancel_invoices(self):
|
|
"""
|
|
Suppose the lots are printed on the invoices.
|
|
The user sells 2 tracked-by-usn products, he delivers 2 products and invoices them
|
|
Then he adds credit notes and issues a full refund. Receive the products.
|
|
The reversed invoice should also have correct USN
|
|
"""
|
|
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)]})
|
|
|
|
so = self.env['sale.order'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'order_line': [
|
|
(0, 0, {'name': self.product_by_usn.name, 'product_id': self.product_by_usn.id, 'product_uom_qty': 2}),
|
|
],
|
|
})
|
|
so.action_confirm()
|
|
|
|
picking = so.picking_ids
|
|
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()
|
|
invoice01.action_post()
|
|
|
|
html = self.env['ir.actions.report']._render_qweb_html('account.report_invoice_with_payments', invoice01.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0001', "There should be a line that specifies 1 x USN0001")
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0002', "There should be a line that specifies 1 x USN0002")
|
|
|
|
# Refund the invoice
|
|
refund_wizard = self.env['account.move.reversal'].with_context(active_model="account.move", active_ids=invoice01.ids).create({
|
|
'journal_id': invoice01.journal_id.id,
|
|
})
|
|
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()
|
|
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_operations')
|
|
with move_form.move_line_ids.edit(0) as line:
|
|
line.lot_id = self.usn01
|
|
line.quantity = 1
|
|
with move_form.move_line_ids.edit(1) as line:
|
|
line.lot_id = self.usn02
|
|
line.quantity = 1
|
|
move_form.save()
|
|
pick_return.move_ids.picked = True
|
|
pick_return.button_validate()
|
|
|
|
# reversed invoice
|
|
html = self.env['ir.actions.report']._render_qweb_html('account.report_invoice_with_payments', refund_invoice.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0001', "There should be a line that specifies 1 x USN0001")
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0002', "There should be a line that specifies 1 x USN0002")
|
|
|
|
def test_refund_modify_invoices(self):
|
|
"""
|
|
Suppose the lots are printed on the invoices.
|
|
The user sells 1 tracked-by-usn products, he delivers 1 and invoices it
|
|
Then he adds credit notes and issues full refund and new draft invoice.
|
|
The new draft invoice should have correct USN
|
|
"""
|
|
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)]})
|
|
|
|
so = self.env['sale.order'].create({
|
|
'partner_id': self.partner_a.id,
|
|
'order_line': [
|
|
(0, 0, {'name': self.product_by_usn.name, 'product_id': self.product_by_usn.id, 'product_uom_qty': 1}),
|
|
],
|
|
})
|
|
so.action_confirm()
|
|
|
|
picking = so.picking_ids
|
|
picking.move_ids.move_line_ids[0].quantity = 1
|
|
picking.move_ids.picked = True
|
|
picking.button_validate()
|
|
|
|
invoice01 = so._create_invoices()
|
|
invoice01.action_post()
|
|
|
|
html = self.env['ir.actions.report']._render_qweb_html('account.report_invoice_with_payments', invoice01.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0001', "There should be a line that specifies 1 x USN0001")
|
|
|
|
# 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({
|
|
'journal_id': invoice01.journal_id.id,
|
|
})
|
|
res = refund_wizard.modify_moves()
|
|
invoice02 = self.env['account.move'].browse(res['res_id'])
|
|
invoice02.action_post()
|
|
|
|
# new draft invoice
|
|
html = self.env['ir.actions.report']._render_qweb_html('account.report_invoice_with_payments', invoice02.ids)[0]
|
|
text = html2plaintext(html)
|
|
self.assertRegex(text, r'Product By USN\n1.00Units\nUSN0001', "There should be a line that specifies 1 x USN0001")
|