443 lines
18 KiB
Python
443 lines
18 KiB
Python
# Copyright (C) 2021-TODAY Akretion
|
|
# @author Magno Costa <magno.costa@akretion.com.br>
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
|
|
from odoo import exceptions
|
|
from odoo.tests import Form
|
|
|
|
from odoo.addons.stock_picking_invoicing.tests.common import TestPickingInvoicingCommon
|
|
|
|
|
|
class TestSaleStock(TestPickingInvoicingCommon):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.account_move_model = cls.env["account.move"]
|
|
cls.invoice_wizard = cls.env["stock.invoice.onshipping"]
|
|
cls.stock_return_picking = cls.env["stock.return.picking"]
|
|
cls.stock_picking = cls.env["stock.picking"]
|
|
# In order to avoid errors in the tests CI environment when the tests
|
|
# Create of Invoice by Sale Order using sale.advance.payment.inv object
|
|
# is necessary let default policy as sale_order, just affect demo data.
|
|
# TODO: Is there other form to avoid this problem?
|
|
cls.companies = cls.env["res.company"].search(
|
|
[("sale_invoicing_policy", "=", "sale_order")]
|
|
)
|
|
for company in cls.companies:
|
|
company.sale_invoicing_policy = "stock_picking"
|
|
|
|
def test_01_sale_stock_return(self):
|
|
"""
|
|
Test a SO with a product invoiced on delivery. Deliver and invoice
|
|
the SO, then do a return of the picking. Check that a refund
|
|
invoice is well generated.
|
|
"""
|
|
# intial so
|
|
self.partner = self.env.ref(
|
|
"sale_stock_picking_invoicing.res_partner_2_address"
|
|
)
|
|
self.product = self.env.ref("product.product_delivery_01")
|
|
so_vals = {
|
|
"partner_id": self.partner.id,
|
|
"partner_invoice_id": self.partner.id,
|
|
"partner_shipping_id": self.partner.id,
|
|
"order_line": [
|
|
(
|
|
0,
|
|
0,
|
|
{
|
|
"name": self.product.name,
|
|
"product_id": self.product.id,
|
|
"product_uom_qty": 3.0,
|
|
"product_uom": self.product.uom_id.id,
|
|
"price_unit": self.product.list_price,
|
|
},
|
|
)
|
|
],
|
|
"pricelist_id": self.env.ref(
|
|
"sale_stock_picking_invoicing.demo_pricelist"
|
|
).id,
|
|
}
|
|
self.so = self.env["sale.order"].create(so_vals)
|
|
|
|
# confirm our standard so, check the picking
|
|
self.so.action_confirm()
|
|
self.assertTrue(
|
|
self.so.picking_ids,
|
|
'Sale Stock: no picking created for "invoice on '
|
|
'delivery" storable products',
|
|
)
|
|
|
|
# set stock.picking to be invoiced
|
|
self.assertTrue(
|
|
len(self.so.picking_ids) == 1,
|
|
"More than one stock " "picking for sale.order",
|
|
)
|
|
|
|
# Check Sale Invoicing Policy Warning to force create Invoice from Picking
|
|
with self.assertRaises(exceptions.UserError):
|
|
self.so._create_invoices(final=True)
|
|
|
|
self.so.picking_ids.set_to_be_invoiced()
|
|
|
|
# validate stock.picking
|
|
stock_picking = self.so.picking_ids
|
|
|
|
# compare sale.order.line with stock.move
|
|
stock_move = stock_picking.move_ids
|
|
sale_order_line = self.so.order_line
|
|
|
|
sm_fields = [key for key in self.env["stock.move"]._fields.keys()]
|
|
sol_fields = [key for key in self.env["sale.order.line"]._fields.keys()]
|
|
|
|
skipped_fields = [
|
|
"id",
|
|
# 'S00029/FURN_7777: Stock>Customers' != 'S00029 - Office Chair'
|
|
"display_name",
|
|
"state",
|
|
# Price Unit in move is different from sale line
|
|
# TODO: Should be equal? After Confirmed stock picking
|
|
# the value will be change based Stock Valuation
|
|
# configuration.
|
|
"price_unit",
|
|
# There are a diference for field Name
|
|
# '[FURN_7777] Office Chair' != 'Office Chair'
|
|
"name",
|
|
]
|
|
common_fields = list(set(sm_fields) & set(sol_fields) - set(skipped_fields))
|
|
|
|
for field in common_fields:
|
|
self.assertEqual(
|
|
stock_move[field],
|
|
sale_order_line[field],
|
|
f"Field {field} failed to transfer from sale.order.line "
|
|
"to stock.move",
|
|
)
|
|
|
|
def test_picking_sale_order_product_and_service(self):
|
|
"""
|
|
Test Sale Order with product and service
|
|
"""
|
|
|
|
# Ensure the company's sale_invoicing_policy is set to "stock_picking"
|
|
company = self.env.ref("base.main_company")
|
|
company.sale_invoicing_policy = "stock_picking"
|
|
sale_order_form = sale_order_form = Form(
|
|
self.env.ref("sale_stock_picking_invoicing.main_company-sale_order_2")
|
|
)
|
|
# Necessary to get the currency
|
|
sale_order_form.pricelist_id = self.env.ref(
|
|
"sale_stock_picking_invoicing.demo_pricelist"
|
|
)
|
|
sale_order_2 = sale_order_form.save()
|
|
sale_order_2.action_confirm()
|
|
# Method to create invoice in sale order should work only
|
|
# for lines where products are of TYPE Service
|
|
sale_order_2._create_invoices()
|
|
# Should be exist one Invoice
|
|
self.assertEqual(1, sale_order_2.invoice_count)
|
|
for invoice in sale_order_2.invoice_ids:
|
|
line = invoice.invoice_line_ids.filtered(
|
|
lambda ln: ln.product_id.type == "service"
|
|
)
|
|
self.assertEqual(line.product_id.type, "service")
|
|
# Invoice of Service
|
|
invoice.action_post()
|
|
self.assertEqual(
|
|
invoice.state, "posted", "Invoice should be in state Posted"
|
|
)
|
|
|
|
picking = sale_order_2.picking_ids
|
|
# Only the line of Type Product
|
|
self.assertEqual(len(picking.move_ids_without_package), 1)
|
|
self.assertEqual(picking.invoice_state, "2binvoiced")
|
|
self.picking_move_state(picking)
|
|
|
|
# Test Create Invoice from Sale when raise UseError
|
|
context = {
|
|
"active_model": "sale.order",
|
|
"active_id": sale_order_2.id,
|
|
"active_ids": sale_order_2.ids,
|
|
}
|
|
payment = (
|
|
self.env["sale.advance.payment.inv"]
|
|
.with_context(**context)
|
|
.create(
|
|
{
|
|
"advance_payment_method": "delivered",
|
|
}
|
|
)
|
|
)
|
|
with self.assertRaises(exceptions.UserError):
|
|
payment.with_context(**context).create_invoices()
|
|
|
|
invoice = self.create_invoice_wizard(picking)
|
|
self.assertEqual(picking.invoice_state, "invoiced")
|
|
self.assertIn(invoice, picking.invoice_ids)
|
|
self.assertIn(picking, invoice.picking_ids)
|
|
# Picking with Partner Shipping from Sale Order
|
|
self.assertEqual(picking.partner_id, sale_order_2.partner_shipping_id)
|
|
# Invoice created with Partner Invoice from Sale Order
|
|
self.assertEqual(invoice.partner_id, sale_order_2.partner_invoice_id)
|
|
# Invoice created with Partner Shipping from Picking
|
|
self.assertEqual(invoice.partner_shipping_id, picking.partner_id)
|
|
# When informed Payment Term in Sale Orde should be
|
|
# used instead of the default in Partner.
|
|
self.assertEqual(invoice.invoice_payment_term_id, sale_order_2.payment_term_id)
|
|
|
|
# 1 Product 1 Note should be created
|
|
self.assertEqual(len(invoice.invoice_line_ids), 2)
|
|
|
|
# In the Sale Order should be exist two Invoices, one
|
|
# for Product other for Service
|
|
self.assertEqual(2, sale_order_2.invoice_count)
|
|
|
|
# Confirm Invoice
|
|
invoice.action_post()
|
|
self.assertEqual(invoice.state, "posted", "Invoice should be in state Posted.")
|
|
|
|
# Check Invoiced QTY
|
|
for line in sale_order_2.order_line.filtered(
|
|
lambda ln: ln.product_id.type == "product"
|
|
):
|
|
self.assertEqual(line.product_uom_qty, line.qty_invoiced)
|
|
# Test the qty_to_invoice
|
|
line.product_id.invoice_policy = "order"
|
|
self.assertEqual(line.qty_to_invoice, 0.0)
|
|
|
|
# Check if the Sale Lines fields are equals to Invoice Lines
|
|
sol_fields = [key for key in self.env["sale.order.line"]._fields.keys()]
|
|
|
|
acl_fields = [key for key in self.env["account.move.line"]._fields.keys()]
|
|
|
|
skipped_fields = [
|
|
"id",
|
|
"display_name",
|
|
"state",
|
|
"create_date",
|
|
# By th TAX 15% automatic add in invoice the value change
|
|
"price_total",
|
|
# Necessary after call onchange_partner_id
|
|
"write_date",
|
|
"__last_update",
|
|
# Field sequence add in creation of Invoice
|
|
"sequence",
|
|
# In the sale.orde.line display_type has only line_section
|
|
# and line_note, the acccount.move.line has more options
|
|
"display_type",
|
|
]
|
|
|
|
common_fields = list(set(acl_fields) & set(sol_fields) - set(skipped_fields))
|
|
sale_order_line = picking.move_ids_without_package.filtered(
|
|
lambda ln: ln.sale_line_id
|
|
).sale_line_id
|
|
invoice_lines = picking.invoice_ids.invoice_line_ids.filtered(
|
|
lambda ln: ln.product_id
|
|
)
|
|
# Necessary for get analytic_precision
|
|
# this problem only occours in the tests, by some reason not
|
|
# identify yet, but works in the screen the default behavior
|
|
with Form(invoice_lines) as line:
|
|
line.save()
|
|
|
|
for field in common_fields:
|
|
self.assertEqual(
|
|
sale_order_line[field],
|
|
invoice_lines[field],
|
|
f"Field {field} failed to transfer from sale.order.line "
|
|
"to account.invoice.line",
|
|
)
|
|
|
|
# Return Picking
|
|
picking_devolution = self.return_picking_wizard(picking)
|
|
|
|
self.assertEqual(picking_devolution.invoice_state, "2binvoiced")
|
|
for line in picking_devolution.move_ids:
|
|
self.assertEqual(line.invoice_state, "2binvoiced")
|
|
|
|
self.picking_move_state(picking_devolution)
|
|
self.assertEqual(picking_devolution.state, "done", "Change state fail.")
|
|
|
|
invoice_devolution = self.create_invoice_wizard(picking_devolution)
|
|
# Confirm Invoice
|
|
invoice_devolution.action_post()
|
|
self.assertEqual(
|
|
invoice_devolution.state, "posted", "Invoice should be in state Posted"
|
|
)
|
|
# Test need to be comment because there are a problem with module
|
|
# sale_line_refund_to_invoice_qty
|
|
# https://github.com/OCA/account-invoicing/blob/
|
|
# 14.0/sale_line_refund_to_invoice_qty/models/sale.py#L20
|
|
# when the tests run in CI of the repo the test fail.
|
|
# TODO: The module should be compatible with this case?
|
|
# Check Invoiced QTY update after Refund
|
|
# for line in sale_order_2.order_line:
|
|
# # Check Product line
|
|
# if line.product_id.type == "product":
|
|
# # self.assertEqual(0.0, line.qty_invoiced)
|
|
|
|
def test_picking_invoicing_partner_shipping_invoiced(self):
|
|
"""
|
|
Test the invoice generation grouped by partner/product with 2
|
|
picking and 2 moves per picking, but Partner to Shipping is
|
|
different from Partner to Invoice.
|
|
"""
|
|
sale_order_1 = self.env.ref(
|
|
"sale_stock_picking_invoicing.main_company-sale_order_1"
|
|
)
|
|
sale_order_1.action_confirm()
|
|
picking = sale_order_1.picking_ids
|
|
self.picking_move_state(picking)
|
|
sale_order_2 = self.env.ref(
|
|
"sale_stock_picking_invoicing.main_company-sale_order_2"
|
|
)
|
|
sale_order_2.action_confirm()
|
|
picking2 = sale_order_2.picking_ids
|
|
self.picking_move_state(picking2)
|
|
pickings = picking | picking2
|
|
invoice = self.create_invoice_wizard(pickings)
|
|
# Groupping Invoice
|
|
self.assertEqual(len(invoice), 1)
|
|
# Invoice should be create with the partner_invoice_id
|
|
self.assertEqual(invoice.partner_id, sale_order_1.partner_invoice_id)
|
|
# Invoice partner shipping should be the same of picking
|
|
self.assertEqual(invoice.partner_shipping_id, picking.partner_id)
|
|
self.assertIn(invoice, picking.invoice_ids)
|
|
self.assertIn(picking, invoice.picking_ids)
|
|
self.assertIn(invoice, picking2.invoice_ids)
|
|
self.assertIn(picking2, invoice.picking_ids)
|
|
|
|
# TODO: Grouping sale line with KEY should be analise
|
|
# self.assertEqual(len(invoice.invoice_line_ids), 2)
|
|
# 3 Products, 2 Note and 2 Section
|
|
self.assertEqual(len(invoice.invoice_line_ids), 7)
|
|
for inv_line in invoice.invoice_line_ids.filtered(lambda ln: ln.product_id):
|
|
self.assertTrue(inv_line.tax_ids, "Error to map Sale Tax in invoice.line.")
|
|
# Post the Invoice to validate the fields
|
|
invoice.action_post()
|
|
|
|
def test_ungrouping_pickings_partner_shipping_different(self):
|
|
"""
|
|
Test the invoice generation grouped by partner/product with 3
|
|
picking and 2 moves per picking, the 3 has the same Partner to
|
|
Invoice but one has Partner to Shipping so shouldn't be grouping.
|
|
"""
|
|
|
|
sale_order_1 = self.env.ref(
|
|
"sale_stock_picking_invoicing.main_company-sale_order_1"
|
|
)
|
|
sale_order_1.action_confirm()
|
|
picking = sale_order_1.picking_ids
|
|
self.picking_move_state(picking)
|
|
|
|
sale_order_3 = self.env.ref(
|
|
"sale_stock_picking_invoicing.main_company-sale_order_3"
|
|
)
|
|
sale_order_3.action_confirm()
|
|
picking3 = sale_order_3.picking_ids
|
|
self.picking_move_state(picking3)
|
|
|
|
sale_order_4 = self.env.ref(
|
|
"sale_stock_picking_invoicing.main_company-sale_order_4"
|
|
)
|
|
sale_order_4.action_confirm()
|
|
picking4 = sale_order_4.picking_ids
|
|
self.picking_move_state(picking4)
|
|
|
|
pickings = picking | picking3 | picking4
|
|
invoices = self.create_invoice_wizard(pickings)
|
|
# Even with same Partner Invoice if the Partner Shipping
|
|
# are different should not be Groupping
|
|
self.assertEqual(len(invoices), 2)
|
|
|
|
# Invoice that has different Partner Shipping
|
|
# should be not groupping
|
|
invoice_pick_1 = invoices.filtered(
|
|
lambda t: t.partner_id != t.partner_shipping_id
|
|
)
|
|
# Invoice should be create with partner_invoice_id
|
|
self.assertEqual(invoice_pick_1.partner_id, sale_order_1.partner_invoice_id)
|
|
# Invoice create with Partner Shipping used in Picking
|
|
self.assertEqual(invoice_pick_1.partner_shipping_id, picking.partner_id)
|
|
|
|
# Groupping Invoice
|
|
invoice_pick_3_4 = invoices.filtered(
|
|
lambda t: t.partner_id == t.partner_shipping_id
|
|
)
|
|
self.assertIn(invoice_pick_3_4, picking3.invoice_ids)
|
|
self.assertIn(invoice_pick_3_4, picking4.invoice_ids)
|
|
|
|
def test_down_payment(self):
|
|
"""Test the case with Down Payment"""
|
|
sale_order_1 = self.env.ref(
|
|
"sale_stock_picking_invoicing.main_company-sale_order_1"
|
|
)
|
|
sale_order_1.action_confirm()
|
|
# Create Invoice Sale
|
|
context = {
|
|
"active_model": "sale.order",
|
|
"active_id": sale_order_1.id,
|
|
"active_ids": sale_order_1.ids,
|
|
}
|
|
# DownPayment
|
|
payment_wizard = (
|
|
self.env["sale.advance.payment.inv"]
|
|
.with_context(**context)
|
|
.create(
|
|
{
|
|
"advance_payment_method": "percentage",
|
|
"amount": 50,
|
|
}
|
|
)
|
|
)
|
|
payment_wizard.create_invoices()
|
|
|
|
invoice_down_payment = sale_order_1.invoice_ids[0]
|
|
invoice_down_payment.action_post()
|
|
payment_register = Form(
|
|
self.env["account.payment.register"].with_context(
|
|
active_model="account.move",
|
|
active_ids=invoice_down_payment.ids,
|
|
)
|
|
)
|
|
journal_cash = self.env["account.journal"].search(
|
|
[
|
|
("type", "=", "cash"),
|
|
("company_id", "=", invoice_down_payment.company_id.id),
|
|
],
|
|
limit=1,
|
|
)
|
|
payment_register.journal_id = journal_cash
|
|
payment_register.amount = invoice_down_payment.amount_total
|
|
payment_register.save()._create_payments()
|
|
|
|
picking = sale_order_1.picking_ids
|
|
self.picking_move_state(picking)
|
|
invoice = self.create_invoice_wizard(picking)
|
|
# 2 Products, 2 Down Payment, 1 Note and 1 Section
|
|
self.assertEqual(len(invoice.invoice_line_ids), 6)
|
|
line_section = invoice.invoice_line_ids.filtered(
|
|
lambda line: line.display_type == "line_section"
|
|
)
|
|
assert line_section, "Invoice without Line Section for Down Payment."
|
|
down_payment_line = invoice.invoice_line_ids.filtered(
|
|
lambda line: line.sale_line_ids.is_downpayment
|
|
)
|
|
assert down_payment_line, "Invoice without Down Payment line."
|
|
|
|
def test_default_value_sale_invoicing_policy(self):
|
|
"""Test default value for sale_invoicing_policy"""
|
|
company = self.env["res.company"].create(
|
|
{
|
|
"name": "Test",
|
|
}
|
|
)
|
|
self.assertEqual(company.sale_invoicing_policy, "sale_order")
|
|
|
|
def test_picking_invocing_without_sale_order(self):
|
|
"""Test Picking Invoicing without Sale Order"""
|
|
picking = self.env.ref("stock_picking_invoicing.stock_picking_invoicing_1")
|
|
self.picking_move_state(picking)
|
|
invoice = self.create_invoice_wizard(picking)
|
|
self.assertEqual(len(invoice), 1)
|