diff --git a/account_credit_hold/__init__.py b/account_credit_hold/__init__.py new file mode 100644 index 0000000..0650744 --- /dev/null +++ b/account_credit_hold/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/account_credit_hold/__manifest__.py b/account_credit_hold/__manifest__.py new file mode 100644 index 0000000..0212a4d --- /dev/null +++ b/account_credit_hold/__manifest__.py @@ -0,0 +1,21 @@ +{ + 'name': 'Account Credit Hold', + 'version': '16.0.1.0.0', + 'summary': 'Allows setting clients on credit hold, blocking the ability confirm a new sales order.', + 'description': 'Allows setting clients on hold, blocking the ability confirm a new sales order.', + 'category': 'Accounting/Accounting', + 'author': 'Bemade Inc.', + 'maintainer': 'Marc Durepos ', + 'website': 'http://www.bemade.org', + 'license': 'LGPL-3', + 'depends': ['sale', 'account_followup', 'stock'], + 'data': [ + 'views/account_followup_views.xml', + 'views/sale_order_views.xml', + 'views/res_partner_views.xml', + 'views/stock_picking_views.xml', + ], + 'demo': [], + 'installable': True, + 'auto_install': False +} diff --git a/account_credit_hold/models/__init__.py b/account_credit_hold/models/__init__.py new file mode 100644 index 0000000..0c2c11c --- /dev/null +++ b/account_credit_hold/models/__init__.py @@ -0,0 +1,5 @@ +from . import account_followup +from . import res_partner +from . import sale_order +from . import stock_picking +from . import account_followup_report diff --git a/account_credit_hold/models/account_followup.py b/account_credit_hold/models/account_followup.py new file mode 100644 index 0000000..5af3363 --- /dev/null +++ b/account_credit_hold/models/account_followup.py @@ -0,0 +1,8 @@ +from odoo import fields, models, api + + +class FollowupLine(models.Model): + _inherit = 'account_followup.followup.line' + + account_hold = fields.Boolean(string="Place on Credit Hold", + help="Place clients on account hold, restricting confirmation of new orders.") diff --git a/account_credit_hold/models/account_followup_report.py b/account_credit_hold/models/account_followup_report.py new file mode 100644 index 0000000..a28efdc --- /dev/null +++ b/account_credit_hold/models/account_followup_report.py @@ -0,0 +1,12 @@ +from odoo import models, fields, api, _ + + +class FollowUpReport(models.AbstractModel): + _inherit = 'account.followup.report' + + def _get_line_info(self, followup_line): + res = super()._get_line_info(followup_line) + res.update({ + 'credit_hold': followup_line.account_hold + }) + return res diff --git a/account_credit_hold/models/res_partner.py b/account_credit_hold/models/res_partner.py new file mode 100644 index 0000000..3cc765d --- /dev/null +++ b/account_credit_hold/models/res_partner.py @@ -0,0 +1,63 @@ +from odoo import fields, models, api, _ +from datetime import date + + +class Partner(models.Model): + _inherit = 'res.partner' + + postpone_hold_until = fields.Date(string="Postpone Hold", + help="Grace period specific to this partner despite unpaid invoices.", ) + + hold_bg = fields.Boolean(string="Hold (technical)", + compute="_compute_hold_bg", + store=True, + default=False) + on_hold = fields.Boolean(string="Account on Hold", + help="Client account is on hold for unpaid overdue invoices.", + compute="_compute_on_hold") + + @api.depends('postpone_hold_until', 'hold_bg') + def _compute_on_hold(self): + # manually re-compute hold_bg since followup_status doesn't get updated in Python but gets recalculated + # by an SQL query every time + self._compute_hold_bg() + for rec in self: + if rec.hold_bg and not (rec.postpone_hold_until and rec.postpone_hold_until > date.today()): + rec.on_hold = True + else: + if rec.on_hold: + rec.message_post(_("Credit hold lifted.")) + rec.on_hold = False + + @api.autovacuum + def _cleanup_expired_hold_postponements(self): + expired_holds = self.search([('postpone_hold_until', '<=', date.today())]) + expired_holds.write({'postpone_hold_until': False}) + + def action_credit_hold(self): + for rec in self: + rec.hold_bg = True + rec.message_post(body=_('Placed on credit hold.')) + + def action_lift_credit_hold(self): + for rec in self: + rec.hold_bg = False + rec.message_post(body=_('Credit hold lifted.')) + + def _execute_followup_partner(self): + res = super()._execute_followup_partner() + if self.followup_status == 'in_need_of_action': + if self.followup_line_id.account_hold: + self.action_credit_hold() + return res + + @api.depends('followup_status', 'followup_line_id') + def _compute_hold_bg(self): + first_followup_level = self._get_first_followup_level() + for rec in self: + prev_hold_bg = rec.hold_bg + level = rec.followup_line_id + if rec.followup_status == 'no_action_needed' and level == first_followup_level: + rec.hold_bg = False + else: + rec.hold_bg = prev_hold_bg diff --git a/account_credit_hold/models/sale_order.py b/account_credit_hold/models/sale_order.py new file mode 100644 index 0000000..390e97e --- /dev/null +++ b/account_credit_hold/models/sale_order.py @@ -0,0 +1,17 @@ +from odoo import fields, models, api, _ +from odoo.exceptions import UserError + + +class SaleOrder(models.Model): + _inherit = "sale.order" + + client_on_hold = fields.Boolean(string='Client on Hold', + help="Whether or not a client has been put on hold due to unpaid invoices.", + related="partner_id.on_hold") + + @api.depends('client_on_hold') + def action_confirm(self): + if any(self.mapped('client_on_hold')): + raise UserError(_("This client is on credit hold. No new orders can be confirmed until past-due invoices " + "are paid or the accounting team postpones the hold.")) + super().action_confirm() diff --git a/account_credit_hold/models/stock_picking.py b/account_credit_hold/models/stock_picking.py new file mode 100644 index 0000000..0d46e32 --- /dev/null +++ b/account_credit_hold/models/stock_picking.py @@ -0,0 +1,9 @@ +from odoo import fields, models, api + + +class ModelName(models.Model): + _inherit = "stock.picking" + + client_on_hold = fields.Boolean(string='Client on Hold', + help="Whether or not a client has been put on hold due to unpaid invoices.", + related="partner_id.on_hold") diff --git a/account_credit_hold/readme.md b/account_credit_hold/readme.md new file mode 100644 index 0000000..b11668c --- /dev/null +++ b/account_credit_hold/readme.md @@ -0,0 +1,35 @@ +# Overview + +This module adds the notion of placing clients on credit hold to the followup levels from the Odoo Enterprise +account_followup module. It adds an option to followup levels to mark clients matching the followup criteria as on +credit hold. This hold restricts the confirmation of new sales orders for these clients. + +Accountant and admin users can set a date until which the account hold will be +postponed on a specific partner's form view. This effectively gives clients an extra +grace period, allowing orders to be confirmed until the period ends. + +# Change Log +## 15.0.2.0.0 (2023-05-04) + +Complete remake of the module, making the "Credit Hold" an action that is either manually or +automatically triggered from the Accounting > Followup Reports section or by setting the automatic application field +on followup levels. + +## 15.0.1.1.0 (2023-05-03) + +Adds a ribbon to stock pickings for clients on hold, and therefore a dependency on stock. + +## 15.0.1.0.2 (2023-05-03) + +Fix to sale order view and sale order confirmation for clients not on hold. + +## 15.0.1.0.1 (2023-05-03) + +Fix clients on hold when status is "outstanding_invoices". + +## 15.0.1.0.0 (2023-05-02) Initial Release + +Initial release of the module, including a setting on follow-up levels to toggle placing on credit hold. Blocks +the confirmation of sales orders for clients on credit hold. Red "Credit Hold" banner appears on sales orders and +partner form view when a client is on credit hold. Credit hold can be postponed by setting the "Postpone Hold" field +on the partner form view. \ No newline at end of file diff --git a/account_credit_hold/views/account_followup_views.xml b/account_credit_hold/views/account_followup_views.xml new file mode 100644 index 0000000..10a7165 --- /dev/null +++ b/account_credit_hold/views/account_followup_views.xml @@ -0,0 +1,52 @@ + + + + + + account_credit_hold.account_followup_line.form + account_followup.followup.line + + + + + + + + + + customer.statements.form.view.inherit + res.partner + + + + + + + + + + + + + + \ No newline at end of file diff --git a/bemade_fsm/static/tests/tours/equipment_tour.js b/bemade_fsm/static/tests/tours/equipment_tour.js deleted file mode 100644 index ec69e1e..0000000 --- a/bemade_fsm/static/tests/tours/equipment_tour.js +++ /dev/null @@ -1,104 +0,0 @@ -/** @odoo-module **/ - -import tour from 'web_tour.tour'; - -const TEST_COMPANY = "Test Partner"; -const TEST_EQPT1 = "Test Equipment 1"; -const TEST_EQPT2 = "Test Equipment 2"; -tour.register('equipment_base_tour', { - test: true, - url: '/web', - }, /* Create a new "Test Equipment" and link it to "Test Partner Company" */ - [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Service menu', - trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]', - }, { - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', - }, { - content: 'Navigate to the Equipment menu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', - }, { - content: 'Click the create button', - trigger: '.o_list_button_add', - }, { - content: 'Add a tag', - trigger: 'input[name="pid_tag"]', - run: 'text TestPIDTag', - }, { - content: 'Set the name', - trigger: 'input[name="name"]', - run: `text ${TEST_EQPT2}`, - }, { - content: 'Set the partner', - trigger: 'div[name="partner_location_id"] div div input', - run: `text ${TEST_COMPANY}`, - }, { - content: 'Click the partner in the dropdown', - trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`, - }, { - content: 'Save equipment', - trigger: 'button.o_list_button_save', - }, { - /* Navigate to the client and make sure that there are two equipments saved (one from the Python test case) */ - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', - }, { - content: 'Click on the clients submenu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_clients"]', - }, { - content: 'Search for the Test Partner Company', - trigger: 'input.o_searchview_input', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Customers))', - run: `text ${TEST_COMPANY}` - }, { - content: 'Validate Search', - trigger: '.o_menu_item.o_selection_focus', - run: 'click', - }, { - content: 'Open the test client.', - trigger: `div.o_kanban_record:has(span:contains(${TEST_COMPANY}))`, - }, { - content: 'Click the Field Service tab.', - trigger: 'a.nav-link:contains(Field Service)', - extra_trigger: `h1 span.o_field_partner_autocomplete[name="name"]:contains(${TEST_COMPANY})`, - }, { - content: 'Make sure we have a first test equipment', - trigger: `td:contains(${TEST_EQPT1})`, - run: function () { - }, - }, { - content: 'Make sure we have a second test equipment', - trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT2}))`, - run: function () { - }, - } - ]); - -tour.register('equipment_sale_order_tour', { - test: true, - url: '/web', -}, [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Sales menu', - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', -}, { - content: 'Create an order', - trigger: 'button.o_list_button_add', -}, { - content: 'Select the test partner', - trigger: 'div[name="partner_id"] input', - run: `text ${TEST_COMPANY}`, -}, { - content: 'Click the partner in the dropdown', - trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`, -}, { - content: 'Save', - trigger: 'button.o_form_button_save', -}, { - content: 'Navigate to the Field Service tab.', - trigger: 'a.nav-link[role="tab"]:contains(Field Service)' -}, { - content: 'Check that the equipment is listed in the tab.', - trigger: `li.ui-menu-item > a:contains(${TEST_EQPT1})`, -} -]); \ No newline at end of file diff --git a/bemade_fsm/static/tests/tours/sale_order_tour.js b/bemade_fsm/static/tests/tours/sale_order_tour.js deleted file mode 100644 index 6ea8eee..0000000 --- a/bemade_fsm/static/tests/tours/sale_order_tour.js +++ /dev/null @@ -1,40 +0,0 @@ -/** @odoo-module **/ - -import tour from 'web_tour.tour'; - -const SO_NAME = "TEST ORDER 2" -const PRODUCT_NAME = "Test Product 3" -tour.register('sale_order_tour', { - test: true, - url: '/web', - }, - [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Service menu', - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - }, { - content: 'Search for the sales order', - trigger: 'input.o_searchview_input', - run: `text ${SO_NAME}`, - }, { - content: 'Validate Search', - trigger: '.o_menu_item.o_selection_focus', - run: 'click', - }, { - content: 'Open the test order', - trigger: `.o_data_cell[name="client_order_ref"]:contains(${SO_NAME})`, - }, { - content: 'Click the view tasks button', - trigger: 'button[name="action_view_task"]', - }, /*{ - content: 'Click the first task', - trigger: `div.o_kanban_record:has(span:contains(${PRODUCT_NAME}))`, - },*/ { - content: 'Click on the ready to invoice button', - trigger: 'button[name="action_fsm_validate"]', - extra_trigger: `li.breadcrumb-item.active:has(span:contains(${PRODUCT_NAME}))` - }, { - content: 'View the SO', - trigger: 'button[name="action_view_so"]', - // extra_trigger: 'button[title="Current state"]:contains(Done)', - } - ]); diff --git a/bemade_fsm/static/tests/tours/task_equipment_tour.js b/bemade_fsm/static/tests/tours/task_equipment_tour.js deleted file mode 100644 index f8a244d..0000000 --- a/bemade_fsm/static/tests/tours/task_equipment_tour.js +++ /dev/null @@ -1,75 +0,0 @@ -/** @odoo-module **/ - -import tour from 'web_tour.tour'; -const TEST_COMPANY = "Test Partner Company"; -const TEST_EQPT1 = "Test Equipment 1"; -const TEST_EQPT2 = "Test Equipment 2"; -tour.register('task_equipment_tour', { - test: true, - url: '/web', - }, /* Create a new "Test Equipment" and link it to "Test Partner Company" */ - [tour.stepUtils.showAppsMenuItem(), { - content: 'Navigate to the Service menu', - trigger: '.o_app[data-menu-xmlid="industry_fsm.fsm_menu_root"]', - }, { - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', - }, { - content: 'Navigate to the Equipment menu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_equipment"]', - }, { - content: 'Click the create button', - trigger: '.o_list_button_add', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Equipment))', - }, { - content: 'Add a tag', - trigger: 'input[name="pid_tag"]', - run: 'text TestPIDTag', - }, { - content: 'Set the name', - trigger: 'input[name="name"]', - run: `text ${TEST_EQPT2}`, - }, { - content: 'Set the partner', - trigger: 'div[name="partner_location_id"] div div input', - run: 'text Test Partner Company', - }, { - content: 'Click the partner in the dropdown', - trigger: `li a.dropdown-item:contains(${TEST_COMPANY})`, - }, { - content: 'Save equipment', - trigger: 'button.o_list_button_save', - }, { - /* Navigate to the client and make sure that there are two equipments saved (one from the Python test case) */ - content: 'Navigate to the Clients submenu', - trigger: 'button.dropdown-toggle[data-menu-xmlid="bemade_fsm.menu_service_client"]', - }, { - content: 'Click on the clients submenu', - trigger: '.dropdown-item[data-menu-xmlid="bemade_fsm.menu_service_client_clients"]', - }, { - content: 'Search for the Test Partner Company', - trigger: 'input.o_searchview_input', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Customers))', - run: `text ${TEST_COMPANY}` - }, { - content: 'Validate Search', - trigger: '.o_menu_item.o_selection_focus', - run: 'click', - }, { - content: 'Open the test client.', - trigger: `div.o_kanban_record:has(span:contains(${TEST_COMPANY}))`, - }, { - content: 'Click the Field Service tab.', - trigger: 'a.nav-link:contains(Field Service)', - extra_trigger: `h1 span.o_field_partner_autocomplete[name="name"]:contains(${TEST_COMPANY})`, - }, { - content: 'Make sure we have a first test equipment', - /*trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT1}))`,*/ - trigger: `td:contains(${TEST_EQPT1})`, - run: function() {}, - }, { - content: 'Make sure we have a second test equipment', - trigger: `div[name="equipment_ids"]:has(td:contains(${TEST_EQPT2}))`, - run: function() {}, - } - ]) \ No newline at end of file diff --git a/bemade_fsm/static/tests/tours/task_template_tour.js b/bemade_fsm/static/tests/tours/task_template_tour.js deleted file mode 100644 index 3573a45..0000000 --- a/bemade_fsm/static/tests/tours/task_template_tour.js +++ /dev/null @@ -1,47 +0,0 @@ -/** @odoo-module */ - -import tour from 'web_tour.tour'; - -tour.register('task_template_tour', { - test: true, - url: '/web', - }, /* Make sure the test task is visible via the Project > Task Templates path */ - [tour.stepUtils.showAppsMenuItem(), { - trigger: '.o_app[data-menu-xmlid="project.menu_main_pm"]', - }, { - content: 'Open the task templates list view.', - trigger: '.o_nav_entry[data-menu-xmlid="bemade_fsm.project_task_template_menu"]', - }, { - content: 'Open the existing task template.', - trigger: '.o_data_cell:contains("Template 1"):parent()', - }, { - content: 'Confirm that the header contains the task title.', - trigger: 'span[name="name"]:contains("Template 1")', - }, /* Make sure we can see the task template from the product */ - tour.stepUtils.toggleHomeMenu(), - { - content: 'Open the sales menu.', - trigger: '.o_app[data-menu-xmlid="sale.sale_menu_root"]', - }, { - content: 'Open the product dropdown menu.', - trigger: 'button[data-menu-xmlid="sale.product_menu_catalog"]', - }, { - content: 'Click the products menu item.', - trigger: 'a.dropdown-item[data-menu-xmlid="sale.menu_product_template_action"]', - }, { - content: 'Search for the test product.', - trigger: 'input.o_searchview_input', - extra_trigger: 'li.breadcrumb-item.active:has(span:contains(Products))', - run: 'text Test Product 1', - }, { - trigger: '.o_menu_item.o_selection_focus', - content: 'Validate search', - run: 'click', - }, { - content: 'Open the test product.', - trigger: 'div.o_kanban_record:has(span:contains(Test Product 1))', - }, { - content: 'Ensure the product_template_id field is displayed with the "Template 1" mention.', - trigger: 'a.o_quick_editable[name="task_template_id"]:first-child:contains("Template 1")', - }, - ]) \ No newline at end of file diff --git a/bemade_fsm/tests/__init__.py b/bemade_fsm/tests/__init__.py index 381536f..4b317ee 100644 --- a/bemade_fsm/tests/__init__.py +++ b/bemade_fsm/tests/__init__.py @@ -4,3 +4,4 @@ from . import test_sale_order from . import test_equipment from . import test_fsm_contact_setting from . import test_fsm_visit +from . import test_task diff --git a/bemade_fsm/tests/test_sale_order.py b/bemade_fsm/tests/test_sale_order.py index 2801ff3..1e39371 100644 --- a/bemade_fsm/tests/test_sale_order.py +++ b/bemade_fsm/tests/test_sale_order.py @@ -251,7 +251,7 @@ class TestSalesOrder(BemadeFSMBaseTest): self.assertFalse(visit_task.user_ids) self.assertFalse(subtask1.user_ids) self.assertFalse(subtask2.user_ids) - + def test_long_line_name_overflows_to_task_description(self): so = self._generate_sale_order() product = self._generate_product() diff --git a/bemade_fsm/tests/test_task.py b/bemade_fsm/tests/test_task.py new file mode 100644 index 0000000..f5e7ae6 --- /dev/null +++ b/bemade_fsm/tests/test_task.py @@ -0,0 +1,42 @@ +from .test_bemade_fsm_common import BemadeFSMBaseTest +from odoo.tests.common import tagged +from odoo import Command + + +@tagged('post_install', '-at_install') +class TaskTest(BemadeFSMBaseTest): + + def test_reassigning_assignment_propagating_task_changes_subtasks(self): + # This creation step is a bit lazy - we use defaults to make tasks with a hierachy and settings we want + so = self._generate_sale_order() + template = self._generate_task_template(names=['Parent', 'Child'], structure=[2]) + product = self._generate_product(task_template=template) + sol = self._generate_sale_order_line(sale_order=so, product=product) + user = self._generate_project_manager_user('Bob', 'Bob') + so.action_confirm() + task = sol.task_id + + task.write({ + 'user_ids': [Command.set([user.id])], + 'propagate_assignment': True, + }) + + self.assertTrue(all([t.user_ids == user for t in task | task._get_all_subtasks()])) + + def test_reassigning_assignment_non_propagating_task_doesnt_change_subtasks(self): + so = self._generate_sale_order() + template = self._generate_task_template(names=['Parent', 'Child', 'Grandchild'], structure=[2, 1]) + product = self._generate_product(task_template=template) + sol = self._generate_sale_order_line(sale_order=so, product=product) + user = self._generate_project_manager_user('Bob', 'Bob') + so.action_confirm() + task = sol.task_id + task.child_ids.write({'propagate_assignment': False}) # Stop propagation after the first level + + task.write({ + 'user_ids': [Command.set([user.id])], + 'propagate_assignment': True, + }) + + self.assertTrue(all([t.user_ids == user for t in task | task.child_ids])) + self.assertFalse(any([t.user_ids for t in task.child_ids.child_ids])) diff --git a/bemade_fsm/tests/test_task_template.py b/bemade_fsm/tests/test_task_template.py index d0cf173..178911a 100644 --- a/bemade_fsm/tests/test_task_template.py +++ b/bemade_fsm/tests/test_task_template.py @@ -78,3 +78,17 @@ class TestTaskTemplate(BemadeFSMBaseTest): self.assertEqual(sol1.task_id.name, "Short Name 1") self.assertEqual(sol2.task_id.name, "Short Name 2") self.assertEqual(sol3.task_id.name, "Task") + + def test_task_creation_directly_from_template(self): + project = self.env.ref("industry_fsm.fsm_project") + template = self._generate_task_template(names=['Task', 'Child', 'Grandchild'], structure=[2, 1]) + + task = template.create_task_from_self(project, "My new task") + + self.assertEqual(len(task.child_ids), len(template.subtasks)) + self.assertEqual(len(task.child_ids[0].child_ids), len(template.subtasks[0].subtasks)) + self.assertEqual(len(task.child_ids[1].child_ids), len(template.subtasks[1].subtasks)) + self.assertEqual(task.name, "My new task") + self.assertEqual(task.child_ids[0].name, template.subtasks[0].name) + self.assertTrue(all([t.project_id == project for t in task | task._get_all_subtasks()])) + diff --git a/bemade_fsm/views/task_views.xml b/bemade_fsm/views/task_views.xml index ddc48b8..9d8d201 100644 --- a/bemade_fsm/views/task_views.xml +++ b/bemade_fsm/views/task_views.xml @@ -46,6 +46,9 @@ + + + @@ -168,4 +171,4 @@ - \ No newline at end of file + diff --git a/bemade_fsm/wizard/__init__.py b/bemade_fsm/wizard/__init__.py new file mode 100644 index 0000000..5cc0245 --- /dev/null +++ b/bemade_fsm/wizard/__init__.py @@ -0,0 +1 @@ +from . import new_task_from_template diff --git a/bemade_fsm/wizard/new_task_from_template.py b/bemade_fsm/wizard/new_task_from_template.py new file mode 100644 index 0000000..6c8c5e1 --- /dev/null +++ b/bemade_fsm/wizard/new_task_from_template.py @@ -0,0 +1,51 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + + +class NewTaskFromTemplateWizard(models.TransientModel): + _name = "project.task.from.template.wizard" + _description = "Create Task from Template Wizard" + + project_id = fields.Many2one( + comodel_name='project.project', + string='Project', + help='The project the new task should be created in.', + required=True, + ) + + task_template_id = fields.Many2one( + comodel_name='project.task.template', + string='Task Template', + help='The template to use when creating the new task.', + required=True, + ) + + new_task_title = fields.Char( + help='The title (name) for the newly created task. If left blank, the name of the template will be used.', + ) + + def default_get(self, fields_list): + res = super().default_get(fields_list) + active_id = self.env.context.get('active_id', False) + active_model = self.env.context.get('active_model', False) + if not active_model: + params = self.env.context.get('params', False) + active_model = params and params.get('model', False) + if active_model == 'project.task.template' and active_id and 'task_template_id' in fields_list: + res.update({'task_template_id': active_id}) + if active_model == 'project.task' and 'project_id' in fields_list: + res.update({'project_id': self.env.ref('industry_fsm.fsm_project').id}) + return res + + def action_create_task_from_template(self): + self.ensure_one() + task = self.task_template_id.create_task_from_self(self.project_id, self.new_task_title) + return { + 'type': 'ir.actions.act_window', + 'res_model': 'project.task', + 'res_id': task.id, + 'view_mode': 'form', + 'target': 'current', + } + + diff --git a/bemade_fsm/wizard/new_task_from_template.xml b/bemade_fsm/wizard/new_task_from_template.xml new file mode 100644 index 0000000..1290b8f --- /dev/null +++ b/bemade_fsm/wizard/new_task_from_template.xml @@ -0,0 +1,29 @@ + + + + project.task.from.template.wizard.view.form + project.task.from.template.wizard + +
+ + + + + + + +
+
+
+
+
+ + New Task from Template + project.task.from.template.wizard + + form + new + +
\ No newline at end of file diff --git a/bemade_full_formview_from_modal/__init__.py b/bemade_full_formview_from_modal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bemade_full_formview_from_modal/__manifest__.py b/bemade_full_formview_from_modal/__manifest__.py new file mode 100644 index 0000000..2427ab3 --- /dev/null +++ b/bemade_full_formview_from_modal/__manifest__.py @@ -0,0 +1,37 @@ +# +# Bemade Inc. +# +# Copyright (C) 2023-June Bemade Inc. (). +# Author: Marc Durepos (Contact : mdurepos@durpro.com) +# +# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1) +# It is forbidden to publish, distribute, sublicense, or sell copies of the Software +# or modified copies of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +{ + 'name': 'Full Form from Dialog', + 'version': '16.0.1.0.0', + 'summary': 'Allows opening the full form view from the dialog (modal) view.', + 'description': 'Adds a button to open the full form view when viewing the form view for a record in a dialog.', + 'category': 'Technical', + 'author': 'Bemade Inc.', + 'website': 'http://www.bemade.org', + 'license': 'OPL-1', + 'depends': [], + 'data': [], + 'assets': { + 'web.assets_backend': [ + 'bemade_full_formview_from_modal/static/src/**/*', + ], + }, + 'installable': True, + 'auto_install': False +} diff --git a/bemade_full_formview_from_modal/static/src/view_dialogs/form_view_dialog.js b/bemade_full_formview_from_modal/static/src/view_dialogs/form_view_dialog.js new file mode 100644 index 0000000..06e1418 --- /dev/null +++ b/bemade_full_formview_from_modal/static/src/view_dialogs/form_view_dialog.js @@ -0,0 +1,22 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { FormController } from "@web/views/form/form_controller"; +import { useService } from "@web/core/utils/hooks"; + +patch(FormController.prototype, "bemade_full_formview_from_modal.FormController", { + setup () { + this._super(); + this.action = useService("action") + }, + onOpenButtonClicked: function () { + this.action.doAction({ + type: "ir.actions.act_window", + res_model: this.props.resModel, + res_id: this.props.resId, + views: [[false, "form"]], + target: "current", + context: this.props.context, + }) + } +}); diff --git a/bemade_full_formview_from_modal/static/src/view_dialogs/form_view_dialog.xml b/bemade_full_formview_from_modal/static/src/view_dialogs/form_view_dialog.xml new file mode 100644 index 0000000..65aefe9 --- /dev/null +++ b/bemade_full_formview_from_modal/static/src/view_dialogs/form_view_dialog.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/bemade_packing_wizard/__manifest__.py b/bemade_packing_wizard/__manifest__.py index 41b136d..124c309 100644 --- a/bemade_packing_wizard/__manifest__.py +++ b/bemade_packing_wizard/__manifest__.py @@ -1,12 +1,12 @@ { 'name': 'Packing wizard', - 'version': '15.0.1.0.0', + 'version': '16.0.1.0.0', 'category': 'Extra Tools', 'summary': 'Allow automated packing type creation', 'description': """ This module allows users simply enter all the package dimensions on every packing and it will create the packing type automatically if it not exists, use it the create the package with the weight and related package type. - + You need to activate the auto create package feature on the delivery carrier to use this feature. """, 'depends': [ diff --git a/bemade_partner_email_domain/__manifest__.py b/bemade_partner_email_domain/__manifest__.py index 5047c18..9f8fbe9 100644 --- a/bemade_partner_email_domain/__manifest__.py +++ b/bemade_partner_email_domain/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Automated Partner Association by Email Domain', - 'version': '15.0.0.0.0', + 'version': '16.0.0.0.0', 'category': 'Extra Tools', 'summary': 'Automatically associates partners with companies using matching email domains', 'description': """ diff --git a/bemade_payslip_done_as_not_paid/__init__.py b/bemade_payslip_done_as_not_paid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bemade_payslip_done_as_not_paid/__manifest__.py b/bemade_payslip_done_as_not_paid/__manifest__.py new file mode 100644 index 0000000..e5e382b --- /dev/null +++ b/bemade_payslip_done_as_not_paid/__manifest__.py @@ -0,0 +1,31 @@ +# +# Bemade Inc. +# +# Copyright (C) 2023-June Bemade Inc. (). +# Author: Marc Durepos (Contact : marc@bemade.org) +# +# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1) +# It is forbidden to publish, distribute, sublicense, or sell copies of the Software +# or modified copies of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. +# +{ + 'name': 'Payslip Done as To Pay', + 'version': '16.0.1.0.0', + 'summary': 'Payslips in "Done" state appear as "To Pay"', + 'category': 'Human Resources/Payroll', + 'author': 'Bemade Inc.', + 'website': 'http://www.bemade.org', + 'license': 'OPL-1', + 'depends': ['hr_payroll'], + 'data': ['views/hr_payslip_views.xml'], + 'installable': True, + 'auto_install': True, +} diff --git a/bemade_payslip_done_as_not_paid/views/hr_payslip_views.xml b/bemade_payslip_done_as_not_paid/views/hr_payslip_views.xml new file mode 100644 index 0000000..14fda51 --- /dev/null +++ b/bemade_payslip_done_as_not_paid/views/hr_payslip_views.xml @@ -0,0 +1,6 @@ + + + + [('state', 'in', ['draft', 'verify', 'done'])] + + \ No newline at end of file diff --git a/bemade_sports_clinic/__manifest__.py b/bemade_sports_clinic/__manifest__.py index 8b604e0..81b355f 100644 --- a/bemade_sports_clinic/__manifest__.py +++ b/bemade_sports_clinic/__manifest__.py @@ -18,7 +18,7 @@ # { 'name': 'Sports Clinic Management', - 'version': '16.0.1.5.0', + 'version': '16.0.1.5.2', 'summary': 'Manage the patients of a sports medicine clinic.', 'description': """ Adds the notion of sports teams, players (patients), coaches and treatment diff --git a/bemade_sports_clinic/models/patient.py b/bemade_sports_clinic/models/patient.py index a15db38..201bea2 100644 --- a/bemade_sports_clinic/models/patient.py +++ b/bemade_sports_clinic/models/patient.py @@ -61,7 +61,7 @@ class Patient(models.Model): "return to match play.") is_injured = fields.Boolean(compute="_compute_is_injured") stage = fields.Selection( - selection=[('no_play', 'Injured'), ('practice_ok', 'Cleared for Practice'), ('healthy', 'Cleared to Play')], + selection=[('no_play', 'Injured'), ('practice_ok', 'Practice OK'), ('healthy', 'Play OK')], compute='_compute_stage') last_consultation_date = fields.Date() active_injury_count = fields.Integer(compute='_compute_active_injury_count') diff --git a/bemade_sports_clinic/views/sports_clinic_portal_views.xml b/bemade_sports_clinic/views/sports_clinic_portal_views.xml index a91b59e..63d0cb7 100644 --- a/bemade_sports_clinic/views/sports_clinic_portal_views.xml +++ b/bemade_sports_clinic/views/sports_clinic_portal_views.xml @@ -165,18 +165,18 @@ - Active Injury - Notes + Active Injury + Notes - + - + diff --git a/bemade_sports_clinic/views/sports_patient_views.xml b/bemade_sports_clinic/views/sports_patient_views.xml index 6157357..3b1fccf 100644 --- a/bemade_sports_clinic/views/sports_patient_views.xml +++ b/bemade_sports_clinic/views/sports_patient_views.xml @@ -62,6 +62,49 @@ + + sports.patient.view.kanban + sports.patient + + + + + + + + + + + + + + + + +
+ +
+
+
+ +
+
+ Status: +
+
+
+ DOB: + +
+
+
+
+
+
+
+
+ sports.patient.view.form sports.patient diff --git a/bemade_stock_quant_valuation/__manifest__.py b/bemade_stock_quant_valuation/__manifest__.py index 19ffd69..05a8fca 100644 --- a/bemade_stock_quant_valuation/__manifest__.py +++ b/bemade_stock_quant_valuation/__manifest__.py @@ -18,7 +18,7 @@ # { 'name': 'Stock Quant Valuation', - 'version': '15.0.1.0.0', + 'version': '16.0.1.0.0', 'summary': 'Adds valuation to stock quant for better inventory adjustment management', 'description': '', 'category': 'Inventory', diff --git a/temporary_change_image_size/__manifest__.py b/temporary_change_image_size/__manifest__.py index 54a9ac5..611f8cf 100644 --- a/temporary_change_image_size/__manifest__.py +++ b/temporary_change_image_size/__manifest__.py @@ -1,6 +1,6 @@ { 'name': 'Temporary Change for Image Full Size', - 'version': '15.0.0.0.0', + 'version': '16.0.0.0.0', 'category': 'Extra Tools', 'summary': 'Temporary Change for Image Full Size', 'description': """