Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[16.0][FIX] account_cutoff_accrual_sale : Fixes for SO invoiced on order + [ADD] support for down payment #326

Open
wants to merge 8 commits into
base: 16.0
Choose a base branch
from
Open
5 changes: 0 additions & 5 deletions account_cutoff_accrual_order_base/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,6 @@ def setUpClass(cls):
cls.products = cls.env.ref("product.product_delivery_01") | cls.env.ref(
"product.product_delivery_02"
)
cls.stock_location = cls.env.ref("stock.stock_location_stock")
for p in cls.products:
cls.env["stock.quant"]._update_available_quantity(
p, cls.stock_location, 100
)
cls.products |= cls.env.ref("product.expense_product")
# analytic account
cls.default_plan = cls.env["account.analytic.plan"].create(
Expand Down
7 changes: 7 additions & 0 deletions account_cutoff_accrual_purchase_stock/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ class TestAccountCutoffAccrualPurchaseCommon(TestAccountCutoffAccrualOrderCommon
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.stock_location = cls.env.ref("stock.stock_location_stock")
for p in cls.products.filtered(
lambda product: product.detailed_type == "product"
):
cls.env["stock.quant"]._update_available_quantity(
p, cls.stock_location, 100
)
# Removing all existing PO
cls.env.cr.execute("DELETE FROM purchase_order;")
# Create PO
Expand Down
71 changes: 65 additions & 6 deletions account_cutoff_accrual_sale/models/sale_order_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging

from odoo import api, fields, models
from odoo.osv import expression

_logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -36,14 +37,62 @@
def _get_cutoff_accrual_product_qty(self):
return self.product_uom_qty

def _get_cutoff_accrual_price_unit(self):
if self.is_downpayment:
return self.price_unit
return super()._get_cutoff_accrual_price_unit()

@api.model
def _get_cutoff_accrual_lines_domain(self, cutoff):
domain = super()._get_cutoff_accrual_lines_domain(cutoff)
# The line could be invoiceable but not the order (see delivery
# module).
domain.append(("invoice_status", "=", "to invoice"))
domain.append(("order_id.invoice_status", "=", "to invoice"))
domain = expression.AND(
(
domain,
(
("state", "in", ("sale", "done")),
("display_type", "=", False),
),
)
)
return domain

@api.model
def _get_cutoff_accrual_lines_query(self, cutoff):
query = super()._get_cutoff_accrual_lines_query(cutoff)
self.flush_model(
[
"qty_delivered_method",
"qty_delivered",
"qty_invoiced",
"qty_to_invoice",
"is_downpayment",
]
)
# The delivery line could be invoiceable but not the order (see
# delivery module). So check also the SO invoice status.
so_alias = query.join(
self._table, "order_id", self.order_id._table, "id", "order_id"
)
self.order_id.flush_model(["invoice_status"])
# For stock products, we always consider the delivered quantity as it
# impacts the stock valuation.
# Otherwise, we consider the invoice policy by checking the
# qty_to_invoice.
query.add_where(
f"""
CASE
WHEN "{self._table}".qty_delivered_method = 'stock_move'
THEN "{self._table}".qty_delivered != "{self._table}".qty_invoiced
ELSE "{self._table}".qty_to_invoice != 0
AND (
"{so_alias}".invoice_status = 'to invoice'
OR "{self._table}".is_downpayment
)
END
"""
)
return query

def _prepare_cutoff_accrual_line(self, cutoff):
res = super()._prepare_cutoff_accrual_line(cutoff)
if not res:
Expand Down Expand Up @@ -96,6 +145,8 @@
if self.create_date >= cutoff_nextday:
# A line added after the cutoff cannot be delivered in the past
return 0
# In case of service, we consider what should be invoiced and this is
# given by the invoice policy.
if self.product_id.invoice_policy == "order":
return self.product_uom_qty
return self.qty_delivered
Expand All @@ -106,6 +157,14 @@
if self.create_date >= cutoff_nextday:
# A line added after the cutoff cannot be delivered in the past
return 0
if self.product_id.invoice_policy == "order":
return self.product_uom_qty
# In case of stock, we always consider what is delivered as this
# impacted the stock valuation.
return self.qty_delivered

def _get_cutoff_accrual_delivered_min_date(self):
"""Return first delivery date"""
self.ensure_one()
if self.product_id.invoice_policy == "order":
date = self.order_id.date_order
return date.date()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to use right TZ

return

Check warning on line 170 in account_cutoff_accrual_sale/models/sale_order_line.py

View check run for this annotation

Codecov / codecov/patch

account_cutoff_accrual_sale/models/sale_order_line.py#L170

Added line #L170 was not covered by tests
83 changes: 81 additions & 2 deletions account_cutoff_accrual_sale/tests/test_cutoff_revenue.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,79 @@ def test_revenue_tax_line(self):
self.assertEqual(line.tax_line_ids.amount, amount * 15 / 100)
self.assertEqual(line.tax_line_ids.cutoff_amount, amount * 15 / 100)

def test_accrued_revenue_downpayment(self):
"""Test cutoff based on SO with downpayment."""
cutoff = self.revenue_cutoff
self.so.action_confirm()
downpayment_amount = 10
self.env["sale.advance.payment.inv"].create(
{
"advance_payment_method": "fixed",
"sale_order_ids": [Command.set(self.so.ids)],
"fixed_amount": downpayment_amount,
}
).create_invoices()
self.so.invoice_ids.action_post()
cutoff.get_lines()
downpayment_so_line = self.so.order_line.filtered(
lambda line: line.is_downpayment and not line.display_type
)
self.assertEqual(
len(downpayment_so_line), 1, "1 down payment SO line should be found"
)
self.assertEqual(len(cutoff.line_ids), 2, "2 cutoff line should be found")
downpayment_cutoff_line = cutoff.line_ids.filtered(
lambda line: line.product_id == downpayment_so_line.product_id
)
self.assertEqual(
len(downpayment_cutoff_line),
1,
"1 down payment Cutoff line should be found",
)
self.assertEqual(
downpayment_cutoff_line.cutoff_amount,
-downpayment_amount,
"Down payment cutoff amount incorrect",
)
# Make invoice
invoice = self.so._create_invoices(final=True)
# - invoice is in draft, no change to cutoff
self.assertEqual(len(cutoff.line_ids), 2, "2 cutoff line should be found")
downpayment_cutoff_line = cutoff.line_ids.filtered(
lambda line: line.product_id == downpayment_so_line.product_id
)
self.assertEqual(
len(downpayment_cutoff_line),
1,
"1 down payment Cutoff line should be found",
)
self.assertEqual(
downpayment_cutoff_line.cutoff_amount,
-downpayment_amount,
"Down payment cutoff amount incorrect",
)
# Validate invoice
invoice.action_post()
self.assertEqual(len(cutoff.line_ids), 2, "2 cutoff line should be found")
for line in cutoff.line_ids:
self.assertEqual(line.cutoff_amount, 0, "SO line cutoff amount incorrect")
# Make a refund - the refund reset the SO lines qty_invoiced
self._refund_invoice(invoice)
self.assertEqual(len(cutoff.line_ids), 2, "2 cutoff line should be found")
downpayment_cutoff_line = cutoff.line_ids.filtered(
lambda line: line.product_id == downpayment_so_line.product_id
)
self.assertEqual(
len(downpayment_cutoff_line),
1,
"1 down payment Cutoff line should be found",
)
self.assertEqual(
downpayment_cutoff_line.cutoff_amount,
-downpayment_amount,
"Down payment cutoff amount incorrect",
)

# Make tests for product with invoice policy on order

def test_accrued_revenue_on_so_not_invoiced(self):
Expand Down Expand Up @@ -201,8 +274,14 @@ def test_accrued_revenue_on_so_all_invoiced_after_cutoff(self):
# Validate invoice after cutoff
self.so.invoice_ids.invoice_date = cutoff.cutoff_date + timedelta(days=1)
self.so.invoice_ids.action_post()
# as there is no delivery and invoice is after cutoff, no line is generated
self.assertEqual(len(cutoff.line_ids), 0, "No cutoff lines should be found")
# as there is no delivery and invoice is after cutoff, one line is
# generated for the service
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
for line in cutoff.line_ids:
self.assertEqual(
line.cutoff_amount, amount, "SO line cutoff amount incorrect"
)
cutoff.get_lines()
self.assertEqual(len(cutoff.line_ids), 1, "1 cutoff line should be found")
amount = self.qty * self.price
Expand Down
2 changes: 1 addition & 1 deletion account_cutoff_accrual_sale_stock/__manifest__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"version": "16.0.1.0.0",
"category": "Accounting & Finance",
"license": "AGPL-3",
"summary": "Glue module for Cut-Off Accruals on Sales with Stock Deliveries",
"summary": "Glue module for Cut-Off Accruals on Sales with Stock",
"author": "BCIM, Odoo Community Association (OCA)",
"maintainers": ["jbaudoux"],
"website": "https://github.com/OCA/account-closing",
Expand Down
2 changes: 2 additions & 0 deletions account_cutoff_accrual_sale_stock/models/sale_order_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def _get_cutoff_accrual_lines_delivered_after(self, cutoff):
def _get_cutoff_accrual_delivered_min_date(self):
"""Return first delivery date"""
self.ensure_one()
if self.qty_delivered_method != "stock_move":
return super()._get_cutoff_accrual_delivered_min_date()
stock_moves = self.move_ids.filtered(lambda m: m.state == "done")
if not stock_moves:
return
Expand Down
2 changes: 2 additions & 0 deletions account_cutoff_accrual_sale_stock/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
from . import test_cutoff_revenue
from . import test_cutoff_revenue_on_delivery
from . import test_cutoff_revenue_on_order
43 changes: 36 additions & 7 deletions account_cutoff_accrual_sale_stock/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,44 @@


class TestAccountCutoffAccrualSaleStockCommon(TestAccountCutoffAccrualSaleCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.stock_location = cls.env.ref("stock.stock_location_stock")
for p in cls.products.filtered(
lambda product: product.detailed_type == "product"
):
cls.env["stock.quant"]._update_available_quantity(
p, cls.stock_location, 100
)

def _get_service_lines(self, cutoff):
return cutoff.line_ids.filtered(
lambda line: line.product_id.detailed_type == "service"
)

def _get_product_lines(self, cutoff):
return cutoff.line_ids.filtered(
lambda line: line.product_id.detailed_type == "product"
)

def _confirm_so_and_do_picking(self, qty_done):
self.so.action_confirm()
# Make invoice what product on order
self.so._create_invoices(final=True)
self.assertEqual(
self.so.invoice_status,
"no",
'SO invoice_status should be "nothing to invoice" after confirming',
)
if self.so.invoice_status == "to invoice":
# Make invoice for product on order
invoice = self.so._create_invoices(final=True)
invoice.action_post()
self.assertEqual(
self.so.invoice_status,
"no",
'SO invoice_status should be "nothing to invoice" after confirming',
)
else:
invoice = self.env["account.move"]
self._do_picking(qty_done)
return invoice

def _do_picking(self, qty_done):
# Deliver
pick = self.so.picking_ids
pick.action_assign()
Expand Down
Loading
Loading