201 lines
7.1 KiB
Python
201 lines
7.1 KiB
Python
# -*- 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"),
|
||
}
|