mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[ADD] Cron no_show and no_checkout
This commit is contained in:
@@ -1,6 +1,38 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
<odoo>
|
<odoo>
|
||||||
<data noupdate="0">
|
<data noupdate="0">
|
||||||
|
<!-- Set reservation like No Show if the client does not show up -->
|
||||||
|
<record model="ir.cron" id="noshow_reservations">
|
||||||
|
<field name="name">Automatic No Show Reservation</field>
|
||||||
|
<field name="interval_number">1</field>
|
||||||
|
<field name="user_id" ref="base.user_root" />
|
||||||
|
<field name="interval_type">days</field>
|
||||||
|
<field name="numbercall">-1</field>
|
||||||
|
<field name="doall" eval="False" />
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="model_id" ref="model_pms_reservation" />
|
||||||
|
<field
|
||||||
|
name="nextcall"
|
||||||
|
eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 06:00:00')"
|
||||||
|
/>
|
||||||
|
<field name="code">model.auto_no_show()</field>
|
||||||
|
</record>
|
||||||
|
<!-- Set reservation like No Checout if checkout is not confirmed-->
|
||||||
|
<record model="ir.cron" id="nocheckout_reservations">
|
||||||
|
<field name="name">Automatic No Checkout Reservations</field>
|
||||||
|
<field name="interval_number">1</field>
|
||||||
|
<field name="user_id" ref="base.user_root" />
|
||||||
|
<field name="interval_type">days</field>
|
||||||
|
<field name="numbercall">-1</field>
|
||||||
|
<field name="doall" eval="False" />
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="model_id" ref="model_pms_reservation" />
|
||||||
|
<field
|
||||||
|
name="nextcall"
|
||||||
|
eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 12:00:00')"
|
||||||
|
/>
|
||||||
|
<field name="code">model.auto_no_checkout()</field>
|
||||||
|
</record>
|
||||||
<!-- Scheduler For To Inform Guest About Reservation Before 24 Hours -->
|
<!-- Scheduler For To Inform Guest About Reservation Before 24 Hours -->
|
||||||
<record model="ir.cron" id="autocheckout_reservations">
|
<record model="ir.cron" id="autocheckout_reservations">
|
||||||
<field name="name">Automatic Checkout on past reservations</field>
|
<field name="name">Automatic Checkout on past reservations</field>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class PmsCheckinPartner(models.Model):
|
|||||||
for record in self.filtered("reservation_id"):
|
for record in self.filtered("reservation_id"):
|
||||||
record.folio_id = record.reservation_id.folio_id
|
record.folio_id = record.reservation_id.folio_id
|
||||||
|
|
||||||
@api.depends(lambda self: self._checkin_mandatory_fields(), "reservation_id.state")
|
@api.depends(lambda self: self._checkin_mandatory_fields(depends=True))
|
||||||
def _compute_state(self):
|
def _compute_state(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
if not record.state:
|
if not record.state:
|
||||||
@@ -98,7 +98,11 @@ class PmsCheckinPartner(models.Model):
|
|||||||
else:
|
else:
|
||||||
record.state = "precheckin"
|
record.state = "precheckin"
|
||||||
|
|
||||||
def _checkin_mandatory_fields(self):
|
@api.model
|
||||||
|
def _checkin_mandatory_fields(self, depends=False):
|
||||||
|
# api.depends need "reservation_id.state" in de lambda function
|
||||||
|
if depends:
|
||||||
|
return ["reservation_id.state", "name"]
|
||||||
return ["name"]
|
return ["name"]
|
||||||
|
|
||||||
# Constraints and onchanges
|
# Constraints and onchanges
|
||||||
@@ -156,6 +160,22 @@ class PmsCheckinPartner(models.Model):
|
|||||||
record.update(vals)
|
record.update(vals)
|
||||||
if record.reservation_id.state == "confirm":
|
if record.reservation_id.state == "confirm":
|
||||||
record.reservation_id.state = "onboard"
|
record.reservation_id.state = "onboard"
|
||||||
|
if self._context.get("popup"):
|
||||||
|
self.ensure_one()
|
||||||
|
kanban_id = self.env.ref("pms.pms_checkin_partner_kanban_view").id
|
||||||
|
return {
|
||||||
|
"name": _("Register Checkins"),
|
||||||
|
"views": [[kanban_id, "tree"]],
|
||||||
|
"res_model": "pms.checkin.partner",
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"context": {
|
||||||
|
"create": False,
|
||||||
|
"edit": True,
|
||||||
|
"popup": True,
|
||||||
|
},
|
||||||
|
"domain": [("reservation_id", "=", self.reservation_id.id)],
|
||||||
|
"target": "new",
|
||||||
|
}
|
||||||
|
|
||||||
def action_done(self):
|
def action_done(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
|
|||||||
@@ -245,7 +245,9 @@ class PmsFolio(models.Model):
|
|||||||
)
|
)
|
||||||
# Checkin Fields-----------------------------------------------------
|
# Checkin Fields-----------------------------------------------------
|
||||||
reservation_pending_arrival_ids = fields.One2many(
|
reservation_pending_arrival_ids = fields.One2many(
|
||||||
string="Pending Arrival Rooms", compute="_compute_reservations_pending_arrival"
|
comodel_name="pms.checkin.partner",
|
||||||
|
string="Pending Arrival Rooms",
|
||||||
|
compute="_compute_reservations_pending_arrival",
|
||||||
)
|
)
|
||||||
reservations_pending_count = fields.Integer(
|
reservations_pending_count = fields.Integer(
|
||||||
compute="_compute_reservations_pending_arrival"
|
compute="_compute_reservations_pending_arrival"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# Copyright 2017 Dario Lodeiros
|
# Copyright 2017 Dario Lodeiros
|
||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
import logging
|
import logging
|
||||||
from datetime import timedelta
|
from datetime import datetime, time, timedelta
|
||||||
|
|
||||||
from odoo import _, api, fields, models
|
from odoo import _, api, fields, models
|
||||||
from odoo.exceptions import UserError, ValidationError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
@@ -274,6 +274,8 @@ class PmsReservation(models.Model):
|
|||||||
("onboard", "On Board"),
|
("onboard", "On Board"),
|
||||||
("done", "Out"),
|
("done", "Out"),
|
||||||
("cancelled", "Cancelled"),
|
("cancelled", "Cancelled"),
|
||||||
|
("no_show", "No Show"),
|
||||||
|
("no_checkout", "No Checkout"),
|
||||||
],
|
],
|
||||||
string="Status",
|
string="Status",
|
||||||
default=lambda *a: "draft",
|
default=lambda *a: "draft",
|
||||||
@@ -653,12 +655,9 @@ class PmsReservation(models.Model):
|
|||||||
if value not in (False, True):
|
if value not in (False, True):
|
||||||
raise UserError(_("Invalid domain right operand %s", value))
|
raise UserError(_("Invalid domain right operand %s", value))
|
||||||
|
|
||||||
searching_for_true = (operator == "=" and value) or (
|
|
||||||
operator == "!=" and not value
|
|
||||||
)
|
|
||||||
today = fields.Date.context_today(self)
|
today = fields.Date.context_today(self)
|
||||||
|
|
||||||
return [("checkin", searching_for_true, today)]
|
return [("checkin", operator, today)]
|
||||||
|
|
||||||
def _compute_departure_today(self):
|
def _compute_departure_today(self):
|
||||||
for record in self:
|
for record in self:
|
||||||
@@ -1224,13 +1223,60 @@ class PmsReservation(models.Model):
|
|||||||
|
|
||||||
def action_checks(self):
|
def action_checks(self):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
action = self.env.ref("pms.open_pms_reservation_form_tree_all").read()[0]
|
tree_id = self.env.ref("pms.pms_checkin_partner_reservation_view_tree").id
|
||||||
action["views"] = [
|
return {
|
||||||
(self.env.ref("pms.pms_reservation_checkin_view_form").id, "form")
|
"name": _("Register Partners"),
|
||||||
]
|
"views": [[tree_id, "tree"]],
|
||||||
action["res_id"] = self.id
|
"res_model": "pms.checkin.partner",
|
||||||
action["target"] = "new"
|
"type": "ir.actions.act_window",
|
||||||
return action
|
"context": {
|
||||||
|
"create": False,
|
||||||
|
"edit": True,
|
||||||
|
"popup": True,
|
||||||
|
},
|
||||||
|
"domain": [("reservation_id", "=", self.id), ("state", "=", "draft")],
|
||||||
|
"target": "new",
|
||||||
|
}
|
||||||
|
|
||||||
|
def action_onboard(self):
|
||||||
|
self.ensure_one()
|
||||||
|
kanban_id = self.env.ref("pms.pms_checkin_partner_kanban_view").id
|
||||||
|
return {
|
||||||
|
"name": _("Register Checkins"),
|
||||||
|
"views": [[kanban_id, "kanban"]],
|
||||||
|
"res_model": "pms.checkin.partner",
|
||||||
|
"type": "ir.actions.act_window",
|
||||||
|
"context": {
|
||||||
|
"create": False,
|
||||||
|
"edit": True,
|
||||||
|
"popup": True,
|
||||||
|
},
|
||||||
|
"domain": [("reservation_id", "=", self.id), ("state", "=", "precheckin")],
|
||||||
|
"target": "new",
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def auto_no_show(self):
|
||||||
|
no_show_reservations = self.env["pms.reservation"].search(
|
||||||
|
[
|
||||||
|
("state", "in", ("draft", "confirm")),
|
||||||
|
("checkin", "<", fields.Date.today()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
no_show_reservations.state = "no_show"
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def auto_no_checkout(self):
|
||||||
|
reservations = self.env["pms.reservation"].search(
|
||||||
|
[("state", "in", ("onboard",))]
|
||||||
|
)
|
||||||
|
for reservation in reservations:
|
||||||
|
checkout_hour = int(reservation.departure_hour[0:2])
|
||||||
|
checkout_minut = int(reservation.departure_hour[3:5])
|
||||||
|
checkout_time = time(checkout_hour, checkout_minut, 00)
|
||||||
|
checkout_datetime = datetime.combine(reservation.checkout, checkout_time)
|
||||||
|
if checkout_datetime <= fields.Datetime.now():
|
||||||
|
reservation.state = "no_checkout"
|
||||||
|
|
||||||
def unify(self):
|
def unify(self):
|
||||||
# TODO
|
# TODO
|
||||||
|
|||||||
@@ -338,3 +338,48 @@ class TestPmsCheckinPartner(TestHotel):
|
|||||||
int(2 * 100 / 3),
|
int(2 * 100 / 3),
|
||||||
"Fail the checkins data ratio on reservation",
|
"Fail the checkins data ratio on reservation",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_auto_no_show(self):
|
||||||
|
|
||||||
|
# ARRANGE
|
||||||
|
self.arrange_folio_reservations()
|
||||||
|
PmsReservation = self.env["pms.reservation"]
|
||||||
|
|
||||||
|
# ACTION
|
||||||
|
freezer = freeze_time("2012-01-15 10:00:00")
|
||||||
|
freezer.start()
|
||||||
|
PmsReservation.auto_no_show()
|
||||||
|
|
||||||
|
no_show_reservations = PmsReservation.search([("state", "=", "no_show")])
|
||||||
|
|
||||||
|
# ASSERT
|
||||||
|
self.assertEqual(
|
||||||
|
len(no_show_reservations),
|
||||||
|
3,
|
||||||
|
"Reservations not set like No Show",
|
||||||
|
)
|
||||||
|
freezer.stop()
|
||||||
|
|
||||||
|
def test_auto_no_checkout(self):
|
||||||
|
|
||||||
|
# ARRANGE
|
||||||
|
self.arrange_single_checkin()
|
||||||
|
PmsReservation = self.env["pms.reservation"]
|
||||||
|
self.checkin1.action_on_board()
|
||||||
|
|
||||||
|
# ACTION
|
||||||
|
freezer = freeze_time("2012-01-17 12:00:00")
|
||||||
|
freezer.start()
|
||||||
|
PmsReservation.auto_no_checkout()
|
||||||
|
|
||||||
|
no_checkout_reservations = PmsReservation.search(
|
||||||
|
[("state", "=", "no_checkout")]
|
||||||
|
)
|
||||||
|
freezer.stop()
|
||||||
|
|
||||||
|
# ASSERT
|
||||||
|
self.assertEqual(
|
||||||
|
len(no_checkout_reservations),
|
||||||
|
1,
|
||||||
|
"Reservations not set like No checkout",
|
||||||
|
)
|
||||||
|
|||||||
@@ -49,7 +49,6 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree
|
<tree
|
||||||
editable="bottom"
|
editable="bottom"
|
||||||
create="1"
|
|
||||||
decoration-danger="state == 'draft'"
|
decoration-danger="state == 'draft'"
|
||||||
decoration-info="state == 'done'"
|
decoration-info="state == 'done'"
|
||||||
decoration-muted="state == 'cancelled'"
|
decoration-muted="state == 'cancelled'"
|
||||||
@@ -61,7 +60,7 @@
|
|||||||
icon="fa-2x fa-check-circle"
|
icon="fa-2x fa-check-circle"
|
||||||
name="action_on_board"
|
name="action_on_board"
|
||||||
help="Get in"
|
help="Get in"
|
||||||
attrs="{'invisible': [('state','!=','preconfirm')]}"
|
attrs="{'invisible': [('state','!=','precheckin')]}"
|
||||||
/>
|
/>
|
||||||
<field name="identifier" />
|
<field name="identifier" />
|
||||||
<field name="partner_id" required="True" />
|
<field name="partner_id" required="True" />
|
||||||
@@ -71,7 +70,7 @@
|
|||||||
<field name="departure" />
|
<field name="departure" />
|
||||||
<field name="reservation_id" invisible="1" />
|
<field name="reservation_id" invisible="1" />
|
||||||
<field name="folio_id" force_save="1" invisible="1" />
|
<field name="folio_id" force_save="1" invisible="1" />
|
||||||
<field name="state" invisible="1" />
|
<field name="state" invisible="0" />
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
@@ -114,7 +113,12 @@
|
|||||||
<field name="name">pms.checkin.partner.kanban</field>
|
<field name="name">pms.checkin.partner.kanban</field>
|
||||||
<field name="model">pms.checkin.partner</field>
|
<field name="model">pms.checkin.partner</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<kanban default_group_by="state" class="o_res_partner_kanban" sample="1">
|
<kanban
|
||||||
|
default_group_by="state"
|
||||||
|
class="o_res_partner_kanban"
|
||||||
|
sample="1"
|
||||||
|
create="false"
|
||||||
|
>
|
||||||
<field name="id" />
|
<field name="id" />
|
||||||
<field name="identifier" />
|
<field name="identifier" />
|
||||||
<field name="partner_id" />
|
<field name="partner_id" />
|
||||||
|
|||||||
@@ -120,7 +120,7 @@
|
|||||||
<button
|
<button
|
||||||
type="object"
|
type="object"
|
||||||
class="oe_stat_button"
|
class="oe_stat_button"
|
||||||
name="action_checks"
|
name="action_onboard"
|
||||||
attrs="{'invisible': [
|
attrs="{'invisible': [
|
||||||
('state', '!=', ('onboard')),
|
('state', '!=', ('onboard')),
|
||||||
('arrival_today', '=', False),
|
('arrival_today', '=', False),
|
||||||
@@ -556,108 +556,6 @@
|
|||||||
</calendar>
|
</calendar>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
<!-- Form view Checkin Partners from reservation -->
|
|
||||||
<record model="ir.ui.view" id="pms_reservation_checkin_view_form">
|
|
||||||
<field name="name">pms.reservation.checkin.form</field>
|
|
||||||
<field name="model">pms.reservation</field>
|
|
||||||
<field name="priority">100</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form string="Reservation">
|
|
||||||
<field name="reservation_type" invisible="1" />
|
|
||||||
<field name="splitted" invisible="1" />
|
|
||||||
<field name="state" invisible="1" />
|
|
||||||
<field name="overbooking" invisible="1" />
|
|
||||||
<field name="folio_id" invisible="1" />
|
|
||||||
<field name="adults" invisible="1" />
|
|
||||||
<field name="children" invisible="1" />
|
|
||||||
<div
|
|
||||||
class="alert alert-info"
|
|
||||||
role="alert"
|
|
||||||
style="margin-bottom:0px;"
|
|
||||||
attrs="{'invisible': ['|',('shared_folio','=',False),('splitted', '=', True)]}"
|
|
||||||
>
|
|
||||||
This reservation has other reservantions and/or services in the
|
|
||||||
folio, you can check it in the
|
|
||||||
<bold>
|
|
||||||
<button
|
|
||||||
class="alert-link"
|
|
||||||
type="object"
|
|
||||||
name="open_folio"
|
|
||||||
string="Folio Form"
|
|
||||||
/>
|
|
||||||
</bold>
|
|
||||||
</div>
|
|
||||||
<field name="shared_folio" invisible="1" />
|
|
||||||
<field name="allowed_room_ids" invisible="1" />
|
|
||||||
<sheet>
|
|
||||||
<span
|
|
||||||
class="label label-danger"
|
|
||||||
attrs="{'invisible': [('state', 'not in', ('cancelled'))]}"
|
|
||||||
>
|
|
||||||
Cancelled Reservation!
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="label label-warning"
|
|
||||||
attrs="{'invisible': [('overbooking', '=', False)]}"
|
|
||||||
>
|
|
||||||
OverBooking!
|
|
||||||
</span>
|
|
||||||
<h2>
|
|
||||||
<field
|
|
||||||
name="preferred_room_id"
|
|
||||||
readonly="1"
|
|
||||||
nolabel="1"
|
|
||||||
options="{'no_create': True,'no_open': True}"
|
|
||||||
style="margin-right: 30px;"
|
|
||||||
/>
|
|
||||||
<!-- <field
|
|
||||||
name="partner_id"
|
|
||||||
readonly="1"
|
|
||||||
options="{'no_open': True}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="fa-user"
|
|
||||||
style="margin-left:20px;"
|
|
||||||
attrs="{'invisible': [('reservation_type','not in',('normal'))]}"
|
|
||||||
/>
|
|
||||||
<span
|
|
||||||
class="fa-black-tie"
|
|
||||||
style="margin-left:20px; color: #C67;"
|
|
||||||
attrs="{'invisible': [('reservation_type','not in',('staff'))]}"
|
|
||||||
/> -->
|
|
||||||
<h3>
|
|
||||||
From
|
|
||||||
<span class="fa-sign-in" style="margin: 5px;" />
|
|
||||||
<field
|
|
||||||
name="checkin"
|
|
||||||
style="margin-right: 10px;"
|
|
||||||
readonly="1"
|
|
||||||
/>
|
|
||||||
to
|
|
||||||
<span class="fa-sign-out" style="margin-right: 5px;" />
|
|
||||||
<field name="checkout" readonly="1" />
|
|
||||||
</h3>
|
|
||||||
</h2>
|
|
||||||
<group>
|
|
||||||
<field
|
|
||||||
name="segmentation_ids"
|
|
||||||
widget="many2many_tags"
|
|
||||||
placeholder="Segmentation..."
|
|
||||||
options="{'no_create': True,'no_open': True}"
|
|
||||||
/>
|
|
||||||
</group>
|
|
||||||
<field
|
|
||||||
name="checkin_partner_ids"
|
|
||||||
context="{
|
|
||||||
'default_reservation_id': id,
|
|
||||||
'reservation_id': id,
|
|
||||||
'tree_view_ref':'pms.pms_checkin_partner_reservation_view_tree',
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</sheet>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
<record model="ir.ui.view" id="pms_reservation_view_tree">
|
<record model="ir.ui.view" id="pms_reservation_view_tree">
|
||||||
<field name="name">pms.reservation.tree</field>
|
<field name="name">pms.reservation.tree</field>
|
||||||
<field name="model">pms.reservation</field>
|
<field name="model">pms.reservation</field>
|
||||||
@@ -799,7 +697,7 @@
|
|||||||
<filter
|
<filter
|
||||||
string="To enter"
|
string="To enter"
|
||||||
name="to_enter"
|
name="to_enter"
|
||||||
domain="[('state', '=', 'confirm')]"
|
domain="[('arrival_today', '=', True),('state', 'in', ('draft','confirm'))]"
|
||||||
/>
|
/>
|
||||||
<filter
|
<filter
|
||||||
string="Overbookings"
|
string="Overbookings"
|
||||||
|
|||||||
Reference in New Issue
Block a user