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

[IMP] sale_invoice_plan: ease mass invoicing and report on pending installments #12

Open
wants to merge 4 commits into
base: 16.0-MIG-sale_invoice_plan
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sale_invoice_plan/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Copyright 2019 Ecosoft Co., Ltd (http://ecosoft.co.th/)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from . import sale
from . import sale_order_line
from . import sale_invoice_plan
62 changes: 59 additions & 3 deletions sale_invoice_plan/models/sale.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class SaleOrder(models.Model):
invoice_plan_ids = fields.One2many(
comodel_name="sale.invoice.plan",
inverse_name="sale_id",
string="Inovice Plan",
string="Invoice Plan",
copy=False,
)
use_invoice_plan = fields.Boolean(
Expand All @@ -33,14 +33,40 @@ class SaleOrder(models.Model):
compute="_compute_invoice_plan_total",
string="Total Amount",
)
# Reporting
next_installment_id = fields.Many2one(
"sale.invoice.plan",
string="Next Installment To Invoice",
compute="_compute_next_to_invoice_plan_id",
store=True,
)
next_installment_date = fields.Date(
related="next_installment_id.plan_date",
string="Next Installment Date",
store=True,
)
invoiced_installment_amount = fields.Monetary(
compute="_compute_invoiced_installment_amount",
store=True,
)
pending_installment_amount = fields.Monetary(
compute="_compute_pending_installment_amount",
store=True,
)

@api.depends("invoice_plan_ids")
@api.depends("invoice_plan_ids.percent", "invoice_plan_ids.amount")
def _compute_invoice_plan_total(self):
for rec in self:
installments = rec.invoice_plan_ids.filtered("installment")
rec.invoice_plan_total_percent = sum(installments.mapped("percent"))
rec.invoice_plan_total_amount = sum(installments.mapped("amount"))

@api.depends(
"use_invoice_plan",
"state",
"invoice_status",
"invoice_plan_ids.invoiced",
)
def _compute_invoice_plan_process(self):
for rec in self:
has_invoice_plan = rec.use_invoice_plan and rec.invoice_plan_ids
Expand All @@ -50,7 +76,37 @@ def _compute_invoice_plan_process(self):
and has_invoice_plan
and to_invoice
and rec.invoice_status in ["to invoice", "no"]
and "advance" in to_invoice.mapped("invoice_type")
)

@api.depends("invoice_plan_ids.invoice_move_ids", "invoice_plan_ids.invoiced")
def _compute_next_to_invoice_plan_id(self):
for sale in self:
last_invoiced = sale.invoice_plan_ids.filtered("invoice_move_ids")[-1:]
last_installment = last_invoiced.installment or 0
next_to_invoice = sale.invoice_plan_ids.filtered(
lambda x: x.installment == last_installment + 1
)
sale.next_installment_id = next_to_invoice

@api.depends(
"next_installment_id",
"invoice_plan_ids.amount_invoiced",
"invoice_plan_ids.invoice_move_ids.state",
)
def _compute_invoiced_installment_amount(self):
for sale in self:
# Invoice Plan computations are not always triggered when changes happen
# So we need to force that recomputation
sale.invoice_plan_ids._compute_invoiced()
invoiced_plans = sale.invoice_plan_ids.filtered("invoiced")
invoiced_amount = sum(invoiced_plans.mapped("amount_invoiced"))
sale.invoiced_installment_amount = invoiced_amount

@api.depends("invoice_plan_total_amount", "invoiced_installment_amount")
def _compute_pending_installment_amount(self):
for sale in self:
sale.pending_installment_amount = (
sale.invoice_plan_total_amount - sale.invoiced_installment_amount
)

@api.constrains("invoice_plan_ids")
Expand Down
18 changes: 12 additions & 6 deletions sale_invoice_plan/models/sale_invoice_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class SaleInvoicePlan(models.Model):
column2="move_id",
string="Invoices",
readonly=True,
copy=False,
)
amount_invoiced = fields.Float(compute="_compute_invoiced")
to_invoice = fields.Boolean(
Expand Down Expand Up @@ -93,22 +94,27 @@ def _compute_no_edit(self):

@api.depends("percent")
def _compute_amount(self):
"""Amount to be invoiced"""
for rec in self:
amount_untaxed = rec.sale_id._origin.amount_untaxed
amount_untaxed = rec.sale_id.amount_untaxed
# With invoice already created, no recompute
if rec.invoiced:
rec.amount = rec.amount_invoiced
rec.percent = rec.amount / amount_untaxed * 100
continue
# For last line, amount is the left over
if rec.last:
elif rec.last:
installments = rec.sale_id.invoice_plan_ids.filtered(
lambda l: l.invoice_type == "installment"
)
prev_amount = sum((installments - rec).mapped("amount"))
rec.amount = amount_untaxed - prev_amount
continue
rec.amount = rec.percent * amount_untaxed / 100
else:
# Simulate the amount to be invoiced, as close as possible
# For this, round quantities on each order line
rec.amount = sum(
sol._get_installment_invoice_amount(rec.percent)
for sol in rec.sale_id.order_line
)
rec.percent = rec.amount / amount_untaxed * 100

@api.onchange("amount", "percent")
def _inverse_amount(self):
Expand Down
16 changes: 16 additions & 0 deletions sale_invoice_plan/models/sale_order_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Copyright 2023 Open Source Integrators
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)

from odoo import models
from odoo.tools.float_utils import float_round


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

def _get_installment_invoice_amount(self, percent):
"""Simulate the invoice lime amount, after applying a percentage to it"""
prec = self.env["decimal.precision"].precision_get("Product Unit of Measure")
quantity = float_round(self.product_uom_qty * percent / 100, prec)
invoice_amount = self.currency_id.round(self.price_unit * quantity)
return invoice_amount
58 changes: 56 additions & 2 deletions sale_invoice_plan/views/sale_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,12 @@
<label for="use_invoice_plan" />
</div>
</xpath>
<xpath expr="//button[@name='payment_action_capture']" position="before">
<xpath expr="//button[@name='action_quotation_send']" position="before">
<field name="invoice_plan_process" invisible="1" />
<button
name="%(action_view_sale_make_planned_invoice)d"
string="Create Invoice by Plan"
type="action"
class="btn-primary"
attrs="{'invisible': [('invoice_plan_process', '=', False)]}"
/>
</xpath>
Expand Down Expand Up @@ -121,6 +120,61 @@
</xpath>
</field>
</record>
<record id="view_order_tree_invoice_plan" model="ir.ui.view">
<field name="name">view.order.tree.invoice.plan</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_quotation_tree" />
<field name="arch" type="xml">
<tree position="inside">
<field name="next_installment_date" optional="hide" />
<field name="invoiced_installment_amount" optional="hide" />
<field name="pending_installment_amount" optional="hide" />
</tree>
</field>
</record>
<record id="view_order_search_invoice_plan" model="ir.ui.view">
<field name="name">view.order.search.invoice.plan</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_sales_order_filter" />
<field name="arch" type="xml">
<xpath expr="//filter[@name='my_sale_orders_filter']" position="after">
<separator />
<filter
string="Has Invoice Plan"
name="has_invoice_plan"
domain="[('invoice_plan_ids','!=', False)]"
/>
<filter
string="Has Pending Installment Amount"
name="has_pending_installment_amount"
domain="[('pending_installment_amount','!=', 0)]"
/>
<filter
string="This Months Installments"
name="this_months_installments"
domain="[
('next_installment_date', '>=', (context_today() - relativedelta(day=1)).strftime('%Y-%m-%d')),
('next_installment_date', '&lt;=', (context_today() + relativedelta(months=1, day=1, days=-1)).strftime('%Y-%m-%d'))]"
/>
<filter
string="Previous Month Installments"
name="last_months_installments"
domain="[
('next_installment_date', '>=', (context_today() - relativedelta(months=1, day=1)).strftime('%Y-%m-%d')),
('next_installment_date', '&lt;=', (context_today() - relativedelta(day=1, days=1)).strftime('%Y-%m-%d'))]"
/>
<separator />
</xpath>
<group position="inside">
<filter
string="Next Installment Date"
name="groupby_next_installment_date"
domain="[]"
context="{'group_by': 'next_installment_date'}"
/>
</group>
</field>
</record>
<!-- Invoice Plan Lines -->
<record id="view_sale_invoice_plan_filter" model="ir.ui.view">
<field name="name">view.sale.invoice.plan.filter</field>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</field>
</record>
<record id="action_view_sale_make_planned_invoice" model="ir.actions.act_window">
<field name="name">Invoice Order</field>
<field name="name">Invoice Installments</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.make.planned.invoice</field>
<field name="binding_view_types">form</field>
Expand Down
Loading