diff --git a/contract_exception/__init__.py b/contract_exception/__init__.py new file mode 100644 index 0000000000..b046ff82fc --- /dev/null +++ b/contract_exception/__init__.py @@ -0,0 +1 @@ +from . import models, wizard diff --git a/contract_exception/__manifest__.py b/contract_exception/__manifest__.py new file mode 100644 index 0000000000..4a856a513e --- /dev/null +++ b/contract_exception/__manifest__.py @@ -0,0 +1,22 @@ +# Copyright 2024 Foodles (http://www.foodles.co/) +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html +{ + "name": "Contract Exception", + "version": "14.0.1.0.0", + "category": "Contract Management", + "author": "Odoo Community Association (OCA), Foodles", + "maintainers": [""], + "website": "https://github.com/OCA/contract", + "depends": [ + "base_exception", + "contract", + ], + "data": [ + "security/ir.model.access.csv", + "data/contract_exception_data.xml", + "views/contract_views.xml", + "wizard/contract_exception_confirm_view.xml", + ], + "license": "AGPL-3", + "installable": True, +} diff --git a/contract_exception/data/contract_exception_data.xml b/contract_exception/data/contract_exception_data.xml new file mode 100644 index 0000000000..1031ca2d41 --- /dev/null +++ b/contract_exception/data/contract_exception_data.xml @@ -0,0 +1,16 @@ + + + + + Test Contracts + + + 20 + minutes + -1 + + + code + model.test_all_contracts() + + diff --git a/contract_exception/models/__init__.py b/contract_exception/models/__init__.py new file mode 100644 index 0000000000..0c523d8527 --- /dev/null +++ b/contract_exception/models/__init__.py @@ -0,0 +1,5 @@ +from . import ( + contract, + contract_line, + exception_rule, +) diff --git a/contract_exception/models/contract.py b/contract_exception/models/contract.py new file mode 100644 index 0000000000..5a2b904c2f --- /dev/null +++ b/contract_exception/models/contract.py @@ -0,0 +1,48 @@ +from odoo import api, models + + +class Contract(models.Model): + _inherit = ["contract.contract", "base.exception"] + _name = "contract.contract" + + @api.model + def create(self, vals): + record = super().create(vals) + record._contract_check_exception(vals) + return record + + def write(self, vals): + result = super().write(vals) + self._contract_check_exception(vals) + return result + + @api.model + def _reverse_field(self): + return "contract_ids" + + def _fields_trigger_check_exception(self): + return ["ignore_exception", "contract_line_ids"] + + def detect_exceptions(self): + all_exceptions = super().detect_exceptions() + lines = self.mapped("contract_line_ids") + all_exceptions += lines.detect_exceptions() + return all_exceptions + + def _contract_check_exception(self, vals): + check_exceptions = any( + field in vals for field in self._fields_trigger_check_exception() + ) + if check_exceptions: + self.detect_exceptions() + + @api.model + def test_all_contracts(self): + contract_set = self.search([]) + contract_set.detect_exceptions() + return True + + @api.model + def _get_popup_action(self): + action = self.env.ref("contract_exception.action_contract_exception_confirm") + return action diff --git a/contract_exception/models/contract_line.py b/contract_exception/models/contract_line.py new file mode 100644 index 0000000000..917172ed0a --- /dev/null +++ b/contract_exception/models/contract_line.py @@ -0,0 +1,49 @@ +# Copyright 2021 ForgeFlow (http://www.forgeflow.com) +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html). + +import html + +from odoo import api, fields, models + + +class ContractLIne(models.Model): + _inherit = ["contract.line", "base.exception.method"] + _name = "contract.line" + + ignore_exception = fields.Boolean( + related="contract_id.ignore_exception", store=True, string="Ignore Exceptions" + ) + exception_ids = fields.Many2many( + "exception.rule", string="Exceptions", copy=False, readonly=True + ) + exceptions_summary = fields.Html( + readonly=True, compute="_compute_exceptions_summary" + ) + + @api.depends("exception_ids", "ignore_exception") + def _compute_exceptions_summary(self): + for rec in self: + if rec.exception_ids and not rec.ignore_exception: + rec.exceptions_summary = rec._get_exception_summary() + else: + rec.exceptions_summary = False + + def _get_exception_summary(self): + return "" % "".join( + [ + "
  • %s: %s
  • " + % tuple(map(html.escape, (e.name, e.description))) + for e in self.exception_ids + ] + ) + + def _get_main_records(self): + return self.mapped("contract_id") + + @api.model + def _reverse_field(self): + return "contract_ids" + + def _detect_exceptions(self, rule): + records = super()._detect_exceptions(rule) + return records.mapped("contract_id") diff --git a/contract_exception/models/exception_rule.py b/contract_exception/models/exception_rule.py new file mode 100644 index 0000000000..73f832634a --- /dev/null +++ b/contract_exception/models/exception_rule.py @@ -0,0 +1,19 @@ +from odoo import fields, models + + +class ExceptionRule(models.Model): + _inherit = "exception.rule" + + contract_ids = fields.Many2many( + comodel_name="contract.contract", string="Contracts" + ) + model = fields.Selection( + selection_add=[ + ("contract.contract", "Contract"), + ("contract.line", "Contract line"), + ], + ondelete={ + "contract.contract": "cascade", + "contract.line": "cascade", + }, + ) diff --git a/contract_exception/security/ir.model.access.csv b/contract_exception/security/ir.model.access.csv new file mode 100644 index 0000000000..e2598f5721 --- /dev/null +++ b/contract_exception/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_contract_exception_confirm,contract.exception.confirm,model_contract_exception_confirm,base_exception.group_exception_rule_manager,1,1,1,1 diff --git a/contract_exception/views/contract_views.xml b/contract_exception/views/contract_views.xml new file mode 100644 index 0000000000..826a6c4fe5 --- /dev/null +++ b/contract_exception/views/contract_views.xml @@ -0,0 +1,104 @@ + + + + + Contract Exception Rules + exception.rule + tree,form + + [('model', 'in', ['contract.contract', 'contract.line'])] + {'active_test': False, 'default_model' : 'contract.contract'} + + + + contract_exception.view_contract_form + contract.contract + + + + + + + + + + + + + + + + + + contract_exception.view_order_tree + contract.contract + + + + + + + + + + contract_exception.view_contracts_filter + contract.contract + + + + + + + + + diff --git a/contract_exception/wizard/__init__.py b/contract_exception/wizard/__init__.py new file mode 100644 index 0000000000..f12ac31969 --- /dev/null +++ b/contract_exception/wizard/__init__.py @@ -0,0 +1 @@ +from . import contract_exception_confirm, contract_manually_single_invoice diff --git a/contract_exception/wizard/contract_exception_confirm.py b/contract_exception/wizard/contract_exception_confirm.py new file mode 100644 index 0000000000..49658c9308 --- /dev/null +++ b/contract_exception/wizard/contract_exception_confirm.py @@ -0,0 +1,20 @@ +from odoo import fields, models + + +class ContractExceptionConfirm(models.TransientModel): + _name = "contract.exception.confirm" + _description = "Contract exception wizard" + _inherit = ["exception.rule.confirm"] + + related_model_id = fields.Many2one( + comodel_name="contract.contract", string="Contract" + ) + + date = fields.Date(required=True) + + def action_confirm(self): + self.ensure_one() + if self.ignore: + self.related_model_id.ignore_exception = True + self.related_model_id.generate_invoices_manually(self.date) + return super().action_confirm() diff --git a/contract_exception/wizard/contract_exception_confirm_view.xml b/contract_exception/wizard/contract_exception_confirm_view.xml new file mode 100644 index 0000000000..43c700b00d --- /dev/null +++ b/contract_exception/wizard/contract_exception_confirm_view.xml @@ -0,0 +1,43 @@ + + + + Contract Exceptions + contract.exception.confirm + +
    + + + + + + + + + + + + +
    +
    +
    +
    +
    + + Outstanding exceptions to manage + ir.actions.act_window + contract.exception.confirm + form + + new + +
    diff --git a/contract_exception/wizard/contract_manually_single_invoice.py b/contract_exception/wizard/contract_manually_single_invoice.py new file mode 100644 index 0000000000..8e1360dcb1 --- /dev/null +++ b/contract_exception/wizard/contract_manually_single_invoice.py @@ -0,0 +1,15 @@ +from odoo import models + + +class ContractManuallySingleInvoice(models.TransientModel): + _inherit = "contract.manually.single.invoice" + + def create_invoice(self): + if ( + self.contract_id.detect_exceptions() + and not self.contract_id.ignore_exception + ): + action = self.contract_id._popup_exceptions() + action.get("context").update({"default_date": self.date}) + return action + return self.contract_id.generate_invoices_manually(date=self.date) diff --git a/setup/contract_exception/odoo/addons/contract_exception b/setup/contract_exception/odoo/addons/contract_exception new file mode 120000 index 0000000000..bdc4a2808f --- /dev/null +++ b/setup/contract_exception/odoo/addons/contract_exception @@ -0,0 +1 @@ +../../../../contract_exception \ No newline at end of file diff --git a/setup/contract_exception/setup.py b/setup/contract_exception/setup.py new file mode 100644 index 0000000000..28c57bb640 --- /dev/null +++ b/setup/contract_exception/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +)