Ecocentral/ecocentral#172: migrate purchase_rappel to 18.0

This commit is contained in:
Luis 2026-06-23 14:28:38 +02:00
parent 1d6747e703
commit a754df3914
24 changed files with 1588 additions and 0 deletions

View file

@ -0,0 +1,3 @@
# Copyright 2024 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from . import account_move_rappel_wizard

View file

@ -0,0 +1,220 @@
# Copyright 2024 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from collections import defaultdict
from odoo import _, api, fields, models
from odoo.exceptions import UserError
class AccountMoveRappelWizard(models.TransientModel):
"""Wizard to generate rappel commission (sale) invoices from selected vendor bills.
One sale invoice is created per vendor. Each original bill becomes one invoice
line whose amount is computed as: taxable_base × commission_rate / 100.
"""
_name = "account.move.rappel.wizard"
_description = "Create Rappel Commission Invoice"
move_ids = fields.Many2many(
comodel_name="account.move",
string="Vendor Bills",
readonly=True,
)
invoice_date = fields.Date(
string="Invoice Date",
default=fields.Date.context_today,
required=True,
)
company_id = fields.Many2one(
comodel_name="res.company",
default=lambda self: self.env.company,
required=True,
)
journal_id = fields.Many2one(
comodel_name="account.journal",
string="Journal",
domain="[('type', '=', 'sale'), ('company_id', '=', company_id)]",
required=True,
)
product_id = fields.Many2one(
comodel_name="product.product",
string="Commission Product",
required=True,
)
@api.model
def default_get(self, fields_list):
res = super().default_get(fields_list)
company = self.env.company
active_ids = self.env.context.get("active_ids", [])
moves = self.env["account.move"].browse(active_ids)
# Filter only posted vendor bills and refunds
valid_moves = moves.filtered(
lambda m: m.move_type in ("in_invoice", "in_refund")
and m.state == "posted"
)
if not valid_moves:
raise UserError(
_(
"No posted vendor bills or refunds found in the selection. "
"Only posted (validated) vendor bills and refunds can be used to "
"create rappel commission invoices."
)
)
# Filter only pending bills
pending_moves = valid_moves.filtered(lambda m: m.rappel_pending)
if not pending_moves:
raise UserError(
_(
"None of the selected vendor bills are pending commission. "
"They may have already been commissioned."
)
)
# Warn about bills whose partner has no rappel configured
no_rappel = pending_moves.filtered(lambda m: not m.partner_id.rappel_commission)
if no_rappel and len(no_rappel) == len(pending_moves):
raise UserError(
_(
"None of the selected vendors have the rappel commission enabled. "
"Please configure the commission rate on the vendor contact first."
)
)
res.update(
{
"move_ids": [(6, 0, pending_moves.ids)],
"journal_id": company.rappel_journal_id.id or False,
"product_id": company.rappel_product_id.id or False,
}
)
return res
def action_create_rappel_invoices(self):
self.ensure_one()
if not self.journal_id:
raise UserError(
_(
"Please select a journal for the rappel commission invoices, or "
"configure a default one in Purchase Settings."
)
)
if not self.product_id:
raise UserError(
_(
"Please select a product for the rappel commission invoices, or "
"configure a default one in Purchase Settings."
)
)
# Group eligible bills by partner (only partners with rappel enabled)
bills_by_partner = defaultdict(lambda: self.env["account.move"])
for bill in self.move_ids:
if (
bill.partner_id.rappel_commission
and bill.partner_id.rappel_commission_rate
):
bills_by_partner[bill.partner_id] |= bill
if not bills_by_partner:
raise UserError(
_(
"None of the selected vendor bills belong to vendors with a rappel "
"commission rate configured."
)
)
created_invoices = self.env["account.move"]
for partner, bills in bills_by_partner.items():
rate = partner.rappel_commission_rate
invoice_lines = []
for bill in bills:
taxable_base = bill.amount_untaxed
commission_amount = taxable_base * rate / 100.0
vendor_ref = bill.ref or bill.name
if bill.move_type == "in_refund":
# Vendor refunds reduce the commission: negate the amount so
# the resulting line subtracts from the rappel invoice total.
# amount_untaxed is always positive in Odoo, so we flip the
# sign here and display a negative base in the description.
commission_amount = -commission_amount
display_base = -taxable_base
name = _(
"Rappel commission %(rate)s%% — Refund %(bill)s "
"(taxable base: %(base)s %(currency)s)",
rate=rate,
bill=vendor_ref,
base=f"{display_base:,.2f}",
currency=bill.currency_id.name,
)
else:
name = _(
"Rappel commission %(rate)s%% — Bill %(bill)s "
"(taxable base: %(base)s %(currency)s)",
rate=rate,
bill=vendor_ref,
base=f"{taxable_base:,.2f}",
currency=bill.currency_id.name,
)
invoice_lines.append(
(
0,
0,
{
"product_id": self.product_id.id,
"name": name,
"quantity": 1,
"price_unit": commission_amount,
"tax_ids": [
(
6,
0,
self.product_id.taxes_id.filtered(
lambda t: t.company_id == self.company_id
).ids,
)
],
},
)
)
invoice_vals = {
"move_type": "out_invoice",
"partner_id": partner.id,
"invoice_date": self.invoice_date,
"journal_id": self.journal_id.id,
"company_id": self.company_id.id,
"invoice_line_ids": invoice_lines,
"narration": _(
"Rappel commission invoice generated from %d vendor bill(s).",
len(bills),
),
}
created_invoice = self.env["account.move"].create(invoice_vals)
created_invoices |= created_invoice
# Mark source bills as commissioned
bills.write({"rappel_pending": False})
# Open the created invoices
if len(created_invoices) == 1:
return {
"type": "ir.actions.act_window",
"res_model": "account.move",
"res_id": created_invoices.id,
"view_mode": "form",
"views": [(False, "form")],
}
return {
"type": "ir.actions.act_window",
"res_model": "account.move",
"domain": [("id", "in", created_invoices.ids)],
"view_mode": "list,form",
"name": _("Rappel Commission Invoices"),
}

View file

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Wizard form view -->
<record id="view_account_move_rappel_wizard_form" model="ir.ui.view">
<field name="name">account.move.rappel.wizard.form</field>
<field name="model">account.move.rappel.wizard</field>
<field name="arch" type="xml">
<form string="Create Rappel Commission Invoice">
<sheet>
<field name="company_id" invisible="1"/>
<group>
<group>
<field name="invoice_date"/>
<field name="journal_id"/>
<field name="product_id"/>
</group>
<group>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<notebook>
<page string="Vendor Bills" name="vendor_bills">
<field name="move_ids">
<list string="Vendor Bills">
<field name="name"/>
<field name="move_type"
widget="badge"
decoration-info="move_type == 'in_invoice'"
decoration-danger="move_type == 'in_refund'"/>
<field name="partner_id"/>
<field name="invoice_date"/>
<field name="amount_untaxed"
string="Taxable Base"/>
<field name="rappel_pending"/>
</list>
</field>
</page>
</notebook>
</sheet>
<footer>
<button name="action_create_rappel_invoices"
type="object"
string="Create Rappel Invoice(s)"
class="btn-primary"/>
<button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
<!-- Action bound to the vendor bills list view (Action menu) -->
<record id="action_open_rappel_wizard" model="ir.actions.act_window">
<field name="name">Create Rappel Commission Invoice</field>
<field name="res_model">account.move.rappel.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="account.model_account_move"/>
<field name="binding_view_types">list</field>
</record>
</odoo>