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

40
purchase_rappel/README.md Normal file
View file

@ -0,0 +1,40 @@
# Purchase Rappel Commission
This module allows managing **rappel commission invoices** for vendors.
## Features
- Configure a rappel commission rate per vendor.
- Automatically mark vendor bills as "pending commission".
- Generate customer (sale) invoices from selected vendor bills via a wizard.
- Group invoices by vendor and compute commission amounts based on the taxable base.
## Configuration
Go to **Purchase > Configuration > Settings > Invoicing** and set:
- **Rappel Journal**: Sales journal where rappel commission invoices will be posted.
- **Commission Product**: Product used as invoice line in rappel commission invoices (should have an income account and taxes configured).
Open a vendor contact and go to the **Sales & Purchases** tab:
- Enable **Rappel Commission**.
- Set the **Commission Rate (%)** (e.g., 5.00).
## Usage
When a vendor bill is created for a partner with rappel enabled, the bill is automatically marked as **Pending Commission**. This flag is visible in the vendor bill list and form views.
To generate rappel invoices:
1. Go to **Invoicing > Vendors > Bills**.
2. Select one or more posted vendor bills with pending commission.
3. Click **Action > Create Rappel Commission Invoice**.
4. In the wizard, confirm the **Invoice Date**, **Journal**, and **Product**.
5. Click **Create Rappel Invoice(s)**.
The system will create one sale invoice per vendor, with one line per original bill. The commission amount is computed as:
taxable_base × commission_rate / 100
The original vendor bills are automatically unmarked as pending.

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models
from . import wizard

View file

@ -0,0 +1,22 @@
# Copyright 2024 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
{
"name": "Purchase Rappel Commission",
"version": "17.0.1.0.0",
"summary": "Manage rappel commission invoices for vendors",
"category": "Purchase",
"author": "Criptomart",
"website": "https://criptomart.net",
"license": "AGPL-3",
"depends": ["purchase", "account"],
"data": [
"security/ir.model.access.csv",
"wizard/account_move_rappel_wizard_views.xml",
"views/res_partner_views.xml",
"views/res_config_settings_views.xml",
"views/account_move_views.xml",
],
"installable": True,
"application": False,
}

167
purchase_rappel/i18n/ca.po Normal file
View file

@ -0,0 +1,167 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_rappel
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-12 10:00+0000\n"
"PO-Revision-Date: 2024-06-12 10:00+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: ca\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_company__rappel_journal_id
#: model:ir.model.fields,field_description:purchase_rappel.field_res_config_settings__rappel_journal_id
msgid "Rappel Journal"
msgstr "Diari de Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_company__rappel_journal_id
#: model:ir.model.fields,help:purchase_rappel.field_res_config_settings__rappel_journal_id
msgid "Sales journal used to post rappel commission invoices."
msgstr "Diari de vendes utilitzat per registrar les factures de comissió per rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_company__rappel_product_id
#: model:ir.model.fields,field_description:purchase_rappel.field_res_config_settings__rappel_product_id
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__product_id
msgid "Commission Product"
msgstr "Producte de Comissió"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_company__rappel_product_id
#: model:ir.model.fields,help:purchase_rappel.field_res_config_settings__rappel_product_id
msgid "Product used as invoice line in rappel commission invoices."
msgstr "Producte utilitzat com a línia de factura en les factures de comissió per rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_partner__rappel_commission
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_partner_form_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.res_config_settings_view_form_rappel
msgid "Rappel Commission"
msgstr "Comissió per Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_partner__rappel_commission
msgid "If enabled, vendor bills from this partner will be eligible for rappel commission invoicing."
msgstr "Si està activat, les factures de proveïdor d'aquest contacte seran elegibles per a la facturació de comissió per rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_partner__rappel_commission_rate
msgid "Commission Rate (%)"
msgstr "Taxa de Comissió (%)"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_partner__rappel_commission_rate
msgid "Percentage applied on the taxable base of vendor bills to calculate the rappel commission amount."
msgstr "Percentatge aplicat sobre la base imposable de les factures de proveïdor per calcular l'import de la comissió per rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move__rappel_pending
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_invoice_filter_rappel
msgid "Pending Commission"
msgstr "Pendent de Comissió"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_account_move__rappel_pending
msgid "Indicates that this vendor bill has not yet been included in a rappel commission invoice. Automatically set based on the vendor's configuration."
msgstr "Indica que aquesta factura de proveïdor encara no ha estat inclosa en una factura de comissió per rappel. S'estableix automàticament segons la configuració del proveïdor."
#. module: purchase_rappel
#: model:ir.model,name:purchase_rappel.model_account_move_rappel_wizard
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Create Rappel Commission Invoice"
msgstr "Crear Factura de Comissió per Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__move_ids
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Vendor Bills"
msgstr "Factures de Proveïdor"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__invoice_date
msgid "Invoice Date"
msgstr "Data de Factura"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__journal_id
msgid "Journal"
msgstr "Diari"
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "No posted vendor bills found in the selection. Only posted (validated) vendor bills can be used to create rappel commission invoices."
msgstr "No s'han trobat factures de proveïdor comptabilitzades en la selecció. Només les factures de proveïdor comptabilitzades (validades) es poden fer servir per crear factures de comissió per rappel."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendor bills are pending commission. They may have already been commissioned."
msgstr "Cap de les factures de proveïdor seleccionades està pendent de comissió. Pot ser que ja hagin estat comissionades."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendors have the rappel commission enabled. Please configure the commission rate on the vendor contact first."
msgstr "Cap dels proveïdors seleccionats té la comissió per rappel activada. Configureu primer la taxa de comissió en el contacte del proveïdor."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Please select a journal for the rappel commission invoices, or configure a default one in Purchase Settings."
msgstr "Seleccioneu un diari per a les factures de comissió per rappel, o configureu-ne un per defecte en els Ajustos de Compres."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Please select a product for the rappel commission invoices, or configure a default one in Purchase Settings."
msgstr "Seleccioneu un producte per a les factures de comissió per rappel, o configureu-ne un per defecte en els Ajustos de Compres."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendor bills belong to vendors with a rappel commission rate configured."
msgstr "Cap de les factures de proveïdor seleccionades pertany a proveïdors amb una taxa de comissió per rappel configurada."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Rappel commission %(rate)s%% — Bill %(bill)s (taxable base: %(base)s %(currency)s)"
msgstr "Comissió per rappel %(rate)s%% — Factura %(bill)s (base imposable: %(base)s %(currency)s)"
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Rappel commission invoice generated from %d vendor bill(s)."
msgstr "Factura de comissió per rappel generada a partir de %d factura(es) de proveïdor."
#. module: purchase_rappel
#: model:ir.actions.act_window,name:purchase_rappel.action_open_rappel_wizard
msgid "Rappel Commission Invoices"
msgstr "Factures de Comissió per Rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.res_config_settings_view_form_rappel
msgid "Configure the journal and product for rappel commission invoices"
msgstr "Configureu el diari i el producte per a les factures de comissió per rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_invoice_filter_rappel
msgid "Vendor bills not yet included in a rappel commission invoice"
msgstr "Factures de proveïdor que encara no estan incloses en una factura de comissió per rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Taxable Base"
msgstr "Base Imposable"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Create Rappel Invoice(s)"
msgstr "Crear Factura(es) de Rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Cancel"
msgstr "Cancel·lar"

167
purchase_rappel/i18n/es.po Normal file
View file

@ -0,0 +1,167 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_rappel
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-12 10:00+0000\n"
"PO-Revision-Date: 2024-06-12 10:00+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_company__rappel_journal_id
#: model:ir.model.fields,field_description:purchase_rappel.field_res_config_settings__rappel_journal_id
msgid "Rappel Journal"
msgstr "Diario de Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_company__rappel_journal_id
#: model:ir.model.fields,help:purchase_rappel.field_res_config_settings__rappel_journal_id
msgid "Sales journal used to post rappel commission invoices."
msgstr "Diario de ventas utilizado para registrar las facturas de comisión por rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_company__rappel_product_id
#: model:ir.model.fields,field_description:purchase_rappel.field_res_config_settings__rappel_product_id
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__product_id
msgid "Commission Product"
msgstr "Producto de Comisión"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_company__rappel_product_id
#: model:ir.model.fields,help:purchase_rappel.field_res_config_settings__rappel_product_id
msgid "Product used as invoice line in rappel commission invoices."
msgstr "Producto utilizado como línea de factura en las facturas de comisión por rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_partner__rappel_commission
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_partner_form_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.res_config_settings_view_form_rappel
msgid "Rappel Commission"
msgstr "Comisión por Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_partner__rappel_commission
msgid "If enabled, vendor bills from this partner will be eligible for rappel commission invoicing."
msgstr "Si está activado, las facturas de proveedor de este contacto serán elegibles para la facturación de comisión por rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_partner__rappel_commission_rate
msgid "Commission Rate (%)"
msgstr "Tasa de Comisión (%)"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_partner__rappel_commission_rate
msgid "Percentage applied on the taxable base of vendor bills to calculate the rappel commission amount."
msgstr "Porcentaje aplicado sobre la base imponible de las facturas de proveedor para calcular el importe de la comisión por rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move__rappel_pending
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_invoice_filter_rappel
msgid "Pending Commission"
msgstr "Pendiente de Comisión"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_account_move__rappel_pending
msgid "Indicates that this vendor bill has not yet been included in a rappel commission invoice. Automatically set based on the vendor's configuration."
msgstr "Indica que esta factura de proveedor aún no ha sido incluida en una factura de comisión por rappel. Se establece automáticamente según la configuración del proveedor."
#. module: purchase_rappel
#: model:ir.model,name:purchase_rappel.model_account_move_rappel_wizard
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Create Rappel Commission Invoice"
msgstr "Crear Factura de Comisión por Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__move_ids
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Vendor Bills"
msgstr "Facturas de Proveedor"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__invoice_date
msgid "Invoice Date"
msgstr "Fecha de Factura"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__journal_id
msgid "Journal"
msgstr "Diario"
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "No posted vendor bills found in the selection. Only posted (validated) vendor bills can be used to create rappel commission invoices."
msgstr "No se encontraron facturas de proveedor contabilizadas en la selección. Solo las facturas de proveedor contabilizadas (validadas) pueden usarse para crear facturas de comisión por rappel."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendor bills are pending commission. They may have already been commissioned."
msgstr "Ninguna de las facturas de proveedor seleccionadas está pendiente de comisión. Puede que ya hayan sido comisionadas."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendors have the rappel commission enabled. Please configure the commission rate on the vendor contact first."
msgstr "Ninguno de los proveedores seleccionados tiene la comisión por rappel activada. Configure primero la tasa de comisión en el contacto del proveedor."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Please select a journal for the rappel commission invoices, or configure a default one in Purchase Settings."
msgstr "Seleccione un diario para las facturas de comisión por rappel, o configure uno por defecto en los Ajustes de Compras."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Please select a product for the rappel commission invoices, or configure a default one in Purchase Settings."
msgstr "Seleccione un producto para las facturas de comisión por rappel, o configure uno por defecto en los Ajustes de Compras."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendor bills belong to vendors with a rappel commission rate configured."
msgstr "Ninguna de las facturas de proveedor seleccionadas pertenece a proveedores con una tasa de comisión por rappel configurada."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Rappel commission %(rate)s%% — Bill %(bill)s (taxable base: %(base)s %(currency)s)"
msgstr "Comisión por rappel %(rate)s%% — Factura %(bill)s (base imponible: %(base)s %(currency)s)"
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Rappel commission invoice generated from %d vendor bill(s)."
msgstr "Factura de comisión por rappel generada a partir de %d factura(s) de proveedor."
#. module: purchase_rappel
#: model:ir.actions.act_window,name:purchase_rappel.action_open_rappel_wizard
msgid "Rappel Commission Invoices"
msgstr "Facturas de Comisión por Rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.res_config_settings_view_form_rappel
msgid "Configure the journal and product for rappel commission invoices"
msgstr "Configure el diario y el producto para las facturas de comisión por rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_invoice_filter_rappel
msgid "Vendor bills not yet included in a rappel commission invoice"
msgstr "Facturas de proveedor que aún no están incluidas en una factura de comisión por rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Taxable Base"
msgstr "Base Imponible"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Create Rappel Invoice(s)"
msgstr "Crear Factura(s) de Rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Cancel"
msgstr "Cancelar"

167
purchase_rappel/i18n/gl.po Normal file
View file

@ -0,0 +1,167 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_rappel
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-12 10:00+0000\n"
"PO-Revision-Date: 2024-06-12 10:00+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: gl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_company__rappel_journal_id
#: model:ir.model.fields,field_description:purchase_rappel.field_res_config_settings__rappel_journal_id
msgid "Rappel Journal"
msgstr "Diario de Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_company__rappel_journal_id
#: model:ir.model.fields,help:purchase_rappel.field_res_config_settings__rappel_journal_id
msgid "Sales journal used to post rappel commission invoices."
msgstr "Diario de vendas utilizado para rexistrar as facturas de comisión por rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_company__rappel_product_id
#: model:ir.model.fields,field_description:purchase_rappel.field_res_config_settings__rappel_product_id
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__product_id
msgid "Commission Product"
msgstr "Produto de Comisión"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_company__rappel_product_id
#: model:ir.model.fields,help:purchase_rappel.field_res_config_settings__rappel_product_id
msgid "Product used as invoice line in rappel commission invoices."
msgstr "Produto utilizado como liña de factura nas facturas de comisión por rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_partner__rappel_commission
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_partner_form_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.res_config_settings_view_form_rappel
msgid "Rappel Commission"
msgstr "Comisión por Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_partner__rappel_commission
msgid "If enabled, vendor bills from this partner will be eligible for rappel commission invoicing."
msgstr "Se está activado, as facturas de provedor deste contacto serán elixibles para a facturación de comisión por rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_partner__rappel_commission_rate
msgid "Commission Rate (%)"
msgstr "Taxa de Comisión (%)"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_partner__rappel_commission_rate
msgid "Percentage applied on the taxable base of vendor bills to calculate the rappel commission amount."
msgstr "Porcentaxe aplicado sobre a base imponible das facturas de provedor para calcular o importe da comisión por rappel."
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move__rappel_pending
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_invoice_filter_rappel
msgid "Pending Commission"
msgstr "Pendente de Comisión"
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_account_move__rappel_pending
msgid "Indicates that this vendor bill has not yet been included in a rappel commission invoice. Automatically set based on the vendor's configuration."
msgstr "Indica que esta factura de provedor aínda non foi incluída nunha factura de comisión por rappel. Establécese automaticamente segundo a configuración do provedor."
#. module: purchase_rappel
#: model:ir.model,name:purchase_rappel.model_account_move_rappel_wizard
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Create Rappel Commission Invoice"
msgstr "Crear Factura de Comisión por Rappel"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__move_ids
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Vendor Bills"
msgstr "Facturas de Provedor"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__invoice_date
msgid "Invoice Date"
msgstr "Data de Factura"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__journal_id
msgid "Journal"
msgstr "Diario"
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "No posted vendor bills found in the selection. Only posted (validated) vendor bills can be used to create rappel commission invoices."
msgstr "Non se atoparon facturas de provedor contabilizadas na selección. Só as facturas de provedor contabilizadas (validadas) poden usarse para crear facturas de comisión por rappel."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendor bills are pending commission. They may have already been commissioned."
msgstr "Ningunha das facturas de provedor seleccionadas está pendente de comisión. Pode que xa fosen comisionadas."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendors have the rappel commission enabled. Please configure the commission rate on the vendor contact first."
msgstr "Ningún dos provedores seleccionados ten a comisión por rappel activada. Configure primeiro a taxa de comisión no contacto do provedor."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Please select a journal for the rappel commission invoices, or configure a default one in Purchase Settings."
msgstr "Seleccione un diario para as facturas de comisión por rappel, ou configure un por defecto nos Axustes de Compras."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Please select a product for the rappel commission invoices, or configure a default one in Purchase Settings."
msgstr "Seleccione un produto para as facturas de comisión por rappel, ou configure un por defecto nos Axustes de Compras."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendor bills belong to vendors with a rappel commission rate configured."
msgstr "Ningunha das facturas de provedor seleccionadas pertence a provedores cunha taxa de comisión por rappel configurada."
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Rappel commission %(rate)s%% — Bill %(bill)s (taxable base: %(base)s %(currency)s)"
msgstr "Comisión por rappel %(rate)s%% — Factura %(bill)s (base imponible: %(base)s %(currency)s)"
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Rappel commission invoice generated from %d vendor bill(s)."
msgstr "Factura de comisión por rappel xerada a partir de %d factura(s) de provedor."
#. module: purchase_rappel
#: model:ir.actions.act_window,name:purchase_rappel.action_open_rappel_wizard
msgid "Rappel Commission Invoices"
msgstr "Facturas de Comisión por Rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.res_config_settings_view_form_rappel
msgid "Configure the journal and product for rappel commission invoices"
msgstr "Configure o diario e o produto para as facturas de comisión por rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_invoice_filter_rappel
msgid "Vendor bills not yet included in a rappel commission invoice"
msgstr "Facturas de provedor que aínda non están incluídas nunha factura de comisión por rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Taxable Base"
msgstr "Base Imponible"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Create Rappel Invoice(s)"
msgstr "Crear Factura(s) de Rappel"
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Cancel"
msgstr "Cancelar"

View file

@ -0,0 +1,166 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_rappel
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-06-12 10:00+0000\n"
"PO-Revision-Date: 2024-06-12 10:00+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_company__rappel_journal_id
#: model:ir.model.fields,field_description:purchase_rappel.field_res_config_settings__rappel_journal_id
msgid "Rappel Journal"
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_company__rappel_journal_id
#: model:ir.model.fields,help:purchase_rappel.field_res_config_settings__rappel_journal_id
msgid "Sales journal used to post rappel commission invoices."
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_company__rappel_product_id
#: model:ir.model.fields,field_description:purchase_rappel.field_res_config_settings__rappel_product_id
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__product_id
msgid "Commission Product"
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_company__rappel_product_id
#: model:ir.model.fields,help:purchase_rappel.field_res_config_settings__rappel_product_id
msgid "Product used as invoice line in rappel commission invoices."
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_partner__rappel_commission
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_partner_form_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.res_config_settings_view_form_rappel
msgid "Rappel Commission"
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_partner__rappel_commission
msgid "If enabled, vendor bills from this partner will be eligible for rappel commission invoicing."
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_res_partner__rappel_commission_rate
msgid "Commission Rate (%)"
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_res_partner__rappel_commission_rate
msgid "Percentage applied on the taxable base of vendor bills to calculate the rappel commission amount."
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move__rappel_pending
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_invoice_filter_rappel
msgid "Pending Commission"
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,help:purchase_rappel.field_account_move__rappel_pending
msgid "Indicates that this vendor bill has not yet been included in a rappel commission invoice. Automatically set based on the vendor's configuration."
msgstr ""
#. module: purchase_rappel
#: model:ir.model,name:purchase_rappel.model_account_move_rappel_wizard
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Create Rappel Commission Invoice"
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__move_ids
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Vendor Bills"
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__invoice_date
msgid "Invoice Date"
msgstr ""
#. module: purchase_rappel
#: model:ir.model.fields,field_description:purchase_rappel.field_account_move_rappel_wizard__journal_id
msgid "Journal"
msgstr ""
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "No posted vendor bills found in the selection. Only posted (validated) vendor bills can be used to create rappel commission invoices."
msgstr ""
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendor bills are pending commission. They may have already been commissioned."
msgstr ""
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendors have the rappel commission enabled. Please configure the commission rate on the vendor contact first."
msgstr ""
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Please select a journal for the rappel commission invoices, or configure a default one in Purchase Settings."
msgstr ""
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Please select a product for the rappel commission invoices, or configure a default one in Purchase Settings."
msgstr ""
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "None of the selected vendor bills belong to vendors with a rappel commission rate configured."
msgstr ""
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Rappel commission %(rate)s%% — Bill %(bill)s (taxable base: %(base)s %(currency)s)"
msgstr ""
#. module: purchase_rappel
#: code:addons/purchase_rappel/wizard/account_move_rappel_wizard.py:0
msgid "Rappel commission invoice generated from %d vendor bill(s)."
msgstr ""
#. module: purchase_rappel
#: model:ir.actions.act_window,name:purchase_rappel.action_open_rappel_wizard
msgid "Rappel Commission Invoices"
msgstr ""
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.res_config_settings_view_form_rappel
msgid "Configure the journal and product for rappel commission invoices"
msgstr ""
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_invoice_filter_rappel
msgid "Vendor bills not yet included in a rappel commission invoice"
msgstr ""
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Taxable Base"
msgstr ""
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Create Rappel Invoice(s)"
msgstr ""
#. module: purchase_rappel
#: model_terms:ir.ui.view,arch_db:purchase_rappel.view_account_move_rappel_wizard_form
msgid "Cancel"
msgstr ""

View file

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import res_company
from . import res_partner
from . import res_config_settings
from . import account_move

View file

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class AccountMove(models.Model):
_inherit = "account.move"
rappel_pending = fields.Boolean(
string="Pending Commission",
copy=False,
help="Indicates that this vendor bill has not yet been included in a rappel "
"commission invoice. Automatically set based on the vendor's configuration.",
)
@api.model_create_multi
def create(self, vals_list):
moves = super().create(vals_list)
for move in moves:
if move.move_type == "in_invoice" and move.partner_id.rappel_commission:
move.rappel_pending = True
return moves
@api.onchange("partner_id")
def _onchange_partner_id_rappel(self):
if self.move_type == "in_invoice":
self.rappel_pending = bool(
self.partner_id and self.partner_id.rappel_commission
)

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
rappel_journal_id = fields.Many2one(
comodel_name="account.journal",
string="Rappel Journal",
domain="[('type', '=', 'sale'), ('company_id', '=', id)]",
help="Sales journal used to post rappel commission invoices.",
)
rappel_product_id = fields.Many2one(
comodel_name="product.product",
string="Commission Product",
help="Product used as invoice line in rappel commission invoices.",
)

View file

@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
rappel_journal_id = fields.Many2one(
comodel_name="account.journal",
string="Rappel Journal",
related="company_id.rappel_journal_id",
domain=[("type", "=", "sale")],
readonly=False,
help="Sales journal used to post rappel commission invoices.",
)
rappel_product_id = fields.Many2one(
comodel_name="product.product",
string="Commission Product",
related="company_id.rappel_product_id",
readonly=False,
help="Product used as invoice line in rappel commission invoices.",
)

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from odoo import fields, models
class ResPartner(models.Model):
_inherit = "res.partner"
rappel_commission = fields.Boolean(
string="Rappel Commission",
help="If enabled, vendor bills from this partner will be eligible for rappel "
"commission invoicing.",
)
rappel_commission_rate = fields.Float(
string="Commission Rate (%)",
digits=(5, 4),
help="Percentage applied on the taxable base of vendor bills to calculate the "
"rappel commission amount.",
)

View file

@ -0,0 +1,13 @@
## Company Settings
Go to **Purchase > Configuration > Settings > Invoicing** and set:
- **Rappel Journal**: Sales journal where rappel commission invoices will be posted.
- **Commission Product**: Product used as invoice line in rappel commission invoices (should have an income account and taxes configured).
## Vendor Configuration
Open a vendor contact and go to the **Sales & Purchases** tab:
- Enable **Rappel Commission**.
- Set the **Commission Rate (%)** (e.g., 5.00).

View file

@ -0,0 +1,7 @@
This module allows managing **rappel commission invoices** for vendors.
Features:
- Configure a rappel commission rate per vendor.
- Automatically mark vendor bills as "pending commission".
- Generate customer (sale) invoices from selected vendor bills via a wizard.
- Group invoices by vendor and compute commission amounts based on the taxable base.

View file

@ -0,0 +1,15 @@
When a vendor bill is created for a partner with rappel enabled, the bill is automatically marked as **Pending Commission**. This flag is visible in the vendor bill list and form views.
To generate rappel invoices:
1. Go to **Invoicing > Vendors > Bills**.
2. Select one or more posted vendor bills with pending commission.
3. Click **Action > Create Rappel Commission Invoice**.
4. In the wizard, confirm the **Invoice Date**, **Journal**, and **Product**.
5. Click **Create Rappel Invoice(s)**.
The system will create one sale invoice per vendor, with one line per original bill. The commission amount is computed as:
taxable_base × commission_rate / 100
The original vendor bills are automatically unmarked as pending.

View file

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_account_move_rappel_wizard_user,account.move.rappel.wizard user,model_account_move_rappel_wizard,account.group_account_invoice,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_account_move_rappel_wizard_user account.move.rappel.wizard user model_account_move_rappel_wizard account.group_account_invoice 1 1 1 1

View file

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

View file

@ -0,0 +1,238 @@
# Copyright 2024 Criptomart
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import fields
from odoo.exceptions import UserError
from odoo.tests import tagged
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
@tagged("post_install", "-at_install")
class TestPurchaseRappel(AccountTestInvoicingCommon):
@classmethod
def setUpClass(cls, chart_template_ref=None):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.vendor_rappel = cls.env["res.partner"].create(
{
"name": "Vendor with Rappel",
"rappel_commission": True,
"rappel_commission_rate": 5.0,
"property_account_receivable_id": cls.company_data[
"default_account_receivable"
].id,
"property_account_payable_id": cls.company_data[
"default_account_payable"
].id,
}
)
cls.vendor_no_rappel = cls.env["res.partner"].create(
{
"name": "Vendor without Rappel",
"rappel_commission": False,
"property_account_receivable_id": cls.company_data[
"default_account_receivable"
].id,
"property_account_payable_id": cls.company_data[
"default_account_payable"
].id,
}
)
cls.vendor_zero_rate = cls.env["res.partner"].create(
{
"name": "Vendor Zero Rate",
"rappel_commission": True,
"rappel_commission_rate": 0.0,
"property_account_receivable_id": cls.company_data[
"default_account_receivable"
].id,
"property_account_payable_id": cls.company_data[
"default_account_payable"
].id,
}
)
cls.rappel_product = cls.env["product.product"].create(
{
"name": "Rappel Commission Product",
"type": "service",
"list_price": 100.0,
"property_account_income_id": cls.company_data[
"default_account_revenue"
].id,
"taxes_id": [(6, 0, [cls.tax_sale_a.id])],
}
)
cls.rappel_journal = cls.company_data["default_journal_sale"]
cls.company_data["company"].write(
{
"rappel_journal_id": cls.rappel_journal.id,
"rappel_product_id": cls.rappel_product.id,
}
)
def _create_vendor_bill(self, partner, amount=1000.0, post=True):
move = self.init_invoice(
"in_invoice",
partner=partner,
products=self.product_a,
post=False,
)
move.invoice_line_ids[0].price_unit = amount
if post:
move.action_post()
return move
def test_rappel_pending_on_create(self):
bill_rappel = self._create_vendor_bill(self.vendor_rappel)
self.assertTrue(bill_rappel.rappel_pending)
bill_no_rappel = self._create_vendor_bill(self.vendor_no_rappel)
self.assertFalse(bill_no_rappel.rappel_pending)
def test_rappel_pending_onchange(self):
move = self.env["account.move"].new(
{
"move_type": "in_invoice",
"partner_id": self.vendor_no_rappel.id,
"invoice_date": fields.Date.from_string("2019-01-01"),
}
)
move._onchange_partner_id_rappel()
self.assertFalse(move.rappel_pending)
move.partner_id = self.vendor_rappel
move._onchange_partner_id_rappel()
self.assertTrue(move.rappel_pending)
def test_wizard_default_get_valid(self):
bill = self._create_vendor_bill(self.vendor_rappel)
wizard = (
self.env["account.move.rappel.wizard"]
.with_context(active_ids=bill.ids)
.create({})
)
self.assertIn(bill, wizard.move_ids)
self.assertEqual(wizard.journal_id, self.rappel_journal)
self.assertEqual(wizard.product_id, self.rappel_product)
def test_wizard_default_get_no_valid(self):
bill = self._create_vendor_bill(self.vendor_rappel, post=False)
with self.assertRaises(UserError):
self.env["account.move.rappel.wizard"].with_context(
active_ids=bill.ids
).create({})
def test_wizard_default_get_no_rappel(self):
bill = self._create_vendor_bill(self.vendor_no_rappel)
with self.assertRaises(UserError):
self.env["account.move.rappel.wizard"].with_context(
active_ids=bill.ids
).create({})
def test_wizard_default_get_not_pending(self):
bill = self._create_vendor_bill(self.vendor_rappel)
bill.rappel_pending = False
with self.assertRaises(UserError):
self.env["account.move.rappel.wizard"].with_context(
active_ids=bill.ids
).create({})
def test_wizard_create_invoice(self):
bill = self._create_vendor_bill(self.vendor_rappel, amount=2000.0)
self.assertTrue(bill.rappel_pending)
wizard = (
self.env["account.move.rappel.wizard"]
.with_context(active_ids=bill.ids)
.create({})
)
action = wizard.action_create_rappel_invoices()
rappel_invoice = self.env["account.move"].browse(action["res_id"])
self.assertEqual(rappel_invoice.move_type, "out_invoice")
self.assertEqual(rappel_invoice.partner_id, self.vendor_rappel)
self.assertEqual(len(rappel_invoice.invoice_line_ids), 1)
expected_amount = 2000.0 * 5.0 / 100.0
self.assertEqual(rappel_invoice.invoice_line_ids[0].price_unit, expected_amount)
self.assertFalse(bill.rappel_pending)
def test_wizard_grouping(self):
bill1 = self._create_vendor_bill(self.vendor_rappel, amount=1000.0)
vendor_rappel_2 = self.env["res.partner"].create(
{
"name": "Vendor Rappel 2",
"rappel_commission": True,
"rappel_commission_rate": 10.0,
"property_account_receivable_id": self.company_data[
"default_account_receivable"
].id,
"property_account_payable_id": self.company_data[
"default_account_payable"
].id,
}
)
bill2 = self._create_vendor_bill(vendor_rappel_2, amount=2000.0)
wizard = (
self.env["account.move.rappel.wizard"]
.with_context(active_ids=(bill1 + bill2).ids)
.create({})
)
action = wizard.action_create_rappel_invoices()
invoices = self.env["account.move"].search(action.get("domain", []))
self.assertEqual(len(invoices), 2)
invoice_vendor_1 = invoices.filtered(
lambda m: m.partner_id == self.vendor_rappel
)
self.assertEqual(
invoice_vendor_1.invoice_line_ids[0].price_unit,
1000.0 * 5.0 / 100.0,
)
invoice_vendor_2 = invoices.filtered(lambda m: m.partner_id == vendor_rappel_2)
self.assertEqual(
invoice_vendor_2.invoice_line_ids[0].price_unit,
2000.0 * 10.0 / 100.0,
)
self.assertFalse(bill1.rappel_pending)
self.assertFalse(bill2.rappel_pending)
def test_wizard_no_journal(self):
bill = self._create_vendor_bill(self.vendor_rappel)
wizard = self.env["account.move.rappel.wizard"].new(
{
"move_ids": [(6, 0, bill.ids)],
"journal_id": False,
"product_id": self.rappel_product.id,
"company_id": self.env.company.id,
}
)
with self.assertRaises(UserError):
wizard.action_create_rappel_invoices()
def test_wizard_no_product(self):
bill = self._create_vendor_bill(self.vendor_rappel)
wizard = self.env["account.move.rappel.wizard"].new(
{
"move_ids": [(6, 0, bill.ids)],
"journal_id": self.rappel_journal.id,
"product_id": False,
"company_id": self.env.company.id,
}
)
with self.assertRaises(UserError):
wizard.action_create_rappel_invoices()
def test_wizard_zero_rate(self):
bill = self._create_vendor_bill(self.vendor_zero_rate, amount=1000.0)
wizard = (
self.env["account.move.rappel.wizard"]
.with_context(active_ids=bill.ids)
.create({})
)
with self.assertRaises(UserError):
wizard.action_create_rappel_invoices()

View file

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Extend vendor bills list view to show rappel_pending column -->
<record id="view_in_invoice_bill_tree_rappel" model="ir.ui.view">
<field name="name">account.move.tree.rappel</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_invoice_tree"/>
<field name="arch" type="xml">
<field name="to_check" position="after">
<field name="rappel_pending"
optional="show"
widget="boolean_toggle"
column_invisible="context.get('default_move_type') != 'in_invoice'"/>
</field>
</field>
</record>
<!-- Extend the invoice search view to add the "Pending Commission" filter -->
<record id="view_account_invoice_filter_rappel" model="ir.ui.view">
<field name="name">account.invoice.select.rappel</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_account_invoice_filter"/>
<field name="arch" type="xml">
<filter name="to_check" position="after">
<separator/>
<filter name="rappel_pending"
string="Pending Commission"
domain="[('rappel_pending', '=', True)]"
help="Vendor bills not yet included in a rappel commission invoice"/>
</filter>
</field>
</record>
<!-- Extend vendor bill form view to show rappel_pending field -->
<record id="view_move_form_rappel" model="ir.ui.view">
<field name="name">account.move.form.rappel</field>
<field name="model">account.move</field>
<field name="inherit_id" ref="account.view_move_form"/>
<field name="arch" type="xml">
<field name="to_check" position="after">
<field name="rappel_pending"
invisible="move_type != 'in_invoice'"
widget="boolean_toggle"/>
</field>
</field>
</record>
</odoo>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_config_settings_view_form_rappel" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.purchase.rappel</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="purchase.res_config_settings_view_form_purchase"/>
<field name="arch" type="xml">
<block name="invoicing_settings_container" position="inside">
<setting
id="rappel_commission_settings"
string="Rappel Commission"
help="Configure the journal and product for rappel commission invoices">
<field name="rappel_journal_id"/>
<div class="content-group">
<div class="row mt8">
<label for="rappel_product_id" class="col-lg-4 o_light_label"/>
<field name="rappel_product_id"/>
</div>
</div>
</setting>
</block>
</field>
</record>
</odoo>

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_partner_form_rappel" model="ir.ui.view">
<field name="name">res.partner.form.rappel</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<page name="sales_purchases" position="inside">
<group string="Rappel Commission" name="rappel_commission_group">
<field name="rappel_commission"/>
<field name="rappel_commission_rate"
invisible="not rappel_commission"
required="rappel_commission"/>
</group>
</page>
</field>
</record>
</odoo>

View file

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import account_move_rappel_wizard

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

View file

@ -0,0 +1,58 @@
<?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">
<tree string="Vendor Bills" editable="0">
<field name="name"/>
<field name="partner_id"/>
<field name="invoice_date"/>
<field name="amount_untaxed"
string="Taxable Base"/>
<field name="rappel_pending"/>
</tree>
</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>