addons-cm/purchase_rappel/wizard/account_move_rappel_wizard.py

219 lines
8.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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 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"),
}