Skip to content

Commit

Permalink
[IMP] sale_stock_picking_invoicing: Other SaleLine
Browse files Browse the repository at this point in the history
  • Loading branch information
mbcosta committed Oct 24, 2024
1 parent aa04375 commit 5fca51a
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 129 deletions.
3 changes: 2 additions & 1 deletion sale_stock_picking_invoicing/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"data": [
"views/res_company_view.xml",
"views/res_config_settings_view.xml",
"views/sale_order_view.xml",
# Wizards
"wizards/stock_invoice_onshipping_view.xml",
],
"demo": [
"demo/sale_order_demo.xml",
Expand Down
24 changes: 20 additions & 4 deletions sale_stock_picking_invoicing/models/res_company.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,34 @@
# @author Magno Costa <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import fields, models
from odoo import api, fields, models


class ResCompany(models.Model):
_inherit = "res.company"

@api.model
def _default_sale_invoicing_policy(self):
# In order to avoid errors in the CI tests environment when Created
# Invoice from Sale Order using sale.advance.payment.inv object
# is necessary let default policy as sale_order
# TODO: Is there other form to avoid this problem?
result = "stock_picking"
module_base = self.env["ir.module.module"].search([("name", "=", "base")])
if module_base.demo:
result = "sale_order"
return result

sale_invoicing_policy = fields.Selection(
selection=[
("sale_order", "Sale Order"),
("stock_picking", "Stock Picking"),
],
help="Define, when Product Type are not service, if Invoice"
" should be created from Sale Order or from Stock Picking.",
default="stock_picking",
string="Sale Invoicing Policy",
help="If set to Sale Order, keep native Odoo behaviour for creation of"
" invoices from Sale Orders.\n"
"If set to Stock Picking, disallow creation of Invoices from Sale Orders"
" for the cases where Product Type are 'Product', in case of 'Service'"
" still will be possible create from Sale Order.",
default=_default_sale_invoicing_policy,
)
56 changes: 26 additions & 30 deletions sale_stock_picking_invoicing/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,36 @@
# @author Magno Costa <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import api, fields, models
from odoo import _, models
from odoo.exceptions import UserError


class SaleOrder(models.Model):
_inherit = "sale.order"

# Make Invisible Invoice Button
button_create_invoice_invisible = fields.Boolean(
compute="_compute_get_button_create_invoice_invisible"
)

@api.depends("state", "order_line.invoice_status")
def _compute_get_button_create_invoice_invisible(self):
for record in self:
button_create_invoice_invisible = False

lines = record.order_line.filtered(
lambda line: line.invoice_status == "to invoice"
def _get_invoiceable_lines(self, final=False):
"""Return the invoiceable lines for order `self`."""
lines = super()._get_invoiceable_lines(final)
model = self.env.context.get("active_model")
if (
self.company_id.sale_invoicing_policy == "stock_picking"
and model != "stock.picking"
):
new_lines = lines.filtered(
lambda ln: ln.product_id.type != "product" and not ln.is_downpayment
)

# Only after Confirmed Sale Order the button appear
if record.state != "sale":
button_create_invoice_invisible = True
if new_lines:
# Case lines with Product Type 'service'
lines = new_lines
else:
if record.company_id.sale_invoicing_policy == "stock_picking":
# The creation of Invoice to Services should
# be possible in Sale Order
if not any(line.product_id.type == "service" for line in lines):
button_create_invoice_invisible = True
else:
# In the case of Sale Create Invoice Policy based on Sale Order
# when the Button to Create Invoice clicked will be create
# automatic Invoice for Products and Services
if not lines:
button_create_invoice_invisible = True

record.button_create_invoice_invisible = button_create_invoice_invisible
# Case only Products Type 'product'
raise UserError(

Check warning on line 28 in sale_stock_picking_invoicing/models/sale_order.py

View check run for this annotation

Codecov / codecov/patch

sale_stock_picking_invoicing/models/sale_order.py#L28

Added line #L28 was not covered by tests
_(
"When 'Sale Invoicing Policy' is defined as"
"'Stock Picking' the Invoice can only be created"
" from the Stock Picking, if necessary you can change"
" in the Company or Sale Settings."
)
)

return lines
151 changes: 101 additions & 50 deletions sale_stock_picking_invoicing/tests/test_sale_stock.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# @author Magno Costa <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from odoo import exceptions

# TODO: In v16 check the possiblity to use the commom.py
# from stock_picking_invoicing
# https://github.com/OCA/account-invoicing/blob/16.0/
Expand All @@ -17,6 +19,15 @@ def setUpClass(cls):
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 _run_picking_onchanges(self, record):
record.onchange_picking_type()
Expand Down Expand Up @@ -162,8 +173,6 @@ def test_picking_sale_order_product_and_service(self):
# Necessary to get the currency
sale_order_2.onchange_partner_id()
sale_order_2.action_confirm()
self.assertTrue(sale_order_2.state == "sale")
self.assertTrue(sale_order_2.invoice_status == "to invoice")
# Method to create invoice in sale order should work only
# for lines where products are of TYPE Service
sale_order_2._create_invoices()
Expand All @@ -185,7 +194,24 @@ def test_picking_sale_order_product_and_service(self):
self.assertEqual(len(picking.move_ids_without_package), 1)
self.assertEqual(picking.invoice_state, "2binvoiced")
self.picking_move_state(picking)
self.assertEqual(picking.state, "done")

# 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")
Expand Down Expand Up @@ -236,6 +262,8 @@ def test_picking_sale_order_product_and_service(self):
# Necessary after call onchange_partner_id
"write_date",
"__last_update",
# Field sequence add in creation of Invoice
"sequence",
]

common_fields = list(set(acl_fields) & set(sol_fields) - set(skipped_fields))
Expand Down Expand Up @@ -300,14 +328,10 @@ def test_picking_invoicing_partner_shipping_invoiced(self):
sale_order_2.action_confirm()
picking2 = sale_order_2.picking_ids
self.picking_move_state(picking2)
self.assertEqual(picking.state, "done")
self.assertEqual(picking2.state, "done")
pickings = picking | picking2
invoice = self.create_invoice_wizard(pickings)
# Groupping Invoice
self.assertEqual(len(invoice), 1)
self.assertEqual(picking.invoice_state, "invoiced")
self.assertEqual(picking2.invoice_state, "invoiced")
# 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
Expand All @@ -321,7 +345,7 @@ def test_picking_invoicing_partner_shipping_invoiced(self):
# 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:
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()
Expand All @@ -339,32 +363,26 @@ def test_ungrouping_pickings_partner_shipping_different(self):
sale_order_1.action_confirm()
picking = sale_order_1.picking_ids
self.picking_move_state(picking)
self.assertEqual(picking.state, "done")

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)
self.assertEqual(picking3.state, "done")

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)
self.assertEqual(picking4.state, "done")

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)
self.assertEqual(picking.invoice_state, "invoiced")
self.assertEqual(picking3.invoice_state, "invoiced")
self.assertEqual(picking4.invoice_state, "invoiced")

# Invoice that has different Partner Shipping
# should be not groupping
Expand All @@ -383,47 +401,80 @@ def test_ungrouping_pickings_partner_shipping_different(self):
self.assertIn(invoice_pick_3_4, picking3.invoice_ids)
self.assertIn(invoice_pick_3_4, picking4.invoice_ids)

def test_button_create_bill_in_view(self):
"""
Test Field to make Button Create Bill invisible.
"""
sale_products = self.env.ref(
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"
)
# Caso do Pedido de Compra em Rascunho
self.assertTrue(
sale_products.button_create_invoice_invisible,
"Field to make invisible the Button Create Bill should be"
" invisible when Sale Order is not in state Sale.",
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,
}
)
)
# Caso somente com Produtos
sale_products.action_confirm()
self.assertTrue(
sale_products.button_create_invoice_invisible,
"Field to make invisible the button Create Bill should be"
" invisible when Sale Order has only products.",
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,
)
)
picking = sale_products.picking_ids
self.picking_move_state(picking)
self.create_invoice_wizard(picking)

# Service and Product
sale_service_product = self.env.ref(
"sale_stock_picking_invoicing.main_company-sale_order_2"
journal_cash = self.env["account.journal"].search(
[
("type", "=", "cash"),
("company_id", "=", invoice_down_payment.company_id.id),
],
limit=1,
)
sale_service_product.action_confirm()
self.assertFalse(
sale_service_product.button_create_invoice_invisible,
"Field to make invisible the Button Create Bill should be"
" False when the Sale Order has Service and Product.",
payment_register.journal_id = journal_cash
payment_method_manual_in = self.env.ref(
"account.account_payment_method_manual_in"
)
payment_register.payment_method_id = payment_method_manual_in
payment_register.amount = invoice_down_payment.amount_total
payment_register.save()._create_payments()

# Sale Invoice Policy based on sale_order
sale = self.env.ref("sale_stock_picking_invoicing.main_company-sale_order_3")
sale.company_id.sale_invoicing_policy = "sale_order"
sale.action_confirm()
self.assertTrue(
sale.button_create_invoice_invisible,
"Field to make invisible the button Create Bill should be"
" invisible when Sale Invoice Policy based on sale_order.",
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)
Loading

0 comments on commit 5fca51a

Please sign in to comment.