add purchase_rappel. manage rappel commission for vendors

This commit is contained in:
Luis 2026-06-12 11:48:32 +02:00
parent 4fbf3284f4
commit 4c07dd042d
24 changed files with 1458 additions and 0 deletions

View file

@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
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
valid_moves = moves.filtered(
lambda m: m.move_type == "in_invoice" and m.state == "posted"
)
if not valid_moves:
raise UserError(
_(
"No posted vendor bills found in the selection. "
"Only posted (validated) vendor bills 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
name = _(
"Rappel commission %(rate)s%% — Bill %(bill)s "
"(taxable base: %(base)s %(currency)s)",
rate=rate,
bill=bill.name,
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"),
}