diff --git a/pms/demo/pms_folio.xml b/pms/demo/pms_folio.xml index 63d8651b..bbc7e1f5 100644 --- a/pms/demo/pms_folio.xml +++ b/pms/demo/pms_folio.xml @@ -93,7 +93,7 @@ - out + out_service - out + out_service - "out" + "out_service" diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py index 1899af85..550b8050 100644 --- a/pms/models/pms_folio.py +++ b/pms/models/pms_folio.py @@ -282,7 +282,11 @@ class PmsFolio(models.Model): help="The type of the reservation. " "Can be 'Normal', 'Staff' or 'Out of Service'", default=lambda *a: "normal", - selection=[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")], + selection=[ + ("normal", "Normal"), + ("staff", "Staff"), + ("out_service", "Out of Service"), + ], ) date_order = fields.Datetime( string="Order Date", @@ -795,7 +799,7 @@ def _compute_ratio_checkin_data(self): ) def _compute_amount(self): for record in self: - if record.reservation_type in ("staff", "out"): + if record.reservation_type in ("staff", "out_service"): vals = { "pending_amount": 0, "invoices_paid": 0, diff --git a/pms/models/pms_reservation.py b/pms/models/pms_reservation.py index f62d4d13..6ef305f8 100644 --- a/pms/models/pms_reservation.py +++ b/pms/models/pms_reservation.py @@ -573,7 +573,7 @@ class PmsReservation(models.Model): def _compute_date_order(self): for record in self: - record.date_order = datetime.datetime.today() + record.date_order = fields.Date.today() # TODO: # consider near_to_checkin & pending_notifications to order @@ -676,7 +676,7 @@ def _compute_allowed_room_ids(self): @api.depends("reservation_type", "agency_id", "folio_id") def _compute_partner_id(self): for reservation in self: - if reservation.reservation_type == "out": + if reservation.reservation_type == "out_service": reservation.partner_id = reservation.pms_property_id.partner_id.id elif not reservation.partner_id: if reservation.folio_id: @@ -1093,7 +1093,7 @@ def _compute_shared_folio(self): def _compute_checkin_partner_count(self): for record in self: - if record.reservation_type != "out": + if record.reservation_type != "out_service": record.checkin_partner_count = len(record.checkin_partner_ids) record.checkin_partner_pending_count = record.adults - len( record.checkin_partner_ids diff --git a/pms/models/pms_room.py b/pms/models/pms_room.py index 5511930c..4d3a8351 100644 --- a/pms/models/pms_room.py +++ b/pms/models/pms_room.py @@ -39,6 +39,7 @@ class PmsRoom(models.Model): default=lambda self: self.env.user.get_active_property_ids()[0], comodel_name="pms.property", ondelete="restrict", + check_pms_properties=True, ) room_type_id = fields.Many2one( string="Property Room Type", @@ -77,6 +78,42 @@ class PmsRoom(models.Model): " Order, Delivery Order and Customer Invoice/Credit Note", translate=True, ) + occupation_status = fields.Selection( + string="Room Status", + selection=[ + ("occupied", "Occupied"), + ("free", "Free"), + ("out_service", "Out of service"), + ], + help="Room status based on occupancy, next arrival, or out of service", + compute="_compute_occupation_status", + ) + next_departure_reservation_id = fields.Many2one( + string="Currently reservation", + comodel_name="pms.reservation", + help="Reservation that currently occupies the room", + compute="_compute_occupation_status", + ) + next_departure_datetime = fields.Datetime( + string="Next departure", + help="Expected departure from the reservation " "currently occupying the room", + compute="_compute_occupation_status", + ) + next_arrival_reservation_id = fields.Many2one( + string="Next Reservation", + comodel_name="pms.reservation", + help="Next expected reservation", + compute="_compute_next_arrival", + ) + next_arrival_datetime = fields.Datetime( + string="Next arrival", + help="Next expected arrival date", + compute="_compute_next_arrival", + ) + color = fields.Integer( + string="Color Index", + default=1, + ) _sql_constraints = [ ( @@ -87,6 +124,63 @@ class PmsRoom(models.Model): ) ] + def _compute_occupation_status(self): + for record in self: + # Set Status and next departure fields + current_reservation = ( + self.env["pms.reservation.line"] + .search( + [ + ("state", "in", ("onboard", "departure_delayed")), + ("room_id", "=", record.id), + ] + ) + .reservation_id + ) + + record.occupation_status = "free" + record.next_departure_datetime = False + record.next_departure_reservation_id = False + + if current_reservation: + record.occupation_status = "occupied" + record.next_departure_reservation_id = current_reservation + record.next_departure_datetime = current_reservation.checkout_datetime + if current_reservation.reservation_type == "out_service": + record.occupation_status = "out_service" + + def _compute_next_arrival(self): + today = fields.Date.today() + for record in self: + future_reservation_dates = ( + self.env["pms.reservation.line"] + .search( + [ + ("state", "in", ("draft", "confirm", "arrival_delayed")), + ("room_id", "=", record.id), + ("date", ">=", today), + ] + ) + .mapped("date") + ) + + record.next_arrival_reservation_id = ( + self.env["pms.reservation.line"] + .search( + [ + ("room_id", "=", record.id), + ("date", "=", min(future_reservation_dates)), + ] + ) + .reservation_id + ) + + record.next_arrival_datetime = ( + record.next_arrival_reservation_id.checkin_datetime + if record.next_arrival_reservation_id + else False + ) + def name_get(self): result = [] for room in self: diff --git a/pms/views/pms_folio_views.xml b/pms/views/pms_folio_views.xml index ca945c88..8c55d40e 100644 --- a/pms/views/pms_folio_views.xml +++ b/pms/views/pms_folio_views.xml @@ -160,12 +160,12 @@ diff --git a/pms/views/pms_reservation_views.xml b/pms/views/pms_reservation_views.xml index c9599b96..57a18bf8 100644 --- a/pms/views/pms_reservation_views.xml +++ b/pms/views/pms_reservation_views.xml @@ -215,7 +215,7 @@ name="partner_id" default_focus="1" placeholder="Lastname, Firstname" - attrs="{'invisible':[('reservation_type','in',('out'))]}" + attrs="{'invisible':[('reservation_type','in',('out_service'))]}" required="1" />

@@ -259,7 +259,7 @@ @@ -296,7 +296,7 @@ nolabel="1" name="partner_internal_comment" string="Partner Note" - attrs="{'invisible': [('reservation_type','in',('out'))]}" + attrs="{'invisible': [('reservation_type','in',('out_service'))]}" /> @@ -323,11 +323,11 @@ /> @@ -575,7 +575,7 @@ diff --git a/pms_housekeeping/__manifest__.py b/pms_housekeeping/__manifest__.py index ca3528f2..1ff19de0 100644 --- a/pms_housekeeping/__manifest__.py +++ b/pms_housekeeping/__manifest__.py @@ -14,11 +14,11 @@ "hr", ], "data": [ - # "wizard/housekeeping_rack.xml", - "views/pms_room_view.xml", - "views/pms_reservation_view.xml", - "views/pms_housekeeping_task_view.xml", - "views/pms_housekeeping_views.xml", + "views/pms_housekeeping_templates.xml", + "views/pms_reservation_views.xml", + "views/pms_housekeeping_task_type_views.xml", + "views/pms_housekeeping_task_views.xml", + "views/pms_room_views.xml", "security/ir.model.access.csv", "data/cron_jobs.xml", ], diff --git a/pms_housekeeping/demo/pms_housekeeping.xml b/pms_housekeeping/demo/pms_housekeeping.xml index ea698c10..0ffc0ce5 100644 --- a/pms_housekeeping/demo/pms_housekeeping.xml +++ b/pms_housekeeping/demo/pms_housekeeping.xml @@ -2,63 +2,63 @@ - + Full clean exit - + Soft clean occupied - + Review - + Inspect - + exit - + Inspect - + occupied - + - + to_do - + - + to_do - + - + draft - + - + draft - + Need clean it again - + to_do - + - + draft diff --git a/pms_housekeeping/models/__init__.py b/pms_housekeeping/models/__init__.py index 4ec8da67..b2c15eb6 100644 --- a/pms_housekeeping/models/__init__.py +++ b/pms_housekeeping/models/__init__.py @@ -1,7 +1,7 @@ # Copyright 2021 Jose Luis Algara # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from . import pms_housekeeping_task_type from . import pms_housekeeping_task -from . import pms_housekeeping from . import pms_reservation from . import pms_room diff --git a/pms_housekeeping/models/pms_housekeeping.py b/pms_housekeeping/models/pms_housekeeping.py deleted file mode 100644 index f97a935d..00000000 --- a/pms_housekeeping/models/pms_housekeeping.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2020 Jose Luis Algara (Alda Hotels ) -# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). - -from odoo import fields, models - - -class HouseKeeping(models.Model): - _name = "pms.housekeeping" - _description = "HouseKeeping" - # HouseKeeping 'log' - - # Fields declaration - - task_date = fields.Date( - string="Clean date", default=lambda self: fields.Datetime.now(), required=True - ) - task_start = fields.Datetime(string="Task start at") - task_end = fields.Datetime(string="Task end at") - room_id = fields.Many2one("pms.room", string="Room") - employee_id = fields.Many2one("hr.employee", string="Employee") - task_id = fields.Many2one("pms.housekeeping.task", string="Task", required=True) - notes = fields.Text("Internal Notes") - lostfound = fields.Text("Lost and Found") - state = fields.Selection( - string="Task State", - selection=[ - ("draft", "Draft"), - ("to_do", "To Do"), - ("in_progress", "In Progress"), - ("done", "Done"), - ], - default="draft", - ) - color = fields.Integer("Color Index") - - # Default Methods ang Gets - def name_get(self): - result = [] - for task in self: - name = task.task_id.name - result.append((task.id, name)) - return result diff --git a/pms_housekeeping/models/pms_housekeeping_task.py b/pms_housekeeping/models/pms_housekeeping_task.py index d3e09ceb..5164b6ea 100644 --- a/pms_housekeeping/models/pms_housekeeping_task.py +++ b/pms_housekeeping/models/pms_housekeeping_task.py @@ -4,39 +4,49 @@ from odoo import fields, models -class HouseKeepingTask(models.Model): +class PmsHouseKeepingTask(models.Model): _name = "pms.housekeeping.task" - _description = "HouseKeeping Tasks" - # HouseKeeping 'Task types' + _description = "HouseKeeping Task" - # Fields declaration - active = fields.Boolean("Active", default=True) - name = fields.Char("Task Name", translate=True, required=True) - pms_property_ids = fields.Many2many( - string="Properties", - help="Properties with access to the element;" - " if not set, all properties can access", - required=False, - comodel_name="pms.property", - relation="pms_housekeepink_task_pms_property_rel", - column1="pms_housekeepink_task_id", - column2="pms_property_id", - ondelete="restrict", - check_pms_properties=True, + task_date = fields.Date( + string="Clean date", + help="Date the task was done or;" " is scheduled to be done", + default=lambda self: fields.Datetime.now(), + required=True, ) - - clean_type = fields.Selection( - string="Clean type", + task_start = fields.Datetime(string="Task start at") + task_end = fields.Datetime(string="Task end at") + room_id = fields.Many2one( + comodel_name="pms.room", + string="Room", + ) + employee_id = fields.Many2one( + string="Employee", + comodel_name="hr.employee", + ) + task_type_id = fields.Many2one( + string="Task", + comodel_name="pms.housekeeping.task.type", + required=True, + ) + notes = fields.Text(string="Internal Notes") + lostfound = fields.Text(string="Lost and Found") + state = fields.Selection( + string="Task State", selection=[ - ("occupied", "Occupied"), - ("exit", "Exit"), - ("picked_up", "Picked up"), - ("staff", "Staff"), - ("clean", "Clean"), - ("inspected", "Inspected"), - ("dont_disturb", "Don't disturb"), + ("draft", "Draft"), + ("to_do", "To Do"), + ("in_progress", "In Progress"), + ("done", "Done"), ], + default="draft", ) - def_employee_id = fields.Many2one( - "hr.employee", string="Employee assigned by default" - ) + color = fields.Integer(string="Color Index") + + # Default Methods ang Gets + def name_get(self): + result = [] + for task in self: + name = task.task_type_id.name + result.append((task.id, name)) + return result diff --git a/pms_housekeeping/models/pms_housekeeping_task_type.py b/pms_housekeeping/models/pms_housekeeping_task_type.py new file mode 100644 index 00000000..080ea66a --- /dev/null +++ b/pms_housekeeping/models/pms_housekeeping_task_type.py @@ -0,0 +1,42 @@ +# Copyright 2020 Jose Luis Algara (Alda Hotels ) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class PmsHouseKeepingTaskType(models.Model): + _name = "pms.housekeeping.task.type" + _description = "HouseKeeping Task Types" + + active = fields.Boolean(string="Active", default=True) + name = fields.Char(string="Task Name", translate=True, required=True) + pms_property_ids = fields.Many2many( + string="Properties", + help="Properties with access to the element;" + " if not set, all properties can access", + required=False, + comodel_name="pms.property", + relation="pms_housekeepink_task_pms_property_rel", + column1="pms_housekeepink_task_id", + column2="pms_property_id", + ondelete="restrict", + check_pms_properties=True, + ) + + clean_type = fields.Selection( + string="Clean type", + selection=[ + ("occupied", "Occupied"), + ("exit", "Exit"), + ("picked_up", "Picked up"), + ("staff", "Staff"), + ("clean", "Clean"), + ("inspected", "Inspected"), + ("dont_disturb", "Don't disturb"), + ], + ) + default_employee_id = fields.Many2one( + string="Default House Keeper", + help="Employee assigned by default", + comodel_name="hr.employee", + ) diff --git a/pms_housekeeping/models/pms_reservation.py b/pms_housekeeping/models/pms_reservation.py index 10cd0546..624c2677 100644 --- a/pms_housekeeping/models/pms_reservation.py +++ b/pms_housekeeping/models/pms_reservation.py @@ -11,3 +11,14 @@ class PmsRoom(models.Model): string="Dont disturb", default=False, ) + + # def action_reservation_checkout(self): + # for record in self: + # if not record.allowed_checkout: + # raise UserError(_("This reservation cannot be check out")) + # record.state = "done" + # if record.checkin_partner_ids: + # record.checkin_partner_ids.filtered( + # lambda check: check.state == "onboard" + # ).action_done() + # return True diff --git a/pms_housekeeping/models/pms_room.py b/pms_housekeeping/models/pms_room.py index 17a31b69..11deeea8 100644 --- a/pms_housekeeping/models/pms_room.py +++ b/pms_housekeeping/models/pms_room.py @@ -25,9 +25,9 @@ def kanban_card_color(state): class PmsRoom(models.Model): _inherit = "pms.room" - housekeeping_ids = fields.One2many( + housekeeping_task_ids = fields.One2many( string="Housekeeping tasks", - comodel_name="pms.housekeeping", + comodel_name="pms.housekeeping.task", inverse_name="room_id", domain=[("task_date", "=", datetime.now().date())], ) @@ -84,7 +84,7 @@ def get_clean_status(self, date_clean=False, margin_days=5): lambda reservation: reservation.date < date_clean ) - if today_res.reservation_id.reservation_type == "out": + if today_res.reservation_id.reservation_type == "out_service": status = "dont_disturb" return status if len(today_res) == 0: @@ -117,8 +117,8 @@ def add_today_tasks(self): for task in tasks: new_task = self.env["pms.housekeeping"] employee = ( - task.def_employee_id.id - if len(task.def_employee_id) > 0 + task.default_employee_id.id + if len(task.default_employee_id) > 0 else room.clean_employee_id.id ) new_task.create( diff --git a/pms_housekeeping/security/ir.model.access.csv b/pms_housekeeping/security/ir.model.access.csv index 5c5e86ea..4e0c0c4f 100644 --- a/pms_housekeeping/security/ir.model.access.csv +++ b/pms_housekeeping/security/ir.model.access.csv @@ -1,3 +1,3 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink -user_access_pms_housekeeping,user_access_pms_housekeeping,model_pms_housekeeping,pms.group_pms_user,1,1,1,1 user_access_pms_housekeeping_task,user_access_pms_housekeeping_task,model_pms_housekeeping_task,pms.group_pms_user,1,1,1,1 +user_access_pms_housekeeping_task_type,user_access_pms_housekeeping_task_type,model_pms_housekeeping_task_type,pms.group_pms_user,1,1,1,1 diff --git a/pms_housekeeping/static/src/scss/pms_room_rack.scss b/pms_housekeeping/static/src/scss/pms_room_rack.scss new file mode 100644 index 00000000..1b970b1c --- /dev/null +++ b/pms_housekeeping/static/src/scss/pms_room_rack.scss @@ -0,0 +1,122 @@ +.o_kanban_view.o_kanban_dashboard { + &.o_room_rack_kanban { + &.o_kanban_ungrouped .o_kanban_record { + width: 350px; + &:not(.o_kanban_ghost) { + height: 180px; + } + } + + .ribbon_free { + &::before, + &::after { + display: none; + } + + span { + background-color: #7bad83 !important; + } + } + .ribbon_out_service { + &::before, + &::after { + display: none; + } + + span { + background-color: #792f36 !important; + } + } + .ribbon { + &::before, + &::after { + display: none; + } + + span { + background-color: $o-brand-odoo; + padding: 5px; + font-size: small; + z-index: unset; + height: auto; + } + } + .ribbon-top-right { + margin-top: -$o-kanban-dashboard-vpadding; + + span { + left: 0px; + right: 30px; + } + } + } + + .o_kanban_record_subtitle { + height: 1em; + } + + .o_room_rack_kanban_boxes { + display: flex; + flex-flow: row nowrap; + + .o_room_rack_kanban_box { + position: relative; + padding: 0 0 0 0; + flex: 1 1 auto; + display: flex; + flex-flow: row nowrap; + justify-content: center; + + &:first-child { + justify-content: flex-start; + padding-left: 16px; + } + div:last-child { + justify-content: flex-end; + text-align: right; + } + .o_link_trackers { + .fa { + color: $o-brand-primary; + } + } + .o_value { + font-weight: 800; + } + + > a { + font-weight: 500; + + &.o_needaction { + font-size: small; + font-weight: 400; + margin-left: 4px; + @include o-hover-opacity(0.5, 1); + + &:before { + content: "/ "; + } + + &:after { + content: "\f086"; + font: normal normal normal 14px/1 FontAwesome; + } + } + } + } + } +} + +.o_kanban_view .oe_kanban_card { + .o_kanban_state_with_padding { + padding-left: 7%; + padding-bottom: 5%; + width: 12px; + } +} + +.o_recruitment_list { + .o_list_button { + text-align: right; + } +} diff --git a/pms_housekeeping/views/pms_housekeeping_task_type_views.xml b/pms_housekeeping/views/pms_housekeeping_task_type_views.xml new file mode 100644 index 00000000..a5b821d0 --- /dev/null +++ b/pms_housekeeping/views/pms_housekeeping_task_type_views.xml @@ -0,0 +1,34 @@ + + + + + pms.housekeeping.task.type.view.tree + pms.housekeeping.task.type + tree + + + + + + + + + + + Housekeeping Task Types + pms.housekeeping.task.type + tree,form + + + + + diff --git a/pms_housekeeping/views/pms_housekeeping_task_view.xml b/pms_housekeeping/views/pms_housekeeping_task_view.xml deleted file mode 100644 index 5727605f..00000000 --- a/pms_housekeeping/views/pms_housekeeping_task_view.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - Housekeeping Tasks view - pms.housekeeping.task - tree - primary - - - - - - - - - - - - - Housekeeping Tasks - pms.housekeeping.task - tree,form - - - - - diff --git a/pms_housekeeping/views/pms_housekeeping_views.xml b/pms_housekeeping/views/pms_housekeeping_task_views.xml similarity index 83% rename from pms_housekeeping/views/pms_housekeeping_views.xml rename to pms_housekeeping/views/pms_housekeeping_task_views.xml index 10053de7..3694fca3 100644 --- a/pms_housekeeping/views/pms_housekeeping_views.xml +++ b/pms_housekeeping/views/pms_housekeeping_task_views.xml @@ -1,10 +1,9 @@ - - Housekeeping tree view - pms.housekeeping + + pms.housekeeping.task.view.tree + pms.housekeeping.task tree - primary @@ -13,7 +12,7 @@ - + @@ -22,16 +21,16 @@ - - Housekeeping form view - pms.housekeeping + + pms.housekeeping.task.view.form + pms.housekeeping.task form
- + @@ -54,11 +53,10 @@ - - Housekeeping today tasks kanban view - pms.housekeeping + + pms.housekeeping.rack.task.view.kanban + pms.housekeeping.task kanban - primary @@ -115,7 +113,7 @@
- +
@@ -167,13 +165,14 @@ - - pms.housekeeping + + pms.housekeeping.rack.task.view.search + pms.housekeeping.task - + @@ -185,7 +184,7 @@ - - Housekeeping - pms.housekeeping + + Housekeeping Tasks + pms.housekeeping.task tree,form - - Housekeeping - pms.housekeeping + + HouseKeeping Tasks + pms.housekeeping.task [('task_date', '=',(context_today().strftime('%Y-%m-%d')))] kanban,tree,form @@ -232,24 +231,17 @@ sequence="17" parent="pms.pms_management_menu" /> - diff --git a/pms_housekeeping/views/pms_housekeeping_templates.xml b/pms_housekeeping/views/pms_housekeeping_templates.xml new file mode 100644 index 00000000..de47a191 --- /dev/null +++ b/pms_housekeeping/views/pms_housekeeping_templates.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/pms_housekeeping/views/pms_reservation_view.xml b/pms_housekeeping/views/pms_reservation_views.xml similarity index 76% rename from pms_housekeeping/views/pms_reservation_view.xml rename to pms_housekeeping/views/pms_reservation_views.xml index 79131a42..394c3e3c 100644 --- a/pms_housekeeping/views/pms_reservation_view.xml +++ b/pms_housekeeping/views/pms_reservation_views.xml @@ -1,8 +1,8 @@ - - view.model.form + + pms.reservation.view.form pms.reservation diff --git a/pms_housekeeping/views/pms_room_view.xml b/pms_housekeeping/views/pms_room_view.xml deleted file mode 100644 index 7468463b..00000000 --- a/pms_housekeeping/views/pms_room_view.xml +++ /dev/null @@ -1,212 +0,0 @@ - - - - - view.model.form - pms.room - - - - - - - - - - - - - Room rack form view - pms.room - form - - - - - - - - - - - - - - - - - - - - - - - - - - - - Room rack tree view - pms.room - tree - - - - - - - - - - - - - - - pms.room - - - - - - - - - - - - - - - - - - - Room rack kanban view - pms.room - kanban - primary - - - - - - - -
- -
- -
- - -
- -
- -
- - - -
-
-
-
- - - - - - - Housekeeping - pms.room - kanban,tree,form - - - - diff --git a/pms_housekeeping/views/pms_room_views.xml b/pms_housekeeping/views/pms_room_views.xml new file mode 100644 index 00000000..1ce7a35a --- /dev/null +++ b/pms_housekeeping/views/pms_room_views.xml @@ -0,0 +1,256 @@ + + + + + pms.room.view.form + pms.room + + + + + + + + + + + + + pms.room.rack.view.form + pms.room + form + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ + + pms.room.rack.view.tree + pms.room + tree + + + + + + + + + + + + + + + + + + room.rack.view.search + pms.room + + + + + + + + + + + + + + + + + + + pms.room.rack.view.kanban + pms.room + kanban + + + + + + + + + + + + + + + +
+
+ +
+ Arrival +
+ +
+
+
+
+ +
+ +
+ +
+ +
+ +
+ + + + +
+
+ +
+
+
+
+
+
+
+
+ + + Housekeeping + pms.room + kanban,tree,form + + + + + + +