From 771077b2b09d229ec90f2eacbd8f671c1e4df43a Mon Sep 17 00:00:00 2001 From: mathis Date: Wed, 18 Jun 2025 08:51:16 -0400 Subject: [PATCH 1/5] Commercial invoice - Default currency to USD --- commercial_invoice/models/commercial_invoice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commercial_invoice/models/commercial_invoice.py b/commercial_invoice/models/commercial_invoice.py index f9316f3..365cf16 100644 --- a/commercial_invoice/models/commercial_invoice.py +++ b/commercial_invoice/models/commercial_invoice.py @@ -35,7 +35,7 @@ class CommercialInvoice(models.Model): "res.currency", string="Currency", required=True, - default=lambda self: self.env.company.currency_id, + default=lambda self: self.env.ref("base.USD") ) related_parties = fields.Boolean(string="Related Parties", default=False) From 2164e2be54267ce34beebf4357a5b4f70af571db Mon Sep 17 00:00:00 2001 From: Marc Durepos Date: Mon, 30 Jun 2025 11:25:53 -0400 Subject: [PATCH 2/5] delivery_carrier_partner_account: fix and refactoring - Fix: partners can no longer have default carrier accounts that are archived. - Refactor: remove write/create overrides to replace them with computed/stored fields. --- .../__manifest__.py | 2 +- .../models/delivery_carrier_account.py | 16 --- .../models/res_partner.py | 103 +++++++++++++++--- .../tests/test_res_partner.py | 71 ++++++++++-- 4 files changed, 146 insertions(+), 46 deletions(-) diff --git a/delivery_carrier_partner_account/__manifest__.py b/delivery_carrier_partner_account/__manifest__.py index 08c316f..a504df0 100644 --- a/delivery_carrier_partner_account/__manifest__.py +++ b/delivery_carrier_partner_account/__manifest__.py @@ -19,7 +19,7 @@ # { "name": "Carrier Accounts by Partner", - "version": "18.0.0.1.3", + "version": "18.0.0.1.4", "summary": "Add one or many carrier accounts per partner", "category": "Delivery", "author": "Bemade Inc.", diff --git a/delivery_carrier_partner_account/models/delivery_carrier_account.py b/delivery_carrier_partner_account/models/delivery_carrier_account.py index 136fe82..abcf440 100644 --- a/delivery_carrier_partner_account/models/delivery_carrier_account.py +++ b/delivery_carrier_partner_account/models/delivery_carrier_account.py @@ -38,19 +38,3 @@ class DeliveryCarrierAccount(models.Model): def _compute_display_name(self): for record in self: record.display_name = record.account_number - - def write(self, vals): - res = super().write(vals) - for partner in self.partner_id.filtered( - lambda partner: not partner.default_carrier_account_id - ): - partner.default_carrier_account_id = partner.carrier_account_ids[0] - return res - - @api.model_create_multi - def create(self, vals_list): - res = super().create(vals_list) - for rec in res: - if not rec.partner_id.default_carrier_account_id: - rec.partner_id.default_carrier_account_id = rec - return res diff --git a/delivery_carrier_partner_account/models/res_partner.py b/delivery_carrier_partner_account/models/res_partner.py index 92459ff..495f9cb 100644 --- a/delivery_carrier_partner_account/models/res_partner.py +++ b/delivery_carrier_partner_account/models/res_partner.py @@ -13,29 +13,77 @@ class Partner(models.Model): default_carrier_account_id = fields.Many2one( comodel_name="delivery.carrier.account", + compute="_compute_logistic_defaults", + inverse="_inverse_default_carrier_account_id", + store=True, tracking=1, ondelete="restrict", ) - def write(self, vals): - update_default_carrier = ( - "carrier_account_ids" in vals and not self.default_carrier_account_id - ) - res = super().write(vals) - if update_default_carrier and self.carrier_account_ids: - self.default_carrier_account_id = self.carrier_account_ids[0] - self.property_delivery_carrier_id = ( - self.default_carrier_account_id.delivery_carrier_id - ) - return res + property_delivery_carrier_id = fields.Many2one( + compute="_compute_logistic_defaults", + inverse="_inverse_property_delivery_carrier_id", + store=True, + ) - @api.model_create_multi - def create(self, vals_list): - res = super().create(vals_list) - for rec in res: - if not rec.default_carrier_account_id and rec.carrier_account_ids: - rec.default_carrier_account_id = rec.carrier_account_ids[0] - return res + def _inverse_default_carrier_account_id(self): + pass + + def _inverse_property_delivery_carrier_id(self): + pass + + @api.depends( + "carrier_account_ids", + "carrier_account_ids.active", + "carrier_account_ids.delivery_carrier_id", + ) + def _compute_logistic_defaults(self): + # Unset default carrier account if it is archived + for partner in self.filtered( + lambda partner: partner.default_carrier_account_id + and not partner.default_carrier_account_id.active + ): + partner.default_carrier_account_id = False + + # Unset the default carrier if no accounts are available + for partner in self.filtered( + lambda partner: partner.property_delivery_carrier_id + ).filtered( + lambda partner: not partner.carrier_account_ids.filtered( + lambda account: account.delivery_carrier_id + == partner.property_delivery_carrier_id + and account.active + ) + ): + partner.property_delivery_carrier_id = False + + # Set default carrier account if not set and accounts available + for partner in self.filtered( + lambda partner: not partner.default_carrier_account_id + and partner.carrier_account_ids.filtered("active") + ): + partner.default_carrier_account_id = partner.carrier_account_ids.filtered( + "active" + )[0] + + # Set default carrier if not set and default account is set + for partner in self.filtered( + lambda partner: not partner.property_delivery_carrier_id + and partner.default_carrier_account_id + ): + partner.property_delivery_carrier_id = ( + partner.default_carrier_account_id.delivery_carrier_id + ) + + # Reset default carrier if account is set and doesn't match + for partner in self.filtered( + lambda partner: partner.default_carrier_account_id + and partner.default_carrier_account_id.delivery_carrier_id + != partner.property_delivery_carrier_id + ): + partner.property_delivery_carrier_id = ( + partner.default_carrier_account_id.delivery_carrier_id + ) def get_carrier_account(self, carrier): self.ensure_one() @@ -52,3 +100,22 @@ class Partner(models.Model): if commercial_patner_accounts: return commercial_patner_accounts[0] return self.env["delivery.carrier.account"] + + def process_carrier_account_archiving(self): + for partner in self: + # Unset default carrier account if it is archived + if ( + partner.default_carrier_account_id + and not partner.default_carrier_account_id.active + ): + partner.default_carrier_account_id = False + # Unset default carrier if not more accounts available + if ( + partner.property_delivery_carrier_id + and not partner.carrier_account_ids.filtered( + lambda account: account.delivery_carrier_id + == partner.property_delivery_carrier_id + and account.active + ) + ): + partner.property_delivery_carrier_id = False diff --git a/delivery_carrier_partner_account/tests/test_res_partner.py b/delivery_carrier_partner_account/tests/test_res_partner.py index d2c1e46..f1d2ccd 100644 --- a/delivery_carrier_partner_account/tests/test_res_partner.py +++ b/delivery_carrier_partner_account/tests/test_res_partner.py @@ -1,4 +1,4 @@ -from odoo.tests import TransactionCase, tagged +from odoo.tests import TransactionCase, tagged, Form from odoo import Command @@ -12,16 +12,15 @@ class TestResPartner(TransactionCase): partner = self.env["res.partner"].create( { "name": "Test Partner", - "carrier_account_ids": [ - Command.create( - { - "delivery_carrier_id": self.env.ref( - "delivery.free_delivery_carrier" - ).id, - "account_number": "1234567890", - } - ) - ], + } + ) + account = self.env["delivery.carrier.account"].create( + { + "partner_id": partner.id, + "delivery_carrier_id": self.env.ref( + "delivery.free_delivery_carrier" + ).id, + "account_number": "1234567890", } ) @@ -97,3 +96,53 @@ class TestResPartner(TransactionCase): ) self.assertEqual(partner.default_carrier_account_id, new_account) + + def test_no_archived_default_carrier_account(self): + partner = self.env["res.partner"].create( + { + "name": "Test Partner", + } + ) + account = self.env["delivery.carrier.account"].create( + { + "partner_id": partner.id, + "delivery_carrier_id": self.env.ref( + "delivery.free_delivery_carrier" + ).id, + "account_number": "1234567890", + } + ) + + with Form(account) as account_form: + account_form.active = False + + self.assertFalse(partner.default_carrier_account_id) + + def test_multiple_carrier_accounts_reset_default_on_archive(self): + partner = self.env["res.partner"].create( + { + "name": "Test Partner", + } + ) + account1 = self.env["delivery.carrier.account"].create( + { + "partner_id": partner.id, + "delivery_carrier_id": self.env.ref( + "delivery.free_delivery_carrier" + ).id, + "account_number": "1234567890", + } + ) + account2 = self.env["delivery.carrier.account"].create( + { + "partner_id": partner.id, + "delivery_carrier_id": self.env.ref( + "delivery.free_delivery_carrier" + ).id, + "account_number": "1234567891", + } + ) + self.assertEqual(partner.default_carrier_account_id, account1) + with Form(account1) as account_form: + account_form.active = False + self.assertEqual(partner.default_carrier_account_id, account2) From aa16543a86e5e27b5c52eb8c4e689b27f6d2b68a Mon Sep 17 00:00:00 2001 From: xtremxpert Date: Wed, 9 Jul 2025 08:23:03 -0400 Subject: [PATCH 3/5] port of durpro_helpdek_sale to helpdesk sale order + base for AI --- helpdesk_ai_sale_order/__init__.py | 6 + helpdesk_ai_sale_order/__manifest__.py | 32 ++++ helpdesk_ai_sale_order/migration18.md | 22 +++ helpdesk_ai_sale_order/models/__init__.py | 8 + helpdesk_ai_sale_order/models/account_move.py | 12 ++ .../models/helpdesk_team.py | 7 + .../models/helpdesk_ticket.py | 90 +++++++++ helpdesk_ai_sale_order/models/sale_order.py | 37 ++++ .../static/description/icon.png | Bin 0 -> 3648 bytes helpdesk_ai_sale_order/tests/__init__.py | 1 + .../tests/test_helpdesk_sale.py | 65 +++++++ .../views/account_move_view.xml | 18 ++ .../views/helpdesk_team_views.xml | 15 ++ .../views/helpdesk_ticket_views.xml | 32 ++++ helpdesk_ai_sale_order/views/sale_view.xml | 26 +++ helpdesk_ai_sale_order/wizard/__init__.py | 4 + .../wizard/sale_advance_payment.py | 10 + helpdesk_sale_order_ai/__init__.py | 3 + helpdesk_sale_order_ai/__manifest__.py | 27 +++ helpdesk_sale_order_ai/models/__init__.py | 4 + .../models/helpdesk_team.py | 25 +++ .../models/helpdesk_ticket.py | 181 ++++++++++++++++++ .../views/helpdesk_team_views.xml | 14 ++ 23 files changed, 639 insertions(+) create mode 100644 helpdesk_ai_sale_order/__init__.py create mode 100644 helpdesk_ai_sale_order/__manifest__.py create mode 100644 helpdesk_ai_sale_order/migration18.md create mode 100644 helpdesk_ai_sale_order/models/__init__.py create mode 100644 helpdesk_ai_sale_order/models/account_move.py create mode 100644 helpdesk_ai_sale_order/models/helpdesk_team.py create mode 100644 helpdesk_ai_sale_order/models/helpdesk_ticket.py create mode 100644 helpdesk_ai_sale_order/models/sale_order.py create mode 100644 helpdesk_ai_sale_order/static/description/icon.png create mode 100644 helpdesk_ai_sale_order/tests/__init__.py create mode 100644 helpdesk_ai_sale_order/tests/test_helpdesk_sale.py create mode 100644 helpdesk_ai_sale_order/views/account_move_view.xml create mode 100644 helpdesk_ai_sale_order/views/helpdesk_team_views.xml create mode 100644 helpdesk_ai_sale_order/views/helpdesk_ticket_views.xml create mode 100644 helpdesk_ai_sale_order/views/sale_view.xml create mode 100644 helpdesk_ai_sale_order/wizard/__init__.py create mode 100644 helpdesk_ai_sale_order/wizard/sale_advance_payment.py create mode 100644 helpdesk_sale_order_ai/__init__.py create mode 100644 helpdesk_sale_order_ai/__manifest__.py create mode 100644 helpdesk_sale_order_ai/models/__init__.py create mode 100644 helpdesk_sale_order_ai/models/helpdesk_team.py create mode 100644 helpdesk_sale_order_ai/models/helpdesk_ticket.py create mode 100644 helpdesk_sale_order_ai/views/helpdesk_team_views.xml diff --git a/helpdesk_ai_sale_order/__init__.py b/helpdesk_ai_sale_order/__init__.py new file mode 100644 index 0000000..c6d481c --- /dev/null +++ b/helpdesk_ai_sale_order/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from . import models +from . import wizard + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/helpdesk_ai_sale_order/__manifest__.py b/helpdesk_ai_sale_order/__manifest__.py new file mode 100644 index 0000000..1ea1e8e --- /dev/null +++ b/helpdesk_ai_sale_order/__manifest__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +{ + 'name': 'Helpdesk Sale Order', + 'license': 'LGPL-3', + 'version': '18.0.0.1', + 'category' : 'Sales/Sales', + 'summary': """Allows your helpdesk team to create sales orders from helpdesk tickets.""", + 'description': """ + Convert helpdesk tickets into sales orders. + + This feature is useful for companies that sell products or services to their customers + and might receive orders through their helpdesk tickets or simply used orders@theirodoo.com + as a triage helpdesk ticket. + """, + 'author': 'Bemade', + 'maintainer': 'it@bemade.org', + 'depends': [ + 'sale_management', + 'helpdesk', + 'helpdesk_sale', + 'sale_project' + ], + 'data': [ + 'views/account_move_view.xml', + 'views/helpdesk_ticket_views.xml', + 'views/sale_view.xml', + 'views/helpdesk_team_views.xml' + ], + 'installable': True, + 'application': False, +} diff --git a/helpdesk_ai_sale_order/migration18.md b/helpdesk_ai_sale_order/migration18.md new file mode 100644 index 0000000..dc98f30 --- /dev/null +++ b/helpdesk_ai_sale_order/migration18.md @@ -0,0 +1,22 @@ +# Migration de durpro_helpdesk_sale vers Odoo 18.0 + +## Description +Module d'intégration entre le helpdesk et les ventes pour Durpro. + +## Fonctionnalités Ajoutées + +### Création de commandes de vente depuis les tickets +- Vérification Odoo 18.0 : À vérifier +- Différences avec la version native : À documenter +- Alternatives disponibles : À identifier + +## Modèles et Champs Modifiés + +### Modèle HelpdeskTicket +- Ajouts/Modifications : À documenter +- Recherche dans le projet : À effectuer +- Existence dans Odoo standard/enterprise : À vérifier +- Recommandations de migration : À formuler + +## Vues à Modifier +- Liste des vues tree à convertir en list : À identifier \ No newline at end of file diff --git a/helpdesk_ai_sale_order/models/__init__.py b/helpdesk_ai_sale_order/models/__init__.py new file mode 100644 index 0000000..d1aeada --- /dev/null +++ b/helpdesk_ai_sale_order/models/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +from . import sale_order +from . import account_move +from . import helpdesk_team +from . import helpdesk_ticket + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/helpdesk_ai_sale_order/models/account_move.py b/helpdesk_ai_sale_order/models/account_move.py new file mode 100644 index 0000000..447b66d --- /dev/null +++ b/helpdesk_ai_sale_order/models/account_move.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api + + +class AccountMove(models.Model): + _inherit = 'account.move' + + helpdesk_ticket_id = fields.Many2one( + 'helpdesk.ticket', + string='Helpdesk Ticket', + copy=False, + ) diff --git a/helpdesk_ai_sale_order/models/helpdesk_team.py b/helpdesk_ai_sale_order/models/helpdesk_team.py new file mode 100644 index 0000000..1a8790d --- /dev/null +++ b/helpdesk_ai_sale_order/models/helpdesk_team.py @@ -0,0 +1,7 @@ +from odoo import fields, models + + +class HelpdeskTeam(models.Model): + _inherit = 'helpdesk.team' + + use_sale_orders = fields.Boolean('Sales', help="Create quotes from tickets") \ No newline at end of file diff --git a/helpdesk_ai_sale_order/models/helpdesk_ticket.py b/helpdesk_ai_sale_order/models/helpdesk_ticket.py new file mode 100644 index 0000000..1498eeb --- /dev/null +++ b/helpdesk_ai_sale_order/models/helpdesk_ticket.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + + +from odoo import models, fields, api, _, Command +from odoo.exceptions import UserError + + +class HelpdeskTicket(models.Model): + _inherit = "helpdesk.ticket" + + sale_order_ids = fields.One2many( + "sale.order", + "helpdesk_ticket_id", + string="Sale Orders", + help="Sale orders associated to this ticket.", + copy=False, + ) + sale_order_count = fields.Integer(compute="_compute_sale_order_count") + team_use_sale_orders = fields.Boolean(related="team_id.use_sale_orders", string="Team Uses Sales Orders", readonly=True) + + @api.depends("sale_order_ids") + def _compute_sale_order_count(self): + self.sale_order_count = len(self.sale_order_ids) + + def action_view_sale_order(self): + self.ensure_one() + sale_order_form_view = self.env.ref("sale.view_order_form") + sale_order_tree_view = self.env.ref("sale.view_order_tree") + + action = { + "type": "ir.actions.act_window", + "res_model": "sale.order", + "context": {"create": False}, + } + + if self.sale_order_count == 1: + action.update( + res_id=self.sale_order_ids[0].id, + views=[(sale_order_form_view.id, "form")], + ) + else: + action.update( + domain=[("id", "in", self.sale_order_ids.ids)], + views=[ + (sale_order_tree_view.id, "tree"), + (sale_order_form_view.id, "form"), + ], + name=_("Purchase Orders from Ticket"), + ) + return action + + def action_convert_to_sale_order(self): + self.ensure_one() + if not self.team_use_sale_orders: + raise UserError(_("Creating quotes from tickets is not enabled for this helpdesk team.")) + + so_values = self._generate_so_values() + so = self.env["sale.order"].create([so_values]) + self.message_change_thread(so) + attachments = self.env["ir.attachment"].search( + [("res_model", "=", "helpdesk.ticket"), ("res_id", "=", self.id)] + ) + attachments.sudo().write({"res_model": "sale.order", "res_id": so.id}) + activities = self.activity_ids + activities.sudo().write( + { + "res_model_id": self.env.ref("sale.model_sale_order").id, + "res_id": so.id, + "res_model": "sale.order", + } + ) + # The activities will be linked to the SO through the res_model and res_id fields + self.action_archive() + return self.action_view_sale_order() + + def _generate_so_values(self): + team = self.user_id.sale_team_id if self.user_id else self.env.user.sale_team_id + if not team: + raise UserError( + _( + "Creating sale orders is reserved to sales users. Assign the user to sale team first." + ) + ) + team_id = team.id + return { + "partner_id": self.partner_id.id, + "helpdesk_ticket_id": self.id, + "company_id": self.company_id.id, + "team_id": team_id, + } diff --git a/helpdesk_ai_sale_order/models/sale_order.py b/helpdesk_ai_sale_order/models/sale_order.py new file mode 100644 index 0000000..9378b4a --- /dev/null +++ b/helpdesk_ai_sale_order/models/sale_order.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api + + +class SaleOrder(models.Model): + _inherit = 'sale.order' + + helpdesk_ticket_id = fields.Many2one( + 'helpdesk.ticket', + string='Helpdesk Ticket', + copy=False, + ) + + def action_view_ticket(self): + return { + 'type': 'ir.actions.act_window', + 'res_model': 'helpdesk.ticket', + 'view_mode': 'form', + 'res_id': self.helpdesk_ticket_id.id, + } + + def _prepare_invoice(self): + result = super(SaleOrder, self)._prepare_invoice() + result.update({'helpdesk_ticket_id': self.helpdesk_ticket_id.id}) + return result + + def create(self, vals_list): + sos = super().create(vals_list) + for so in sos.filtered('helpdesk_ticket_id'): + so.message_post_with_source( + 'helpdesk.ticket_creation', + render_values={ + 'self': so, + 'ticket': so.helpdesk_ticket_id + }, subtype_id=self.env.ref('mail.mt_note').id + ) + return sos diff --git a/helpdesk_ai_sale_order/static/description/icon.png b/helpdesk_ai_sale_order/static/description/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1a0c0fb0050e7f95fe1010bafdd7c5ba621b1ebf GIT binary patch literal 3648 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 literal 0 HcmV?d00001 diff --git a/helpdesk_ai_sale_order/tests/__init__.py b/helpdesk_ai_sale_order/tests/__init__.py new file mode 100644 index 0000000..2c7eba5 --- /dev/null +++ b/helpdesk_ai_sale_order/tests/__init__.py @@ -0,0 +1 @@ +from . import test_helpdesk_sale diff --git a/helpdesk_ai_sale_order/tests/test_helpdesk_sale.py b/helpdesk_ai_sale_order/tests/test_helpdesk_sale.py new file mode 100644 index 0000000..c57151b --- /dev/null +++ b/helpdesk_ai_sale_order/tests/test_helpdesk_sale.py @@ -0,0 +1,65 @@ +from odoo.tests import TransactionCase, tagged +from odoo import Command + + +@tagged("post_install", "-at_install") +class TestHelpdeskSale(TransactionCase): + def test_convert_ticket_to_sale_order_moves_messages(self): + # User needs to be part of a sales team, so set that up first + sale_team = self.env.ref("sales_team.crm_team_1") + helpdesk_team = self.env.ref("helpdesk.helpdesk_team1") + self.env.user.sale_team_id = sale_team + + ticket = self.env["helpdesk.ticket"].create( + { + "name": "Test Ticket", + "description": "Test description", + "partner_id": self.env.ref("base.res_partner_2").id, + "user_id": self.env.user.id, + "team_id": helpdesk_team.id, + } + ) + ticket.message_post(body="This is a test email message") + ticket.message_post(body="This is another message") + ticket.message_post(body="This is a comment.", message_type="comment") + + num_messages = len(ticket.message_ids) + self.assertGreaterEqual(num_messages, 3) + messages = ticket.message_ids + + action = ticket.action_convert_to_sale_order() + so = self.env["sale.order"].browse(action["res_id"]) + + for message in messages: + self.assertIn(message.body, so.message_ids.mapped("body")) + self.assertFalse(ticket.active) + + def test_convert_ticket_to_sale_order_moves_activities(self): + # User needs to be part of a sales team, so set that up first + sale_team = self.env.ref("sales_team.crm_team_1") + helpdesk_team = self.env.ref("helpdesk.helpdesk_team1") + self.env.user.sale_team_id = sale_team + + ticket = self.env["helpdesk.ticket"].create( + { + "name": "Test Ticket", + "description": "Test description", + "partner_id": self.env.ref("base.res_partner_2").id, + "user_id": self.env.user.id, + "team_id": helpdesk_team.id, + } + ) + activity = self.env["mail.activity"].create( + { + "activity_type_id": self.env.ref("mail.mail_activity_data_call").id, + "res_model_id": self.env.ref("helpdesk.model_helpdesk_ticket").id, + "res_id": ticket.id, + "user_id": self.env.user.id, + } + ) + + action = ticket.action_convert_to_sale_order() + self.env.invalidate_all(flush=True) + so = self.env["sale.order"].browse(action["res_id"]) + + self.assertIn(activity, so.activity_ids) diff --git a/helpdesk_ai_sale_order/views/account_move_view.xml b/helpdesk_ai_sale_order/views/account_move_view.xml new file mode 100644 index 0000000..2686d33 --- /dev/null +++ b/helpdesk_ai_sale_order/views/account_move_view.xml @@ -0,0 +1,18 @@ + + + + + + account.move.from.inherited.helpdesk + account.move + + + + + + + + + + + diff --git a/helpdesk_ai_sale_order/views/helpdesk_team_views.xml b/helpdesk_ai_sale_order/views/helpdesk_team_views.xml new file mode 100644 index 0000000..60498e1 --- /dev/null +++ b/helpdesk_ai_sale_order/views/helpdesk_team_views.xml @@ -0,0 +1,15 @@ + + + + helpdesk.team.form.inherit.sale.order + helpdesk.team + + + + + + + + + + diff --git a/helpdesk_ai_sale_order/views/helpdesk_ticket_views.xml b/helpdesk_ai_sale_order/views/helpdesk_ticket_views.xml new file mode 100644 index 0000000..e9a3578 --- /dev/null +++ b/helpdesk_ai_sale_order/views/helpdesk_ticket_views.xml @@ -0,0 +1,32 @@ + + + + + helpdesk.ticket.form.view.inherit.saleorder + helpdesk.ticket + + + + + + + + + + + + diff --git a/helpdesk_ai_sale_order/views/sale_view.xml b/helpdesk_ai_sale_order/views/sale_view.xml new file mode 100644 index 0000000..b575981 --- /dev/null +++ b/helpdesk_ai_sale_order/views/sale_view.xml @@ -0,0 +1,26 @@ + + + + + + sale.order.from.inherited.saleorder + sale.order + + + + +