diff --git a/partner_financial_risk/README.rst b/partner_financial_risk/README.rst new file mode 100644 index 000000000..16cbd383a --- /dev/null +++ b/partner_financial_risk/README.rst @@ -0,0 +1,74 @@ +.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 + +====================== +Partner Financial Risk +====================== + +Adds a new page in partner to manage its *Financial Risk*. + +If any limit is exceeded, you won't be able to confirm any of its invoices +unless you are authorized (Account Adviser group). + +Configuration +============= + +To configure this module, you need to: + +#. Go to *Invoicing/Accounting > Configuration > Settings > Invoicing & Payments* +#. In the *Financial Risk* section, fill *Unpaid Margin* for setting the number + of days to last after the due date to consider an invoice as unpaid. + +Usage +===== + +To use this module, you need to: + +#. Go to *Invoicing/Accounting > Sales > Customers*. +#. Select an existing customer or create a new one. +#. Open the *Financial Risk* tab. +#. Set limits and choose options to compute in credit limit. +#. Go to *Invoicing/Accounting > Sales > Customer invoices* and create new + customer invoices. +#. Test the restriction trying to create an invoice for the partner for an + amount higher of the limit you have set. + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/134/9.0 + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smashing it by providing a detailed and welcomed feedback. + + +Credits +======= + +Contributors +------------ + +* Carlos Dauden +* Pedro M. Baeza + + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +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. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/partner_financial_risk/__init__.py b/partner_financial_risk/__init__.py new file mode 100644 index 000000000..35e7c9600 --- /dev/null +++ b/partner_financial_risk/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from . import models +from . import wizard diff --git a/partner_financial_risk/__openerp__.py b/partner_financial_risk/__openerp__.py new file mode 100644 index 000000000..a6eb5161c --- /dev/null +++ b/partner_financial_risk/__openerp__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + 'name': 'Partner Financial Risk', + 'summary': 'Manage partner risk', + 'version': '9.0.1.0.0', + 'category': 'Sales Management', + 'license': 'AGPL-3', + 'author': 'Tecnativa, Odoo Community Association (OCA)', + 'website': 'https://www.tecnativa.com', + 'depends': ['account'], + 'data': [ + 'data/partner_financial_risk_data.xml', + 'views/res_config_view.xml', + 'views/res_partner_view.xml', + 'views/account_invoice_view.xml', + 'wizard/partner_risk_exceeded_view.xml', + ], + 'installable': True, +} diff --git a/partner_financial_risk/data/partner_financial_risk_data.xml b/partner_financial_risk/data/partner_financial_risk_data.xml new file mode 100644 index 000000000..709db5ce4 --- /dev/null +++ b/partner_financial_risk/data/partner_financial_risk_data.xml @@ -0,0 +1,15 @@ + + + + + Financial risk: Process due invoices + 1 + days + -1 + + + + + + \ No newline at end of file diff --git a/partner_financial_risk/i18n/es.po b/partner_financial_risk/i18n/es.po new file mode 100644 index 000000000..5f0e3e5e3 --- /dev/null +++ b/partner_financial_risk/i18n/es.po @@ -0,0 +1,321 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * partner_financial_risk +# +# Translators: +# Carlos Dauden , 2016 +msgid "" +msgstr "" +"Project-Id-Version: partner-contact (9.0)\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-09-18 22:47+0000\n" +"PO-Revision-Date: 2016-09-19 01:14+0100\n" +"Last-Translator: Carlos Incaser \n" +"Language-Team: Spanish (http://www.transifex.com/oca/OCA-partner-contact-9-0/" +"language/es/)\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.5.4\n" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.partner_risk_exceeded_wizard +msgid "Cancel" +msgstr "Cancelar" + +#. module: partner_financial_risk +#: model:ir.model,name:partner_financial_risk.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.partner_risk_exceeded_wizard +msgid "Continue" +msgstr "Continuar" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_continue_method +msgid "Continue method" +msgstr "Método continuo" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_create_date +msgid "Created on" +msgstr "Creado en" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_credit_policy +msgid "Credit policy" +msgstr "Póliza de crédito" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_partner_id +msgid "Customer" +msgstr "Cliente" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_account_config_settings_invoice_unpaid_margin +#: model:ir.model.fields,help:partner_financial_risk.field_res_company_invoice_unpaid_margin +msgid "Days after due date to set an invoice as unpaid" +msgstr "" +"Días después de la fecha de vencimiento para considerar una factura como " +"impagada" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_account_amount +msgid "Difference between accounting credit and rest of totals" +msgstr "Diferencia entre el saldo contable y el resto de totales" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_exception_msg +msgid "Exception msg" +msgstr "Se ha producido una anomalía" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.res_partner_view_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.view_account_config +msgid "Financial Risk" +msgstr "Riesgo financiero" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.view_account_config +msgid "Financial Risk div" +msgstr "Riesgo financiero div" + +#. module: partner_financial_risk +#: code:addons/partner_financial_risk/models/account_invoice.py:19 +#, python-format +msgid "Financial risk exceeded.\n" +msgstr "Riesgo financiero excedido.\n" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_account_amount_include +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_draft_include +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_open_include +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_unpaid_include +msgid "Full risk computation" +msgstr "Cómputo de riesgo total" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.res_partner_view_risk +msgid "General Limits" +msgstr "Límites generales" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_id +msgid "ID" +msgstr "ID (identificación)" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_draft_include +msgid "Include Draft Invoices" +msgstr "Incluir facturas borrador" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_open_include +msgid "Include Open Invoices" +msgstr "Incluir facturas abiertas" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_account_amount_include +msgid "Include Other Account Amount" +msgstr "Incluir otros saldos contables" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_unpaid_include +msgid "Include Unpaid Invoices" +msgstr "Incluir facturas impagadas" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.res_partner_view_risk +msgid "Info" +msgstr "Información" + +#. module: partner_financial_risk +#: model:ir.model,name:partner_financial_risk.model_account_invoice +msgid "Invoice" +msgstr "Factura" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_exception +msgid "It Indicate if partner risk exceeded" +msgstr "Indica si se ha excedido el riesgo" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz___last_update +msgid "Last Modified on" +msgstr "Última modificación en" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_write_uid +msgid "Last Updated by" +msgstr "Última actualización de" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_write_date +msgid "Last Updated on" +msgstr "Última actualización en" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_draft_limit +msgid "Limit In Draft Invoices" +msgstr "Límite en facturas borrador" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_open_limit +msgid "Limit In Open Invoices" +msgstr "Límite en facturas abiertas" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_unpaid_limit +msgid "Limit In Unpaid Invoices" +msgstr "Límite en facturas impagadas" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_account_amount_limit +msgid "Limit Other Account Amount" +msgstr "Límite en otros saldos contables" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_account_config_settings_invoice_unpaid_margin +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_company_invoice_unpaid_margin +msgid "Maturity Margin" +msgstr "Margen vencimiento" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_partner_risk_exceeded_wiz_origin_reference +msgid "Object" +msgstr "Objeto" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_account_amount +msgid "Other Account Amount" +msgstr "Otros saldos contables" + +#. module: partner_financial_risk +#: model:ir.model,name:partner_financial_risk.model_res_partner +msgid "Partner" +msgstr "Empresa" + +#. module: partner_financial_risk +#: code:addons/partner_financial_risk/wizard/parner_risk_exceeded.py:25 +#, python-format +msgid "Partner risk exceeded" +msgstr "Empresa con riesgo excedido" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_unpaid +msgid "" +"Residual amount of invoices in Open state and the date due is exceeded, " +"considering Unpaid Margin set in account settings" +msgstr "" +"Importe pendiente en facturas abiertas cuya fecha de vencimiento se ha " +"excedido, considerando el margen de días establecido en configuración de " +"contabilidad" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_open +msgid "" +"Residual amount of invoices in Open state and the date due is not exceeded, " +"considering Due Margin set in account settings" +msgstr "" +"Importe pendiente en facturas abiertas cuya fecha de vencimiento no se ha " +"excedido, considerando el margen de días establecido en configuración de " +"contabilidad" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_exception +msgid "Risk Exception" +msgstr "Excepción por riesgo" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_allow_edit +msgid "Risk allow edit" +msgstr "Permitir editar riesgo" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.partner_risk_exceeded_wizard +msgid "Risk exceeded" +msgstr "Riesgo excedido" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_account_amount_limit +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_draft_limit +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_open_limit +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_unpaid_limit +msgid "Set 0 if it is not locked" +msgstr "Establece 0 si no está bloqueado" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.res_partner_view_risk +msgid "Specific Limits" +msgstr "Límites específicos" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_total +msgid "Sum of total risk included" +msgstr "Suma de riesgo total incluido" + +#. module: partner_financial_risk +#: model:ir.ui.view,arch_db:partner_financial_risk.partner_risk_exceeded_wizard +msgid "The partner has exceeded his risk" +msgstr "La empresa ha excedido su riesgo" + +#. module: partner_financial_risk +#: code:addons/partner_financial_risk/models/account_invoice.py:28 +#, python-format +msgid "This invoice exceeds the financial risk.\n" +msgstr "Esta factura excede el riesgo financiero.\n" + +#. module: partner_financial_risk +#: code:addons/partner_financial_risk/models/account_invoice.py:23 +#, python-format +msgid "This invoice exceeds the open invoices risk.\n" +msgstr "Esta factura excede el riesgo de facturas abiertas.\n" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_draft +msgid "Total Draft Invoices" +msgstr "Total facturas borrador" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_open +msgid "Total Open Invoices" +msgstr "Total facturas borrador abiertas" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_total +msgid "Total Risk" +msgstr "Riesgo total" + +#. module: partner_financial_risk +#: model:ir.model.fields,field_description:partner_financial_risk.field_res_partner_risk_invoice_unpaid +msgid "Total Unpaid Invoices" +msgstr "Total facturas impagadas" + +#. module: partner_financial_risk +#: model:ir.model.fields,help:partner_financial_risk.field_res_partner_risk_invoice_draft +msgid "Total amount of invoices in Draft or Pro-forma state" +msgstr "Importe total de facturas borrador o pro-forma" + +#. module: partner_financial_risk +#: model:ir.model,name:partner_financial_risk.model_account_config_settings +msgid "account.config.settings" +msgstr "" + +#. module: partner_financial_risk +#: model:ir.model,name:partner_financial_risk.model_partner_risk_exceeded_wiz +msgid "partner.risk.exceeded.wiz" +msgstr "" diff --git a/partner_financial_risk/models/__init__.py b/partner_financial_risk/models/__init__.py new file mode 100644 index 000000000..c9c4795e4 --- /dev/null +++ b/partner_financial_risk/models/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from . import account_invoice +from . import res_company +from . import res_config +from . import res_partner diff --git a/partner_financial_risk/models/account_invoice.py b/partner_financial_risk/models/account_invoice.py new file mode 100644 index 000000000..a67fff0c2 --- /dev/null +++ b/partner_financial_risk/models/account_invoice.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import api, models, _ + + +class AccountInvoice(models.Model): + _inherit = 'account.invoice' + + @api.multi + def invoice_open(self): + if self.env.context.get('bypass_risk', False): + return self.signal_workflow('invoice_open') + for invoice in self: + partner = invoice.partner_id + exception_msg = "" + if partner.risk_exception: + exception_msg = _("Financial risk exceeded.\n") + elif partner.risk_invoice_open_limit and ( + (partner.risk_invoice_open + invoice.amount_total) > + partner.risk_invoice_open_limit): + exception_msg = _( + "This invoice exceeds the open invoices risk.\n") + elif partner.risk_invoice_open_include and ( + (partner.risk_total + invoice.amount_total) > + partner.credit_limit): + exception_msg = _( + "This invoice exceeds the financial risk.\n") + if exception_msg: + return self.env['partner.risk.exceeded.wiz'].create({ + 'exception_msg': exception_msg, + 'partner_id': partner.id, + 'origin_reference': + '%s,%s' % (self._model, invoice.id), + 'continue_method': 'invoice_open', + }).action_show() + return self.signal_workflow('invoice_open') diff --git a/partner_financial_risk/models/res_company.py b/partner_financial_risk/models/res_company.py new file mode 100644 index 000000000..667764ef3 --- /dev/null +++ b/partner_financial_risk/models/res_company.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import fields, models + + +class ResCompany(models.Model): + _inherit = 'res.company' + + invoice_unpaid_margin = fields.Integer( + string="Maturity Margin", + help="Days after due date to set an invoice as unpaid") diff --git a/partner_financial_risk/models/res_config.py b/partner_financial_risk/models/res_config.py new file mode 100644 index 000000000..213eca0a1 --- /dev/null +++ b/partner_financial_risk/models/res_config.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import fields, models + + +class AccountConfigSettings(models.TransientModel): + _inherit = 'account.config.settings' + + invoice_unpaid_margin = fields.Integer( + related='company_id.invoice_unpaid_margin') diff --git a/partner_financial_risk/models/res_partner.py b/partner_financial_risk/models/res_partner.py new file mode 100644 index 000000000..42f43b1a4 --- /dev/null +++ b/partner_financial_risk/models/res_partner.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from datetime import datetime +from dateutil.relativedelta import relativedelta +from openerp import api, fields, models + + +class ResPartner(models.Model): + _inherit = 'res.partner' + + risk_invoice_draft_include = fields.Boolean( + string='Include Draft Invoices', help='Full risk computation') + risk_invoice_draft_limit = fields.Monetary( + string='Limit In Draft Invoices', help='Set 0 if it is not locked') + risk_invoice_draft = fields.Monetary( + compute='_compute_risk_invoice', store=True, + string='Total Draft Invoices', + help='Total amount of invoices in Draft or Pro-forma state') + risk_invoice_open_include = fields.Boolean( + string='Include Open Invoices', help='Full risk computation') + risk_invoice_open_limit = fields.Monetary( + string='Limit In Open Invoices', help='Set 0 if it is not locked') + risk_invoice_open = fields.Monetary( + compute='_compute_risk_invoice', store=True, + string='Total Open Invoices', + help='Residual amount of invoices in Open state and the date due is ' + 'not exceeded, considering Due Margin set in account ' + 'settings') + risk_invoice_unpaid_include = fields.Boolean( + string='Include Unpaid Invoices', help='Full risk computation') + risk_invoice_unpaid_limit = fields.Monetary( + string='Limit In Unpaid Invoices', help='Set 0 if it is not locked') + risk_invoice_unpaid = fields.Monetary( + compute='_compute_risk_invoice', store=True, + string='Total Unpaid Invoices', + help='Residual amount of invoices in Open state and the date due is ' + 'exceeded, considering Unpaid Margin set in account settings') + + risk_account_amount_include = fields.Boolean( + string='Include Other Account Amount', help='Full risk computation') + risk_account_amount_limit = fields.Monetary( + string='Limit Other Account Amount', help='Set 0 if it is not locked') + risk_account_amount = fields.Monetary( + compute='_compute_risk_account_amount', + string='Other Account Amount', + help='Difference between accounting credit and rest of totals') + + risk_total = fields.Monetary( + compute='_compute_risk_exception', + string='Total Risk', help='Sum of total risk included') + risk_exception = fields.Boolean( + compute='_compute_risk_exception', + string='Risk Exception', + help='It Indicate if partner risk exceeded') + credit_policy = fields.Char() + risk_allow_edit = fields.Boolean(compute='_compute_risk_allow_edit') + + @api.multi + def _compute_risk_allow_edit(self): + is_editable = self.env.user.has_group( + 'base.group_sale_manager') or self.env.user.has_group( + 'account.group_account_manager') + for partner in self: + partner.risk_allow_edit = is_editable + + @api.multi + @api.depends('invoice_ids', 'invoice_ids.state', + 'invoice_ids.amount_total', 'invoice_ids.residual', + 'invoice_ids.company_id.invoice_unpaid_margin') + def _compute_risk_invoice(self): + max_date = self._max_risk_date_due() + for partner in self: + invoices_out = partner.invoice_ids.filtered( + lambda x: x.type == 'out_invoice') + invoices = invoices_out.filtered( + lambda x: x.state in ['draft', 'proforma', 'proforma2']) + partner.risk_invoice_draft = sum(invoices.mapped('amount_total')) + invoices = invoices_out.filtered( + lambda x: x.state == 'open' and x.date_due >= max_date) + partner.risk_invoice_open = sum(invoices.mapped('residual')) + invoices = invoices_out.filtered( + lambda x: x.state == 'open' and x.date_due < max_date) + partner.risk_invoice_unpaid = sum(invoices.mapped('residual')) + + @api.multi + @api.depends('credit', 'risk_invoice_open', 'risk_invoice_unpaid') + def _compute_risk_account_amount(self): + for partner in self: + partner.risk_account_amount = ( + partner.credit - partner.risk_invoice_open - + partner.risk_invoice_unpaid) + + @api.multi + @api.depends(lambda x: x._get_depends_compute_risk_exception()) + def _compute_risk_exception(self): + risk_field_list = self._risk_field_list() + for partner in self: + amount = 0.0 + for risk_field in risk_field_list: + field_value = getattr(partner, risk_field[0], 0.0) + max_value = getattr(partner, risk_field[1], 0.0) + if max_value and field_value > max_value: + partner.risk_exception = True + if getattr(partner, risk_field[2], False): + amount += field_value + partner.risk_total = amount + if amount > partner.credit_limit: + partner.risk_exception = True + + @api.model + def _max_risk_date_due(self): + return fields.Date.to_string(datetime.today().date() - relativedelta( + days=self.env.user.company_id.invoice_unpaid_margin)) + + @api.model + def _risk_field_list(self): + return [ + ('risk_invoice_draft', 'risk_invoice_draft_limit', + 'risk_invoice_draft_include'), + ('risk_invoice_open', 'risk_invoice_open_limit', + 'risk_invoice_open_include'), + ('risk_invoice_unpaid', 'risk_invoice_unpaid_limit', + 'risk_invoice_unpaid_include'), + ('risk_account_amount', 'risk_account_amount_limit', + 'risk_account_amount_include'), + ] + + @api.model + def _get_depends_compute_risk_exception(self): + # TODO: Improve code without performance loss + tuple_list = self._risk_field_list() + res = [x[0] for x in tuple_list] + res.extend([x[1] for x in tuple_list]) + res.extend([x[2] for x in tuple_list]) + res.append('credit_limit') + return res + + @api.model + def process_unpaid_invoices(self): + today = fields.Date.today() + ConfigParameter = self.env['ir.config_parameter'] + last_check = ConfigParameter.get_param( + 'partner_financial_risk.last_check', default='2016-01-01') + invoices = self.env['account.invoice'].search([ + ('date_due', '>=', last_check), ('date_due', '<', today)]) + invoices.mapped('partner_id')._compute_risk_invoice() + ConfigParameter.set_param('partner_financial_risk.last_check', today) + return True diff --git a/partner_financial_risk/tests/__init__.py b/partner_financial_risk/tests/__init__.py new file mode 100644 index 000000000..115520d1c --- /dev/null +++ b/partner_financial_risk/tests/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import test_partner_financial_risk diff --git a/partner_financial_risk/tests/test_partner_financial_risk.py b/partner_financial_risk/tests/test_partner_financial_risk.py new file mode 100644 index 000000000..f1bfe7130 --- /dev/null +++ b/partner_financial_risk/tests/test_partner_financial_risk.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp.tests.common import TransactionCase +from openerp import fields + + +class TestPartnerFinancialRisk(TransactionCase): + def setUp(self): + super(TestPartnerFinancialRisk, self).setUp() + self.env.user.groups_id |= self.env.ref('base.group_sale_manager') + self.partner = self.env['res.partner'].create({ + 'name': 'Partner test', + 'customer': True, + }) + self.journal = self.env['account.journal'].create({ + 'type': 'sale', + 'name': 'Test Sales', + 'code': 'TSALE', + }) + self.prod_account = self.env.ref('account.demo_coffee_machine_account') + self.inv_account = self.env.ref('account.demo_sale_of_land_account') + self.invoice = self.env['account.invoice'].create({ + 'journal_id': self.journal.id, + 'company_id': self.env.user.company_id.id, + 'currency_id': self.env.user.company_id.currency_id.id, + 'partner_id': self.partner.id, + 'invoice_line_ids': [(0, 0, { + 'account_id': self.prod_account.id, + 'name': 'Test line', + 'price_unit': 50, + 'quantity': 10, + })] + }) + + def test_invoices(self): + self.partner.risk_invoice_draft_include = True + self.assertAlmostEqual(self.partner.risk_invoice_draft, 500.0) + self.assertAlmostEqual(self.partner.risk_total, 500.0) + self.invoice.signal_workflow('invoice_open') + self.assertAlmostEqual(self.partner.risk_invoice_draft, 0.0) + self.assertFalse(self.invoice.date_due) + self.partner.risk_invoice_unpaid_include = True + self.assertAlmostEqual(self.partner.risk_total, 500.0) + self.partner.credit_limit = 100.0 + self.assertTrue(self.partner.risk_exception) + self.partner.credit_limit = 1000.0 + self.assertFalse(self.partner.risk_exception) + self.partner.risk_invoice_unpaid_limit = 499.0 + self.assertTrue(self.partner.risk_exception) + invoice2 = self.invoice.copy() + wiz_dic = invoice2.invoice_open() + wiz = self.env[wiz_dic['res_model']].browse(wiz_dic['res_id']) + self.assertEqual(wiz.exception_msg, "Financial risk exceeded.\n") + self.partner.risk_invoice_unpaid_limit = 0.0 + self.assertFalse(self.partner.risk_exception) + self.partner.risk_invoice_open_limit = 300.0 + invoice2.date_due = fields.Date.today() + wiz_dic = invoice2.invoice_open() + wiz = self.env[wiz_dic['res_model']].browse(wiz_dic['res_id']) + self.assertEqual(wiz.exception_msg, + "This invoice exceeds the open invoices risk.\n") + self.partner.risk_invoice_open_limit = 0.0 + self.partner.risk_invoice_draft_include = False + self.partner.risk_invoice_open_include = True + self.partner.credit_limit = 900.0 + wiz_dic = invoice2.invoice_open() + wiz = self.env[wiz_dic['res_model']].browse(wiz_dic['res_id']) + self.assertEqual(wiz.exception_msg, + "This invoice exceeds the financial risk.\n") + self.assertAlmostEqual(self.partner.risk_invoice_open, 0.0) + wiz.button_continue() + self.assertAlmostEqual(self.partner.risk_invoice_open, 500.0) + self.assertTrue(self.partner.risk_allow_edit) + self.partner.process_unpaid_invoices() + self.assertEqual(self.env['ir.config_parameter'].get_param( + 'partner_financial_risk.last_check'), + fields.Date.today()) diff --git a/partner_financial_risk/views/account_invoice_view.xml b/partner_financial_risk/views/account_invoice_view.xml new file mode 100644 index 000000000..273b507d0 --- /dev/null +++ b/partner_financial_risk/views/account_invoice_view.xml @@ -0,0 +1,15 @@ + + + + + account.invoice.partner.risk.form + account.invoice + + + + + + diff --git a/partner_financial_risk/views/res_config_view.xml b/partner_financial_risk/views/res_config_view.xml new file mode 100644 index 000000000..30158e6c7 --- /dev/null +++ b/partner_financial_risk/views/res_config_view.xml @@ -0,0 +1,21 @@ + + + + + + account settings + account.config.settings + + + + + + + + diff --git a/partner_financial_risk/views/res_partner_view.xml b/partner_financial_risk/views/res_partner_view.xml new file mode 100644 index 000000000..f3bf24d2b --- /dev/null +++ b/partner_financial_risk/views/res_partner_view.xml @@ -0,0 +1,58 @@ + + + + + res.partner.view.risk + res.partner + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/partner_financial_risk/wizard/__init__.py b/partner_financial_risk/wizard/__init__.py new file mode 100644 index 000000000..15fd34531 --- /dev/null +++ b/partner_financial_risk/wizard/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import parner_risk_exceeded diff --git a/partner_financial_risk/wizard/parner_risk_exceeded.py b/partner_financial_risk/wizard/parner_risk_exceeded.py new file mode 100644 index 000000000..e110569b5 --- /dev/null +++ b/partner_financial_risk/wizard/parner_risk_exceeded.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# © 2016 Carlos Dauden +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from openerp import api, fields, models, _ + + +class PartnerRiskExceededWiz(models.TransientModel): + _name = 'partner.risk.exceeded.wiz' + + partner_id = fields.Many2one( + comodel_name='res.partner', readonly=True, string='Customer') + exception_msg = fields.Text(readonly=True) + origin_reference = fields.Reference( + lambda self: [ + (m.model, m.name) for m in self.env['ir.model'].search([])], + string='Object') + continue_method = fields.Char() + + @api.multi + def action_show(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': _('Partner risk exceeded'), + 'res_model': self._name, + 'res_id': self.id, + 'view_type': 'form', + 'view_mode': 'form', + 'target': 'new', + } + + @api.multi + def button_continue(self): + self.ensure_one() + return getattr(self.origin_reference.with_context( + bypass_risk=True), self.continue_method)() diff --git a/partner_financial_risk/wizard/partner_risk_exceeded_view.xml b/partner_financial_risk/wizard/partner_risk_exceeded_view.xml new file mode 100644 index 000000000..5f1b19648 --- /dev/null +++ b/partner_financial_risk/wizard/partner_risk_exceeded_view.xml @@ -0,0 +1,30 @@ + + + + + Partner risk exceeded + partner.risk.exceeded.wiz + +
+

The partner has exceeded his risk

+ + + + + + +
+
+