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

108
purchase_unreconciled/README.rst Executable file
View File

@@ -0,0 +1,108 @@
=====================
Purchase Unreconciled
=====================
..
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3c45ee3a44f051bf83a42cc8c8690589a1c5f56b1879aaf2084deb8e7aaaaa2f
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
.. |badge1| image:: https://img.shields.io/badge/maturity-Alpha-red.png
:target: https://odoo-community.org/page/development-status
:alt: Alpha
.. |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--financial--tools-lightgray.png?logo=github
:target: https://github.com/OCA/account-financial-tools/tree/18.0/purchase_unreconciled
:alt: OCA/account-financial-tools
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/account-financial-tools-18-0/account-financial-tools-18-0-purchase_unreconciled
: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-financial-tools&target_branch=18.0
:alt: Try me on Runboat
|badge1| |badge2| |badge3| |badge4| |badge5|
This module adds a new fields "Unreconciled" on Purchase Orders, that
allows to find PO's with unreconciled journal items related.
This module allows to reconcile those PO in a single click. In
accounting settings users will be able to set up a specific account for
write-off.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
`More details on development status <https://odoo-community.org/page/development-status>`_
**Table of contents**
.. contents::
:local:
Usage
=====
Accountants will be able to find a filters in Purchase Orders that shows
outstanding balances in interim accounts. Also there is a link in the PO
to those outstanding journal items.
Locking the PO will automatically reconcile the outstanding balance for
the stock iterim accounts.
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/account-financial-tools/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-financial-tools/issues/new?body=module:%20purchase_unreconciled%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
-------
* ForgeFlow S.L.
Contributors
------------
- ForgeFlow S.L. <contact@forgeflow.com>
- Lois Rilo <lois.rilo@forgeflow.com>
- Aaron Henriquez <ahenriquez@forgeflow.com>
- Miquel Raich <miquel.raich@forgeflow.com>
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.
.. |maintainer-AaronHForgeFlow| image:: https://github.com/AaronHForgeFlow.png?size=40px
:target: https://github.com/AaronHForgeFlow
:alt: AaronHForgeFlow
Current `maintainer <https://odoo-community.org/page/maintainer-role>`__:
|maintainer-AaronHForgeFlow|
This module is part of the `OCA/account-financial-tools <https://github.com/OCA/account-financial-tools/tree/18.0/purchase_unreconciled>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

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

View File

@@ -0,0 +1,21 @@
# Copyright 2019-22 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
{
"name": "Purchase Unreconciled",
"version": "18.0.1.0.0",
"author": "ForgeFlow S.L., Odoo Community Association (OCA)",
"website": "https://github.com/OCA/account-financial-tools",
"category": "Purchases",
"depends": ["account_move_line_purchase_info", "purchase_stock"],
"data": [
"security/ir.model.access.csv",
"views/purchase_order_view.xml",
"views/res_config_settings_view.xml",
"wizards/purchase_unreconciled_exceeded_view.xml",
],
"license": "AGPL-3",
"installable": True,
"development_status": "Alpha",
"maintainers": ["AaronHForgeFlow"],
}

263
purchase_unreconciled/i18n/it.po Executable file
View File

@@ -0,0 +1,263 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_unreconciled
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 17.0\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-10-22 10: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: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_order__amount_unreconciled
msgid "Amount Unreconciled"
msgstr "Valore non riconciliato"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Amount Unreconciled exceeded"
msgstr "Valore non riconciliato superato"
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/account_move_line.py:0
#, python-format
msgid "Automatic writeoff"
msgstr "Storno automatico"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Cancel"
msgstr "Annulla"
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/account_move_line.py:0
#, python-format
msgid "Cannot write-off more than one account"
msgstr "Non si può stornare più di un conto"
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_res_company
msgid "Companies"
msgstr "Aziende"
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_res_config_settings
msgid "Config Settings"
msgstr "Impostazioni configurazione"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__continue_method
msgid "Continue Method"
msgstr "Continua metodo"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__create_uid
msgid "Created by"
msgstr "Creato da"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__create_date
msgid "Created on"
msgstr "Creato il"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__display_name
msgid "Display Name"
msgstr "Nome visualizzato"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__exception_msg
msgid "Exception Msg"
msgstr "Msg di eccezione"
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
#, python-format
msgid ""
"Finance Warning: \n"
"Unreconciled amount is too high. Total unreconciled amount: %(amount_unreconciled)s Maximum unreconciled amount accepted: %(amount_allowed)s "
msgstr ""
"Avviso finanziario:\n"
"l'importo non riconciliato è troppo alto. Importo totale non riconciliato: "
"%(amount_unreconciled)s Importo massimo non riconciliato accettato: "
"%(amount_allowed)s "
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__id
msgid "ID"
msgstr "ID"
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_purchase_order__unreconciled
msgid ""
"Indicates that a Purchase Order has related Journal items not reconciled.\n"
"Note that if it is false it can be either that everything is reconciled or that the related accounts do not allow reconciliation"
msgstr ""
"Indica che un ordine di acquisto ha movimenti contabili correlati non "
"riconciliati. \n"
"Notare che se è falso può essere che tutto è riconciliato o che i conti "
"correlati non consentono la riconciliazione"
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_account_move_line
msgid "Journal Item"
msgstr "Movimento contabile"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__write_uid
msgid "Last Updated by"
msgstr "Ultimo aggiornamento di"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__write_date
msgid "Last Updated on"
msgstr "Ultimo aggiornamento il"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Lock and Reconcile"
msgstr "Blocca e riconcilia"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__origin_reference
msgid "Object"
msgstr "Oggetto"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__purchase_id
msgid "Order Number"
msgstr "Numero ordine"
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_res_company__purchase_reconcile_tolerance
#: model:ir.model.fields,help:purchase_unreconciled.field_res_config_settings__purchase_reconcile_tolerance
msgid ""
"Percentage of tolerance of residual amount vs total amount of the Purchase "
"Order. Leave zero to accept all discrepancies"
msgstr ""
"Percentuale di tolleranza dell'importo residuo rispetto all'importo totale "
"dell'ordine di acquisto. Lasciare zero per accettare tutte le discrepanze"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_lock_auto_reconcile
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_lock_auto_reconcile
msgid "Purchase Lock Auto Reconcile"
msgstr "Blocco acquisto riconciliazione automatica"
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_purchase_order
msgid "Purchase Order"
msgstr "Ordine di acquisto"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_tolerance
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_tolerance
msgid "Purchase Reconcile Tolerance (%)"
msgstr "Tolleranza riconciliazione acquisto (%)"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.res_config_settings_view_form
msgid "Purchase Reconciling"
msgstr "Riconciliazione acquisto"
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_purchase_unreconciled_exceeded_wiz
msgid "Purchase Unreconciled Exceeded Wizard"
msgstr "Procedura guidata acquisto non riconciliato superato"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_view_search
msgid "Purchase orders with unreconciled journal items."
msgstr "Gli ordini di acquisto con movimenti contabili non riconciliati."
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/wizards/purchase_unreconciled_exceeded.py:0
#, python-format
msgid "Purchase unreconciled exceeded"
msgstr "Acquisto non riconciliato superato"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_form
msgid "Reconcile"
msgstr "Riconcilia"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.res_config_settings_view_form
msgid "Reconcile PO upon locking"
msgstr "Riconcilia OA al blocco"
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
#, python-format
msgid "Reconciled journal items"
msgstr "Movimenti contabili riconciliati"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "The order has exceeded its amount unreconciled"
msgstr "L'ordine ha superato il suo valore non riconciliato"
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
#, python-format
msgid ""
"The write-off account and journal for purchases is missing. An accountant "
"must fill that information"
msgstr ""
"Manca il conto di svalutazione e il registro per gli acquisti. Un contabile "
"deve compilare queste informazioni"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_order__unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_view_search
msgid "Unreconciled"
msgstr "Non riconciliato"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_form
msgid "Unreconciled Journal Items"
msgstr "Movimenti contabili non riconciliati"
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
#, python-format
msgid "Unsupported search operator"
msgstr "Operatore di ricerca non supportato"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_account_id
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_account_id
msgid "Write-Off Account On Purchases"
msgstr "Conto storno vendite"
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_res_company__purchase_reconcile_account_id
#: model:ir.model.fields,help:purchase_unreconciled.field_res_config_settings__purchase_reconcile_account_id
msgid "Write-off account to reconcile Unreconciled Purchase Orders"
msgstr "Conto storno per riconciliare ordini di acquisto non riconciliati"
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_journal_id
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_journal_id
msgid "WriteOff Journal for Purchases"
msgstr "Registro storno per acquisti"
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.unreconciled_amount_purchase_order_view_tree
msgid "sum_ar"
msgstr "sum_ar"

View File

@@ -0,0 +1,242 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_unreconciled
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 18.0\n"
"Report-Msgid-Bugs-To: \n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: pt_BR\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_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_order__amount_unreconciled
msgid "Amount Unreconciled"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Amount Unreconciled exceeded"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/account_move_line.py:0
msgid "Automatic writeoff"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Cancel"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/account_move_line.py:0
msgid "Cannot write-off more than one account"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_res_company
msgid "Companies"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__continue_method
msgid "Continue Method"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__create_uid
msgid "Created by"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__create_date
msgid "Created on"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__display_name
msgid "Display Name"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__exception_msg
msgid "Exception Msg"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
msgid ""
"Finance Warning: \n"
"Unreconciled amount is too high. Total unreconciled amount: %(amount_unreconciled)s Maximum unreconciled amount accepted: %(amount_allowed)s "
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__id
msgid "ID"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_purchase_order__unreconciled
msgid ""
"Indicates that a Purchase Order has related Journal items not reconciled.\n"
"Note that if it is false it can be either that everything is reconciled or that the related accounts do not allow reconciliation"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_account_move_line
msgid "Journal Item"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__write_uid
msgid "Last Updated by"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__write_date
msgid "Last Updated on"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Lock and Reconcile"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__origin_reference
msgid "Object"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__purchase_id
msgid "Order Number"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_res_company__purchase_reconcile_tolerance
#: model:ir.model.fields,help:purchase_unreconciled.field_res_config_settings__purchase_reconcile_tolerance
msgid ""
"Percentage of tolerance of residual amount vs total amount of the Purchase "
"Order. Leave zero to accept all discrepancies"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_lock_auto_reconcile
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_lock_auto_reconcile
msgid "Purchase Lock Auto Reconcile"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_purchase_order
msgid "Purchase Order"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_tolerance
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_tolerance
msgid "Purchase Reconcile Tolerance (%)"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.res_config_settings_view_form
msgid "Purchase Reconciling"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_purchase_unreconciled_exceeded_wiz
msgid "Purchase Unreconciled Exceeded Wizard"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_view_search
msgid "Purchase orders with unreconciled journal items."
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/wizards/purchase_unreconciled_exceeded.py:0
msgid "Purchase unreconciled exceeded"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_form
msgid "Reconcile"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.res_config_settings_view_form
msgid "Reconcile PO upon locking"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
msgid "Reconciled journal items"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "The order has exceeded its amount unreconciled"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
msgid ""
"The write-off account and journal for purchases is missing. An accountant "
"must fill that information"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_order__unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_view_search
msgid "Unreconciled"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_form
msgid "Unreconciled Journal Items"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
msgid "Unsupported search operator"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_account_id
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_account_id
msgid "Write-Off Account On Purchases"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_res_company__purchase_reconcile_account_id
#: model:ir.model.fields,help:purchase_unreconciled.field_res_config_settings__purchase_reconcile_account_id
msgid "Write-off account to reconcile Unreconciled Purchase Orders"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_journal_id
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_journal_id
msgid "WriteOff Journal for Purchases"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.unreconciled_amount_purchase_order_view_tree
msgid "sum_ar"
msgstr ""

View File

@@ -0,0 +1,241 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_unreconciled
#
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: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_order__amount_unreconciled
msgid "Amount Unreconciled"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Amount Unreconciled exceeded"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/account_move_line.py:0
msgid "Automatic writeoff"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Cancel"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/account_move_line.py:0
msgid "Cannot write-off more than one account"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_res_company
msgid "Companies"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_res_config_settings
msgid "Config Settings"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__continue_method
msgid "Continue Method"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__create_uid
msgid "Created by"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__create_date
msgid "Created on"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__display_name
msgid "Display Name"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__exception_msg
msgid "Exception Msg"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
msgid ""
"Finance Warning: \n"
"Unreconciled amount is too high. Total unreconciled amount: %(amount_unreconciled)s Maximum unreconciled amount accepted: %(amount_allowed)s "
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__id
msgid "ID"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_purchase_order__unreconciled
msgid ""
"Indicates that a Purchase Order has related Journal items not reconciled.\n"
"Note that if it is false it can be either that everything is reconciled or that the related accounts do not allow reconciliation"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_account_move_line
msgid "Journal Item"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__write_uid
msgid "Last Updated by"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__write_date
msgid "Last Updated on"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "Lock and Reconcile"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__origin_reference
msgid "Object"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_unreconciled_exceeded_wiz__purchase_id
msgid "Order Number"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_res_company__purchase_reconcile_tolerance
#: model:ir.model.fields,help:purchase_unreconciled.field_res_config_settings__purchase_reconcile_tolerance
msgid ""
"Percentage of tolerance of residual amount vs total amount of the Purchase "
"Order. Leave zero to accept all discrepancies"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_lock_auto_reconcile
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_lock_auto_reconcile
msgid "Purchase Lock Auto Reconcile"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_purchase_order
msgid "Purchase Order"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_tolerance
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_tolerance
msgid "Purchase Reconcile Tolerance (%)"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.res_config_settings_view_form
msgid "Purchase Reconciling"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model,name:purchase_unreconciled.model_purchase_unreconciled_exceeded_wiz
msgid "Purchase Unreconciled Exceeded Wizard"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_view_search
msgid "Purchase orders with unreconciled journal items."
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/wizards/purchase_unreconciled_exceeded.py:0
msgid "Purchase unreconciled exceeded"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_form
msgid "Reconcile"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.res_config_settings_view_form
msgid "Reconcile PO upon locking"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
msgid "Reconciled journal items"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_unreconciled_exceeded_wizard
msgid "The order has exceeded its amount unreconciled"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
msgid ""
"The write-off account and journal for purchases is missing. An accountant "
"must fill that information"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_purchase_order__unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_view_search
msgid "Unreconciled"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.purchase_order_form
msgid "Unreconciled Journal Items"
msgstr ""
#. module: purchase_unreconciled
#. odoo-python
#: code:addons/purchase_unreconciled/models/purchase_order.py:0
msgid "Unsupported search operator"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_account_id
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_account_id
msgid "Write-Off Account On Purchases"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,help:purchase_unreconciled.field_res_company__purchase_reconcile_account_id
#: model:ir.model.fields,help:purchase_unreconciled.field_res_config_settings__purchase_reconcile_account_id
msgid "Write-off account to reconcile Unreconciled Purchase Orders"
msgstr ""
#. module: purchase_unreconciled
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_company__purchase_reconcile_journal_id
#: model:ir.model.fields,field_description:purchase_unreconciled.field_res_config_settings__purchase_reconcile_journal_id
msgid "WriteOff Journal for Purchases"
msgstr ""
#. module: purchase_unreconciled
#: model_terms:ir.ui.view,arch_db:purchase_unreconciled.unreconciled_amount_purchase_order_view_tree
msgid "sum_ar"
msgstr ""

View File

@@ -0,0 +1,4 @@
from . import purchase_order
from . import company
from . import res_config_settings
from . import account_move_line

View File

@@ -0,0 +1,90 @@
# Copyright 2019-21 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from datetime import datetime
from odoo import _, models
from odoo.exceptions import ValidationError
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
def _get_writeoff_amounts(self):
precision = self.env["decimal.precision"].precision_get("Account")
writeoff_amount = round(
sum(line["amount_residual"] for line in self), precision
)
writeoff_amount_curr = round(
sum(line["amount_residual_currency"] for line in self), precision
)
if writeoff_amount_curr and not writeoff_amount:
# Data inconsistency, do not create the write-off
return (0.0, 0.0, True)
first_currency = self[0]["currency_id"]
if all([line["currency_id"] == first_currency for line in self]):
same_curr = True
else:
same_curr = False
return (
writeoff_amount,
writeoff_amount_curr,
same_curr,
)
def _create_writeoff(self, writeoff_vals):
(
amount_writeoff,
amount_writeoff_curr,
same_curr,
) = self._get_writeoff_amounts()
if not amount_writeoff:
return self.env["account.move.line"]
partners = self.mapped("partner_id")
move_date = writeoff_vals.get("date", datetime.now())
write_off_vals = {
"name": _("Automatic writeoff"),
"amount_currency": same_curr and amount_writeoff_curr or amount_writeoff,
"debit": amount_writeoff > 0.0 and amount_writeoff or 0.0,
"credit": amount_writeoff < 0.0 and -amount_writeoff or 0.0,
"partner_id": len(partners) == 1 and partners.id or False,
"account_id": writeoff_vals["account_id"],
"date": move_date,
"journal_id": writeoff_vals["journal_id"],
"currency_id": writeoff_vals.get("currency_id", False),
"product_id": writeoff_vals["product_id"],
"purchase_order_id": writeoff_vals["purchase_order_id"],
"purchase_line_id": writeoff_vals["purchase_line_id"],
}
counterpart_account = self.mapped("account_id")
if len(counterpart_account) != 1:
raise ValidationError(_("Cannot write-off more than one account"))
counter_part = write_off_vals.copy()
counter_part["debit"] = write_off_vals["credit"]
counter_part["credit"] = write_off_vals["debit"]
counter_part["amount_currency"] = -write_off_vals["amount_currency"]
counter_part["account_id"] = (counterpart_account.id,)
move = self.env["account.move"].create(
{
"date": move_date,
"journal_id": writeoff_vals["journal_id"],
"currency_id": writeoff_vals.get("currency_id", False),
"line_ids": [(0, 0, write_off_vals), (0, 0, counter_part)],
}
)
if writeoff_vals.get("purchase_order_id", False):
# done this way because purchase_order_id is a related field and will
# not being assign on create. Cannot assign purchase_line_id because
# it is a generic write-off for the whole PO
self.env.cr.execute(
"""UPDATE account_move_line SET purchase_order_id = %s
WHERE id in %s
""",
(writeoff_vals["purchase_order_id"], tuple(move.line_ids.ids)),
)
move.action_post()
return move.line_ids.filtered(
lambda line: line.account_id.id == counterpart_account.id
)

View File

@@ -0,0 +1,27 @@
# Copyright 2019-21 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
purchase_reconcile_account_id = fields.Many2one(
"account.account",
domain=lambda self: [("deprecated", "=", False)],
string="Write-Off Account On Purchases",
ondelete="restrict",
copy=False,
help="Write-off account to reconcile Unreconciled Purchase Orders",
)
purchase_reconcile_journal_id = fields.Many2one(
"account.journal", string="WriteOff Journal for Purchases"
)
purchase_lock_auto_reconcile = fields.Boolean()
purchase_reconcile_tolerance = fields.Float(
string="Purchase Reconcile Tolerance (%)",
default=0.0,
help="Percentage of tolerance of residual amount vs total amount of the "
"Purchase Order. Leave zero to accept all discrepancies",
)

View File

@@ -0,0 +1,278 @@
# Copyright 2019-21 ForgeFlow S.L..
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import _, api, exceptions, fields, models
from odoo.osv import expression
from odoo.tools import float_is_zero
class PurchaseOrder(models.Model):
_inherit = "purchase.order"
unreconciled = fields.Boolean(
compute="_compute_unreconciled",
search="_search_unreconciled",
help="Indicates that a Purchase Order has related Journal items not "
"reconciled.\nNote that if it is false it can be either that "
"everything is reconciled or that the related accounts do not "
"allow reconciliation",
)
amount_unreconciled = fields.Float(compute="_compute_unreconciled")
def _get_account_domain(self):
included_accounts = (
(
self.env["product.category"]
.with_company(self.company_id.id)
.search([("property_valuation", "=", "real_time")])
)
.mapped("property_stock_account_input_categ_id")
.ids
)
return [("account_id", "in", included_accounts)]
@api.model
def _get_purchase_unreconciled_base_domain(self):
unreconciled_domain = [
("account_id.reconcile", "=", True),
("move_id.state", "=", "posted"),
("company_id", "in", self.env.companies.ids),
# same condition than Odoo Unreconciled filter
("amount_residual", "!=", 0.0),
("balance", "!=", 0.0),
]
return unreconciled_domain
def _compute_unreconciled(self):
acc_item = self.env["account.move.line"]
for rec in self:
domain = rec.with_company(
rec.company_id
)._get_purchase_unreconciled_base_domain()
domain_account = rec._get_account_domain()
unreconciled_domain = expression.AND([domain, domain_account])
unreconciled_domain = expression.AND(
[unreconciled_domain, [("purchase_order_id", "=", rec.id)]]
)
unreconciled_items = acc_item.search(unreconciled_domain)
rec.unreconciled = len(unreconciled_items) > 0
rec.amount_unreconciled = sum(unreconciled_items.mapped("amount_residual"))
def _search_unreconciled(self, operator, value):
if operator not in ("=", "!=") or not isinstance(value, bool):
raise ValueError(_("Unsupported search operator"))
acc_item = self.env["account.move.line"]
domain = self._get_purchase_unreconciled_base_domain()
domain = expression.AND([domain, [("purchase_order_id", "!=", False)]])
domain_account = self._get_account_domain()
domain = expression.AND([domain_account, domain])
acc_items = acc_item.search(domain)
unreconciled_pos_ids = acc_items.mapped("purchase_order_id").ids
if value:
return [("id", "in", unreconciled_pos_ids)]
else:
return [("id", "not in", unreconciled_pos_ids)]
def action_view_unreconciled(self):
self.ensure_one()
acc_item = self.env["account.move.line"]
domain = self.with_company(
self.company_id.id
)._get_purchase_unreconciled_base_domain()
domain_account = self._get_account_domain()
unreconciled_domain = expression.AND([domain, domain_account])
unreconciled_domain = expression.AND(
[unreconciled_domain, [("purchase_order_id", "=", self.id)]]
)
unreconciled_domain.remove(("amount_residual", "!=", 0.0))
unreconciled_domain.remove("&")
unreconciled_items = acc_item.search(unreconciled_domain)
action = self.env.ref("account.action_account_moves_all")
action_dict = action.read()[0]
action_dict["domain"] = [("id", "in", unreconciled_items.ids)]
return action_dict
def action_reconcile(self):
if (
not self.company_id.purchase_reconcile_account_id
or not self.company_id.purchase_reconcile_journal_id
):
raise exceptions.ValidationError(
_(
"The write-off account and journal for purchases is missing. An "
"accountant must fill that information"
)
)
self.ensure_one()
res = {}
domain = self._get_purchase_unreconciled_base_domain()
domain_account = self._get_account_domain()
unreconciled_domain = expression.AND([domain, domain_account])
unreconciled_domain = expression.AND(
[domain, [("purchase_order_id", "=", self.id)]]
)
unreconciled_domain = expression.AND(
[unreconciled_domain, [("company_id", "=", self.company_id.id)]]
)
writeoff_to_reconcile = self.env["account.move.line"]
all_writeoffs = self.env["account.move.line"]
reconciling_groups = self.env["account.move.line"].read_group(
domain=unreconciled_domain,
fields=["account_id", "product_id", "oca_purchase_line_id"],
groupby=["account_id", "product_id", "oca_purchase_line_id"],
lazy=False,
)
unreconciled_items = self.env["account.move.line"].search(unreconciled_domain)
for group in reconciling_groups:
account_id = group["account_id"][0]
product_id = group["product_id"][0] if group["product_id"] else False
purchase_line_id = (
group["oca_purchase_line_id"][0]
if group["oca_purchase_line_id"]
else False
)
unreconciled_items_group = unreconciled_items.filtered(
lambda line, account_id=account_id, product_id=product_id: (
line.account_id.id == account_id
and line.product_id.id == product_id
)
)
# Check which type of force reconciling we are doing:
# - Force reconciling amount_residual
# - Force reconciling amount_residual_currency
amount_residual_currency_reconcile = any(
unreconciled_items_group.filtered(
lambda item_group,
account_id=account_id: item_group.amount_residual_currency != 0.0
and item_group.account_id.id == account_id
)
)
if amount_residual_currency_reconcile:
residual_field = "amount_residual_currency"
else:
residual_field = "amount_residual"
if float_is_zero(
sum(unreconciled_items_group.mapped(residual_field)),
precision_rounding=self.company_id.currency_id.rounding,
):
moves_to_reconcile = unreconciled_items_group
else:
writeoff_vals = self._get_purchase_writeoff_vals(
unreconciled_items_group, purchase_line_id, product_id
)
writeoff_to_reconcile = unreconciled_items_group._create_writeoff(
writeoff_vals
)
all_writeoffs |= writeoff_to_reconcile
# add writeoff line to reconcile algorithm and finish the reconciliation
moves_to_reconcile = unreconciled_items_group | writeoff_to_reconcile
# Check if reconciliation is total or needs an exchange rate entry to be
# created
if moves_to_reconcile:
moves_to_reconcile.filtered(
lambda move: not move.reconciled
).reconcile()
reconciled_ids = unreconciled_items | all_writeoffs
res = {
"name": _("Reconciled journal items"),
"type": "ir.actions.act_window",
"view_type": "form",
"view_mode": "list,form",
"res_model": "account.move.line",
"domain": [("id", "in", reconciled_ids.ids)],
}
if self.env.context.get("bypass_unreconciled", False):
# When calling the method from the wizard, lock after reconciling
self.button_done()
return res
def _get_purchase_writeoff_vals(self, amls, purchase_line_id, product_id):
writeoff_date = self.env.context.get("writeoff_date", False)
aml_date = max(amls.mapped("move_id.date"))
res = {
"account_id": self.company_id.purchase_reconcile_account_id.id,
"journal_id": self.company_id.purchase_reconcile_journal_id.id,
"purchase_order_id": self.id,
"purchase_line_id": purchase_line_id or False,
"product_id": product_id,
"currency_id": self.currency_id.id or self.env.company.currency_id.id,
"date": aml_date,
}
# hook for custom date:
if writeoff_date:
res.update({"date": writeoff_date})
return res
def reconcile_criteria(self):
"""Gets the criteria where POs are locked or not, by default uses the company
configuration"""
self.ensure_one()
return self.unreconciled and self.company_id.purchase_lock_auto_reconcile
def button_done(self):
for rec in self:
criteria = rec.reconcile_criteria()
if criteria:
if rec.unreconciled:
exception_msg = rec.unreconciled_exception_msg()
if exception_msg:
res = rec.purchase_unreconciled_exception(exception_msg)
return res
else:
rec.action_reconcile()
return super(PurchaseOrder, rec).button_done()
else:
return super(PurchaseOrder, rec).button_done()
else:
return super(PurchaseOrder, rec).button_done()
def purchase_unreconciled_exception(self, exception_msg=None):
"""This mean to be run when the SO cannot be reconciled because it is over
tolerance"""
self.ensure_one()
if exception_msg:
return (
self.env["purchase.unreconciled.exceeded.wiz"]
.create(
{
"exception_msg": exception_msg,
"purchase_id": self.id,
"origin_reference": "{},{}".format("purchase.order", self.id),
"continue_method": "action_reconcile",
}
)
.action_show()
)
def unreconciled_exception_msg(self):
self.ensure_one()
exception_msg = ""
amount_total = self.amount_total
if self.currency_id and self.company_id.currency_id != self.currency_id:
amount_total = self.currency_id._convert(
amount_total,
self.company_id.currency_id,
self.company_id,
fields.Date.today(),
)
if (
self.company_id.purchase_reconcile_tolerance
and amount_total
and abs(self.amount_unreconciled / amount_total)
>= self.company_id.purchase_reconcile_tolerance / 100.0
):
params = {
"amount_unreconciled": self.amount_unreconciled,
"amount_allowed": self.amount_total
* self.company_id.purchase_reconcile_tolerance
/ 100.0,
}
exception_msg = (
_(
"Finance Warning: \nUnreconciled amount is too high. Total "
"unreconciled amount: %(amount_unreconciled)s Maximum unreconciled"
" amount accepted: %(amount_allowed)s "
)
% params
)
return exception_msg

View File

@@ -0,0 +1,21 @@
# Copyright 2019-21 ForgeFlow S.L..
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from odoo import fields, models
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
purchase_reconcile_account_id = fields.Many2one(
related="company_id.purchase_reconcile_account_id", readonly=False
)
purchase_reconcile_journal_id = fields.Many2one(
related="company_id.purchase_reconcile_journal_id", readonly=False
)
purchase_lock_auto_reconcile = fields.Boolean(
related="company_id.purchase_lock_auto_reconcile", readonly=False
)
purchase_reconcile_tolerance = fields.Float(
related="company_id.purchase_reconcile_tolerance", readonly=False
)

View File

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

View File

@@ -0,0 +1,4 @@
- ForgeFlow S.L. \<<contact@forgeflow.com>\>
- Lois Rilo \<<lois.rilo@forgeflow.com>\>
- Aaron Henriquez \<<ahenriquez@forgeflow.com>\>
- Miquel Raich \<<miquel.raich@forgeflow.com>\>

View File

@@ -0,0 +1,6 @@
This module adds a new fields "Unreconciled" on Purchase Orders, that
allows to find PO's with unreconciled journal items related.
This module allows to reconcile those PO in a single click. In
accounting settings users will be able to set up a specific account for
write-off.

View File

@@ -0,0 +1,6 @@
Accountants will be able to find a filters in Purchase Orders that shows
outstanding balances in interim accounts. Also there is a link in the PO
to those outstanding journal items.
Locking the PO will automatically reconcile the outstanding balance for
the stock iterim accounts.

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_purchase_unreconciled_exceeded_wiz,access_purchase_unreconciled_exceeded_wiz,model_purchase_unreconciled_exceeded_wiz,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_purchase_unreconciled_exceeded_wiz access_purchase_unreconciled_exceeded_wiz model_purchase_unreconciled_exceeded_wiz base.group_user 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,449 @@
<!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>Purchase Unreconciled</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="purchase-unreconciled">
<h1 class="title">Purchase Unreconciled</h1>
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! This file is generated by oca-gen-addon-readme !!
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! source digest: sha256:3c45ee3a44f051bf83a42cc8c8690589a1c5f56b1879aaf2084deb8e7aaaaa2f
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external image-reference" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.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-financial-tools/tree/18.0/purchase_unreconciled"><img alt="OCA/account-financial-tools" src="https://img.shields.io/badge/github-OCA%2Faccount--financial--tools-lightgray.png?logo=github" /></a> <a class="reference external image-reference" href="https://translation.odoo-community.org/projects/account-financial-tools-18-0/account-financial-tools-18-0-purchase_unreconciled"><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-financial-tools&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 module adds a new fields “Unreconciled” on Purchase Orders, that
allows to find POs with unreconciled journal items related.</p>
<p>This module allows to reconcile those PO in a single click. In
accounting settings users will be able to set up a specific account for
write-off.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
Only for development or testing purpose, do not use in production.
<a class="reference external" href="https://odoo-community.org/page/development-status">More details on development status</a></p>
</div>
<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="#maintainers" id="toc-entry-6">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#toc-entry-1">Usage</a></h1>
<p>Accountants will be able to find a filters in Purchase Orders that shows
outstanding balances in interim accounts. Also there is a link in the PO
to those outstanding journal items.</p>
<p>Locking the PO will automatically reconcile the outstanding balance for
the stock iterim accounts.</p>
</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-financial-tools/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-financial-tools/issues/new?body=module:%20purchase_unreconciled%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>ForgeFlow S.L.</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#toc-entry-5">Contributors</a></h2>
<ul class="simple">
<li>ForgeFlow S.L. &lt;<a class="reference external" href="mailto:contact&#64;forgeflow.com">contact&#64;forgeflow.com</a>&gt;<ul>
<li>Lois Rilo &lt;<a class="reference external" href="mailto:lois.rilo&#64;forgeflow.com">lois.rilo&#64;forgeflow.com</a>&gt;</li>
<li>Aaron Henriquez &lt;<a class="reference external" href="mailto:ahenriquez&#64;forgeflow.com">ahenriquez&#64;forgeflow.com</a>&gt;</li>
<li>Miquel Raich &lt;<a class="reference external" href="mailto:miquel.raich&#64;forgeflow.com">miquel.raich&#64;forgeflow.com</a>&gt;</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#toc-entry-6">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>Current <a class="reference external" href="https://odoo-community.org/page/maintainer-role">maintainer</a>:</p>
<p><a class="reference external image-reference" href="https://github.com/AaronHForgeFlow"><img alt="AaronHForgeFlow" src="https://github.com/AaronHForgeFlow.png?size=40px" /></a></p>
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/account-financial-tools/tree/18.0/purchase_unreconciled">OCA/account-financial-tools</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_purchase_unreconciled

View File

@@ -0,0 +1,429 @@
# Copyright 2019-21 ForgeFlow S.L.
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from datetime import datetime
from odoo import exceptions, fields
from odoo.tests import Form, SingleTransactionCase
class TestPurchaseUnreconciled(SingleTransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.po_obj = cls.env["purchase.order"]
cls.product_obj = cls.env["product.product"]
cls.category_obj = cls.env["product.category"]
cls.partner_obj = cls.env["res.partner"]
cls.acc_obj = cls.env["account.account"]
cls.account_move_obj = cls.env["account.move"]
cls.company = cls.env.ref("base.main_company")
cls.company.anglo_saxon_accounting = True
expense_type = "expense"
equity_type = "equity"
asset_type = "asset_current"
# Create partner:
cls.partner = cls.partner_obj.create({"name": "Test Vendor"})
# Create product that uses a reconcilable stock input account.
cls.account = cls.acc_obj.create(
{
"name": "Test stock input account",
"code": 9999,
"account_type": asset_type,
"reconcile": True,
"company_ids": [(6, 0, [cls.company.id])],
}
)
cls.writeoff_acc = cls.acc_obj.create(
{
"name": "Write-offf account",
"code": 8888,
"account_type": expense_type,
"reconcile": True,
"company_ids": [(6, 0, [cls.company.id])],
}
)
cls.stock_journal = cls.env["account.journal"].create(
{"name": "Stock Journal", "code": "STJTEST", "type": "general"}
)
# Create account for Goods Received Not Invoiced
name = "Goods Received Not Invoiced"
code = "grni"
acc_type = equity_type
cls.account_grni = cls._create_account(
acc_type, name, code, cls.company, reconcile=True
)
# Create account for Cost of Goods Sold
name = "Cost of Goods Sold"
code = "cogs"
acc_type = expense_type
cls.account_cogs = cls._create_account(acc_type, name, code, cls.company)
# Create account for Goods Delivered Not Invoiced
name = "Goods Delivered Not Invoiced"
code = "gdni"
acc_type = expense_type
cls.account_gdni = cls._create_account(
acc_type, name, code, cls.company, reconcile=True
)
# Create account for Inventory
name = "Inventory"
code = "inventory"
acc_type = asset_type
cls.account_inventory = cls._create_account(acc_type, name, code, cls.company)
cls.product_categ = cls.category_obj.create(
{
"name": "Test Category",
"property_cost_method": "standard",
"property_stock_valuation_account_id": cls.account_inventory.id,
"property_stock_account_input_categ_id": cls.account_grni.id,
"property_account_expense_categ_id": cls.account_cogs.id,
"property_stock_account_output_categ_id": cls.account_gdni.id,
"property_valuation": "real_time",
"property_stock_journal": cls.stock_journal.id,
}
)
cls.product_to_reconcile = cls.product_obj.create(
{
"name": "Purchased Product (To reconcile)",
"type": "consu",
"is_storable": True,
"standard_price": 100.0,
"categ_id": cls.product_categ.id,
}
)
cls.product_to_reconcile2 = cls.product_obj.create(
{
"name": "Purchased Product 2 (To reconcile)",
"type": "consu",
"is_storable": True,
"standard_price": 100.0,
"categ_id": cls.product_categ.id,
}
)
# Create PO's:
cls.po = cls.po_obj.create(
{
"partner_id": cls.partner.id,
"order_line": [
(
0,
0,
{
"product_id": cls.product_to_reconcile.id,
"name": cls.product_to_reconcile.name,
"product_qty": 5.0,
"price_unit": 100.0,
"product_uom": cls.product_to_reconcile.uom_id.id,
"date_planned": fields.Datetime.now(),
},
)
],
}
)
cls.po_2 = cls.po_obj.create(
{
"partner_id": cls.partner.id,
"order_line": [
(
0,
0,
{
"product_id": cls.product_to_reconcile.id,
"name": cls.product_to_reconcile.name,
"product_qty": 5.0,
"price_unit": 100.0,
"product_uom": cls.product_to_reconcile.uom_id.id,
"date_planned": fields.Datetime.now(),
},
)
],
}
)
# company settings for automated valuation
cls.company.purchase_lock_auto_reconcile = True
cls.company.purchase_reconcile_account_id = cls.writeoff_acc
cls.company.purchase_reconcile_journal_id = cls.stock_journal
@classmethod
def _create_account(cls, acc_type, name, code, company, reconcile=False):
"""Create an account."""
account = cls.acc_obj.create(
{
"name": name,
"code": code,
"account_type": acc_type,
"company_ids": [(6, 0, [company.id])],
"reconcile": reconcile,
}
)
return account
def _create_delivery(
self,
product,
qty,
):
return self.env["stock.picking"].create(
{
"name": self.product_to_reconcile.name,
"partner_id": self.partner.id,
"picking_type_id": self.env.ref("stock.picking_type_out").id,
"location_id": self.env.ref("stock.stock_location_stock").id,
"location_dest_id": self.env.ref("stock.stock_location_customers").id,
"move_ids": [
(
0,
0,
{
"name": self.product_to_reconcile.name,
"product_id": self.product_to_reconcile.id,
"product_uom": self.product_to_reconcile.uom_id.id,
"product_uom_qty": qty,
"location_id": self.env.ref(
"stock.stock_location_stock"
).id,
"location_dest_id": self.env.ref(
"stock.stock_location_customers"
).id,
"procure_method": "make_to_stock",
},
)
],
}
)
def _do_picking(self, picking, date):
"""Do picking with only one move on the given date."""
picking.action_confirm()
picking.action_assign()
for move in picking.move_ids:
move.quantity = move.product_uom_qty
move.date = date
picking.button_validate()
def test_01_nothing_to_reconcile(self):
po = self.po
self.assertEqual(po.state, "draft")
po.button_confirm()
self._do_picking(po.picking_ids, fields.Datetime.now())
self.assertTrue(po.unreconciled)
# Invoice created and validated:
po.action_create_invoice()
invoice_ids = po.invoice_ids.filtered(lambda i: i.move_type == "in_invoice")
invoice_ids.invoice_date = datetime.now()
invoice_ids.action_post()
self.assertEqual(po.state, "purchase")
# odoo does it automatically
po._compute_unreconciled()
self.assertFalse(po.unreconciled)
def test_03_search_unreconciled(self):
"""Test searching unreconciled PO's."""
po = self.po_2
po.button_confirm()
self._do_picking(po.picking_ids, fields.Datetime.now())
res = self.po_obj.search([("unreconciled", "=", True)])
po._compute_unreconciled()
self.assertIn(po, res)
self.assertNotIn(self.po, res)
# Test value error:
with self.assertRaises(ValueError):
self.po_obj.search([("unreconciled", "=", "true")])
def test_04_action_reconcile(self):
"""Test reconcile."""
# Invoice created and validated:
po = self.po_2
self.assertTrue(po.unreconciled)
po.action_create_invoice()
invoice_form = Form(
po.invoice_ids.filtered(lambda i: i.move_type == "in_invoice")[0]
)
# v14 reconciles automatically so here we force discrepancy
# with invoice_form.edit(0) as inv_form:
invoice_form.invoice_date = datetime.now()
with invoice_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 99
invoice = invoice_form.save()
invoice.action_post()
self.assertTrue(po.unreconciled)
po.action_reconcile()
po._compute_unreconciled()
self.assertFalse(po.unreconciled)
def test_05_button_done_reconcile(self):
"""Test auto reconcile when locking po."""
po = self.po_2.copy()
po.company_id.purchase_reconcile_account_id = self.writeoff_acc
po.button_confirm()
self._do_picking(po.picking_ids, fields.Datetime.now())
# Invoice created and validated:
# Odoo reconciles automatically so here we force discrepancy
po.action_create_invoice()
invoice_form = Form(
po.invoice_ids.filtered(lambda i: i.move_type == "in_invoice")[0]
)
invoice_form.invoice_date = datetime.now()
with invoice_form.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 99
invoice = invoice_form.save()
invoice.action_post()
self.assertTrue(po.unreconciled)
# check error if raised if not write-off account
with self.assertRaises(exceptions.ValidationError):
self.company.purchase_reconcile_account_id = False
po.button_done()
# restore the write off account
self.company.purchase_reconcile_account_id = self.writeoff_acc
po.button_done()
po._compute_unreconciled()
self.assertFalse(po.unreconciled)
def test_06_dropship_not_reconcile_sale_journal_items(self):
"""
Create a fake dropship and lock the PO before receiving the customer
invoice. The PO should not close the stock interim output account
"""
# to create the fake dropship we create a delivery and attach the
# journals to the purchase order craeted later
self.env["stock.quant"].create(
{
"product_id": self.product_to_reconcile.id,
"location_id": self.env.ref("stock.stock_location_stock").id,
"quantity": 1.0,
}
)
delivery = self._create_delivery(self.product_to_reconcile, 1)
self._do_picking(delivery, fields.Datetime.now())
# We create the PO now and receive it
po = self.po_2.copy()
po.button_confirm()
self._do_picking(po.picking_ids, fields.Datetime.now())
self.assertTrue(po.unreconciled)
# as long stock_dropshipping is not dependency, I force the PO to be in
# the journal items of the delivery
delivery_name = delivery.name
delivery_ji = self.env["account.move.line"].search(
[("move_id.ref", "=", delivery_name)]
)
delivery_ji.write(
{"purchase_line_id": po.order_line[0], "purchase_order_id": po.id}
)
# then I lock the po to force reconciliation
po.button_done()
po._compute_unreconciled()
self.assertFalse(po.unreconciled)
# the PO is reconciled and the stock interim deliverd account is not
# reconciled yet
for jii in delivery_ji:
self.assertFalse(jii.reconciled)
def test_07_multicompany(self):
"""
Force the company in the vendor bill to be wrong. The system will
write-off the journals for the shipment because those are the only ones
with the correct company
"""
po = self.po.copy()
po.button_confirm()
self._do_picking(po.picking_ids, fields.Datetime.now())
# Invoice created and validated:
f = Form(self.account_move_obj.with_context(default_move_type="in_invoice"))
f.partner_id = po.partner_id
f.invoice_date = fields.Date().today()
f.purchase_vendor_bill_id = self.env["purchase.bill.union"].browse(-po.id)
invoice = f.save()
chicago_journal = self.env["account.journal"].create(
{
"name": "chicago",
"code": "ref",
"type": "purchase",
"company_id": self.ref("stock.res_company_1"),
}
)
invoice.write(
{
"name": "/",
}
)
invoice.write(
{
"company_id": self.ref("stock.res_company_1"),
"journal_id": chicago_journal.id,
}
)
invoice.action_post()
self.assertEqual(po.state, "purchase")
# The bill is wrong so this is unreconciled
self.assertTrue(po.unreconciled)
po.button_done()
po._compute_unreconciled()
self.assertFalse(po.unreconciled)
# we check all the journals for the po have the same company
ji = self.env["account.move.line"].search(
[("purchase_order_id", "=", po.id), ("move_id", "!=", invoice.id)]
)
self.assertEqual(po.company_id, ji.mapped("company_id"))
def test_08_reconcile_by_product(self):
"""
Create a write-off by product
"""
po = self.po.copy()
po.write(
{
"order_line": [
(
0,
0,
{
"product_id": self.product_to_reconcile2.id,
"name": self.product_to_reconcile2.name,
"product_qty": 5.0,
"price_unit": 100.0,
"product_uom": self.product_to_reconcile.uom_id.id,
"date_planned": fields.Datetime.now(),
},
)
],
}
)
po.button_confirm()
self._do_picking(po.picking_ids, fields.Datetime.now())
# Invoice created and validated:
f = Form(self.account_move_obj.with_context(default_move_type="in_invoice"))
f.partner_id = po.partner_id
f.invoice_date = fields.Date().today()
f.purchase_vendor_bill_id = self.env["purchase.bill.union"].browse(-po.id)
invoice = f.save()
# force discrepancies
with f.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 99
with f.invoice_line_ids.edit(0) as line_form:
line_form.price_unit = 99
invoice = f.save()
invoice._post()
# The bill is different price so this is unreconciled
po._compute_unreconciled()
self.assertTrue(po.unreconciled)
po.button_done()
po._compute_unreconciled()
self.assertFalse(po.unreconciled)
# we check all the journals are balanced by product
ji_p1 = self.env["account.move.line"].search(
[
("purchase_order_id", "=", po.id),
("product_id", "=", self.product_to_reconcile.id),
("account_id", "=", self.account_grni.id),
]
)
ji_p2 = self.env["account.move.line"].search(
[
("purchase_order_id", "=", po.id),
("product_id", "=", self.product_to_reconcile2.id),
("account_id", "=", self.account_grni.id),
]
)
self.assertEqual(sum(ji_p1.mapped("balance")), 0.0)
self.assertEqual(sum(ji_p2.mapped("balance")), 0.0)

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="purchase_order_form" model="ir.ui.view">
<field name="name">purchase.order.form - purchase_unreconciled</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form" />
<field name="arch" type="xml">
<field name="invoice_status" position="after">
<field name="unreconciled" />
</field>
<div name="button_box" position="inside">
<button
type="object"
string="Unreconciled Journal Items"
name="action_view_unreconciled"
groups="account.group_account_manager"
class="oe_stat_button"
icon="fa-gears"
invisible="not unreconciled"
>
</button>
<button
type="object"
string="Reconcile"
name="action_reconcile"
groups="account.group_account_manager"
class="oe_stat_button"
icon="fa-link"
invisible="not unreconciled or state != 'done'"
>
</button>
</div>
</field>
</record>
<record id="purchase_order_view_search" model="ir.ui.view">
<field name="name">Custom Purchase Unreconciled Search</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_view_search" />
<field name="arch" type="xml">
<filter name="order_date" position="after">
<filter
name="unreconciled"
string="Unreconciled"
domain="[('unreconciled','=', True)]"
groups="account.group_account_manager"
help="Purchase orders with unreconciled journal items."
/>
</filter>
</field>
</record>
<record id="unreconciled_amount_purchase_order_view_tree" model="ir.ui.view">
<field name="name">unreconciled.amount.purchase.order.tree</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_view_tree" />
<field name="arch" type="xml">
<field name="invoice_status" position="after">
<field name="amount_unreconciled" sum="sum_ar" optional="hide" />
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.purchase.unreconciled</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="account.res_config_settings_view_form" />
<field name="arch" type="xml">
<xpath expr="//block[@id='bank_cash']/.." position="after">
<block title="Purchase Reconciling" id="purchase_reconcile">
<setting
id="purchase_lock_auto_reconcile"
help="Reconcile PO upon locking"
company_dependent="1"
>
<field name="purchase_lock_auto_reconcile" />
</setting>
<setting id="purchase_reconcile_account_id">
<field name="purchase_reconcile_account_id" />
</setting>
<setting id="purchase_reconcile_journal_id">
<field name="purchase_reconcile_journal_id" />
</setting>
<setting id="purchase_reconcile_tolerance">
<field name="purchase_reconcile_tolerance" />
</setting>
</block>
</xpath>
</field>
</record>
</odoo>

View File

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

View File

@@ -0,0 +1,36 @@
from odoo import _, fields, models
class PurchaseUnreconciledExceededWiz(models.TransientModel):
_name = "purchase.unreconciled.exceeded.wiz"
_description = "Purchase Unreconciled Exceeded Wizard"
purchase_id = fields.Many2one(
comodel_name="purchase.order", readonly=True, string="Order Number"
)
exception_msg = fields.Text(readonly=True)
origin_reference = fields.Reference(
lambda self: [
(m.model, m.name) for m in self.env["ir.model"].sudo().search([])
],
string="Object",
)
continue_method = fields.Char()
def action_show(self):
self.ensure_one()
return {
"type": "ir.actions.act_window",
"name": _("Purchase unreconciled exceeded"),
"res_model": self._name,
"res_id": self.id,
"view_mode": "form",
"target": "new",
}
def button_continue(self):
self.ensure_one()
return getattr(
self.origin_reference.with_context(bypass_unreconciled=True),
self.continue_method,
)()

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="purchase_unreconciled_exceeded_wizard" model="ir.ui.view">
<field name="name">purchase unreconciled exceeded</field>
<field name="model">purchase.unreconciled.exceeded.wiz</field>
<field name="arch" type="xml">
<form string="Amount Unreconciled exceeded">
<p>The order has exceeded its amount unreconciled</p>
<field name="exception_msg" colspan="2" nolabel="1" />
<group>
<field name="purchase_id" />
</group>
<footer>
<button
string="Lock and Reconcile"
class="oe_highlight"
name="button_continue"
type="object"
groups="account.group_account_user"
/>
<button string="Cancel" class="oe_link" special="cancel" />
</footer>
</form>
</field>
</record>
</odoo>