diff --git a/medical_queue_management/README.rst b/medical_queue_management/README.rst new file mode 100644 index 000000000..d79d0756d --- /dev/null +++ b/medical_queue_management/README.rst @@ -0,0 +1 @@ +TO DO diff --git a/medical_queue_management/__init__.py b/medical_queue_management/__init__.py new file mode 100644 index 000000000..aee8895e7 --- /dev/null +++ b/medical_queue_management/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/medical_queue_management/__manifest__.py b/medical_queue_management/__manifest__.py new file mode 100644 index 000000000..a7505c566 --- /dev/null +++ b/medical_queue_management/__manifest__.py @@ -0,0 +1,39 @@ +# Copyright 2023 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "Medical Queue Management", + "summary": """ + Manage patients with queue""", + "version": "16.0.1.0.0", + "license": "AGPL-3", + "author": "CreuBlanca", + "website": "https://github.com/tegin/cb-medical", + "depends": [ + "queue_management_display", + "cb_medical_careplan_sale", + "web_ir_actions_act_multi", + ], + "data": [ + "views/queue_location.xml", + "views/queue_location_action.xml", + "views/queue_location_group.xml", + "wizards/medical_careplan_add_plan_definition.xml", + "wizards/queue_token_location_kanban_assign.xml", + "views/queue_token_location.xml", + "views/queue_token.xml", + "views/res_partner_queue_location.xml", + "views/res_partner.xml", + "security/ir.model.access.csv", + "views/queue_area.xml", + "views/queue_location_area.xml", + "views/workflow_plan_definition.xml", + "views/medical_request_group.xml", + "views/medical_encounter.xml", + ], + "demo": [], + "qweb": [], + "assets": { + "web.assets_backend": ["/medical_queue_management/static/src/**/*.scss"], + }, +} diff --git a/medical_queue_management/migrations/14.0.1.1.0/post-migration.py b/medical_queue_management/migrations/14.0.1.1.0/post-migration.py new file mode 100644 index 000000000..318e27643 --- /dev/null +++ b/medical_queue_management/migrations/14.0.1.1.0/post-migration.py @@ -0,0 +1,19 @@ +# Copyright 2024 Creu Blanca +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html) +from openupgradelib import openupgrade + + +@openupgrade.migrate() +def migrate(env, version): + openupgrade.logged_query( + env.cr, + """ + UPDATE medical_request_group mrg + SET generate_queue_task = wpd.generate_queue_task, + queue_area_id = wpd.queue_area_id + FROM workflow_plan_definition wpd + WHERE wpd.id = mrg.plan_definition_id + AND mrg.plan_definition_action_id IS NULL + AND mrg.queue_token_location_id IS NOT NULL + """, + ) diff --git a/medical_queue_management/models/__init__.py b/medical_queue_management/models/__init__.py new file mode 100644 index 000000000..7a9b4c32b --- /dev/null +++ b/medical_queue_management/models/__init__.py @@ -0,0 +1,12 @@ +from . import medical_encounter +from . import medical_request_group +from . import workflow_plan_definition +from . import queue_location_area +from . import queue_area +from . import res_partner +from . import res_partner_queue_location +from . import queue_token +from . import queue_token_location +from . import queue_location_group +from . import queue_location_action +from . import queue_location diff --git a/medical_queue_management/models/medical_encounter.py b/medical_queue_management/models/medical_encounter.py new file mode 100644 index 000000000..e94fc0f85 --- /dev/null +++ b/medical_queue_management/models/medical_encounter.py @@ -0,0 +1,22 @@ +# Copyright 2023 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class MedicalEncounter(models.Model): + + _inherit = "medical.encounter" + + queue_token_id = fields.Many2one("queue.token", readonly=True) + + def _generate_token_vals(self): + return {} + + def _get_queue_token(self): + self.ensure_one() + if not self.queue_token_id: + self.queue_token_id = self.env["queue.token"].create( + self._generate_token_vals() + ) + return self.queue_token_id diff --git a/medical_queue_management/models/medical_request_group.py b/medical_queue_management/models/medical_request_group.py new file mode 100644 index 000000000..42f780bbf --- /dev/null +++ b/medical_queue_management/models/medical_request_group.py @@ -0,0 +1,95 @@ +# Copyright 2023 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class MedicalRequestGroup(models.Model): + + _inherit = "medical.request.group" + + queue_token_location_id = fields.Many2one("queue.token.location", readonly=True) + generate_queue_task = fields.Selection( + selection=lambda r: r.env["workflow.plan.definition"] + ._fields["generate_queue_task"] + .selection + ) + queue_area_id = fields.Many2one("queue.area") + + @api.constrains("performer_id", "center_id", "encounter_id", "fhir_state") + def _check_queue_token(self): + for record in self: + record._review_queue_token() + + def _clean_queue_token(self): + if self.queue_token_location_id: + # TODO: Maybe we should cancell in-progress or finished jobs, isn't it :S + if self.queue_token_location_id.state == "draft": + self.queue_token_location_id.state = "cancelled" + self.queue_token_location_id = False + return False + + def _review_queue_token(self): + if self.fhir_state == "cancelled": + return self._clean_queue_token() + if not self.generate_queue_task: + return self._clean_queue_token() + return getattr(self, "_review_queue_token_%s" % self.generate_queue_task)() + + def _review_queue_token_performer(self): + location_area = self.performer_id.queue_location_ids.filtered( + lambda r: r.center_id == self.center_id + ) + if not location_area: + location_area = self.performer_id.queue_location_ids.filtered( + lambda r: not r.center_id + ) + if not location_area: + return self._clean_queue_token() + return self._manage_queue_token( + location=location_area.location_id, group=location_area.group_id + ) + + def _review_queue_token_area(self): + area = self.queue_area_id + location_area = area.location_ids.filtered( + lambda r: r.center_id == self.center_id + ) + if not location_area: + return self._clean_queue_token() + return self._manage_queue_token( + location=location_area.location_id, group=location_area.group_id + ) + + def _manage_queue_token(self, location=False, group=False): + if not location and not group: + return self._clean_queue_token() + if location and group: + raise ValidationError( + _("Location and Group cannot be defined at the same time") + ) + if self.queue_token_location_id: + if self.queue_token_location_id.state in ["draft"]: + self.queue_token_location_id.write( + { + "location_id": location and location.id, + "group_id": group and group.id, + } + ) + return self.queue_token_location_id + return self._create_queue_token(location=location, group=group) + + def _create_queue_token(self, location=False, group=False): + self.queue_token_location_id = self.env["queue.token.location"].create( + self._create_queue_token_vals(location=location, group=group) + ) + return self.queue_token_location_id + + def _create_queue_token_vals(self, location=False, group=False): + token = self.encounter_id._get_queue_token() + return { + "group_id": group and group.id, + "location_id": location and location.id, + "token_id": token.id, + } diff --git a/medical_queue_management/models/queue_area.py b/medical_queue_management/models/queue_area.py new file mode 100644 index 000000000..d53ba3f4a --- /dev/null +++ b/medical_queue_management/models/queue_area.py @@ -0,0 +1,14 @@ +# Copyright 2023 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class QueueArea(models.Model): + + _name = "queue.area" + _description = "Queue Area" + + name = fields.Char(required=True) + active = fields.Boolean(default=True) + location_ids = fields.One2many("queue.location.area", inverse_name="area_id") diff --git a/medical_queue_management/models/queue_location.py b/medical_queue_management/models/queue_location.py new file mode 100644 index 000000000..1828dcc0a --- /dev/null +++ b/medical_queue_management/models/queue_location.py @@ -0,0 +1,12 @@ +# Copyright 2024 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class QueueLocation(models.Model): + + _inherit = "queue.location" + + action_ids = fields.Many2many("queue.location.action") + allows_flag = fields.Boolean() diff --git a/medical_queue_management/models/queue_location_action.py b/medical_queue_management/models/queue_location_action.py new file mode 100644 index 000000000..900b414dd --- /dev/null +++ b/medical_queue_management/models/queue_location_action.py @@ -0,0 +1,17 @@ +# Copyright 2024 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class QueueLocationAction(models.Model): + + _name = "queue.location.action" + _description = "Queue Location Action" # TODO + _order = "sequence asc" + _inherit = ["mail.thread", "mail.activity.mixin"] + + sequence = fields.Integer() + name = fields.Char() + icon = fields.Char() + color = fields.Char() diff --git a/medical_queue_management/models/queue_location_area.py b/medical_queue_management/models/queue_location_area.py new file mode 100644 index 000000000..59fa52b46 --- /dev/null +++ b/medical_queue_management/models/queue_location_area.py @@ -0,0 +1,24 @@ +# Copyright 2023 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class QueueLocationArea(models.Model): + _name = "queue.location.area" + _description = "Location Area" + + area_id = fields.Many2one("queue.area", required=True) + center_id = fields.Many2one( + "res.partner", domain=[("is_center", "=", True)], required=True + ) + location_id = fields.Many2one("queue.location") + group_id = fields.Many2one("queue.location.group") + + _sql_constraints = [ + ( + "center_area_uniq", + "UNIQUE(area_id, center_id)", + "Center for each area must be unique!", + ), + ] diff --git a/medical_queue_management/models/queue_location_group.py b/medical_queue_management/models/queue_location_group.py new file mode 100644 index 000000000..68e43b445 --- /dev/null +++ b/medical_queue_management/models/queue_location_group.py @@ -0,0 +1,11 @@ +# Copyright 2024 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class QueueLocationGroup(models.Model): + + _inherit = "queue.location.group" + + color = fields.Char() diff --git a/medical_queue_management/models/queue_token.py b/medical_queue_management/models/queue_token.py new file mode 100644 index 000000000..d507a4e8f --- /dev/null +++ b/medical_queue_management/models/queue_token.py @@ -0,0 +1,33 @@ +# Copyright 2023 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import api, fields, models + + +class QueueToken(models.Model): + + _inherit = "queue.token" + + encounter_ids = fields.One2many("medical.encounter", inverse_name="queue_token_id") + encounter_count = fields.Integer(compute="_compute_encounter_count") + + @api.depends("encounter_ids") + def _compute_encounter_count(self): + for record in self: + record.encounter_count = len(record.encounter_ids) + + def view_encounter(self): + self.ensure_one() + encounter = self.encounter_ids + encounter.ensure_one() + action = self.env["ir.actions.act_window"]._for_xml_id( + "medical_administration_encounter.action_encounter_medical_his" + ) + action["res_id"] = encounter.id + action["view_mode"] = "form" + action["views"] = [ + (view_id, view_mode) + for view_id, view_mode in action["views"] + if view_mode == "form" + ] + return action diff --git a/medical_queue_management/models/queue_token_location.py b/medical_queue_management/models/queue_token_location.py new file mode 100644 index 000000000..6cb620f71 --- /dev/null +++ b/medical_queue_management/models/queue_token_location.py @@ -0,0 +1,147 @@ +# Copyright 2023 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +import json + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class QueueTokenLocation(models.Model): + + _inherit = "queue.token.location" + request_group_ids = fields.One2many( + "medical.request.group", inverse_name="queue_token_location_id" + ) + patient_id = fields.Many2one( + "medical.patient", store=True, compute="_compute_encounter" + ) + encounter_id = fields.Many2one( + "medical.encounter", store=True, compute="_compute_encounter" + ) + encounter_identifier = fields.Char(related="encounter_id.internal_identifier") + request_group_count = fields.Integer(compute="_compute_request_group_count") + payor_id = fields.Many2one("res.partner", compute="_compute_payor") + info = fields.Text() + color = fields.Char(related="group_id.color") + action_data = fields.Char(compute="_compute_action_data") + allows_flag = fields.Boolean(related="location_id.allows_flag") + action_id = fields.Many2one("queue.location.action", readonly=True) + flagged = fields.Boolean() + + @api.depends("location_id") + def _compute_action_data(self): + for record in self: + actions = record.location_id.action_ids + if record.action_id not in actions: + actions |= record.action_id + record.action_data = json.dumps(actions.read(["name", "icon", "color"])) + + @api.depends("request_group_ids") + def _compute_payor(self): + for record in self: + record.payor_id = record.request_group_ids.payor_id[:1] + + @api.depends("token_id.encounter_ids") + def _compute_encounter(self): + for record in self: + encounter = record.token_id.encounter_ids + if not encounter or len(encounter) > 1: + record.patient_id = False + record.encounter_id = False + continue + record.patient_id = encounter.patient_id + record.encounter_id = encounter + + @api.depends("request_group_ids") + def _compute_request_group_count(self): + for record in self: + record.request_group_count = len(record.request_group_ids) + + def view_encounter(self): + self.ensure_one() + encounter = self.request_group_ids.encounter_id + encounter.ensure_one() + action = self.env["ir.actions.act_window"]._for_xml_id( + "medical_administration_encounter.action_encounter_medical_his" + ) + action["res_id"] = encounter.id + action["view_mode"] = "form" + action["views"] = [ + (view_id, view_mode) + for view_id, view_mode in action["views"] + if view_mode == "form" + ] + return action + + def action_kanban_call(self): + self.ensure_one() + if self.state != "in-progress": + raise ValidationError(_("State must be in-progress")) + self.with_context( + location_id=self.location_id.id, ignore_expected_location=True + ).action_call() + return {"type": "ir.actions.client", "tag": "soft_reload"} + + def action_kanban_leave(self): + self.ensure_one() + if self.state != "in-progress": + raise ValidationError(_("State must be in-progress")) + self.with_context(location_id=self.location_id.id).action_leave() + return {"type": "ir.actions.client", "tag": "soft_reload"} + + def action_kanban_back_to_draft(self): + self.ensure_one() + if self.state != "in-progress": + raise ValidationError(_("State must be in-progress")) + self.with_context(location_id=self.location_id.id).action_back_to_draft() + return {"type": "ir.actions.client", "tag": "soft_reload"} + + def action_kanban_cancel(self): + self.ensure_one() + self.with_context(location_id=self.location_id.id).action_cancel() + return {"type": "ir.actions.client", "tag": "soft_reload"} + + def action_kanban_assign(self): + self.ensure_one() + if self.location_id and self.state == "draft": + self.with_context(location_id=self.location_id.id).action_assign() + self.with_context( + location_id=self.location_id.id, ignore_expected_location=True + ).action_call() + return {"type": "ir.actions.client", "tag": "soft_reload"} + action = self.env["ir.actions.act_window"]._for_xml_id( + "medical_queue_management.queue_token_location_kanban_assign_act_window" + ) + action["context"] = {"default_token_location_id": self.id} + return action + + def edit_info_action(self): + self.ensure_one() + action = self.env["ir.actions.act_window"]._for_xml_id( + "medical_queue_management.queue_token_location_edit_info" + ) + action["res_id"] = self.id + return action + + def force_save(self): + self.ensure_one() + return { + "type": "ir.actions.act_multi", + "actions": [ + {"type": "ir.actions.act_window_close"}, + {"type": "ir.actions.client", "tag": "soft_reload"}, + ], + } + + def assign_location_action(self): + self.ensure_one() + action = self.env["queue.location.action"].browse( + self.env.context.get("action_id") + ) + if action and action in self.location_id.action_ids: + self.action_id = action + + def toggle_flagged(self): + for record in self: + record.flagged = not record.flagged diff --git a/medical_queue_management/models/res_partner.py b/medical_queue_management/models/res_partner.py new file mode 100644 index 000000000..41b5c0781 --- /dev/null +++ b/medical_queue_management/models/res_partner.py @@ -0,0 +1,13 @@ +# Copyright 2023 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResPartner(models.Model): + + _inherit = "res.partner" + + queue_location_ids = fields.One2many( + "res.partner.queue.location", inverse_name="practitioner_id" + ) diff --git a/medical_queue_management/models/res_partner_queue_location.py b/medical_queue_management/models/res_partner_queue_location.py new file mode 100644 index 000000000..126a4a9e7 --- /dev/null +++ b/medical_queue_management/models/res_partner_queue_location.py @@ -0,0 +1,23 @@ +# Copyright 2023 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class ResPartnerQueueLocation(models.Model): + + _name = "res.partner.queue.location" + _description = "Partner Queue Location" + + practitioner_id = fields.Many2one("res.partner", required=True) + center_id = fields.Many2one("res.partner", domain=[("is_center", "=", True)]) + location_id = fields.Many2one("queue.location") + group_id = fields.Many2one("queue.location.group") + + _sql_constraints = [ + ( + "center_area_uniq", + "UNIQUE(practitioner_id, center_id)", + "Center for each area must be unique!", + ), + ] diff --git a/medical_queue_management/models/workflow_plan_definition.py b/medical_queue_management/models/workflow_plan_definition.py new file mode 100644 index 000000000..f056dfb8b --- /dev/null +++ b/medical_queue_management/models/workflow_plan_definition.py @@ -0,0 +1,42 @@ +# Copyright 2023 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError + + +class WorkflowPlanDefinition(models.Model): + _inherit = "workflow.plan.definition" + + generate_queue_task = fields.Selection( + [("performer", "Performer"), ("area", "Area")] + ) + queue_area_id = fields.Many2one("queue.area") + + @api.constrains("generate_queue_task", "performer_required") + def _check_generate_token(self): + for record in self: + if ( + record.generate_queue_task == "performer" + and not record.performer_required + ): + raise ValidationError( + _( + "Performer must be required in order to generate task by performer" + ) + ) + + def _get_request_group_vals(self, vals): + result = super()._get_request_group_vals(vals) + if ( + self.generate_queue_task + and not vals.get("plan_definition_action_id") + and not self.env.context.get("do_not_generate_queue_task", False) + ): + result.update( + { + "generate_queue_task": self.generate_queue_task, + "queue_area_id": self.queue_area_id.id, + } + ) + return result diff --git a/medical_queue_management/security/ir.model.access.csv b/medical_queue_management/security/ir.model.access.csv new file mode 100644 index 000000000..81d66ec48 --- /dev/null +++ b/medical_queue_management/security/ir.model.access.csv @@ -0,0 +1,19 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink + +acl_queue_area_planner,queue.area Planner,model_queue_area,queue_management.group_queue_planner,1,0,0,0 +acl_queue_area_processor,queue.area Processor,model_queue_area,queue_management.group_queue_processor,1,0,0,0 +acl_queue_area_admin,queue.area Administrator,model_queue_area,queue_management.group_queue_admin,1,1,1,0 + +acl_queue_location_area_planner,queue.location.area Planner,model_queue_location_area,queue_management.group_queue_planner,1,0,0,0 +acl_queue_location_area_processor,queue.location.area Processor,model_queue_location_area,queue_management.group_queue_processor,1,0,0,0 +acl_queue_location_area_admin,queue.location.area Administrator,model_queue_location_area,queue_management.group_queue_admin,1,1,1,1 + +acl_partner_location_area_planner,partner.location.area Planner,model_res_partner_queue_location,queue_management.group_queue_planner,1,0,0,0 +acl_partner_location_area_processor,partner.location.area Processor,model_res_partner_queue_location,queue_management.group_queue_processor,1,0,0,0 +acl_partner_location_area_admin,partner.location.area Administrator,model_res_partner_queue_location,queue_management.group_queue_admin,1,1,1,1 + +acl_queue_token_location_kanban_assign,acl_queue_token_location_kanban_assign,model_queue_token_location_kanban_assign,queue_management.group_queue_processor,1,1,1,0 + +acl_queue_location_action_planner,acl_queue_location_action_planner,model_queue_location_action,queue_management.group_queue_planner,1,0,0,0 +acl_queue_location_action_processor,acl_queue_location_action_planner,model_queue_location_action,queue_management.group_queue_processor,1,0,0,0 +acl_queue_location_action_manage,acl_queue_location_action_manage,model_queue_location_action,queue_management.group_queue_admin,1,1,1,0 diff --git a/medical_queue_management/static/src/scss/medical_queue_management.scss b/medical_queue_management/static/src/scss/medical_queue_management.scss new file mode 100644 index 000000000..9bb7a7f5b --- /dev/null +++ b/medical_queue_management/static/src/scss/medical_queue_management.scss @@ -0,0 +1,59 @@ +.o_kanban_renderer { + &.o_kanban_dashboard { + &.o_kanban_queue_token_location { + .o_kanban_record { + min-width: 450px; + } + } + } + .oe_kanban_queue_location { + &.oe_kanban_queue_token_location_draft::after { + background-color: $yellow; + } + &.oe_kanban_queue_token_location_in-progress::after { + background-color: $cyan; + } + &.oe_kanban_queue_token_location_done::after { + background-color: $green; + } + &.oe_kanban_queue_token_location_cancelled { + background-color: $red; + } + .oe_kanban_queue_token_location_data { + width: calc(100% - 30px); + } + .oe_kanban_queue_token_location_actions { + width: 30px; + height: 100%; + top: 0px; + right: 0px; + position: absolute; + display: flex; + flex-direction: column; + height: 100%; + text-align: center; + align-items: center; + + .oe_kanban_queue_token_location_actions_item { + width: 100%; + padding: 5px; + flex-grow: 1; + display: flex; + align-items: center; + &.btn { + border-radius: 0px; + min-height: 15px; + } + .oe_kanban_queue_token_location_actions_item_icon { + width: 100%; + text-align: center; + } + } + } + } + .btn { + &.btn-fill { + width: 100%; + } + } +} diff --git a/medical_queue_management/tests/__init__.py b/medical_queue_management/tests/__init__.py new file mode 100644 index 000000000..22845dc4e --- /dev/null +++ b/medical_queue_management/tests/__init__.py @@ -0,0 +1 @@ +from . import test_medical_queue diff --git a/medical_queue_management/tests/test_medical_queue.py b/medical_queue_management/tests/test_medical_queue.py new file mode 100644 index 000000000..3a8c0b2d0 --- /dev/null +++ b/medical_queue_management/tests/test_medical_queue.py @@ -0,0 +1,757 @@ +# Copyright 2023 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + + +class TestMedicalQueue(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.payor = cls.env["res.partner"].create( + {"name": "Payor", "is_payor": True, "is_medical": True} + ) + cls.coverage_template = cls.env["medical.coverage.template"].create( + {"payor_id": cls.payor.id, "name": "Coverage"} + ) + cls.company = cls.env.ref("base.main_company") + cls.center = cls.env["res.partner"].create( + { + "name": "Center", + "is_medical": True, + "is_center": True, + "encounter_sequence_prefix": "S", + "stock_location_id": cls.env.ref("stock.warehouse0").id, + "stock_picking_type_id": cls.env["stock.picking.type"] + .search([], limit=1) + .id, + } + ) + cls.performer = cls.env["res.partner"].create( + { + "name": "Performer", + "is_medical": True, + "is_practitioner": True, + } + ) + cls.performer_2 = cls.env["res.partner"].create( + { + "name": "Performer", + "is_medical": True, + "is_practitioner": True, + } + ) + cls.center_2 = cls.env["res.partner"].create( + { + "name": "Center2", + "is_medical": True, + "is_center": True, + "encounter_sequence_prefix": "X", + "stock_location_id": cls.env.ref("stock.warehouse0").id, + "stock_picking_type_id": cls.env["stock.picking.type"] + .search([], limit=1) + .id, + } + ) + cls.location = cls.env["res.partner"].create( + { + "name": "Location", + "is_medical": True, + "is_location": True, + "center_id": cls.center.id, + "stock_location_id": cls.env.ref("stock.warehouse0").id, + "stock_picking_type_id": cls.env["stock.picking.type"] + .search([], limit=1) + .id, + } + ) + cls.location_2 = cls.env["res.partner"].create( + { + "name": "Location 2", + "is_medical": True, + "is_location": True, + "center_id": cls.center_2.id, + "stock_location_id": cls.env.ref("stock.warehouse0").id, + "stock_picking_type_id": cls.env["stock.picking.type"] + .search([], limit=1) + .id, + } + ) + cls.agreement = cls.env["medical.coverage.agreement"].create( + { + "name": "Agreement", + "center_ids": [(4, cls.center.id), (4, cls.center_2.id)], + "coverage_template_ids": [(4, cls.coverage_template.id)], + "company_id": cls.company.id, + "authorization_method_id": cls.env.ref( + "medical_financial_coverage_request.without" + ).id, + "authorization_format_id": cls.env.ref( + "medical_financial_coverage_request.format_anything" + ).id, + } + ) + cls.patient_01 = cls.create_patient("Patient 01") + cls.coverage_01 = cls.env["medical.coverage"].create( + { + "patient_id": cls.patient_01.id, + "coverage_template_id": cls.coverage_template.id, + } + ) + cls.product_01 = cls.create_product("Medical resonance") + cls.product_02 = cls.create_product("Report") + cls.product_03 = cls.env["product.product"].create( + { + "type": "service", + "name": "Clinical material", + "is_medication": False, + "lst_price": 10.0, + } + ) + + cls.product_04 = cls.create_product("MR complex") + cls.plan_definition = cls.env["workflow.plan.definition"].create( + {"name": "Plan", "is_billable": True} + ) + + cls.plan_definition2 = cls.env["workflow.plan.definition"].create( + {"name": "Plan2", "is_billable": True} + ) + + cls.activity = cls.env["workflow.activity.definition"].create( + { + "name": "Activity", + "service_id": cls.product_02.id, + "model_id": cls.env.ref( + "medical_clinical_procedure." "model_medical_procedure_request" + ).id, + } + ) + cls.activity2 = cls.env["workflow.activity.definition"].create( + { + "name": "Activity2", + "service_id": cls.product_03.id, + "model_id": cls.env.ref( + "medical_clinical_procedure." "model_medical_procedure_request" + ).id, + } + ) + cls.env["workflow.plan.definition.action"].create( + { + "activity_definition_id": cls.activity.id, + "direct_plan_definition_id": cls.plan_definition.id, + "is_billable": False, + "name": "Action", + } + ) + cls.env["workflow.plan.definition.action"].create( + { + "activity_definition_id": cls.activity2.id, + "direct_plan_definition_id": cls.plan_definition.id, + "is_billable": False, + "name": "Action2", + } + ) + cls.env["workflow.plan.definition.action"].create( + { + "activity_definition_id": cls.activity.id, + "direct_plan_definition_id": cls.plan_definition2.id, + "is_billable": False, + "name": "Action", + } + ) + cls.env["workflow.plan.definition.action"].create( + { + "activity_definition_id": cls.activity2.id, + "direct_plan_definition_id": cls.plan_definition2.id, + "is_billable": False, + "name": "Action2", + } + ) + cls.env["workflow.plan.definition.action"].create( + { + "activity_definition_id": cls.activity2.id, + "direct_plan_definition_id": cls.plan_definition2.id, + "is_billable": False, + "name": "Action3", + } + ) + cls.agreement_line = cls.env["medical.coverage.agreement.item"].create( + { + "product_id": cls.product_01.id, + "coverage_agreement_id": cls.agreement.id, + "plan_definition_id": cls.plan_definition.id, + "total_price": 100, + "authorization_method_id": cls.env.ref( + "medical_financial_coverage_request.without" + ).id, + "authorization_format_id": cls.env.ref( + "medical_financial_coverage_request.format_anything" + ).id, + } + ) + cls.agreement_line2 = cls.env["medical.coverage.agreement.item"].create( + { + "product_id": cls.product_03.id, + "coverage_agreement_id": cls.agreement.id, + "plan_definition_id": cls.plan_definition.id, + "total_price": 100.0, + "authorization_method_id": cls.env.ref( + "medical_financial_coverage_request.without" + ).id, + "authorization_format_id": cls.env.ref( + "medical_financial_coverage_request.format_anything" + ).id, + } + ) + cls.agreement_line3 = cls.env["medical.coverage.agreement.item"].create( + { + "product_id": cls.product_04.id, + "coverage_agreement_id": cls.agreement.id, + "plan_definition_id": cls.plan_definition2.id, + "total_price": 100.0, + "authorization_method_id": cls.env.ref( + "medical_financial_coverage_request.without" + ).id, + "authorization_format_id": cls.env.ref( + "medical_financial_coverage_request.format_anything" + ).id, + } + ) + cls.queue_location = cls.env["queue.location"].create( + {"name": "Queue Location"} + ) + cls.queue_location_2 = cls.env["queue.location"].create( + {"name": "Queue Location 2"} + ) + cls.queue_location_group = cls.env["queue.location.group"].create( + {"name": "Queue Location Group"} + ) + cls.queue_area = cls.env["queue.area"].create({"name": "Area"}) + + @classmethod + def create_patient(cls, name): + return cls.env["medical.patient"].create({"name": name}) + + @classmethod + def create_product(cls, name): + return cls.env["product.product"].create({"type": "service", "name": name}) + + @classmethod + def create_practitioner(cls, name): + return cls.env["res.partner"].create( + {"name": name, "is_practitioner": True, "agent": True} + ) + + def create_careplan_and_group(self, line=None, extra_vals=None): + if line is None: + line = self.agreement_line + if extra_vals is None: + extra_vals = {} + encounter = self.env["medical.encounter"].create( + {"patient_id": self.patient_01.id, "center_id": self.center.id} + ) + careplan = self.env["medical.careplan"].create( + { + "patient_id": encounter.patient_id.id, + "encounter_id": encounter.id, + "center_id": encounter.center_id.id, + "coverage_id": self.coverage_01.id, + } + ) + wizard_vals = { + "careplan_id": careplan.id, + "agreement_line_id": line.id, + } + wizard_vals.update(extra_vals) + wizard = self.env["medical.careplan.add.plan.definition"].create(wizard_vals) + wizard.run() + group = self.env["medical.request.group"].search( + [("careplan_id", "=", careplan.id)] + ) + group.ensure_one() + self.assertEqual(group.center_id, encounter.center_id) + return encounter, careplan, group + + def test_no_queue_token(self): + encounter, careplan, group = self.create_careplan_and_group() + self.assertFalse(encounter.queue_token_id) + self.assertFalse(group.queue_token_location_id) + + def test_queue_token_area_no_matching(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center_2.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertFalse(encounter.queue_token_id) + self.assertFalse(group.queue_token_location_id) + + def test_queue_token_area_location_matching(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + # Testing some stuff of views here + self.assertTrue(encounter.queue_token_id.encounter_count, 1) + action = encounter.queue_token_id.view_encounter() + self.assertEqual( + encounter, self.env[action["res_model"]].browse(action["res_id"]) + ) + self.assertTrue(group.queue_token_location_id.request_group_count, 1) + action = group.queue_token_location_id.view_encounter() + self.assertEqual( + encounter, self.env[action["res_model"]].browse(action["res_id"]) + ) + + def test_queue_token_area_group_matching(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "group_id": self.queue_location_group.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + + def test_queue_token_area_error_matching(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + "group_id": self.queue_location_group.id, + } + ) + with self.assertRaises(ValidationError): + encounter, careplan, group = self.create_careplan_and_group() + + def test_queue_token_area_misconfigured_matching(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertFalse(encounter.queue_token_id) + self.assertFalse(group.queue_token_location_id) + + def test_queue_token_cancelling(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "group_id": self.queue_location_group.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.state, "draft") + token_location = group.queue_token_location_id + group.cancel() + self.assertFalse(group.queue_token_location_id) + token_location.invalidate_recordset() + self.assertEqual(token_location.state, "cancelled") + + def test_queue_token_performer_location_matching(self): + self.plan_definition.write( + { + "generate_queue_task": "performer", + "performer_required": True, + } + ) + self.env["res.partner.queue.location"].create( + { + "practitioner_id": self.performer.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group( + extra_vals={ + "performer_id": self.performer.id, + } + ) + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + + def test_queue_token_performer_location_matching_dont_send(self): + self.plan_definition.write( + { + "generate_queue_task": "performer", + "performer_required": True, + } + ) + self.env["res.partner.queue.location"].create( + { + "practitioner_id": self.performer.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group( + extra_vals={"performer_id": self.performer.id, "send_to_queue": False} + ) + self.assertFalse(encounter.queue_token_id) + + def test_queue_token_change_performer_different_location(self): + self.plan_definition.write( + { + "generate_queue_task": "performer", + "performer_required": True, + } + ) + self.env["res.partner.queue.location"].create( + { + "practitioner_id": self.performer.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + self.env["res.partner.queue.location"].create( + { + "practitioner_id": self.performer_2.id, + "center_id": self.center.id, + "group_id": self.queue_location_group.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group( + extra_vals={ + "performer_id": self.performer.id, + } + ) + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.location_id, self.queue_location) + self.assertFalse(group.queue_token_location_id.group_id) + group.performer_id = self.performer_2 + self.assertFalse(group.queue_token_location_id.location_id) + self.assertEqual( + group.queue_token_location_id.group_id, self.queue_location_group + ) + + def test_queue_token_change_performer_cancel(self): + self.plan_definition.write( + { + "generate_queue_task": "performer", + "performer_required": True, + } + ) + self.env["res.partner.queue.location"].create( + { + "practitioner_id": self.performer.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group( + extra_vals={ + "performer_id": self.performer.id, + } + ) + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.location_id, self.queue_location) + self.assertFalse(group.queue_token_location_id.group_id) + queue_token_location = group.queue_token_location_id + group.performer_id = self.performer_2 + self.assertFalse(group.queue_token_location_id) + self.assertEqual(queue_token_location.state, "cancelled") + + def test_queue_token_performer_all_center(self): + self.plan_definition.write( + { + "generate_queue_task": "performer", + "performer_required": True, + } + ) + self.env["res.partner.queue.location"].create( + { + "practitioner_id": self.performer.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group( + extra_vals={ + "performer_id": self.performer.id, + } + ) + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.location_id, self.queue_location) + self.assertFalse(group.queue_token_location_id.group_id) + + def test_queue_token_performer_group_matching(self): + self.plan_definition.write( + { + "generate_queue_task": "performer", + "performer_required": True, + } + ) + self.env["res.partner.queue.location"].create( + { + "practitioner_id": self.performer.id, + "center_id": self.center.id, + "group_id": self.queue_location_group.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group( + extra_vals={ + "performer_id": self.performer.id, + } + ) + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + + def test_queue_token_performer_misconfigured_matching(self): + self.plan_definition.write( + { + "generate_queue_task": "performer", + "performer_required": True, + } + ) + self.env["res.partner.queue.location"].create( + { + "practitioner_id": self.performer.id, + "center_id": self.center.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group( + extra_vals={ + "performer_id": self.performer.id, + } + ) + self.assertFalse(encounter.queue_token_id) + self.assertFalse(group.queue_token_location_id) + + def test_kanban_group(self): + self.queue_location.group_ids = self.queue_location_group + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "group_id": self.queue_location_group.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.state, "draft") + token_location = group.queue_token_location_id + action = token_location.action_kanban_assign() + self.assertTrue(isinstance(action, dict)) + self.env[action["res_model"]].with_context(**action["context"]).create( + {"location_id": self.queue_location.id} + ).assign() + self.assertEqual(token_location.state, "in-progress") + self.assertTrue(token_location.expected_location_id) + token_location.action_kanban_back_to_draft() + self.assertEqual(token_location.state, "draft") + action = token_location.action_kanban_assign() + self.assertTrue(isinstance(action, dict)) + self.env[action["res_model"]].with_context(**action["context"]).create( + {"location_id": self.queue_location.id} + ).assign() + self.assertEqual(token_location.state, "in-progress") + token_location.expected_location_id = False + token_location.action_kanban_call() + self.assertTrue(token_location.expected_location_id) + token_location.action_kanban_leave() + self.assertEqual(token_location.state, "done") + + def test_kanban_location(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.state, "draft") + token_location = group.queue_token_location_id + token_location.action_kanban_assign() + self.assertEqual(token_location.state, "in-progress") + self.assertTrue(token_location.expected_location_id) + token_location.action_kanban_back_to_draft() + self.assertEqual(token_location.state, "draft") + token_location.action_kanban_assign() + self.assertEqual(token_location.state, "in-progress") + token_location.expected_location_id = False + token_location.action_kanban_call() + self.assertTrue(token_location.expected_location_id) + token_location.action_kanban_leave() + self.assertEqual(token_location.state, "done") + + def test_kanban_call_exception(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.state, "draft") + token_location = group.queue_token_location_id + with self.assertRaises(ValidationError): + token_location.action_kanban_call() + + def test_kanban_leave_exception(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.state, "draft") + token_location = group.queue_token_location_id + with self.assertRaises(ValidationError): + token_location.action_kanban_leave() + + def test_kanban_back_to_draft_exception(self): + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "location_id": self.queue_location.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.state, "draft") + token_location = group.queue_token_location_id + with self.assertRaises(ValidationError): + token_location.action_kanban_back_to_draft() + + def test_kanban_reassign(self): + self.queue_location.group_ids = self.queue_location_group + self.queue_location_2.group_ids = self.queue_location_group + self.plan_definition.write( + { + "generate_queue_task": "area", + "queue_area_id": self.queue_area.id, + } + ) + self.env["queue.location.area"].create( + { + "area_id": self.queue_area.id, + "center_id": self.center.id, + "group_id": self.queue_location_group.id, + } + ) + encounter, careplan, group = self.create_careplan_and_group() + self.assertTrue(encounter.queue_token_id) + self.assertTrue(group.queue_token_location_id) + self.assertEqual(group.queue_token_location_id.state, "draft") + token_location = group.queue_token_location_id + action = token_location.action_kanban_assign() + self.assertTrue(isinstance(action, dict)) + self.env[action["res_model"]].with_context(**action["context"]).create( + {"location_id": self.queue_location.id} + ).assign() + self.assertEqual(token_location.state, "in-progress") + self.assertTrue(token_location.expected_location_id) + self.assertEqual(self.queue_location, token_location.expected_location_id) + action = token_location.with_context( + default_do_not_call=True + ).action_kanban_assign() + self.assertTrue(isinstance(action, dict)) + with self.assertRaises(ValidationError): + self.env[action["res_model"]].with_context(**action["context"]).create( + {"location_id": self.queue_location.id} + ).assign() + self.env[action["res_model"]].with_context(**action["context"]).create( + {"location_id": self.queue_location_2.id} + ).assign() + self.assertEqual(self.queue_location_2, token_location.expected_location_id) diff --git a/medical_queue_management/views/medical_encounter.xml b/medical_queue_management/views/medical_encounter.xml new file mode 100644 index 000000000..06ec657e2 --- /dev/null +++ b/medical_queue_management/views/medical_encounter.xml @@ -0,0 +1,17 @@ + + + + + + + diff --git a/medical_queue_management/views/medical_request_group.xml b/medical_queue_management/views/medical_request_group.xml new file mode 100644 index 000000000..d21aff953 --- /dev/null +++ b/medical_queue_management/views/medical_request_group.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/medical_queue_management/views/queue_area.xml b/medical_queue_management/views/queue_area.xml new file mode 100644 index 000000000..cb0c16dee --- /dev/null +++ b/medical_queue_management/views/queue_area.xml @@ -0,0 +1,61 @@ + + + + + + queue.area.form (in medical_queue_management) + queue.area + +
+
+ + + + + + + + + + + + + + + + queue.area.search (in medical_queue_management) + queue.area + + + + + + + + + queue.area.tree (in medical_queue_management) + queue.area + + + + + + + + + Area + queue.area + tree,form + [] + {} + + + + Area + + + + + + diff --git a/medical_queue_management/views/queue_location.xml b/medical_queue_management/views/queue_location.xml new file mode 100644 index 000000000..16ba314d7 --- /dev/null +++ b/medical_queue_management/views/queue_location.xml @@ -0,0 +1,22 @@ + + + + + + queue.location + + + + + + + + + + + + diff --git a/medical_queue_management/views/queue_location_action.xml b/medical_queue_management/views/queue_location_action.xml new file mode 100644 index 000000000..257cc27d2 --- /dev/null +++ b/medical_queue_management/views/queue_location_action.xml @@ -0,0 +1,64 @@ + + + + + + queue.location.action + +
+
+ +
+ + + + + + + + + +
+
+
+ + + queue.location.action + + + + + + + + + queue.location.action + + + + + + + + + + + Queue Location Action + queue.location.action + tree,form + [] + {} + + + + Location Actions + + + + + +
diff --git a/medical_queue_management/views/queue_location_area.xml b/medical_queue_management/views/queue_location_area.xml new file mode 100644 index 000000000..d8bc83fed --- /dev/null +++ b/medical_queue_management/views/queue_location_area.xml @@ -0,0 +1,49 @@ + + + + + + queue.location.area.form (in medical_queue_management) + queue.location.area + +
+
+ +
+ + + + + + + + + +
+
+
+ + + queue.location.area.tree (in medical_queue_management) + queue.location.area + + + + + + + + + +
diff --git a/medical_queue_management/views/queue_location_group.xml b/medical_queue_management/views/queue_location_group.xml new file mode 100644 index 000000000..d32f3e70e --- /dev/null +++ b/medical_queue_management/views/queue_location_group.xml @@ -0,0 +1,21 @@ + + + + + + queue.location.group + + + + + + + + + + + diff --git a/medical_queue_management/views/queue_token.xml b/medical_queue_management/views/queue_token.xml new file mode 100644 index 000000000..37dcec8e1 --- /dev/null +++ b/medical_queue_management/views/queue_token.xml @@ -0,0 +1,26 @@ + + + + + + queue.token.form (in medical_queue_management) + queue.token + + +
+
+
+
+ + + +
diff --git a/medical_queue_management/views/queue_token_location.xml b/medical_queue_management/views/queue_token_location.xml new file mode 100644 index 000000000..7d9e6e9d0 --- /dev/null +++ b/medical_queue_management/views/queue_token_location.xml @@ -0,0 +1,271 @@ + + + + + + queue.token.location.tree (in medical_queue_management) + queue.token.location + + + + + + + + + + + + + queue.token.location.tree (in medical_queue_management) + queue.token.location + + + + + + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ +
+
+ +
+
+
+

+
+
+
+ +
+
+ +
+ +
+
+
+
+
+
+
+
+
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ + + + +
+
+
+
+
+
+
+ + + queue.token.location.tree (in medical_queue_management) + queue.token.location + 99 + +
+ + + +
+
+
+
+
+ + Edit Info Locations + queue.token.location + form + new + [('encounter_id', '!=', False)] + {} + + + + form + + + + + + My Token Locations + queue.token.location + kanban + [] + {} + + + + My Token Locations + + + + + + +
diff --git a/medical_queue_management/views/res_partner.xml b/medical_queue_management/views/res_partner.xml new file mode 100644 index 000000000..176353989 --- /dev/null +++ b/medical_queue_management/views/res_partner.xml @@ -0,0 +1,25 @@ + + + + + + res.partner.form (in medical_queue_management) + res.partner + + + + + + + + + + + + + diff --git a/medical_queue_management/views/res_partner_queue_location.xml b/medical_queue_management/views/res_partner_queue_location.xml new file mode 100644 index 000000000..fd74e0007 --- /dev/null +++ b/medical_queue_management/views/res_partner_queue_location.xml @@ -0,0 +1,47 @@ + + + + + + res.partner.queue.location.form (in medical_queue_management) + res.partner.queue.location + +
+
+ + + + + + + + + + + + + + + res.partner.queue.location.tree (in medical_queue_management) + res.partner.queue.location + + + + + + + + + + diff --git a/medical_queue_management/views/workflow_plan_definition.xml b/medical_queue_management/views/workflow_plan_definition.xml new file mode 100644 index 000000000..94d37baae --- /dev/null +++ b/medical_queue_management/views/workflow_plan_definition.xml @@ -0,0 +1,25 @@ + + + + + workflow.plan.definition.form (in medical_queue_management) + workflow.plan.definition + + + + + + + + + + diff --git a/medical_queue_management/wizards/__init__.py b/medical_queue_management/wizards/__init__.py new file mode 100644 index 000000000..ff0949112 --- /dev/null +++ b/medical_queue_management/wizards/__init__.py @@ -0,0 +1,2 @@ +from . import medical_careplan_add_plan_definition +from . import queue_token_location_kanban_assign diff --git a/medical_queue_management/wizards/medical_careplan_add_plan_definition.py b/medical_queue_management/wizards/medical_careplan_add_plan_definition.py new file mode 100644 index 000000000..0a89426b2 --- /dev/null +++ b/medical_queue_management/wizards/medical_careplan_add_plan_definition.py @@ -0,0 +1,16 @@ +# Copyright 2024 CreuBlanca +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class MedicalCareplanAddPlanDefinition(models.TransientModel): + + _inherit = "medical.careplan.add.plan.definition" + + send_to_queue = fields.Boolean(default=True) + + def _get_context(self): + result = super()._get_context() + result["do_not_generate_queue_task"] = not self.send_to_queue + return result diff --git a/medical_queue_management/wizards/medical_careplan_add_plan_definition.xml b/medical_queue_management/wizards/medical_careplan_add_plan_definition.xml new file mode 100644 index 000000000..0589eb2cb --- /dev/null +++ b/medical_queue_management/wizards/medical_careplan_add_plan_definition.xml @@ -0,0 +1,22 @@ + + + + + + medical.careplan.add.plan.definition + + + + + + + + + + + + diff --git a/medical_queue_management/wizards/queue_token_location_kanban_assign.py b/medical_queue_management/wizards/queue_token_location_kanban_assign.py new file mode 100644 index 000000000..0cf882221 --- /dev/null +++ b/medical_queue_management/wizards/queue_token_location_kanban_assign.py @@ -0,0 +1,39 @@ +# Copyright 2024 Dixmit +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class QueueTokenLocationKanbanAssign(models.TransientModel): + + _name = "queue.token.location.kanban.assign" + _description = "Queue Token Location Kanban Assign" + + token_location_id = fields.Many2one("queue.token.location", required=True) + group_id = fields.Many2one(related="token_location_id.group_id") + location_id = fields.Many2one("queue.location", required=True) + do_not_call = fields.Boolean() + + def assign(self): + if ( + self.token_location_id.location_id != self.location_id + and self.token_location_id.state == "in-progress" + ): + self.token_location_id._action_back_to_draft( + self.token_location_id.location_id + ) + self.token_location_id.with_context( + location_id=self.location_id.id + ).action_assign() + if not self.do_not_call: + self.token_location_id.with_context( + location_id=self.location_id.id, + ignore_expected_location=True, + ).action_call() + return { + "type": "ir.actions.act_multi", + "actions": [ + {"type": "ir.actions.act_window_close"}, + {"type": "ir.actions.client", "tag": "soft_reload"}, + ], + } diff --git a/medical_queue_management/wizards/queue_token_location_kanban_assign.xml b/medical_queue_management/wizards/queue_token_location_kanban_assign.xml new file mode 100644 index 000000000..97a0841f8 --- /dev/null +++ b/medical_queue_management/wizards/queue_token_location_kanban_assign.xml @@ -0,0 +1,42 @@ + + + + + + queue.token.location.kanban.assign + +
+ + + + + + + +
+
+
+
+
+ + + Queue Token Location Kanban Assign + queue.token.location.kanban.assign + form + {} + new + + + +
diff --git a/setup/medical_queue_management/odoo/addons/medical_queue_management b/setup/medical_queue_management/odoo/addons/medical_queue_management new file mode 120000 index 000000000..6e19933a6 --- /dev/null +++ b/setup/medical_queue_management/odoo/addons/medical_queue_management @@ -0,0 +1 @@ +../../../../medical_queue_management \ No newline at end of file diff --git a/setup/medical_queue_management/setup.py b/setup/medical_queue_management/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/medical_queue_management/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/test-requirements.txt b/test-requirements.txt index eb856f922..83f713cfe 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -27,5 +27,9 @@ odoo-addon-sale-commission-cancel@git+https://github.com/tegin/cb-addons.git@16. odoo-addon-sale-third-party@git+https://github.com/tegin/cb-addons.git@16.0#subdirectory=setup/sale_third_party odoo-addon-sequence-parser@git+https://github.com/tegin/cb-addons.git@16.0#subdirectory=setup/sequence_parser +# kiwi +odoo-addon-queue-management-display@git+https://github.com/tegin/kiwi.git@16.0#subdirectory=setup/queue_management_display +odoo-addon-queue-management@git+https://github.com/tegin/kiwi.git@16.0#subdirectory=setup/queue_management + # TO REMOVE odoo-addon-commission-delegated-partner@git+https://github.com/dixmit/commission.git@16.0-mig-sale_commission_delegated_partner#subdirectory=setup/commission_delegated_partner