Initial commit: Odoo 18.0-20251222 extra-addons
Some checks failed
pre-commit / pre-commit (push) Has been cancelled
tests / Detect unreleased dependencies (push) Has been cancelled
tests / test with OCB (push) Has been cancelled
tests / test with Odoo (push) Has been cancelled

This commit is contained in:
tocmo0nlord
2026-03-13 20:43:25 +00:00
parent 36e847a7df
commit adbe430761
9472 changed files with 1265727 additions and 0 deletions

View File

@@ -0,0 +1,134 @@
======================
Partner Invoicing Mode
======================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:06e5f9742a9035933a781882e9828686e640a2cce3f3583a0ec9a6495a4c1d43
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Faccount--invoicing-lightgray.png?logo=github
:target: https://github.com/OCA/account-invoicing/tree/18.0/partner_invoicing_mode
:alt: OCA/account-invoicing
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-invoicing-18-0/account-invoicing-18-0-partner_invoicing_mode
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png
:target: https://runboat.odoo-community.org/builds?repo=OCA/account-invoicing&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This is a base module for implementing different invoicing mode for
customers. It adds a selection field invoicing_mode in the Invoicing tab
(if enterprise account_accountant is installed -> Accounting tab) of the
partner with a default value (Odoo standard invoicing mode).
It can be used on its own to generate automatically (e.g.: each day) the
invoices for standard invoicing mode.
In core, Odoo is grouping invoicing from a group of sale orders on:
- Company
- Partner
- Currency
This module uses grouping on those keys:
- Company
- Invoiced partner
- Currency
- Payment term (as this can be selected on sale order level)
The following modules use it to install specific invoicing mode :
- partner_invoicing_mode_at_shipping
- partner_invoicing_mode_monthly
- partner_invoicing_mode_weekly
**Table of contents**
.. contents::
:local:
Usage
=====
- To enable the invoices automatic generation, enable the developer
mode, go to Settings > Automation > Scheduled Actions
- Filter the archived actions and activate 'Generate Standard Invoices'.
- Adapt the frequency you want to automatically generate invoices to
your needs.
- The field 'Next Invoice Date' on partner form indicates when the next
invoice generation will take place.
- To manually change the invoicing group (one invoice per order), go to
the Sale Order > Other Information > Invoicing and Payments and check
the 'One Invoice Per Order' box.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-invoicing/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/account-invoicing/issues/new?body=module:%20partner_invoicing_mode%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
Do not contact contributors directly about support or help with technical issues.
Credits
=======
Authors
-------
* Camptocamp
Contributors
------------
- `Camptocamp <https://www.camptocamp.com>`__:
- Thierry Ducrest <thierry.ducrest@camptocamp.com>
- Phuc (Tran Thanh) <phuc@trobz.com>
- `ACSONE SA/NV <https://acsone.eu>`__:
- Denis Roussel <denis.roussel@acsone.eu>
- Nils Coenen <nils.coenen@nico-solutions.de>
- Chau Le <chaulb@trobz.com>
Other credits
-------------
The development of this module has been financially supported by:
- Camptocamp
Maintainers
-----------
This module is maintained by the OCA.
.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org
OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.
This module is part of the `OCA/account-invoicing <https://github.com/OCA/account-invoicing/tree/18.0/partner_invoicing_mode>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1 @@
from . import models

View File

@@ -0,0 +1,19 @@
# Copyright 2020 Camptocamp
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
{
"name": "Partner Invoicing Mode",
"version": "18.0.1.0.0",
"summary": "Base module for handling multiple partner invoicing mode",
"author": "Camptocamp, Odoo Community Association (OCA)",
"website": "https://github.com/OCA/account-invoicing",
"license": "AGPL-3",
"category": "Accounting & Finance",
"depends": ["account", "base_partition", "queue_job", "sale"],
"data": [
"data/queue_job_data.xml",
"data/ir_cron.xml",
"views/res_partner.xml",
"views/res_config_settings.xml",
"views/sale_order.xml",
],
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo noupdate="1">
<record id="ir_cron_generate_standard_invoice" model="ir.cron">
<field name="name">Generate Standard Invoices</field>
<field name="active" eval="False" />
<field name="user_id" ref="base.user_root" />
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="model_id" ref="model_sale_order" />
<field name="code">model.cron_generate_standard_invoices()</field>
<field
name="nextcall"
eval="(DateTime.now().replace(hour=1,minute=0).strftime('%Y-%m-%d %H:%M:%S'))"
/>
</record>
</odoo>

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<!-- Queue Job Channel -->
<record id="invoice_validation" model="queue.job.channel">
<field name="name">invoice_validation</field>
<field name="parent_id" ref="queue_job.channel_root" />
</record>
<!-- Queue Job Function -->
<record id="job_function_validate_invoice" model="queue.job.function">
<field name="model_id" ref="account.model_account_move" />
<field name="method">_validate_invoice</field>
<field name="channel_id" ref="invoice_validation" />
<field
name="related_action"
eval='{"func_name": "related_action_open_invoice"}'
/>
</record>
</odoo>

View File

@@ -0,0 +1,126 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * partner_invoicing_mode
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-05-06 12:38+0000\n"
"Last-Translator: Anna Martínez <anna080678@gmail.com>\n"
"Language-Team: none\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"
"X-Generator: Weblate 4.17\n"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_company
msgid "Companies"
msgstr "Compañías"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_config_settings
msgid "Config Settings"
msgstr "Configuración de Ajustes"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_partner
msgid "Contact"
msgstr "Contacto"
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_partner__one_invoice_per_order
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_users__one_invoice_per_order
msgid "Do not group sale order into one invoice."
msgstr "No agrupar los pedidos de venta en una factura."
#. module: partner_invoicing_mode
#: model:ir.actions.server,name:partner_invoicing_mode.ir_cron_generate_standard_invoice_ir_actions_server
msgid "Generate Standard Invoices"
msgstr "Generar facturas estándar"
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_sale_order__invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.view_partner_property_form
msgid "Invoicing Mode"
msgstr "Modo de facturación"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_account_move
msgid "Journal Entry"
msgstr "Entrada diaria"
#. module: partner_invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
msgid "Last executed on"
msgstr "Última ejecución el"
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_company__invoicing_mode_standard_last_execution
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_config_settings__invoicing_mode_standard_last_execution
msgid "Last execution (standard)"
msgstr "Última ejecución (estándar)"
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_company__invoicing_mode_standard_last_execution
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_config_settings__invoicing_mode_standard_last_execution
msgid "Last execution of standard invoicing."
msgstr "Última ejecución de facturación estándar."
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__next_invoice_date
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__next_invoice_date
msgid "Next Invoice Date"
msgstr "Siguiente fecha de facturación"
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__one_invoice_per_order
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__one_invoice_per_order
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_sale_order__one_invoice_per_order
msgid "One Invoice Per Order"
msgstr "Una factura por pedido"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_queue_job
msgid "Queue Job"
msgstr "Cola de trabajo"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_sale_order
msgid "Sales Order"
msgstr "Órdenes de venta"
#. module: partner_invoicing_mode
#: model:ir.model.fields.selection,name:partner_invoicing_mode.selection__res_partner__invoicing_mode__standard
msgid "Standard"
msgstr "Estándar"
#. module: partner_invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
msgid "Standard Invoicing Options"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_partner__next_invoice_date
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_users__next_invoice_date
msgid "This is the date at which the next invoice will be generated."
msgstr "Esta es la fecha en la que se generará la próxima factura."
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_sale_order__one_invoice_per_order
msgid ""
"You can check or uncheck this if you want the periodic invoicing grouping "
"this sale order with other ones or not."
msgstr ""
"Puede marcar o desmarcar esta opción si desea que la facturación periódica "
"agrupe esta orden de venta con otras o no."
#~ msgid "<span class=\"o_form_label\">Standard Invoicing Options</span>"
#~ msgstr ""
#~ "<span class=\"o_form_label\">Opciones de facturación estandar</span>"

View File

@@ -0,0 +1,125 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * partner_invoicing_mode
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-10-03 09:06+0000\n"
"Last-Translator: mymage <stefano.consolaro@mymage.it>\n"
"Language-Team: none\n"
"Language: it\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"
"X-Generator: Weblate 5.6.2\n"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_company
msgid "Companies"
msgstr "Aziende"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_config_settings
msgid "Config Settings"
msgstr "Impostazioni configurazione"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_partner
msgid "Contact"
msgstr "Contatto"
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_partner__one_invoice_per_order
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_users__one_invoice_per_order
msgid "Do not group sale order into one invoice."
msgstr "Non raggruppare gli ordini di vendita in una fattura."
#. module: partner_invoicing_mode
#: model:ir.actions.server,name:partner_invoicing_mode.ir_cron_generate_standard_invoice_ir_actions_server
msgid "Generate Standard Invoices"
msgstr "Genera fatture standard"
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_sale_order__invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.view_partner_property_form
msgid "Invoicing Mode"
msgstr "Modo fatturazione"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_account_move
msgid "Journal Entry"
msgstr "Registrazione contabile"
#. module: partner_invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
msgid "Last executed on"
msgstr "Ultima esecuzione il"
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_company__invoicing_mode_standard_last_execution
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_config_settings__invoicing_mode_standard_last_execution
msgid "Last execution (standard)"
msgstr "Ultima esecuzione (standard)"
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_company__invoicing_mode_standard_last_execution
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_config_settings__invoicing_mode_standard_last_execution
msgid "Last execution of standard invoicing."
msgstr "Ultima esecuzione della fatturazione standrad."
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__next_invoice_date
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__next_invoice_date
msgid "Next Invoice Date"
msgstr "Data prossima fattura"
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__one_invoice_per_order
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__one_invoice_per_order
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_sale_order__one_invoice_per_order
msgid "One Invoice Per Order"
msgstr "Una fattura per ordine"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_queue_job
msgid "Queue Job"
msgstr "Lavoro in coda"
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_sale_order
msgid "Sales Order"
msgstr "Ordine di vendita"
#. module: partner_invoicing_mode
#: model:ir.model.fields.selection,name:partner_invoicing_mode.selection__res_partner__invoicing_mode__standard
msgid "Standard"
msgstr "Standard"
#. module: partner_invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
msgid "Standard Invoicing Options"
msgstr "Opzioni fatturazione standard"
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_partner__next_invoice_date
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_users__next_invoice_date
msgid "This is the date at which the next invoice will be generated."
msgstr "Questa è la data in cui verrà generata la prossima fattura."
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_sale_order__one_invoice_per_order
msgid ""
"You can check or uncheck this if you want the periodic invoicing grouping "
"this sale order with other ones or not."
msgstr ""
"Si può selezionare o meno se si vuole il raggruppamento di quest'ordine "
"nella fatturazione periodica o meno."
#~ msgid "<span class=\"o_form_label\">Standard Invoicing Options</span>"
#~ msgstr "<span class=\"o_form_label\">Opzioni fatturazione standard</span>"

View File

@@ -0,0 +1,129 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * partner_invoicing_mode
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \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: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_company
msgid "Companies"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_res_partner
msgid "Contact"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_partner__one_invoice_per_order
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_users__one_invoice_per_order
msgid "Do not group sale order into one invoice."
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.actions.server,name:partner_invoicing_mode.ir_cron_generate_standard_invoice_ir_actions_server
msgid "Generate Standard Invoices"
msgstr ""
#. module: partner_invoicing_mode
#. odoo-python
#: code:addons/partner_invoicing_mode/models/sale_order.py:0
msgid "Generate invoices by partner"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_sale_order__invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.view_partner_property_form
msgid "Invoicing Mode"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_account_move
msgid "Journal Entry"
msgstr ""
#. module: partner_invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
msgid "Last executed on"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_company__invoicing_mode_standard_last_execution
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_config_settings__invoicing_mode_standard_last_execution
msgid "Last execution (standard)"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_company__invoicing_mode_standard_last_execution
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_config_settings__invoicing_mode_standard_last_execution
msgid "Last execution of standard invoicing."
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__next_invoice_date
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__next_invoice_date
msgid "Next Invoice Date"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_partner__one_invoice_per_order
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_res_users__one_invoice_per_order
#: model:ir.model.fields,field_description:partner_invoicing_mode.field_sale_order__one_invoice_per_order
msgid "One Invoice Per Order"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_queue_job
msgid "Queue Job"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model,name:partner_invoicing_mode.model_sale_order
msgid "Sales Order"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields.selection,name:partner_invoicing_mode.selection__res_partner__invoicing_mode__standard
msgid "Standard"
msgstr ""
#. module: partner_invoicing_mode
#: model_terms:ir.ui.view,arch_db:partner_invoicing_mode.res_config_settings_view_form
msgid "Standard Invoicing Options"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_partner__next_invoice_date
#: model:ir.model.fields,help:partner_invoicing_mode.field_res_users__next_invoice_date
msgid "This is the date at which the next invoice will be generated."
msgstr ""
#. module: partner_invoicing_mode
#. odoo-python
#: code:addons/partner_invoicing_mode/models/sale_order.py:0
msgid "Validate %s invoice"
msgstr ""
#. module: partner_invoicing_mode
#: model:ir.model.fields,help:partner_invoicing_mode.field_sale_order__one_invoice_per_order
msgid ""
"You can check or uncheck this if you want the periodic invoicing grouping "
"this sale order with other ones or not."
msgstr ""

View File

@@ -0,0 +1,6 @@
from . import account_invoice
from . import queue_job
from . import res_partner
from . import sale_order
from . import res_company
from . import res_config_settings

View File

@@ -0,0 +1,11 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import models
class AccountMove(models.Model):
_inherit = "account.move"
def _validate_invoice(self):
return self.sudo().action_post()

View File

@@ -0,0 +1,15 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import models
class QueueJob(models.Model):
_inherit = "queue.job"
def related_action_open_invoice(self):
"""Open a form view with the invoice related to the job."""
action = self.related_action_open_record()
if len(self.records.exists()) > 1:
action["view_id"] = self.env.ref("account.view_out_invoice_tree").id
return action

View File

@@ -0,0 +1,13 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
invoicing_mode_standard_last_execution = fields.Datetime(
string="Last execution (standard)",
help="Last execution of standard invoicing.",
)

View File

@@ -0,0 +1,11 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
invoicing_mode_standard_last_execution = fields.Datetime(
related="company_id.invoicing_mode_standard_last_execution"
)

View File

@@ -0,0 +1,30 @@
# Copyright 2020 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import api, fields, models
class ResPartner(models.Model):
_inherit = "res.partner"
invoicing_mode = fields.Selection([("standard", "Standard")], default="standard")
one_invoice_per_order = fields.Boolean(
help="Do not group sale order into one invoice.",
)
next_invoice_date = fields.Date(
help="This is the date at which the next invoice will be generated."
)
@api.model
def _commercial_fields(self):
return super()._commercial_fields() + [
"invoicing_mode",
"one_invoice_per_order",
]
def _update_next_invoice_date(self):
"""
This will update the next invoice date from the configuration set on
the partner if needed (not for standard invoicing_mode).
"""
return

View File

@@ -0,0 +1,142 @@
# Copyright 2020 Camptocamp SA
# Copyright 2023 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)
from odoo import api, fields, models
from odoo.fields import Datetime
from odoo.osv.expression import AND
from odoo.addons.queue_job.job import identity_exact
class SaleOrder(models.Model):
_inherit = "sale.order"
invoicing_mode = fields.Selection(
related="partner_invoice_id.invoicing_mode",
store=True,
index=True,
)
one_invoice_per_order = fields.Boolean(
compute="_compute_one_invoice_per_order",
readonly=False,
store=True,
help="You can check or uncheck this if you want the periodic invoicing"
" grouping this sale order with other ones or not.",
)
@api.depends("partner_invoice_id")
def _compute_one_invoice_per_order(self):
# We depends only on partner as if we change it, we should recompute
# but not if the parameter on it has changed (this allows to set a different
# value on each sale order).
for order in self:
order.one_invoice_per_order = order.partner_invoice_id.one_invoice_per_order
@api.model
def cron_generate_standard_invoices(self):
company_ids = self._get_companies_standard_invoicing()
if company_ids:
self.generate_invoices(company_ids)
@api.model
def _get_generate_invoices_state_domain(self):
return [("invoice_status", "=", "to invoice")]
@api.model
def _get_generate_invoices_domain(self, companies, invoicing_mode="standard"):
return AND(
[
self._get_generate_invoices_state_domain(),
[
("invoicing_mode", "=", invoicing_mode),
("company_id", "in", companies.ids),
],
]
)
@api.model
def generate_invoices(
self,
companies=None,
invoicing_mode="standard",
last_execution_field="invoicing_mode_standard_last_execution",
):
"""
Generate invoices in job queues depending on the invoicing
mode (stadndard by default)
"""
if companies is None:
companies = self.env.company
domain = self._get_generate_invoices_domain(
companies=companies, invoicing_mode=invoicing_mode
)
saleorder_groups = self.read_group(
domain,
["partner_invoice_id", "sale_ids:array_agg(id)"],
groupby=self._get_invoice_grouping_keys(),
lazy=False,
)
for saleorder_group in saleorder_groups:
self.with_delay(
identity_key=identity_exact,
description=self.env._("Generate invoices by partner"),
)._generate_invoices_by_partner(saleorder_group["sale_ids"])
companies.write({last_execution_field: Datetime.now()})
return saleorder_groups
@api.model
def _get_invoice_grouping_keys(self) -> list:
"""
We override the standard (in sale) grouping function in order to
add some missing keys. We remove also the partner_id key.
"""
keys = super()._get_invoice_grouping_keys()
if "partner_invoice_id" not in keys:
keys.append("partner_invoice_id")
if "payment_term_id" not in keys:
keys.append("payment_term_id")
# Removing unwanted keys as we group on invoiced partner
if "partner_id" in keys:
keys.remove("partner_id")
return keys
def _get_generated_invoices(self, partition):
"""
Hook to get the generated invoices as in some extended modules,
those invoices should not be validated yet.
"""
return self._create_invoices(grouped=partition, final=True)
def _generate_invoices_by_partner(self, saleorder_ids):
"""Generate invoices for a group of sale order belonging to a customer."""
today = fields.Date.context_today(self)
sale_orders = (
self.browse(saleorder_ids)
.exists()
.filtered_domain(self._get_generate_invoices_state_domain())
)
if not sale_orders:
return "No sale order found to invoice ?"
# Create invoices using grouping when needed, so partition sales
invoice_ids = set()
for partition, partitioned_sales in sale_orders.partition(
lambda sale: sale.one_invoice_per_order
).items():
invoices = partitioned_sales._get_generated_invoices(partition=partition)
# Update invoices date to be sure no long validation (jobs) put
# the further day date on invoices.
invoices.write({"invoice_date": today})
# Update each partner next invoice date
partitioned_sales.partner_invoice_id._update_next_invoice_date()
invoice_ids.update(invoices.ids)
for invoice in invoices:
description = self.env._("Validate %s invoice", invoice.display_name)
invoice.with_delay(
identity_key=identity_exact, description=description
)._validate_invoice()
return self.env["account.move"].browse(invoice_ids)
@api.model
def _get_companies_standard_invoicing(self):
return self.env["res.company"].search([])

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"

View File

@@ -0,0 +1,13 @@
- [Camptocamp](https://www.camptocamp.com):
> - Thierry Ducrest \<<thierry.ducrest@camptocamp.com>\>
- Phuc (Tran Thanh) \<<phuc@trobz.com>\>
- [ACSONE SA/NV](https://acsone.eu):
> - Denis Roussel \<<denis.roussel@acsone.eu>\>
- Nils Coenen \<<nils.coenen@nico-solutions.de>\>
- Chau Le \<<chaulb@trobz.com>\>

View File

@@ -0,0 +1,3 @@
The development of this module has been financially supported by:
- Camptocamp

View File

@@ -0,0 +1,26 @@
This is a base module for implementing different invoicing mode for
customers. It adds a selection field invoicing_mode in the Invoicing tab
(if enterprise account_accountant is installed -> Accounting tab) of the
partner with a default value (Odoo standard invoicing mode).
It can be used on its own to generate automatically (e.g.: each day) the
invoices for standard invoicing mode.
In core, Odoo is grouping invoicing from a group of sale orders on:
- Company
- Partner
- Currency
This module uses grouping on those keys:
- Company
- Invoiced partner
- Currency
- Payment term (as this can be selected on sale order level)
The following modules use it to install specific invoicing mode :
> - partner_invoicing_mode_at_shipping
> - partner_invoicing_mode_monthly
> - partner_invoicing_mode_weekly

View File

@@ -0,0 +1,10 @@
- To enable the invoices automatic generation, enable the developer
mode, go to Settings \> Automation \> Scheduled Actions
- Filter the archived actions and activate 'Generate Standard Invoices'.
- Adapt the frequency you want to automatically generate invoices to
your needs.
- The field 'Next Invoice Date' on partner form indicates when the next
invoice generation will take place.
- To manually change the invoicing group (one invoice per order), go to
the Sale Order \> Other Information \> Invoicing and Payments and
check the 'One Invoice Per Order' box.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,492 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="generator" content="Docutils: https://docutils.sourceforge.io/" />
<title>Partner Invoicing Mode</title>
<style type="text/css">
/*
:Author: David Goodger (goodger@python.org)
:Id: $Id: html4css1.css 9511 2024-01-13 09:50:07Z milde $
:Copyright: This stylesheet has been placed in the public domain.
Default cascading style sheet for the HTML output of Docutils.
Despite the name, some widely supported CSS2 features are used.
See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to
customize this style sheet.
*/
/* used to remove borders from tables and images */
.borderless, table.borderless td, table.borderless th {
border: 0 }
table.borderless td, table.borderless th {
/* Override padding for "table.docutils td" with "! important".
The right padding separates the table cells. */
padding: 0 0.5em 0 0 ! important }
.first {
/* Override more specific margin styles with "! important". */
margin-top: 0 ! important }
.last, .with-subtitle {
margin-bottom: 0 ! important }
.hidden {
display: none }
.subscript {
vertical-align: sub;
font-size: smaller }
.superscript {
vertical-align: super;
font-size: smaller }
a.toc-backref {
text-decoration: none ;
color: black }
blockquote.epigraph {
margin: 2em 5em ; }
dl.docutils dd {
margin-bottom: 0.5em }
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
overflow: hidden;
}
/* Uncomment (and remove this text!) to get bold-faced definition list terms
dl.docutils dt {
font-weight: bold }
*/
div.abstract {
margin: 2em 5em }
div.abstract p.topic-title {
font-weight: bold ;
text-align: center }
div.admonition, div.attention, div.caution, div.danger, div.error,
div.hint, div.important, div.note, div.tip, div.warning {
margin: 2em ;
border: medium outset ;
padding: 1em }
div.admonition p.admonition-title, div.hint p.admonition-title,
div.important p.admonition-title, div.note p.admonition-title,
div.tip p.admonition-title {
font-weight: bold ;
font-family: sans-serif }
div.attention p.admonition-title, div.caution p.admonition-title,
div.danger p.admonition-title, div.error p.admonition-title,
div.warning p.admonition-title, .code .error {
color: red ;
font-weight: bold ;
font-family: sans-serif }
/* Uncomment (and remove this text!) to get reduced vertical space in
compound paragraphs.
div.compound .compound-first, div.compound .compound-middle {
margin-bottom: 0.5em }
div.compound .compound-last, div.compound .compound-middle {
margin-top: 0.5em }
*/
div.dedication {
margin: 2em 5em ;
text-align: center ;
font-style: italic }
div.dedication p.topic-title {
font-weight: bold ;
font-style: normal }
div.figure {
margin-left: 2em ;
margin-right: 2em }
div.footer, div.header {
clear: both;
font-size: smaller }
div.line-block {
display: block ;
margin-top: 1em ;
margin-bottom: 1em }
div.line-block div.line-block {
margin-top: 0 ;
margin-bottom: 0 ;
margin-left: 1.5em }
div.sidebar {
margin: 0 0 0.5em 1em ;
border: medium outset ;
padding: 1em ;
background-color: #ffffee ;
width: 40% ;
float: right ;
clear: right }
div.sidebar p.rubric {
font-family: sans-serif ;
font-size: medium }
div.system-messages {
margin: 5em }
div.system-messages h1 {
color: red }
div.system-message {
border: medium outset ;
padding: 1em }
div.system-message p.system-message-title {
color: red ;
font-weight: bold }
div.topic {
margin: 2em }
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
margin-top: 0.4em }
h1.title {
text-align: center }
h2.subtitle {
text-align: center }
hr.docutils {
width: 75% }
img.align-left, .figure.align-left, object.align-left, table.align-left {
clear: left ;
float: left ;
margin-right: 1em }
img.align-right, .figure.align-right, object.align-right, table.align-right {
clear: right ;
float: right ;
margin-left: 1em }
img.align-center, .figure.align-center, object.align-center {
display: block;
margin-left: auto;
margin-right: auto;
}
table.align-center {
margin-left: auto;
margin-right: auto;
}
.align-left {
text-align: left }
.align-center {
clear: both ;
text-align: center }
.align-right {
text-align: right }
/* reset inner alignment in figures */
div.align-right {
text-align: inherit }
/* div.align-center * { */
/* text-align: left } */
.align-top {
vertical-align: top }
.align-middle {
vertical-align: middle }
.align-bottom {
vertical-align: bottom }
ol.simple, ul.simple {
margin-bottom: 1em }
ol.arabic {
list-style: decimal }
ol.loweralpha {
list-style: lower-alpha }
ol.upperalpha {
list-style: upper-alpha }
ol.lowerroman {
list-style: lower-roman }
ol.upperroman {
list-style: upper-roman }
p.attribution {
text-align: right ;
margin-left: 50% }
p.caption {
font-style: italic }
p.credits {
font-style: italic ;
font-size: smaller }
p.label {
white-space: nowrap }
p.rubric {
font-weight: bold ;
font-size: larger ;
color: maroon ;
text-align: center }
p.sidebar-title {
font-family: sans-serif ;
font-weight: bold ;
font-size: larger }
p.sidebar-subtitle {
font-family: sans-serif ;
font-weight: bold }
p.topic-title {
font-weight: bold }
pre.address {
margin-bottom: 0 ;
margin-top: 0 ;
font: inherit }
pre.literal-block, pre.doctest-block, pre.math, pre.code {
margin-left: 2em ;
margin-right: 2em }
pre.code .ln { color: gray; } /* line numbers */
pre.code, code { background-color: #eeeeee }
pre.code .comment, code .comment { color: #5C6576 }
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
pre.code .literal.string, code .literal.string { color: #0C5404 }
pre.code .name.builtin, code .name.builtin { color: #352B84 }
pre.code .deleted, code .deleted { background-color: #DEB0A1}
pre.code .inserted, code .inserted { background-color: #A3D289}
span.classifier {
font-family: sans-serif ;
font-style: oblique }
span.classifier-delimiter {
font-family: sans-serif ;
font-weight: bold }
span.interpreted {
font-family: sans-serif }
span.option {
white-space: nowrap }
span.pre {
white-space: pre }
span.problematic, pre.problematic {
color: red }
span.section-subtitle {
/* font-size relative to parent (h1..h6 element) */
font-size: 80% }
table.citation {
border-left: solid 1px gray;
margin-left: 1px }
table.docinfo {
margin: 2em 4em }
table.docutils {
margin-top: 0.5em ;
margin-bottom: 0.5em }
table.footnote {
border-left: solid 1px black;
margin-left: 1px }
table.docutils td, table.docutils th,
table.docinfo td, table.docinfo th {
padding-left: 0.5em ;
padding-right: 0.5em ;
vertical-align: top }
table.docutils th.field-name, table.docinfo th.docinfo-name {
font-weight: bold ;
text-align: left ;
white-space: nowrap ;
padding-left: 0 }
/* "booktabs" style (no vertical lines) */
table.docutils.booktabs {
border: 0px;
border-top: 2px solid;
border-bottom: 2px solid;
border-collapse: collapse;
}
table.docutils.booktabs * {
border: 0px;
}
table.docutils.booktabs th {
border-bottom: thin solid;
text-align: left;
}
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
font-size: 100% }
ul.auto-toc {
list-style-type: none }
</style>
</head>
<body>
<div class="document" id="partner-invoicing-mode">
<h1 class="title">Partner Invoicing Mode</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:06e5f9742a9035933a781882e9828686e640a2cce3f3583a0ec9a6495a4c1d43
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external image-reference" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external image-reference" href="https://github.com/OCA/account-invoicing/tree/18.0/partner_invoicing_mode"><img alt="OCA/account-invoicing" src="https://img.shields.io/badge/github-OCA%2Faccount--invoicing-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-invoicing-18-0/account-invoicing-18-0-partner_invoicing_mode"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external image-reference" href="https://runboat.odoo-community.org/builds?repo=OCA/account-invoicing&amp;target_branch=18.0"><img alt="Try me on Runboat" src="https://img.shields.io/badge/runboat-Try%20me-875A7B.png" /></a></p>
<p>This is a base module for implementing different invoicing mode for
customers. It adds a selection field invoicing_mode in the Invoicing tab
(if enterprise account_accountant is installed -&gt; Accounting tab) of the
partner with a default value (Odoo standard invoicing mode).</p>
<p>It can be used on its own to generate automatically (e.g.: each day) the
invoices for standard invoicing mode.</p>
<p>In core, Odoo is grouping invoicing from a group of sale orders on:</p>
<ul class="simple">
<li>Company</li>
<li>Partner</li>
<li>Currency</li>
</ul>
<p>This module uses grouping on those keys:</p>
<ul class="simple">
<li>Company</li>
<li>Invoiced partner</li>
<li>Currency</li>
<li>Payment term (as this can be selected on sale order level)</li>
</ul>
<p>The following modules use it to install specific invoicing mode :</p>
<blockquote>
<ul class="simple">
<li>partner_invoicing_mode_at_shipping</li>
<li>partner_invoicing_mode_monthly</li>
<li>partner_invoicing_mode_weekly</li>
</ul>
</blockquote>
<p><strong>Table of contents</strong></p>
<div class="contents local topic" id="contents">
<ul class="simple">
<li><a class="reference internal" href="#usage" id="toc-entry-1">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="toc-entry-2">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="toc-entry-3">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="toc-entry-4">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="toc-entry-5">Contributors</a></li>
<li><a class="reference internal" href="#other-credits" id="toc-entry-6">Other credits</a></li>
<li><a class="reference internal" href="#maintainers" id="toc-entry-7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<ul class="simple">
<li>To enable the invoices automatic generation, enable the developer
mode, go to Settings &gt; Automation &gt; Scheduled Actions</li>
<li>Filter the archived actions and activate Generate Standard Invoices.</li>
<li>Adapt the frequency you want to automatically generate invoices to
your needs.</li>
<li>The field Next Invoice Date on partner form indicates when the next
invoice generation will take place.</li>
<li>To manually change the invoicing group (one invoice per order), go to
the Sale Order &gt; Other Information &gt; Invoicing and Payments and check
the One Invoice Per Order box.</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#toc-entry-2">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/account-invoicing/issues">GitHub Issues</a>.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us to smash it by providing a detailed and welcomed
<a class="reference external" href="https://github.com/OCA/account-invoicing/issues/new?body=module:%20partner_invoicing_mode%0Aversion:%2018.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
<p>Do not contact contributors directly about support or help with technical issues.</p>
</div>
<div class="section" id="credits">
<h1><a class="toc-backref" href="#toc-entry-3">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#toc-entry-4">Authors</a></h2>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul>
<li><p class="first"><a class="reference external" href="https://www.camptocamp.com">Camptocamp</a>:</p>
<blockquote>
<ul class="simple">
<li>Thierry Ducrest &lt;<a class="reference external" href="mailto:thierry.ducrest&#64;camptocamp.com">thierry.ducrest&#64;camptocamp.com</a>&gt;</li>
</ul>
</blockquote>
</li>
<li><p class="first">Phuc (Tran Thanh) &lt;<a class="reference external" href="mailto:phuc&#64;trobz.com">phuc&#64;trobz.com</a>&gt;</p>
</li>
<li><p class="first"><a class="reference external" href="https://acsone.eu">ACSONE SA/NV</a>:</p>
<blockquote>
<ul class="simple">
<li>Denis Roussel &lt;<a class="reference external" href="mailto:denis.roussel&#64;acsone.eu">denis.roussel&#64;acsone.eu</a>&gt;</li>
</ul>
</blockquote>
</li>
<li><p class="first">Nils Coenen &lt;<a class="reference external" href="mailto:nils.coenen&#64;nico-solutions.de">nils.coenen&#64;nico-solutions.de</a>&gt;</p>
</li>
<li><p class="first">Chau Le &lt;<a class="reference external" href="mailto:chaulb&#64;trobz.com">chaulb&#64;trobz.com</a>&gt;</p>
</li>
</ul>
</div>
<div class="section" id="other-credits">
<h2><a class="toc-backref" href="#toc-entry-6">Other credits</a></h2>
<p>The development of this module has been financially supported by:</p>
<ul class="simple">
<li>Camptocamp</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-7">Maintainers</a></h2>
<p>This module is maintained by the OCA.</p>
<a class="reference external image-reference" href="https://odoo-community.org">
<img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" />
</a>
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.</p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/account-invoicing/tree/18.0/partner_invoicing_mode">OCA/account-invoicing</a> project on GitHub.</p>
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1 @@
from . import test_invoice_mode

View File

@@ -0,0 +1,83 @@
# Copyright 2020 Camptocamp SA
# Copyright 2023 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from odoo import Command
from odoo.addons.base.tests.common import BaseCommon
class CommonPartnerInvoicingMode(BaseCommon):
_invoicing_mode = "standard"
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.SaleOrder = cls.env["sale.order"]
cls.partner = cls.env.ref("base.res_partner_1")
cls.partner.invoicing_mode = cls._invoicing_mode
cls.partner2 = cls.env.ref("base.res_partner_2")
cls.partner2.invoicing_mode = cls._invoicing_mode
cls.product = cls.env.ref("product.product_delivery_01")
cls.pt1 = cls.env["account.payment.term"].create({"name": "Term Two"})
cls.pt2 = cls.env["account.payment.term"].create({"name": "Term One"})
# Create a default pricelist for the test
cls.pricelist = cls.env["product.pricelist"].create(
{
"name": "Test Pricelist",
"currency_id": cls.env.ref("base.main_company").currency_id.id,
"company_id": cls.env.ref("base.main_company").id,
}
)
cls.so1 = cls.env["sale.order"].create(
{
"partner_id": cls.partner.id,
"partner_invoice_id": cls.partner.id,
"partner_shipping_id": cls.partner.id,
"payment_term_id": cls.pt1.id,
"order_line": [
Command.create(
{
"name": "Line one",
"product_id": cls.product.id,
"product_uom_qty": 4,
"product_uom": cls.product.uom_id.id,
"price_unit": 123,
}
)
],
"pricelist_id": cls.pricelist.id, # Use the newly created pricelist
}
)
# Lets give the saleorder the same partner and payment terms
cls.so2 = cls.env["sale.order"].create(
{
"partner_id": cls.partner.id,
"partner_invoice_id": cls.partner.id,
"partner_shipping_id": cls.partner.id,
"payment_term_id": cls.pt1.id,
"order_line": [
Command.create(
{
"name": "Line one",
"product_id": cls.product.id,
"product_uom_qty": 4,
"product_uom": cls.product.uom_id.id,
"price_unit": 123,
},
)
],
"pricelist_id": cls.pricelist.id, # Use the newly created pricelist
}
)
cls.company = cls.so1.company_id
@classmethod
def _confirm_and_deliver(cls, sale_order):
"""
Use standard sale flow to confirm delivered products
"""
sale_order.action_confirm()
for line in sale_order.order_line:
line.qty_delivered = line.product_uom_qty

View File

@@ -0,0 +1,186 @@
# Copyright 2022 Opener B.V.
# Copyright 2023 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html)
from unittest import mock
from freezegun import freeze_time
from odoo import fields
from odoo.tests import Form
from odoo.addons.queue_job.tests.common import trap_jobs
from ..models.res_partner import ResPartner
from .common import CommonPartnerInvoicingMode
class TestInvoiceMode(CommonPartnerInvoicingMode):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.cron = cls.env.ref(
"partner_invoicing_mode.ir_cron_generate_standard_invoice"
)
def test_invoice_job_related_action(self):
"""Dedicated invoice view is present in queue job's multi invoice action"""
invoice1 = self.env.ref("account.1_demo_invoice_1").copy()
invoice2 = self.env.ref("account.1_demo_invoice_1").copy()
job_single = self.env["queue.job"].search(
[("uuid", "=", invoice1.with_delay()._validate_invoice().uuid)]
)
action_single = job_single.open_related_action()
self.assertFalse(action_single.get("view_id"))
job_multi = self.env["queue.job"].search(
[("uuid", "=", (invoice1 + invoice2).with_delay()._validate_invoice().uuid)]
)
action_multi = job_multi.open_related_action()
self.assertEqual(
action_multi["view_id"], self.env.ref("account.view_out_invoice_tree").id
)
def test_invoicing_standard(self):
self.so1.payment_term_id = self.pt1.id
self._confirm_and_deliver(self.so1)
self.assertFalse(self.so1.invoice_ids)
with trap_jobs() as trap:
self.SaleOrder.generate_invoices()
for job in trap.enqueued_jobs:
job.perform()
self.assertTrue(self.so1.invoice_ids)
# No errors are raised when called without anything to invoice
with trap_jobs() as trap:
self.SaleOrder.generate_invoices()
trap.assert_jobs_count(0)
def test_invoicing_standard_cron(self):
self.so1.payment_term_id = self.pt1.id
self._confirm_and_deliver(self.so1)
self.assertFalse(self.so1.invoice_ids)
with trap_jobs() as trap:
self.cron.method_direct_trigger()
for job in trap.enqueued_jobs:
job.perform()
self.assertTrue(self.so1.invoice_ids)
def test_invoicing_standard_grouping(self):
# Confirm and deliver both sale orders
self._confirm_and_deliver(self.so1)
self._confirm_and_deliver(self.so2)
with trap_jobs() as trap:
self.SaleOrder.generate_invoices()
for job in trap.enqueued_jobs:
job.perform()
# Check one invoice is generated
self.assertEqual(1, len(self.so2.invoice_ids))
self.assertEqual(1, len(self.so1.invoice_ids))
# Check the invoice is the same
self.assertEqual(self.so2.invoice_ids, self.so1.invoice_ids)
def test_invoicing_standard_no_grouping(self):
# Confirm and deliver both sale orders
self.so1.one_invoice_per_order = True
self.so2.one_invoice_per_order = True
self._confirm_and_deliver(self.so1)
self._confirm_and_deliver(self.so2)
with trap_jobs() as trap:
self.SaleOrder.generate_invoices()
for job in trap.enqueued_jobs:
job.perform()
# Check one invoice is generated
self.assertEqual(1, len(self.so2.invoice_ids))
self.assertEqual(1, len(self.so1.invoice_ids))
# Check the invoice is the same
self.assertNotEqual(self.so2.invoice_ids, self.so1.invoice_ids)
def test_grouping_change(self):
# Create a sale order with a partner that have one invoice per order enabled
# Check if the sale order created has that option enabled
# Change the partner, then check if the option is disabled
# Then, set it manually and check if value remains.
self.partner2.one_invoice_per_order = True
so3 = self.so1.copy()
so3.partner_id = self.partner2
self.assertTrue(so3.one_invoice_per_order)
so3.partner_id = self.partner
self.assertFalse(so3.one_invoice_per_order)
so3.one_invoice_per_order = True
self.assertTrue(so3.one_invoice_per_order)
def test_update_date(self):
# Check the update next invoice date function has been called
with mock.patch.object(ResPartner, "_update_next_invoice_date") as mock_update:
self._confirm_and_deliver(self.so1)
with trap_jobs() as trap:
self.SaleOrder.generate_invoices()
for job in trap.enqueued_jobs:
job.perform()
mock_update.assert_called()
@freeze_time("2024-03-10")
def test_invoicing_standard_cron_invoice_date(self):
"""
- Generate the invoice at day one
- Execute the validation job at day two
- Check the invoice date is day one
"""
self.so1.payment_term_id = self.pt1.id
self._confirm_and_deliver(self.so1)
self.assertFalse(self.so1.invoice_ids)
with trap_jobs() as trap:
self.cron.method_direct_trigger()
for job in trap.enqueued_jobs:
with trap_jobs() as trap_validate:
job.perform()
self.assertEqual(1, len(trap_validate.enqueued_jobs))
for validate_job in trap_validate.enqueued_jobs:
with freeze_time("2024-03-11"):
validate_job.perform()
self.assertTrue(self.so1.invoice_ids)
self.assertEqual(
fields.Date.from_string("2024-03-10"), self.so1.invoice_ids.date
)
def test_invoicing_standard_grouping_partner_invoicing(self):
# Activate the ability to define a different invoice address
# Create a new partner for invoicing
self.env.user.groups_id |= self.env.ref(
"account.group_delivery_invoice_address"
)
self.partner_invoice = self.env["res.partner"].create(
{"name": "Partner Invoicing"}
)
self._confirm_and_deliver(self.so1)
with Form(self.so2) as so2_form:
so2_form.partner_invoice_id = self.partner_invoice
self._confirm_and_deliver(self.so2)
with trap_jobs() as trap:
self.SaleOrder.generate_invoices()
for job in trap.enqueued_jobs:
job.perform()
# Check one invoice is generated
self.assertEqual(1, len(self.so2.invoice_ids))
self.assertEqual(1, len(self.so1.invoice_ids))
# Check the invoice is the same
self.assertNotEqual(self.so2.invoice_ids, self.so1.invoice_ids)
def test_invoicing_standard_grouping_payment_term_invoicing(self):
# Activate the ability to define a different payment term
self.env.user.groups_id |= self.env.ref(
"account.group_delivery_invoice_address"
)
self._confirm_and_deliver(self.so1)
with Form(self.so2) as so2_form:
so2_form.payment_term_id = self.env.ref(
"account.account_payment_term_21days"
)
self._confirm_and_deliver(self.so2)
with trap_jobs() as trap:
self.SaleOrder.generate_invoices()
for job in trap.enqueued_jobs:
job.perform()
# Check one invoice is generated
self.assertEqual(1, len(self.so2.invoice_ids))
self.assertEqual(1, len(self.so1.invoice_ids))
# Check the invoice is the same
self.assertNotEqual(self.so2.invoice_ids, self.so1.invoice_ids)

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 ACSONE SA/NV
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="inherit_id" ref="account.res_config_settings_view_form" />
<field name="model">res.config.settings</field>
<field name="arch" type="xml">
<xpath expr="//block[@id='invoicing_settings']" position="inside">
<setting
id="invoicing_mode"
string="Invoicing Mode"
help="Standard Invoicing Options"
>
<div class="content-group" id="standard_invoicing_settings">
<div class="mt16">
<label
for="invoicing_mode_standard_last_execution"
string="Last executed on"
class="col-lg-4 o_light_label"
/>
<field
name="invoicing_mode_standard_last_execution"
readonly="1"
/>
</div>
</div>
</setting>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_partner_property_form" model="ir.ui.view">
<field name="name">view_partner_property_form_base_invoicing_mode</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="account.view_partner_property_form" />
<field name="arch" type="xml">
<xpath expr="//page[@name='accounting']/group" position="inside">
<group name="invoicing_mode" string="Invoicing Mode">
<field name="invoicing_mode" />
<field name="one_invoice_per_order" />
<field
name="next_invoice_date"
invisible="invoicing_mode == 'standard'"
/>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<!-- Copyright 2023 ACSONE SA/NV
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). -->
<odoo>
<record id="view_order_form" model="ir.ui.view">
<field name="name">sale.order.form (in partner_invoicing_mode)</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form" />
<field name="arch" type="xml">
<xpath expr="//label[@for='fiscal_position_id']" position="before">
<field name="one_invoice_per_order" />
</xpath>
</field>
</record>
</odoo>