mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[IMP] Refact onchanges/compute on availability and prices process
This commit is contained in:
@@ -1,44 +1,101 @@
|
||||
.. This file is going to be generated by oca-gen-addon-readme. Manual changes will be overwritten.
|
||||
================================
|
||||
PMS (Property Management System)
|
||||
================================
|
||||
|
||||
HOTEL
|
||||
=====
|
||||
This module allows you to manage hotel properties with multi-hotel and multi-company support.
|
||||
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
|
||||
:target: https://odoo-community.org/page/development-status
|
||||
:alt: Beta
|
||||
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
|
||||
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
|
||||
:alt: License: AGPL-3
|
||||
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fvertical--hotel-lightgray.png?logo=github
|
||||
:target: https://github.com/OCA/vertical-hotel/tree/12.0/pms
|
||||
:alt: OCA/vertical-hotel
|
||||
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
|
||||
:target: https://translation.odoo-community.org/projects/vertical-hotel-12-0/vertical-hotel-12-0-pms
|
||||
:alt: Translate me on Weblate
|
||||
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
|
||||
:target: https://runbot.odoo-community.org/runbot/157/12.0
|
||||
:alt: Try me on Runbot
|
||||
|
||||
|badge1| |badge2| |badge3| |badge4| |badge5|
|
||||
|
||||
Description
|
||||
------------
|
||||
This module is an all-in-one property management system (PMS) focused on medium-sized hotels
|
||||
for managing every aspect of your property's daily operations.
|
||||
|
||||
You can manage hotel properties with multi-hotel and multi-company support, including your rooms inventory,
|
||||
reservations, check-in, daily reports, board services, rate and restriction plans among other hotel functionalities.
|
||||
|
||||
**Table of contents**
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
Installation
|
||||
------------
|
||||
============
|
||||
|
||||
This module depends on modules ``base``, ``sale_stock``, ``account_payment_return``, ``partner_firstname``,
|
||||
and ``account_cancel``. Ensure yourself to have all them in your addons list.
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
You will find the hotel settings in `Settings > Users & Companies > Hotels > Your Hotel.
|
||||
=============
|
||||
|
||||
You will find the hotel settings in Settings > Users & Companies > Hotels > Your Hotel.
|
||||
|
||||
This module required additional configuration for company, accounting, invoicing and user privileges.
|
||||
|
||||
Usage
|
||||
-----
|
||||
=====
|
||||
|
||||
To use this module, please, read the complete user guide at https://roomdoo.com.
|
||||
|
||||
Bug Tracker
|
||||
===========
|
||||
|
||||
Bugs are tracked on `GitHub Issues <https://github.com/OCA/vertical-hotel/issues>`_.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||
`feedback <https://github.com/OCA/vertical-hotel/issues/new?body=module:%20pms%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
|
||||
|
||||
Do not contact contributors directly about support or help with technical issues.
|
||||
|
||||
Credits
|
||||
-------
|
||||
=======
|
||||
|
||||
Authors
|
||||
_______
|
||||
- Darío Lodeiros <dariodafoz@gmail.com>
|
||||
- Alexandre Díaz <dev@redneboa.es>
|
||||
- Jose Luis Algara <osotranquilo@gmail.com>
|
||||
- Pablo Quesada <pabloqb@gmail.com>
|
||||
~~~~~~~
|
||||
|
||||
Roadmap
|
||||
-------
|
||||
* [ ]
|
||||
* Dario Lodeiros
|
||||
* Alexadre Diaz
|
||||
* Pablo Quesada
|
||||
* Jose Luis Algara
|
||||
|
||||
Do you want to contribute? Please, do not hesitate to contact any of the authors or contributors.
|
||||
Contributors
|
||||
~~~~~~~~~~~~
|
||||
|
||||
* Dario Lodeiros <dario@commitsun.com>
|
||||
* Alexandre Díaz
|
||||
* Pablo Quesada
|
||||
* Jose Luis Algara
|
||||
|
||||
Maintainers
|
||||
~~~~~~~~~~~
|
||||
|
||||
This module is maintained by the OCA.
|
||||
|
||||
.. image:: https://odoo-community.org/logo.png
|
||||
:alt: Odoo Community Association
|
||||
:target: https://odoo-community.org
|
||||
|
||||
OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.
|
||||
|
||||
This module is part of the `OCA/vertical-hotel <https://github.com/OCA/vertical-hotel/tree/12.0/pms>`_ project on GitHub.
|
||||
|
||||
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
|
||||
@@ -8,10 +8,11 @@
|
||||
"development_status": "Beta",
|
||||
"category": "Generic Modules/Property Management System",
|
||||
"website": "https://github.com/hootel/hootel",
|
||||
"author": "Darío Lodeiros, "
|
||||
"Alexandre Díaz, "
|
||||
"author": "Dario Lodeiros, "
|
||||
"Alexadre Diaz, "
|
||||
"Pablo Quesada, "
|
||||
"Jose Luis Algara, "
|
||||
"Pablo Quesada ",
|
||||
"Odoo Community Association (OCA)",
|
||||
"license": "AGPL-3",
|
||||
"application": True,
|
||||
"installable": True,
|
||||
|
||||
@@ -2,28 +2,45 @@
|
||||
# Copyright 2019 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from odoo import models, _
|
||||
from odoo.http import request
|
||||
from odoo import _, models
|
||||
from odoo.exceptions import MissingError
|
||||
from odoo.http import request
|
||||
|
||||
|
||||
class IrHttp(models.AbstractModel):
|
||||
_inherit = 'ir.http'
|
||||
_inherit = "ir.http"
|
||||
|
||||
def session_info(self):
|
||||
res = super().session_info()
|
||||
user = request.env.user
|
||||
res.update({
|
||||
# current_pms_property should be default_property
|
||||
"user_pms_properties": {
|
||||
'current_pms_property': (user.pms_property_id.id, user.pms_property_id.name),
|
||||
# TODO: filter all properties based on the current set of active companies
|
||||
'allowed_pms_properties': [(property.id, property.name) for property in user.pms_property_ids]
|
||||
res.update(
|
||||
{
|
||||
# current_pms_property should be default_property
|
||||
"user_pms_properties": {
|
||||
"current_pms_property": (
|
||||
user.pms_property_id.id,
|
||||
user.pms_property_id.name,
|
||||
),
|
||||
# TODO: filter all properties based on the current set of active companies
|
||||
"allowed_pms_properties": [
|
||||
(property.id, property.name)
|
||||
for property in user.pms_property_ids
|
||||
],
|
||||
},
|
||||
"display_switch_pms_property_menu": user.has_group('base.group_multi_company') and len(user.pms_property_ids) > 1,
|
||||
})
|
||||
# TODO: This user context update should be placed in other function ¿?
|
||||
res['user_context'].update({'allowed_pms_property_ids': [(property.id) for property in user.pms_property_ids]})
|
||||
"display_switch_pms_property_menu": user.has_group(
|
||||
"base.group_multi_company"
|
||||
)
|
||||
and len(user.pms_property_ids) > 1,
|
||||
}
|
||||
)
|
||||
# TODO: This user context update should be placed in other function ¿?
|
||||
res["user_context"].update(
|
||||
{
|
||||
"allowed_pms_property_ids": [
|
||||
(property.id) for property in user.pms_property_ids
|
||||
]
|
||||
}
|
||||
)
|
||||
# TODO: update current_company based on current_pms_property
|
||||
# if user.pms_property_id.company_id in user.company_ids:
|
||||
# user.company_id = user.pms_property_id.company_id
|
||||
|
||||
@@ -5,7 +5,7 @@ from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ProductPricelist(models.Model):
|
||||
""" Before creating a 'daily' pricelist, you need to consider the following:
|
||||
"""Before creating a 'daily' pricelist, you need to consider the following:
|
||||
A pricelist marked as daily is used as a daily rate plan for room types and
|
||||
therefore is related only with one property.
|
||||
"""
|
||||
@@ -22,7 +22,6 @@ class ProductPricelist(models.Model):
|
||||
pricelist_type = fields.Selection(
|
||||
[("daily", "Daily Plan")], string="Pricelist Type", default="daily"
|
||||
)
|
||||
is_staff = fields.Boolean("Is Staff")
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.constrains("pricelist_type", "pms_property_ids")
|
||||
|
||||
@@ -18,8 +18,7 @@ class PmsBoardServiceRoomType(models.Model):
|
||||
for res in self:
|
||||
if res.pricelist_id:
|
||||
name = u"{} ({})".format(
|
||||
res.pms_board_service_id.name,
|
||||
res.pricelist_id.name,
|
||||
res.pms_board_service_id.name, res.pricelist_id.name,
|
||||
)
|
||||
else:
|
||||
name = u"{} ({})".format(res.pms_board_service_id.name, _("Generic"))
|
||||
|
||||
@@ -569,15 +569,6 @@ class PmsFolio(models.Model):
|
||||
values["team_id"] = self.partner_id.team_id.id
|
||||
self.update(values)
|
||||
|
||||
@api.onchange("pricelist_id")
|
||||
def onchange_pricelist_id(self):
|
||||
values = {
|
||||
"reservation_type": self.env["pms.folio"].calcule_reservation_type(
|
||||
self.pricelist_id.is_staff, self.reservation_type
|
||||
)
|
||||
}
|
||||
self.update(values)
|
||||
|
||||
# Action methods
|
||||
|
||||
def action_pay(self):
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
import logging
|
||||
import re
|
||||
import time
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
@@ -92,22 +90,59 @@ class PmsReservation(models.Model):
|
||||
return True
|
||||
|
||||
# Fields declaration
|
||||
name = fields.Text("Reservation Description", required=True)
|
||||
name = fields.Text(
|
||||
"Reservation Description",
|
||||
required=True,
|
||||
compute="_compute_name",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
room_id = fields.Many2one(
|
||||
"pms.room", string="Room", track_visibility="onchange", ondelete="restrict"
|
||||
"pms.room",
|
||||
string="Room",
|
||||
track_visibility="onchange",
|
||||
ondelete="restrict",
|
||||
compute="_compute_room_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
domain="[('id', 'in', allowed_room_ids)]",
|
||||
)
|
||||
allowed_room_ids = fields.Many2many(
|
||||
"pms.room", string="Allowed Rooms", compute="_compute_allowed_room_ids",
|
||||
)
|
||||
folio_id = fields.Many2one(
|
||||
"pms.folio", string="Folio", track_visibility="onchange", ondelete="cascade"
|
||||
"pms.folio", string="Folio", track_visibility="onchange", ondelete="cascade",
|
||||
)
|
||||
board_service_room_id = fields.Many2one(
|
||||
"pms.board.service.room.type", string="Board Service"
|
||||
"pms.board.service.room.type", string="Board Service",
|
||||
)
|
||||
room_type_id = fields.Many2one(
|
||||
"pms.room.type", string="Room Type", required=True, track_visibility="onchange"
|
||||
"pms.room.type",
|
||||
string="Room Type",
|
||||
required=True,
|
||||
track_visibility="onchange",
|
||||
compute="_compute_room_type_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
"res.partner",
|
||||
track_visibility="onchange",
|
||||
ondelete="restrict",
|
||||
compute="_compute_partner_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
partner_id = fields.Many2one(related="folio_id.partner_id")
|
||||
tour_operator_id = fields.Many2one(related="folio_id.tour_operator_id")
|
||||
partner_invoice_id = fields.Many2one(related="folio_id.partner_invoice_id")
|
||||
partner_invoice_id = fields.Many2one(
|
||||
"res.partner",
|
||||
string="Invoice Address",
|
||||
required=True,
|
||||
help="Invoice address for current sales order.",
|
||||
compute="_compute_partner_invoice_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
partner_invoice_state_id = fields.Many2one(related="partner_invoice_id.state_id")
|
||||
partner_invoice_country_id = fields.Many2one(
|
||||
related="partner_invoice_id.country_id"
|
||||
@@ -121,10 +156,22 @@ class PmsReservation(models.Model):
|
||||
"pms.property", store=True, readonly=True, related="folio_id.pms_property_id"
|
||||
)
|
||||
reservation_line_ids = fields.One2many(
|
||||
"pms.reservation.line", "reservation_id", required=True
|
||||
"pms.reservation.line",
|
||||
"reservation_id",
|
||||
compute="_compute_reservation_line_ids",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
service_ids = fields.One2many("pms.service", "reservation_id")
|
||||
pricelist_id = fields.Many2one("product.pricelist", related="folio_id.pricelist_id")
|
||||
pricelist_id = fields.Many2one(
|
||||
"product.pricelist",
|
||||
string="Pricelist",
|
||||
required=True,
|
||||
ondelete="restrict",
|
||||
compute="_compute_pricelist_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
# TODO: Warning Mens to update pricelist
|
||||
checkin_partner_ids = fields.One2many("pms.checkin.partner", "reservation_id")
|
||||
parent_reservation = fields.Many2one("pms.reservation", string="Parent Reservation")
|
||||
@@ -154,14 +201,14 @@ class PmsReservation(models.Model):
|
||||
localizator = fields.Char(
|
||||
string="Localizator", compute="_compute_localizator", store=True
|
||||
)
|
||||
sequence = fields.Integer(string="Sequence", default=10)
|
||||
reservation_no = fields.Char("Reservation No", size=64, readonly=True)
|
||||
adults = fields.Integer(
|
||||
"Adults",
|
||||
size=64,
|
||||
readonly=False,
|
||||
track_visibility="onchange",
|
||||
help="List of adults there in guest list. ",
|
||||
compute="_compute_adults",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
children = fields.Integer(
|
||||
"Children",
|
||||
@@ -180,10 +227,10 @@ class PmsReservation(models.Model):
|
||||
("cancelled", "Cancelled"),
|
||||
],
|
||||
string="Status",
|
||||
readonly=True,
|
||||
default=lambda *a: "draft",
|
||||
copy=False,
|
||||
track_visibility="onchange",
|
||||
readonly=True,
|
||||
)
|
||||
reservation_type = fields.Selection(
|
||||
related="folio_id.reservation_type", default=lambda *a: "normal"
|
||||
@@ -198,8 +245,8 @@ class PmsReservation(models.Model):
|
||||
out_service_description = fields.Text("Cause of out of service")
|
||||
checkin = fields.Date("Check In", required=True, default=_get_default_checkin)
|
||||
checkout = fields.Date("Check Out", required=True, default=_get_default_checkout)
|
||||
real_checkin = fields.Date("Arrival", required=True, track_visibility="onchange")
|
||||
real_checkout = fields.Date("Departure", required=True, track_visibility="onchange")
|
||||
real_checkin = fields.Date("From", required=True, track_visibility="onchange")
|
||||
real_checkout = fields.Date("To", required=True, track_visibility="onchange")
|
||||
arrival_hour = fields.Char(
|
||||
"Arrival Hour",
|
||||
default=_get_default_arrival_hour,
|
||||
@@ -353,6 +400,138 @@ class PmsReservation(models.Model):
|
||||
)
|
||||
|
||||
# Compute and Search methods
|
||||
@api.depends("checkin", "checkin", "room_type_id")
|
||||
def _compute_name(self):
|
||||
for reservation in self:
|
||||
if (
|
||||
reservation.room_type_id
|
||||
and reservation.checkin
|
||||
and reservation.checkout
|
||||
):
|
||||
checkin_str = reservation.checkin.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
checkout_str = reservation.checkout.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
reservation.name = (
|
||||
reservation.room_type_id.name
|
||||
+ ": "
|
||||
+ checkin_str
|
||||
+ " - "
|
||||
+ checkout_str
|
||||
)
|
||||
|
||||
@api.depends("adults", "room_type_id")
|
||||
def _compute_room_id(self):
|
||||
reservations_no_room = self.filtered_domain([("room_id", "=", False)])
|
||||
reservations_with_room = self - reservations_no_room
|
||||
for reservation in reservations_with_room:
|
||||
if reservation.adults:
|
||||
reservation._check_adults()
|
||||
for reservation in reservations_no_room:
|
||||
if reservation.room_type_id:
|
||||
reservation.room_id = reservation._autoassign()
|
||||
if not reservation.room_id:
|
||||
raise UserError(
|
||||
_("%s: No rooms available") % (self.room_type_id.name)
|
||||
)
|
||||
# TODO: Allow with confirmation message to
|
||||
# change de room if the user change the room_type?
|
||||
|
||||
@api.depends("room_id")
|
||||
def _compute_room_type_id(self):
|
||||
for reservation in self:
|
||||
if reservation.room_id and not reservation.room_type_id:
|
||||
reservation.room_type_id = reservation.room_id.room_type_id.id
|
||||
|
||||
@api.depends("checkin", "checkout", "overbooking", "state", "room_id")
|
||||
def _compute_allowed_room_ids(self):
|
||||
for reservation in self:
|
||||
if reservation.checkin and reservation.checkout:
|
||||
if reservation.overbooking or reservation.state in ("cancelled"):
|
||||
reservation.allowed_room_ids = self.env["pms.room"].search(
|
||||
[("active", "=", True)]
|
||||
)
|
||||
return
|
||||
rooms_available = (
|
||||
self.env["pms.room.type"].check_availability_room_type(
|
||||
dfrom=reservation.checkin,
|
||||
dto=reservation.checkout,
|
||||
room_type_id=False, # Allow chosen any available room
|
||||
)
|
||||
+ self.room_id
|
||||
)
|
||||
if (
|
||||
reservation.room_id
|
||||
and reservation.room_id.id not in rooms_available.ids
|
||||
):
|
||||
room_name = reservation.room_id.name
|
||||
warning_msg = (
|
||||
_(
|
||||
"You tried to change/confirm \
|
||||
reservation with room those already reserved in this \
|
||||
reservation period: %s "
|
||||
)
|
||||
% room_name
|
||||
)
|
||||
raise ValidationError(warning_msg)
|
||||
reservation.allowed_room_ids = rooms_available
|
||||
|
||||
@api.depends("reservation_type")
|
||||
def _compute_partner_id(self):
|
||||
for reservation in self:
|
||||
if reservation.reservation_type == "out":
|
||||
reservation.partner_id = self.env.user.pms_property_id.partner_id.id
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_partner_invoice_id(self):
|
||||
for reservation in self:
|
||||
if reservation.folio_id and reservation.folio_id.partner_id:
|
||||
addr = reservation.folio_id.partner_id.address_get(["invoice"])
|
||||
else:
|
||||
addr = reservation.partner_id.address_get(["invoice"])
|
||||
reservation.partner_invoice_id = addr["invoice"]
|
||||
|
||||
@api.depends("checkin", "checkout")
|
||||
def _compute_reservation_line_ids(self):
|
||||
for reservation in self:
|
||||
cmds = []
|
||||
days_diff = (reservation.checkout - reservation.checkin).days
|
||||
for i in range(0, days_diff):
|
||||
idate = fields.Date.from_string(reservation.checkin) + timedelta(days=i)
|
||||
old_line = reservation.reservation_line_ids.filtered(
|
||||
lambda r: r.date == idate
|
||||
)
|
||||
if not old_line:
|
||||
cmds.append((0, False, {"date": idate},))
|
||||
reservation.reservation_line_ids -= reservation.reservation_line_ids.filtered_domain(
|
||||
[
|
||||
"|",
|
||||
("date", ">", reservation.checkout),
|
||||
("date", "<", reservation.checkin),
|
||||
]
|
||||
)
|
||||
reservation.reservation_line_ids = cmds
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_pricelist_id(self):
|
||||
for reservation in self:
|
||||
pricelist_id = (
|
||||
reservation.partner_id.property_product_pricelist
|
||||
and reservation.partner_id.property_product_pricelist.id
|
||||
or self.env.user.pms_property_id.default_pricelist_id.id
|
||||
)
|
||||
if reservation.pricelist_id.id != pricelist_id:
|
||||
# TODO: Warning change de pricelist?
|
||||
reservation.pricelist_id = pricelist_id
|
||||
|
||||
@api.depends("room_id")
|
||||
def _compute_adults(self):
|
||||
for reservation in self:
|
||||
if reservation.room_id:
|
||||
if reservation.adults:
|
||||
reservation._check_adults()
|
||||
# TODO: Notification if the room capacity is higher than adults?
|
||||
else:
|
||||
reservation.adults = reservation.room_id.capacity
|
||||
|
||||
@api.depends("state", "qty_to_invoice", "qty_invoiced")
|
||||
def _compute_invoice_status(self):
|
||||
"""
|
||||
@@ -423,21 +602,13 @@ class PmsReservation(models.Model):
|
||||
def _computed_nights(self):
|
||||
for res in self:
|
||||
if res.checkin and res.checkout:
|
||||
res.nights = (
|
||||
fields.Date.from_string(res.checkout)
|
||||
- fields.Date.from_string(res.checkin)
|
||||
).days
|
||||
res.nights = (res.checkout - res.checkin).days
|
||||
|
||||
@api.depends("folio_id", "checkin", "checkout")
|
||||
def _compute_localizator(self):
|
||||
# TODO: Compute localizator by reservation
|
||||
for record in self:
|
||||
if record.folio_id:
|
||||
# TODO: Review Datetime type no char v13
|
||||
localizator = re.sub("[^0-9]", "", record.folio_id.name)
|
||||
# checkout = int(re.sub("[^0-9]", "", record.checkout))
|
||||
# checkin = int(re.sub("[^0-9]", "", record.checkin))
|
||||
# localizator += str((checkin + checkout) % 99)
|
||||
record.localizator = localizator
|
||||
record.localizator = fields.date.today()
|
||||
|
||||
@api.depends("service_ids.price_total")
|
||||
def _compute_amount_room_services(self):
|
||||
@@ -549,165 +720,25 @@ class PmsReservation(models.Model):
|
||||
if len(record.checkin_partner_ids) > record.adults + record.children:
|
||||
raise models.ValidationError(_("The room already is completed"))
|
||||
|
||||
@api.onchange("adults", "room_id")
|
||||
def onchange_room_id(self):
|
||||
if self.room_id:
|
||||
write_vals = {}
|
||||
extra_bed = self.service_ids.filtered(
|
||||
lambda r: r.product_id.is_extra_bed is True
|
||||
)
|
||||
if self.room_id.get_capacity(len(extra_bed)) < self.adults:
|
||||
raise UserError(
|
||||
_("%s people do not fit in this room! ;)") % (self.adults)
|
||||
)
|
||||
if self.adults == 0:
|
||||
write_vals.update({"adults": self.room_id.capacity})
|
||||
if not self.room_type_id:
|
||||
write_vals.update({"room_type_id": self.room_id.room_type_id.id})
|
||||
self.update(write_vals)
|
||||
# @api.onchange("checkin_partner_ids")
|
||||
# def onchange_checkin_partner_ids(self):
|
||||
# _logger.info("----------ONCHANGE2-----------")
|
||||
# for record in self:
|
||||
# if len(record.checkin_partner_ids) > record.adults + record.children:
|
||||
# raise models.ValidationError(_("The room already is completed"))
|
||||
|
||||
@api.onchange("cancelled_reason")
|
||||
def onchange_cancelled_reason(self):
|
||||
for record in self:
|
||||
record._compute_cancelled_discount()
|
||||
|
||||
@api.onchange("partner_id")
|
||||
def onchange_partner_id(self):
|
||||
addr = self.partner_id.address_get(["invoice"])
|
||||
pricelist = (
|
||||
self.partner_id.property_product_pricelist
|
||||
and self.partner_id.property_product_pricelist.id
|
||||
or self.env.user.pms_property_id.default_pricelist_id.id
|
||||
)
|
||||
values = {
|
||||
"pricelist_id": pricelist,
|
||||
"partner_invoice_id": addr["invoice"],
|
||||
}
|
||||
self.update(values)
|
||||
|
||||
@api.onchange("pricelist_id")
|
||||
def onchange_pricelist_id(self):
|
||||
values = {
|
||||
"reservation_type": self.env["pms.folio"].calcule_reservation_type(
|
||||
self.pricelist_id.is_staff, self.reservation_type
|
||||
)
|
||||
}
|
||||
self.update(values)
|
||||
|
||||
@api.onchange("reservation_type")
|
||||
def assign_partner_company_on_out_service(self):
|
||||
if self.reservation_type == "out":
|
||||
self.update({"partner_id": self.env.user.company_id.partner_id.id})
|
||||
|
||||
@api.onchange("checkin_partner_ids")
|
||||
def onchange_checkin_partner_ids(self):
|
||||
for record in self:
|
||||
if len(record.checkin_partner_ids) > record.adults + record.children:
|
||||
raise models.ValidationError(_("The room already is completed"))
|
||||
|
||||
@api.onchange("room_type_id", "pricelist_id", "reservation_type")
|
||||
def onchange_overwrite_price_by_day(self):
|
||||
"""
|
||||
We need to overwrite the prices even if they were already established
|
||||
"""
|
||||
if self.room_type_id and self.checkin and self.checkout:
|
||||
days_diff = (
|
||||
fields.Date.from_string(self.checkout)
|
||||
- fields.Date.from_string(self.checkin)
|
||||
).days
|
||||
self.update(
|
||||
self.prepare_reservation_lines(
|
||||
self.checkin,
|
||||
days_diff,
|
||||
self.pricelist_id.id,
|
||||
update_old_prices=True,
|
||||
)
|
||||
)
|
||||
|
||||
@api.onchange("checkin", "checkout")
|
||||
def onchange_dates(self):
|
||||
"""
|
||||
We need to update prices respecting those that were already established
|
||||
"""
|
||||
if not self.checkin:
|
||||
self.checkin = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
if not self.checkout:
|
||||
self.checkout = time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
checkin_dt = fields.Date.from_string(self.checkin)
|
||||
checkout_dt = fields.Date.from_string(self.checkout)
|
||||
if checkin_dt >= checkout_dt:
|
||||
self.checkout = (
|
||||
fields.Date.from_string(self.checkin) + timedelta(days=1)
|
||||
).strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
if self.room_type_id:
|
||||
days_diff = (
|
||||
fields.Date.from_string(self.checkout)
|
||||
- fields.Date.from_string(self.checkin)
|
||||
).days
|
||||
self.update(
|
||||
self.prepare_reservation_lines(
|
||||
self.checkin,
|
||||
days_diff,
|
||||
self.pricelist_id.id,
|
||||
update_old_prices=False,
|
||||
)
|
||||
)
|
||||
|
||||
@api.onchange("checkin", "checkout", "room_type_id")
|
||||
def onchange_room_type_id(self):
|
||||
"""
|
||||
When change de room_type_id, we calc the line description and tax_ids
|
||||
"""
|
||||
if self.room_type_id and self.checkin and self.checkout:
|
||||
checkin_dt = fields.Date.from_string(self.checkin)
|
||||
checkout_dt = fields.Date.from_string(self.checkout)
|
||||
checkin_str = checkin_dt.strftime("%d/%m/%Y")
|
||||
checkout_str = checkout_dt.strftime("%d/%m/%Y")
|
||||
self.name = (
|
||||
self.room_type_id.name + ": " + checkin_str + " - " + checkout_str
|
||||
)
|
||||
self._compute_tax_ids()
|
||||
# self._compute_tax_ids() TODO: refact
|
||||
|
||||
@api.onchange("checkin", "checkout")
|
||||
def onchange_update_service_per_day(self):
|
||||
_logger.info("----------ONCHANGE4-----------")
|
||||
services = self.service_ids.filtered(lambda r: r.per_day is True)
|
||||
for service in services:
|
||||
service.onchange_product_id()
|
||||
|
||||
@api.onchange("checkin", "checkout", "room_id")
|
||||
def onchange_room_availabiltiy_domain(self):
|
||||
self.ensure_one()
|
||||
if self.checkin and self.checkout:
|
||||
if self.overbooking or self.reselling or self.state in ("cancelled"):
|
||||
return
|
||||
occupied = self.env["pms.reservation"].get_reservations(
|
||||
self.checkin,
|
||||
(fields.Date.from_string(self.checkout) - timedelta(days=1)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT
|
||||
),
|
||||
)
|
||||
rooms_occupied = occupied.mapped("room_id.id")
|
||||
if self.room_id:
|
||||
occupied = occupied.filtered(
|
||||
lambda r: r.room_id.id == self.room_id.id
|
||||
and r.id != self._origin.id
|
||||
)
|
||||
if occupied:
|
||||
occupied_name = ", ".join(str(x.folio_id.name) for x in occupied)
|
||||
warning_msg = (
|
||||
_(
|
||||
"You tried to change/confirm \
|
||||
reservation with room those already reserved in this \
|
||||
reservation period: %s "
|
||||
)
|
||||
% occupied_name
|
||||
)
|
||||
raise ValidationError(warning_msg)
|
||||
domain_rooms = [("id", "not in", rooms_occupied)]
|
||||
return {"domain": {"room_id": domain_rooms}}
|
||||
|
||||
@api.onchange("board_service_room_id")
|
||||
def onchange_board_service(self):
|
||||
_logger.info("----------ONCHANGE1-----------")
|
||||
if self.board_service_room_id:
|
||||
board_services = [(5, 0, 0)]
|
||||
for line in self.board_service_room_id.board_service_line_ids:
|
||||
@@ -833,8 +864,6 @@ class PmsReservation(models.Model):
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if "room_id" not in vals:
|
||||
vals.update(self._autoassign(vals))
|
||||
vals.update(self._prepare_add_missing_fields(vals))
|
||||
if "folio_id" in vals and "channel_type" not in vals:
|
||||
folio = self.env["pms.folio"].browse(vals["folio_id"])
|
||||
@@ -858,22 +887,9 @@ class PmsReservation(models.Model):
|
||||
for service in vals["service_ids"]:
|
||||
if service[2]:
|
||||
service[2]["folio_id"] = folio.id
|
||||
user = self.env["res.users"].browse(self.env.uid)
|
||||
if user.has_group("pms.group_pms_call"):
|
||||
vals.update({"to_assign": True, "channel_type": "call"})
|
||||
vals.update(
|
||||
{"last_updated_res": fields.Datetime.now(),}
|
||||
)
|
||||
if self.compute_price_out_vals(vals):
|
||||
days_diff = (
|
||||
fields.Date.from_string(vals["checkout"])
|
||||
- fields.Date.from_string(vals["checkin"])
|
||||
).days
|
||||
vals.update(
|
||||
self.prepare_reservation_lines(
|
||||
vals["checkin"], days_diff, vals["pricelist_id"], vals=vals
|
||||
)
|
||||
) # REVISAR el unlink
|
||||
if (
|
||||
"checkin" in vals
|
||||
and "checkout" in vals
|
||||
@@ -933,17 +949,6 @@ class PmsReservation(models.Model):
|
||||
record.update(
|
||||
{"service_ids": [(6, 0, record.service_ids.ids)] + board_services}
|
||||
)
|
||||
if record.compute_price_out_vals(vals):
|
||||
pricelist_id = (
|
||||
vals["pricelist_id"]
|
||||
if "pricelist_id" in vals
|
||||
else record.pricelist_id.id
|
||||
)
|
||||
record.update(
|
||||
record.prepare_reservation_lines(
|
||||
checkin, days_diff, pricelist_id, vals=vals
|
||||
)
|
||||
) # REVIEW unlink
|
||||
if record.compute_qty_service_day(vals):
|
||||
service_days_diff = (
|
||||
fields.Date.from_string(real_checkout)
|
||||
@@ -967,11 +972,6 @@ class PmsReservation(models.Model):
|
||||
or ("state" in vals and record.state != vals["state"])
|
||||
):
|
||||
record.update({"to_send": True})
|
||||
user = self.env["res.users"].browse(self.env.uid)
|
||||
if user.has_group("pms.group_pms_call"):
|
||||
vals.update(
|
||||
{"to_assign": True,}
|
||||
)
|
||||
record = super(PmsReservation, self).write(vals)
|
||||
return record
|
||||
|
||||
@@ -1025,17 +1025,11 @@ class PmsReservation(models.Model):
|
||||
def _prepare_add_missing_fields(self, values):
|
||||
""" Deduce missing required fields from the onchange """
|
||||
res = {}
|
||||
onchange_fields = ["room_id", "tax_ids", "currency_id", "name", "service_ids"]
|
||||
onchange_fields = ["tax_ids", "currency_id", "service_ids"]
|
||||
if values.get("room_type_id"):
|
||||
if not values.get("reservation_type"):
|
||||
values["reservation_type"] = "normal"
|
||||
line = self.new(values)
|
||||
if any(f not in values for f in onchange_fields):
|
||||
line.onchange_room_id()
|
||||
line.onchange_room_type_id()
|
||||
if "pricelist_id" not in values:
|
||||
line.onchange_partner_id()
|
||||
onchange_fields.append("pricelist_id")
|
||||
for field in onchange_fields:
|
||||
if field == "service_ids":
|
||||
if self.compute_board_services(values):
|
||||
@@ -1047,26 +1041,17 @@ class PmsReservation(models.Model):
|
||||
res[field] = line._fields[field].convert_to_write(line[field], line)
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _autoassign(self, values):
|
||||
res = {}
|
||||
checkin = values.get("checkin")
|
||||
checkout = values.get("checkout")
|
||||
room_type_id = values.get("room_type_id")
|
||||
if checkin and checkout and room_type_id:
|
||||
if "overbooking" not in values:
|
||||
room_chosen = self.env["pms.room.type"].check_availability_room_type(
|
||||
checkin,
|
||||
(fields.Date.from_string(checkout) - timedelta(days=1)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT
|
||||
),
|
||||
room_type_id,
|
||||
)[0]
|
||||
# Check room_chosen exist
|
||||
else:
|
||||
room_chosen = self.env["pms.room.type"].browse(room_type_id).room_ids[0]
|
||||
res.update({"room_id": room_chosen.id})
|
||||
return res
|
||||
def _autoassign(self):
|
||||
self.ensure_one()
|
||||
room_chosen = False
|
||||
rooms_available = self.env["pms.room.type"].check_availability_room_type(
|
||||
dfrom=self.checkin,
|
||||
dto=self.checkout,
|
||||
room_type_id=self.room_type_id.id or False,
|
||||
)
|
||||
if rooms_available:
|
||||
room_chosen = rooms_available[0]
|
||||
return room_chosen
|
||||
|
||||
@api.model
|
||||
def autocheckout(self):
|
||||
@@ -1123,10 +1108,6 @@ class PmsReservation(models.Model):
|
||||
"real_checkout": self.real_checkout,
|
||||
}
|
||||
|
||||
"""
|
||||
STATE WORKFLOW -----------------------------------------------------
|
||||
"""
|
||||
|
||||
def confirm(self):
|
||||
"""
|
||||
@param self: object pointer
|
||||
@@ -1183,7 +1164,7 @@ class PmsReservation(models.Model):
|
||||
if self._context.get("no_penalty", False):
|
||||
_logger.info("Modified Reservation - No Penalty")
|
||||
record.write({"state": "cancelled", "cancelled_reason": cancel_reason})
|
||||
record._compute_cancelled_discount()
|
||||
# record._compute_cancelled_discount()
|
||||
if record.splitted:
|
||||
master_reservation = record.parent_reservation or record
|
||||
splitted_reservs = self.env["pms.reservation"].search(
|
||||
@@ -1237,130 +1218,6 @@ class PmsReservation(models.Model):
|
||||
)
|
||||
splitted_reservs.draft()
|
||||
|
||||
"""
|
||||
PRICE PROCESS ------------------------------------------------------
|
||||
"""
|
||||
|
||||
def compute_price_out_vals(self, vals):
|
||||
"""
|
||||
Compute if It is necesary calc price in write/create
|
||||
"""
|
||||
if not vals:
|
||||
vals = {}
|
||||
if "reservation_line_ids" not in vals and (
|
||||
"checkout" in vals
|
||||
or "checkin" in vals
|
||||
or "room_type_id" in vals
|
||||
or "pricelist_id" in vals
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _compute_cancelled_discount(self):
|
||||
self.ensure_one()
|
||||
pricelist = self.pricelist_id
|
||||
if self.state == "cancelled":
|
||||
# REVIEW: Set 0 qty on cancel room services
|
||||
# (view constrain service_line_days)
|
||||
for service in self.service_ids:
|
||||
service.service_line_ids.write({"day_qty": 0})
|
||||
service._compute_days_qty()
|
||||
if self.cancelled_reason and pricelist and pricelist.cancelation_rule_id:
|
||||
date_start_dt = fields.Date.from_string(
|
||||
self.real_checkin or self.checkin
|
||||
)
|
||||
date_end_dt = fields.Date.from_string(
|
||||
self.real_checkout or self.checkout
|
||||
)
|
||||
days = abs((date_end_dt - date_start_dt).days)
|
||||
rule = pricelist.cancelation_rule_id
|
||||
if self.cancelled_reason == "late":
|
||||
discount = 100 - rule.penalty_late
|
||||
if rule.apply_on_late == "first":
|
||||
days = 1
|
||||
elif rule.apply_on_late == "days":
|
||||
days = rule.days_late
|
||||
elif self.cancelled_reason == "noshow":
|
||||
discount = 100 - rule.penalty_noshow
|
||||
if rule.apply_on_noshow == "first":
|
||||
days = 1
|
||||
elif rule.apply_on_noshow == "days":
|
||||
days = rule.days_late - 1
|
||||
elif self.cancelled_reason == "intime":
|
||||
discount = 100
|
||||
|
||||
checkin = self.real_checkin or self.checkin
|
||||
dates = []
|
||||
for i in range(0, days):
|
||||
dates.append(
|
||||
(fields.Date.from_string(checkin) + timedelta(days=i)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT
|
||||
)
|
||||
)
|
||||
self.reservation_line_ids.filtered(lambda r: r.date in dates).update(
|
||||
{"cancel_discount": discount}
|
||||
)
|
||||
self.reservation_line_ids.filtered(
|
||||
lambda r: r.date not in dates
|
||||
).update({"cancel_discount": 100})
|
||||
else:
|
||||
self.reservation_line_ids.update({"cancel_discount": 0})
|
||||
else:
|
||||
self.reservation_line_ids.update({"cancel_discount": 0})
|
||||
|
||||
@api.model
|
||||
def prepare_reservation_lines(
|
||||
self, dfrom, days, pricelist_id, vals=False, update_old_prices=False
|
||||
):
|
||||
discount = 0
|
||||
cmds = [(5, 0, 0)]
|
||||
if not vals:
|
||||
vals = {}
|
||||
room_type_id = vals.get("room_type_id") or self.room_type_id.id
|
||||
product = self.env["pms.room.type"].browse(room_type_id).product_id
|
||||
partner = self.env["res.partner"].browse(
|
||||
vals.get("partner_id") or self.partner_id.id
|
||||
)
|
||||
if "discount" in vals and vals.get("discount") > 0:
|
||||
discount = vals.get("discount")
|
||||
for i in range(0, days):
|
||||
idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT
|
||||
)
|
||||
old_line = self.reservation_line_ids.filtered(lambda r: r.date == idate)
|
||||
if update_old_prices or not old_line:
|
||||
product = product.with_context(
|
||||
lang=partner.lang,
|
||||
partner=partner.id,
|
||||
quantity=1,
|
||||
date=idate,
|
||||
pricelist=pricelist_id,
|
||||
uom=product.uom_id.id,
|
||||
)
|
||||
# REVIEW this forces to have configured the taxes
|
||||
# included in the price
|
||||
line_price = product.price
|
||||
if old_line and old_line.id:
|
||||
cmds.append(
|
||||
(1, old_line.id, {"price": line_price, "discount": discount})
|
||||
)
|
||||
else:
|
||||
cmds.append(
|
||||
(
|
||||
0,
|
||||
False,
|
||||
{"date": idate, "price": line_price, "discount": discount},
|
||||
)
|
||||
)
|
||||
else:
|
||||
line_price = old_line.price
|
||||
cmds.append((4, old_line.id))
|
||||
return {"reservation_line_ids": cmds}
|
||||
|
||||
"""
|
||||
AVAILABILTY PROCESS ------------------------------------------------
|
||||
"""
|
||||
|
||||
@api.model
|
||||
def get_reservations(self, dfrom, dto):
|
||||
"""
|
||||
@@ -1411,10 +1268,6 @@ class PmsReservation(models.Model):
|
||||
)
|
||||
return reservations_dates
|
||||
|
||||
"""
|
||||
CHECKIN/OUT PROCESS ------------------------------------------------
|
||||
"""
|
||||
|
||||
def _compute_checkin_partner_count(self):
|
||||
_logger.info("_compute_checkin_partner_count")
|
||||
for record in self:
|
||||
@@ -1468,10 +1321,6 @@ class PmsReservation(models.Model):
|
||||
action["target"] = "new"
|
||||
return action
|
||||
|
||||
"""
|
||||
RESERVATION SPLITTED -----------------------------------------------
|
||||
"""
|
||||
|
||||
def split(self, nights):
|
||||
for record in self:
|
||||
date_start_dt = fields.Date.from_string(record.checkin)
|
||||
@@ -1650,10 +1499,6 @@ class PmsReservation(models.Model):
|
||||
action["res_id"] = self.parent_reservation.id
|
||||
return action
|
||||
|
||||
"""
|
||||
MAILING PROCESS
|
||||
"""
|
||||
|
||||
def send_reservation_mail(self):
|
||||
return self.folio_id.send_reservation_mail()
|
||||
|
||||
@@ -1663,10 +1508,6 @@ class PmsReservation(models.Model):
|
||||
def send_cancel_mail(self):
|
||||
return self.folio_id.send_cancel_mail()
|
||||
|
||||
"""
|
||||
INVOICING PROCESS
|
||||
"""
|
||||
|
||||
def _compute_tax_ids(self):
|
||||
for record in self:
|
||||
# If company_id is set, always filter taxes by the company
|
||||
|
||||
@@ -45,12 +45,112 @@ class PmsReservationLine(models.Model):
|
||||
)
|
||||
date = fields.Date("Date")
|
||||
state = fields.Selection(related="reservation_id.state")
|
||||
price = fields.Float(string="Price", digits=("Product Price"))
|
||||
price = fields.Float(
|
||||
string="Price",
|
||||
digits=("Product Price"),
|
||||
compute="_compute_price",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
cancel_discount = fields.Float(
|
||||
string="Cancel Discount (%)", digits=("Discount"), default=0.0
|
||||
string="Cancel Discount (%)",
|
||||
digits=("Discount"),
|
||||
default=0.0,
|
||||
compute="_compute_cancel_discount",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
discount = fields.Float(string="Discount (%)", digits=("Discount"), default=0.0)
|
||||
|
||||
# Compute and Search methods
|
||||
@api.depends(
|
||||
"date",
|
||||
"reservation_id.pricelist_id",
|
||||
"reservation_id.room_type_id",
|
||||
"reservation_id.reservation_type",
|
||||
)
|
||||
def _compute_price(self):
|
||||
for line in self:
|
||||
reservation = line.reservation_id
|
||||
room_type_id = reservation.room_type_id.id
|
||||
product = self.env["pms.room.type"].browse(room_type_id).product_id
|
||||
partner = self.env["res.partner"].browse(reservation.partner_id.id)
|
||||
product = product.with_context(
|
||||
lang=partner.lang,
|
||||
partner=partner.id,
|
||||
quantity=1,
|
||||
date=line.date,
|
||||
pricelist=reservation.pricelist_id.id,
|
||||
uom=product.uom_id.id,
|
||||
)
|
||||
line.price = self.env["account.tax"]._fix_tax_included_price_company(
|
||||
line._get_display_price(product),
|
||||
product.taxes_id,
|
||||
line.reservation_id.tax_ids,
|
||||
line.reservation_id.company_id,
|
||||
)
|
||||
# TODO: Out of service 0 amount
|
||||
|
||||
# TODO: Refact method and allowed cancelled single days
|
||||
@api.depends("reservation_id.cancelled_reason")
|
||||
def _compute_cancel_discount(self):
|
||||
for line in self:
|
||||
line.cancel_discount = 0
|
||||
# reservation = line.reservation_id
|
||||
# pricelist = reservation.pricelist_id
|
||||
# if reservation.state == "cancelled":
|
||||
# # TODO: Set 0 qty on cancel room services change to compute day_qty
|
||||
# # (view constrain service_line_days)
|
||||
# for service in reservation.service_ids:
|
||||
# service.service_line_ids.write({"day_qty": 0})
|
||||
# service._compute_days_qty()
|
||||
# if (
|
||||
# reservation.cancelled_reason
|
||||
# and pricelist
|
||||
# and pricelist.cancelation_rule_id
|
||||
# ):
|
||||
# date_start_dt = fields.Date.from_string(
|
||||
# reservation.real_checkin or reservation.checkin
|
||||
# )
|
||||
# date_end_dt = fields.Date.from_string(
|
||||
# reservation.real_checkout or reservation.checkout
|
||||
# )
|
||||
# days = abs((date_end_dt - date_start_dt).days)
|
||||
# rule = pricelist.cancelation_rule_id
|
||||
# if reservation.cancelled_reason == "late":
|
||||
# discount = 100 - rule.penalty_late
|
||||
# if rule.apply_on_late == "first":
|
||||
# days = 1
|
||||
# elif rule.apply_on_late == "days":
|
||||
# days = rule.days_late
|
||||
# elif reservation.cancelled_reason == "noshow":
|
||||
# discount = 100 - rule.penalty_noshow
|
||||
# if rule.apply_on_noshow == "first":
|
||||
# days = 1
|
||||
# elif rule.apply_on_noshow == "days":
|
||||
# days = rule.days_late - 1
|
||||
# elif reservation.cancelled_reason == "intime":
|
||||
# discount = 100
|
||||
|
||||
# checkin = reservation.real_checkin or reservation.checkin
|
||||
# dates = []
|
||||
# for i in range(0, days):
|
||||
# dates.append(
|
||||
# (
|
||||
# fields.Date.from_string(checkin) + timedelta(days=i)
|
||||
# ).strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
# )
|
||||
# reservation.reservation_line_ids.filtered(
|
||||
# lambda r: r.date in dates
|
||||
# ).update({"cancel_discount": discount})
|
||||
# reservation.reservation_line_ids.filtered(
|
||||
# lambda r: r.date not in dates
|
||||
# ).update({"cancel_discount": 100})
|
||||
# else:
|
||||
# reservation.reservation_line_ids.update({"cancel_discount": 0})
|
||||
# else:
|
||||
# reservation.reservation_line_ids.update({"cancel_discount": 0})
|
||||
|
||||
# Constraints and onchanges
|
||||
@api.constrains("date")
|
||||
def constrains_duplicated_date(self):
|
||||
@@ -71,3 +171,33 @@ class PmsReservationLine(models.Model):
|
||||
lambda r: r.date == record.date
|
||||
)
|
||||
cancel_lines.day_qty = 0
|
||||
|
||||
def _get_display_price(self, product):
|
||||
if self.reservation_id.pricelist_id.discount_policy == "with_discount":
|
||||
return product.with_context(
|
||||
pricelist=self.reservation_id.pricelist_id.id
|
||||
).price
|
||||
product_context = dict(
|
||||
self.env.context,
|
||||
partner_id=self.reservation_id.partner_id.id,
|
||||
date=self.date,
|
||||
uom=product.uom_id.id,
|
||||
)
|
||||
|
||||
final_price, rule_id = self.reservation_id.pricelist_id.with_context(
|
||||
product_context
|
||||
).get_product_price_rule(product, 1.0, self.reservation_id.partner_id)
|
||||
base_price, currency = self.with_context(
|
||||
product_context
|
||||
)._get_real_price_currency(
|
||||
product, rule_id, 1, product.uom_id, self.reservation_id.pricelist_id.id
|
||||
)
|
||||
if currency != self.reservation_id.pricelist_id.currency_id:
|
||||
base_price = currency._convert(
|
||||
base_price,
|
||||
self.reservation_id.pricelist_id.currency_id,
|
||||
self.reservation_id.company_id or self.env.company,
|
||||
fields.Date.today(),
|
||||
)
|
||||
# negative discounts (= surcharge) are included in the display price
|
||||
return max(base_price, final_price)
|
||||
|
||||
@@ -7,10 +7,10 @@ from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class PmsRoom(models.Model):
|
||||
""" The rooms for lodging can be for sleeping, usually called rooms,
|
||||
and also for speeches (conference rooms), parking,
|
||||
relax with cafe con leche, spa...
|
||||
"""
|
||||
"""The rooms for lodging can be for sleeping, usually called rooms,
|
||||
and also for speeches (conference rooms), parking,
|
||||
relax with cafe con leche, spa...
|
||||
"""
|
||||
|
||||
_name = "pms.room"
|
||||
_description = "Property Room"
|
||||
|
||||
@@ -6,7 +6,7 @@ from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class PmsRoomType(models.Model):
|
||||
""" Before creating a 'room type', you need to consider the following:
|
||||
"""Before creating a 'room type', you need to consider the following:
|
||||
With the term 'room type' is meant a sales type of residential accommodation: for
|
||||
example, a Double Room, a Economic Room, an Apartment, a Tent, a Caravan...
|
||||
"""
|
||||
@@ -92,6 +92,7 @@ class PmsRoomType(models.Model):
|
||||
capacities = self.room_ids.mapped("capacity")
|
||||
return min(capacities) if any(capacities) else 0
|
||||
|
||||
# TODO: Change name method by rooms_available()
|
||||
@api.model
|
||||
def check_availability_room_type(self, dfrom, dto, room_type_id=False, notthis=[]):
|
||||
"""
|
||||
@@ -102,7 +103,7 @@ class PmsRoomType(models.Model):
|
||||
reservations_rooms = reservations.mapped("room_id.id")
|
||||
free_rooms = self.env["pms.room"].search(
|
||||
[("id", "not in", reservations_rooms), ("id", "not in", notthis)]
|
||||
)
|
||||
) # TODO: Review if with the new caché V13 We need notthis []¿?
|
||||
if room_type_id:
|
||||
rooms_linked = (
|
||||
self.env["pms.room.type"].search([("id", "=", room_type_id)]).room_ids
|
||||
@@ -145,20 +146,21 @@ class PmsRoomType(models.Model):
|
||||
{"partner_id": partner_id if partner_id else False, "discount": discount,}
|
||||
)
|
||||
rate_vals = {}
|
||||
for room_type in room_types:
|
||||
vals.update({"room_type_id": room_type.id})
|
||||
room_vals = self.env["pms.reservation"].prepare_reservation_lines(
|
||||
date_from,
|
||||
days,
|
||||
pricelist_id=pricelist_id,
|
||||
vals=vals,
|
||||
update_old_prices=False,
|
||||
)
|
||||
rate_vals.update(
|
||||
{
|
||||
room_type.id: [
|
||||
item[2] for item in room_vals["reservation_line_ids"] if item[2]
|
||||
]
|
||||
}
|
||||
)
|
||||
# TODO: Now it is computed field, We need other way to return rates
|
||||
# for room_type in room_types:
|
||||
# vals.update({"room_type_id": room_type.id})
|
||||
# room_vals = self.env["pms.reservation"].prepare_reservation_lines(
|
||||
# date_from,
|
||||
# days,
|
||||
# pricelist_id=pricelist_id,
|
||||
# vals=vals,
|
||||
# update_old_prices=False,
|
||||
# )
|
||||
# rate_vals.update(
|
||||
# {
|
||||
# room_type.id: [
|
||||
# item[2] for item in room_vals["reservation_line_ids"] if item[2]
|
||||
# ]
|
||||
# }
|
||||
# )
|
||||
return rate_vals
|
||||
|
||||
@@ -5,7 +5,7 @@ from odoo import fields, models
|
||||
|
||||
|
||||
class PmsRoomTypeClass(models.Model):
|
||||
""" Before creating a 'room type_class', you need to consider the following:
|
||||
"""Before creating a 'room type_class', you need to consider the following:
|
||||
With the term 'room type class' is meant a physical class of
|
||||
residential accommodation: for example, a Room, a Bed, an Apartment,
|
||||
a Tent, a Caravan...
|
||||
|
||||
@@ -4,8 +4,8 @@ from odoo import api, fields, models
|
||||
|
||||
|
||||
class PmsRoomTypeRestriction(models.Model):
|
||||
""" The room type restriction is used as a daily restriction plan for room types
|
||||
and therefore is related only with one property. """
|
||||
"""The room type restriction is used as a daily restriction plan for room types
|
||||
and therefore is related only with one property."""
|
||||
|
||||
_name = "pms.room.type.restriction"
|
||||
_description = "Reservation restriction plan"
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
.. [ This file is optional, it should explain how to configure
|
||||
the module before using it; it is aimed at advanced users. ]
|
||||
|
||||
You will find the hotel settings in `Settings > Users & Companies > Hotels > Your Hotel.
|
||||
You will find the hotel settings in Settings > Users & Companies > Hotels > Your Hotel.
|
||||
|
||||
This module required additional configuration for company, accounting, invoicing and user privileges.
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
.. authors are taken from the __manifest__.py file
|
||||
* Dario Lodeiros <dario@commitsun.com>
|
||||
* Alexandre Díaz
|
||||
* Pablo Quesada
|
||||
* Jose Luis Algara
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
.. [ This file is optional and contains additional credits, other than
|
||||
authors, contributors, and maintainers. ]
|
||||
@@ -1,5 +1,3 @@
|
||||
.. [ This file must be max 2-3 paragraphs, and is required. ]
|
||||
|
||||
This module is an all-in-one property management system (PMS) focused on medium-sized hotels
|
||||
for managing every aspect of your property's daily operations.
|
||||
|
||||
|
||||
@@ -1,5 +1,2 @@
|
||||
.. [ This file must only be present if there are very specific
|
||||
installation instructions, such as installing non-python dependencies. The audience is systems administrators. ]
|
||||
|
||||
This module depends on modules ``base``, ``sale_stock``, ``account_payment_return``, ``partner_firstname``,
|
||||
and ``account_cancel``. Ensure yourself to have all them in your addons list.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
.. [ Enumerate known caveats and future potential improvements.
|
||||
It is mostly intended for end-users, and can also help potential new contributors discovering new features to implement. ]
|
||||
|
||||
- [ ]
|
||||
@@ -1,6 +1 @@
|
||||
.. [ This file must be present and contains the usage instructions
|
||||
for end-users. As all other rst files included in the README, it MUST NOT contain reStructuredText sections
|
||||
only body text (paragraphs, lists, tables, etc). Should you need a more elaborate structure to explain the addon,
|
||||
please create a Sphinx documentation (which may include this file as a "quick start" section). ]
|
||||
|
||||
To use this module, please, read the complete user guide at https://roomdoo.com.
|
||||
|
||||
445
pms/static/description/index.html
Normal file
445
pms/static/description/index.html
Normal file
@@ -0,0 +1,445 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="generator" content="Docutils 0.15.1: http://docutils.sourceforge.net/" />
|
||||
<title>PMS (Property Management System)</title>
|
||||
<style type="text/css">
|
||||
|
||||
/*
|
||||
:Author: David Goodger (goodger@python.org)
|
||||
:Id: $Id: html4css1.css 7952 2016-07-26 18:15:59Z milde $
|
||||
:Copyright: This stylesheet has been placed in the public domain.
|
||||
|
||||
Default cascading style sheet for the HTML output of Docutils.
|
||||
|
||||
See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to
|
||||
customize this style sheet.
|
||||
*/
|
||||
|
||||
/* used to remove borders from tables and images */
|
||||
.borderless, table.borderless td, table.borderless th {
|
||||
border: 0 }
|
||||
|
||||
table.borderless td, table.borderless th {
|
||||
/* Override padding for "table.docutils td" with "! important".
|
||||
The right padding separates the table cells. */
|
||||
padding: 0 0.5em 0 0 ! important }
|
||||
|
||||
.first {
|
||||
/* Override more specific margin styles with "! important". */
|
||||
margin-top: 0 ! important }
|
||||
|
||||
.last, .with-subtitle {
|
||||
margin-bottom: 0 ! important }
|
||||
|
||||
.hidden {
|
||||
display: none }
|
||||
|
||||
.subscript {
|
||||
vertical-align: sub;
|
||||
font-size: smaller }
|
||||
|
||||
.superscript {
|
||||
vertical-align: super;
|
||||
font-size: smaller }
|
||||
|
||||
a.toc-backref {
|
||||
text-decoration: none ;
|
||||
color: black }
|
||||
|
||||
blockquote.epigraph {
|
||||
margin: 2em 5em ; }
|
||||
|
||||
dl.docutils dd {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Uncomment (and remove this text!) to get bold-faced definition list terms
|
||||
dl.docutils dt {
|
||||
font-weight: bold }
|
||||
*/
|
||||
|
||||
div.abstract {
|
||||
margin: 2em 5em }
|
||||
|
||||
div.abstract p.topic-title {
|
||||
font-weight: bold ;
|
||||
text-align: center }
|
||||
|
||||
div.admonition, div.attention, div.caution, div.danger, div.error,
|
||||
div.hint, div.important, div.note, div.tip, div.warning {
|
||||
margin: 2em ;
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.admonition p.admonition-title, div.hint p.admonition-title,
|
||||
div.important p.admonition-title, div.note p.admonition-title,
|
||||
div.tip p.admonition-title {
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
div.attention p.admonition-title, div.caution p.admonition-title,
|
||||
div.danger p.admonition-title, div.error p.admonition-title,
|
||||
div.warning p.admonition-title, .code .error {
|
||||
color: red ;
|
||||
font-weight: bold ;
|
||||
font-family: sans-serif }
|
||||
|
||||
/* Uncomment (and remove this text!) to get reduced vertical space in
|
||||
compound paragraphs.
|
||||
div.compound .compound-first, div.compound .compound-middle {
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
div.compound .compound-last, div.compound .compound-middle {
|
||||
margin-top: 0.5em }
|
||||
*/
|
||||
|
||||
div.dedication {
|
||||
margin: 2em 5em ;
|
||||
text-align: center ;
|
||||
font-style: italic }
|
||||
|
||||
div.dedication p.topic-title {
|
||||
font-weight: bold ;
|
||||
font-style: normal }
|
||||
|
||||
div.figure {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
div.footer, div.header {
|
||||
clear: both;
|
||||
font-size: smaller }
|
||||
|
||||
div.line-block {
|
||||
display: block ;
|
||||
margin-top: 1em ;
|
||||
margin-bottom: 1em }
|
||||
|
||||
div.line-block div.line-block {
|
||||
margin-top: 0 ;
|
||||
margin-bottom: 0 ;
|
||||
margin-left: 1.5em }
|
||||
|
||||
div.sidebar {
|
||||
margin: 0 0 0.5em 1em ;
|
||||
border: medium outset ;
|
||||
padding: 1em ;
|
||||
background-color: #ffffee ;
|
||||
width: 40% ;
|
||||
float: right ;
|
||||
clear: right }
|
||||
|
||||
div.sidebar p.rubric {
|
||||
font-family: sans-serif ;
|
||||
font-size: medium }
|
||||
|
||||
div.system-messages {
|
||||
margin: 5em }
|
||||
|
||||
div.system-messages h1 {
|
||||
color: red }
|
||||
|
||||
div.system-message {
|
||||
border: medium outset ;
|
||||
padding: 1em }
|
||||
|
||||
div.system-message p.system-message-title {
|
||||
color: red ;
|
||||
font-weight: bold }
|
||||
|
||||
div.topic {
|
||||
margin: 2em }
|
||||
|
||||
h1.section-subtitle, h2.section-subtitle, h3.section-subtitle,
|
||||
h4.section-subtitle, h5.section-subtitle, h6.section-subtitle {
|
||||
margin-top: 0.4em }
|
||||
|
||||
h1.title {
|
||||
text-align: center }
|
||||
|
||||
h2.subtitle {
|
||||
text-align: center }
|
||||
|
||||
hr.docutils {
|
||||
width: 75% }
|
||||
|
||||
img.align-left, .figure.align-left, object.align-left, table.align-left {
|
||||
clear: left ;
|
||||
float: left ;
|
||||
margin-right: 1em }
|
||||
|
||||
img.align-right, .figure.align-right, object.align-right, table.align-right {
|
||||
clear: right ;
|
||||
float: right ;
|
||||
margin-left: 1em }
|
||||
|
||||
img.align-center, .figure.align-center, object.align-center {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
table.align-center {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left }
|
||||
|
||||
.align-center {
|
||||
clear: both ;
|
||||
text-align: center }
|
||||
|
||||
.align-right {
|
||||
text-align: right }
|
||||
|
||||
/* reset inner alignment in figures */
|
||||
div.align-right {
|
||||
text-align: inherit }
|
||||
|
||||
/* div.align-center * { */
|
||||
/* text-align: left } */
|
||||
|
||||
.align-top {
|
||||
vertical-align: top }
|
||||
|
||||
.align-middle {
|
||||
vertical-align: middle }
|
||||
|
||||
.align-bottom {
|
||||
vertical-align: bottom }
|
||||
|
||||
ol.simple, ul.simple {
|
||||
margin-bottom: 1em }
|
||||
|
||||
ol.arabic {
|
||||
list-style: decimal }
|
||||
|
||||
ol.loweralpha {
|
||||
list-style: lower-alpha }
|
||||
|
||||
ol.upperalpha {
|
||||
list-style: upper-alpha }
|
||||
|
||||
ol.lowerroman {
|
||||
list-style: lower-roman }
|
||||
|
||||
ol.upperroman {
|
||||
list-style: upper-roman }
|
||||
|
||||
p.attribution {
|
||||
text-align: right ;
|
||||
margin-left: 50% }
|
||||
|
||||
p.caption {
|
||||
font-style: italic }
|
||||
|
||||
p.credits {
|
||||
font-style: italic ;
|
||||
font-size: smaller }
|
||||
|
||||
p.label {
|
||||
white-space: nowrap }
|
||||
|
||||
p.rubric {
|
||||
font-weight: bold ;
|
||||
font-size: larger ;
|
||||
color: maroon ;
|
||||
text-align: center }
|
||||
|
||||
p.sidebar-title {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold ;
|
||||
font-size: larger }
|
||||
|
||||
p.sidebar-subtitle {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
p.topic-title {
|
||||
font-weight: bold }
|
||||
|
||||
pre.address {
|
||||
margin-bottom: 0 ;
|
||||
margin-top: 0 ;
|
||||
font: inherit }
|
||||
|
||||
pre.literal-block, pre.doctest-block, pre.math, pre.code {
|
||||
margin-left: 2em ;
|
||||
margin-right: 2em }
|
||||
|
||||
pre.code .ln { color: grey; } /* line numbers */
|
||||
pre.code, code { background-color: #eeeeee }
|
||||
pre.code .comment, code .comment { color: #5C6576 }
|
||||
pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold }
|
||||
pre.code .literal.string, code .literal.string { color: #0C5404 }
|
||||
pre.code .name.builtin, code .name.builtin { color: #352B84 }
|
||||
pre.code .deleted, code .deleted { background-color: #DEB0A1}
|
||||
pre.code .inserted, code .inserted { background-color: #A3D289}
|
||||
|
||||
span.classifier {
|
||||
font-family: sans-serif ;
|
||||
font-style: oblique }
|
||||
|
||||
span.classifier-delimiter {
|
||||
font-family: sans-serif ;
|
||||
font-weight: bold }
|
||||
|
||||
span.interpreted {
|
||||
font-family: sans-serif }
|
||||
|
||||
span.option {
|
||||
white-space: nowrap }
|
||||
|
||||
span.pre {
|
||||
white-space: pre }
|
||||
|
||||
span.problematic {
|
||||
color: red }
|
||||
|
||||
span.section-subtitle {
|
||||
/* font-size relative to parent (h1..h6 element) */
|
||||
font-size: 80% }
|
||||
|
||||
table.citation {
|
||||
border-left: solid 1px gray;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docinfo {
|
||||
margin: 2em 4em }
|
||||
|
||||
table.docutils {
|
||||
margin-top: 0.5em ;
|
||||
margin-bottom: 0.5em }
|
||||
|
||||
table.footnote {
|
||||
border-left: solid 1px black;
|
||||
margin-left: 1px }
|
||||
|
||||
table.docutils td, table.docutils th,
|
||||
table.docinfo td, table.docinfo th {
|
||||
padding-left: 0.5em ;
|
||||
padding-right: 0.5em ;
|
||||
vertical-align: top }
|
||||
|
||||
table.docutils th.field-name, table.docinfo th.docinfo-name {
|
||||
font-weight: bold ;
|
||||
text-align: left ;
|
||||
white-space: nowrap ;
|
||||
padding-left: 0 }
|
||||
|
||||
/* "booktabs" style (no vertical lines) */
|
||||
table.docutils.booktabs {
|
||||
border: 0px;
|
||||
border-top: 2px solid;
|
||||
border-bottom: 2px solid;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.docutils.booktabs * {
|
||||
border: 0px;
|
||||
}
|
||||
table.docutils.booktabs th {
|
||||
border-bottom: thin solid;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 tt.docutils, h2 tt.docutils, h3 tt.docutils,
|
||||
h4 tt.docutils, h5 tt.docutils, h6 tt.docutils {
|
||||
font-size: 100% }
|
||||
|
||||
ul.auto-toc {
|
||||
list-style-type: none }
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="document" id="pms-property-management-system">
|
||||
<h1 class="title">PMS (Property Management System)</h1>
|
||||
|
||||
<!-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! This file is generated by oca-gen-addon-readme !!
|
||||
!! changes will be overwritten. !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
|
||||
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Beta" src="https://img.shields.io/badge/maturity-Beta-yellow.png" /></a> <a class="reference external" href="http://www.gnu.org/licenses/agpl-3.0-standalone.html"><img alt="License: AGPL-3" src="https://img.shields.io/badge/licence-AGPL--3-blue.png" /></a> <a class="reference external" href="https://github.com/OCA/vertical-hotel/tree/12.0/pms"><img alt="OCA/vertical-hotel" src="https://img.shields.io/badge/github-OCA%2Fvertical--hotel-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/vertical-hotel-12-0/vertical-hotel-12-0-pms"><img alt="Translate me on Weblate" src="https://img.shields.io/badge/weblate-Translate%20me-F47D42.png" /></a> <a class="reference external" href="https://runbot.odoo-community.org/runbot/157/12.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
|
||||
<p>This module is an all-in-one property management system (PMS) focused on medium-sized hotels
|
||||
for managing every aspect of your property’s daily operations.</p>
|
||||
<p>You can manage hotel properties with multi-hotel and multi-company support, including your rooms inventory,
|
||||
reservations, check-in, daily reports, board services, rate and restriction plans among other hotel functionalities.</p>
|
||||
<p><strong>Table of contents</strong></p>
|
||||
<div class="contents local topic" id="contents">
|
||||
<ul class="simple">
|
||||
<li><a class="reference internal" href="#installation" id="id1">Installation</a></li>
|
||||
<li><a class="reference internal" href="#configuration" id="id2">Configuration</a></li>
|
||||
<li><a class="reference internal" href="#usage" id="id3">Usage</a></li>
|
||||
<li><a class="reference internal" href="#bug-tracker" id="id4">Bug Tracker</a></li>
|
||||
<li><a class="reference internal" href="#credits" id="id5">Credits</a><ul>
|
||||
<li><a class="reference internal" href="#authors" id="id6">Authors</a></li>
|
||||
<li><a class="reference internal" href="#contributors" id="id7">Contributors</a></li>
|
||||
<li><a class="reference internal" href="#maintainers" id="id8">Maintainers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="installation">
|
||||
<h1><a class="toc-backref" href="#id1">Installation</a></h1>
|
||||
<p>This module depends on modules <tt class="docutils literal">base</tt>, <tt class="docutils literal">sale_stock</tt>, <tt class="docutils literal">account_payment_return</tt>, <tt class="docutils literal">partner_firstname</tt>,
|
||||
and <tt class="docutils literal">account_cancel</tt>. Ensure yourself to have all them in your addons list.</p>
|
||||
</div>
|
||||
<div class="section" id="configuration">
|
||||
<h1><a class="toc-backref" href="#id2">Configuration</a></h1>
|
||||
<p>You will find the hotel settings in Settings > Users & Companies > Hotels > Your Hotel.</p>
|
||||
<p>This module required additional configuration for company, accounting, invoicing and user privileges.</p>
|
||||
</div>
|
||||
<div class="section" id="usage">
|
||||
<h1><a class="toc-backref" href="#id3">Usage</a></h1>
|
||||
<p>To use this module, please, read the complete user guide at <a class="reference external" href="https://roomdoo.com">https://roomdoo.com</a>.</p>
|
||||
</div>
|
||||
<div class="section" id="bug-tracker">
|
||||
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
|
||||
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/vertical-hotel/issues">GitHub Issues</a>.
|
||||
In case of trouble, please check there if your issue has already been reported.
|
||||
If you spotted it first, help us smashing it by providing a detailed and welcomed
|
||||
<a class="reference external" href="https://github.com/OCA/vertical-hotel/issues/new?body=module:%20pms%0Aversion:%2012.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**">feedback</a>.</p>
|
||||
<p>Do not contact contributors directly about support or help with technical issues.</p>
|
||||
</div>
|
||||
<div class="section" id="credits">
|
||||
<h1><a class="toc-backref" href="#id5">Credits</a></h1>
|
||||
<div class="section" id="authors">
|
||||
<h2><a class="toc-backref" href="#id6">Authors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Dario Lodeiros</li>
|
||||
<li>Alexadre Diaz</li>
|
||||
<li>Pablo Quesada</li>
|
||||
<li>Jose Luis Algara</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="contributors">
|
||||
<h2><a class="toc-backref" href="#id7">Contributors</a></h2>
|
||||
<ul class="simple">
|
||||
<li>Dario Lodeiros <<a class="reference external" href="mailto:dario@commitsun.com">dario@commitsun.com</a>></li>
|
||||
<li>Alexandre Díaz</li>
|
||||
<li>Pablo Quesada</li>
|
||||
<li>Jose Luis Algara</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section" id="maintainers">
|
||||
<h2><a class="toc-backref" href="#id8">Maintainers</a></h2>
|
||||
<p>This module is maintained by the OCA.</p>
|
||||
<a class="reference external image-reference" href="https://odoo-community.org"><img alt="Odoo Community Association" src="https://odoo-community.org/logo.png" /></a>
|
||||
<p>OCA, or the Odoo Community Association, is a nonprofit organization whose
|
||||
mission is to support the collaborative development of Odoo features and
|
||||
promote its widespread use.</p>
|
||||
<p>This module is part of the <a class="reference external" href="https://github.com/OCA/vertical-hotel/tree/12.0/pms">OCA/vertical-hotel</a> project on GitHub.</p>
|
||||
<p>You are welcome to contribute. To learn how please visit <a class="reference external" href="https://odoo-community.org/page/Contribute">https://odoo-community.org/page/Contribute</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,29 +1,40 @@
|
||||
odoo.define('pms.AbstractWebClient', function (require) {
|
||||
odoo.define("pms.AbstractWebClient", function(require) {
|
||||
"use strict";
|
||||
|
||||
var AbstractWebClient = require('web.AbstractWebClient');
|
||||
var session = require('web.session');
|
||||
var utils = require('web.utils');
|
||||
|
||||
var AbstractWebClient = require("web.AbstractWebClient");
|
||||
var session = require("web.session");
|
||||
var utils = require("web.utils");
|
||||
|
||||
return AbstractWebClient.include({
|
||||
start: function () {
|
||||
start: function() {
|
||||
var state = $.bbq.getState();
|
||||
var current_pms_property_id = session.user_pms_properties.current_pms_property[0]
|
||||
var current_pms_property_id =
|
||||
session.user_pms_properties.current_pms_property[0];
|
||||
if (!state.pms_pids) {
|
||||
state.pms_pids = utils.get_cookie('pms_pids') !== null ? utils.get_cookie('pms_pids') : String(current_pms_property_id);
|
||||
state.pms_pids =
|
||||
utils.get_cookie("pms_pids") !== null
|
||||
? utils.get_cookie("pms_pids")
|
||||
: String(current_pms_property_id);
|
||||
}
|
||||
var statePmsPropertyIDS = _.map(state.pms_pids.split(','), function (pms_pid) { return parseInt(pms_pid) });
|
||||
var userPmsPropertyIDS = _.map(session.user_pms_properties.allowed_pms_properties, function(pms_property) {return pms_property[0]});
|
||||
var statePmsPropertyIDS = _.map(state.pms_pids.split(","), function(
|
||||
pms_pid
|
||||
) {
|
||||
return parseInt(pms_pid);
|
||||
});
|
||||
var userPmsPropertyIDS = _.map(
|
||||
session.user_pms_properties.allowed_pms_properties,
|
||||
function(pms_property) {
|
||||
return pms_property[0];
|
||||
}
|
||||
);
|
||||
// Check that the user has access to all the companies
|
||||
if (!_.isEmpty(_.difference(statePmsPropertyIDS, userPmsPropertyIDS))) {
|
||||
state.pms_pids = String(current_pms_property_id);
|
||||
statePmsPropertyIDS = [current_pms_property_id]
|
||||
statePmsPropertyIDS = [current_pms_property_id];
|
||||
}
|
||||
session.user_context.allowed_pms_property_ids = statePmsPropertyIDS;
|
||||
|
||||
|
||||
return this._super.apply(this, arguments);
|
||||
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
odoo.define('pms.session', function (require) {
|
||||
odoo.define("pms.session", function(require) {
|
||||
"use strict";
|
||||
|
||||
var Session = require('web.Session');
|
||||
var utils = require('web.utils');
|
||||
|
||||
var Session = require("web.Session");
|
||||
var utils = require("web.utils");
|
||||
var modules = odoo._modules;
|
||||
|
||||
var inherited_Session = Session.extend({
|
||||
// TODO: require test and debug
|
||||
setPmsProperties: function (pms_main_property_id, pms_property_ids) {
|
||||
var hash = $.bbq.getState()
|
||||
hash.pms_pids = pms_property_ids.sort(function(a, b) {
|
||||
if (a === pms_main_property_id) {
|
||||
return -1;
|
||||
} else if (b === pms_main_property_id) {
|
||||
return 1;
|
||||
} else {
|
||||
setPmsProperties: function(pms_main_property_id, pms_property_ids) {
|
||||
var hash = $.bbq.getState();
|
||||
hash.pms_pids = pms_property_ids
|
||||
.sort(function(a, b) {
|
||||
if (a === pms_main_property_id) {
|
||||
return -1;
|
||||
} else if (b === pms_main_property_id) {
|
||||
return 1;
|
||||
}
|
||||
return a - b;
|
||||
}
|
||||
}).join(',');
|
||||
utils.set_cookie('pms_pids', hash.pms_pids || String(pms_main_property_id));
|
||||
$.bbq.pushState({'pms_pids': hash.pms_pids}, 0);
|
||||
})
|
||||
.join(",");
|
||||
utils.set_cookie("pms_pids", hash.pms_pids || String(pms_main_property_id));
|
||||
$.bbq.pushState({pms_pids: hash.pms_pids}, 0);
|
||||
location.reload();
|
||||
},
|
||||
});
|
||||
|
||||
var pms_session = new inherited_Session(undefined, undefined, {modules: modules, use_cors: false});
|
||||
|
||||
var pms_session = new inherited_Session(undefined, undefined, {
|
||||
modules: modules,
|
||||
use_cors: false,
|
||||
});
|
||||
pms_session.is_bound = pms_session.session_bind();
|
||||
|
||||
|
||||
return pms_session;
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,126 +1,166 @@
|
||||
odoo.define('web.SwitchPmsMenu', function(require) {
|
||||
odoo.define("web.SwitchPmsMenu", function(require) {
|
||||
"use strict";
|
||||
|
||||
|
||||
/**
|
||||
* When Odoo is configured in multi-property mode, users should obviously be able
|
||||
* to switch their interface from one property to the other. This is the purpose
|
||||
* of this widget, by displaying a dropdown menu in the systray.
|
||||
*/
|
||||
|
||||
var config = require('web.config');
|
||||
var core = require('web.core');
|
||||
var session = require('pms.session');
|
||||
var SystrayMenu = require('web.SystrayMenu');
|
||||
var Widget = require('web.Widget');
|
||||
|
||||
|
||||
var config = require("web.config");
|
||||
var core = require("web.core");
|
||||
var session = require("pms.session");
|
||||
var SystrayMenu = require("web.SystrayMenu");
|
||||
var Widget = require("web.Widget");
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
|
||||
var SwitchPmsMenu = Widget.extend({
|
||||
template: 'SwitchPmsMenu',
|
||||
template: "SwitchPmsMenu",
|
||||
events: {
|
||||
'click .dropdown-item[data-menu] div.pms_log_into': '_onSwitchPmsPropertyClick',
|
||||
'keydown .dropdown-item[data-menu] div.pms_log_into': '_onSwitchPmsPropertyClick',
|
||||
'click .dropdown-item[data-menu] div.pms_toggle_property': '_onTogglePmsPropertyClick',
|
||||
'keydown .dropdown-item[data-menu] div.pms_toggle_property': '_onTogglePmsPropertyClick',
|
||||
"click .dropdown-item[data-menu] div.pms_log_into":
|
||||
"_onSwitchPmsPropertyClick",
|
||||
"keydown .dropdown-item[data-menu] div.pms_log_into":
|
||||
"_onSwitchPmsPropertyClick",
|
||||
"click .dropdown-item[data-menu] div.pms_toggle_property":
|
||||
"_onTogglePmsPropertyClick",
|
||||
"keydown .dropdown-item[data-menu] div.pms_toggle_property":
|
||||
"_onTogglePmsPropertyClick",
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
init: function () {
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
this.isMobile = config.device.isMobile;
|
||||
this._onSwitchPmsPropertyClick = _.debounce(this._onSwitchPmsPropertyClick, 1500, true);
|
||||
this._onSwitchPmsPropertyClick = _.debounce(
|
||||
this._onSwitchPmsPropertyClick,
|
||||
1500,
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
willStart: function () {
|
||||
willStart: function() {
|
||||
var self = this;
|
||||
this.allowed_pms_property_ids = String(session.user_context.allowed_pms_property_ids)
|
||||
.split(',')
|
||||
.map(function (id) {return parseInt(id);});
|
||||
this.user_pms_properties = session.user_pms_properties.allowed_pms_properties;
|
||||
this.allowed_pms_property_ids = String(
|
||||
session.user_context.allowed_pms_property_ids
|
||||
)
|
||||
.split(",")
|
||||
.map(function(id) {
|
||||
return parseInt(id);
|
||||
});
|
||||
this.user_pms_properties =
|
||||
session.user_pms_properties.allowed_pms_properties;
|
||||
this.current_pms_property = this.allowed_pms_property_ids[0];
|
||||
this.current_pms_property_name = _.find(session.user_pms_properties.allowed_pms_properties, function (pms_property) {
|
||||
return pms_property[0] === self.current_pms_property;
|
||||
})[1];
|
||||
this.current_pms_property_name = _.find(
|
||||
session.user_pms_properties.allowed_pms_properties,
|
||||
function(pms_property) {
|
||||
return pms_property[0] === self.current_pms_property;
|
||||
}
|
||||
)[1];
|
||||
return this._super.apply(this, arguments);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent|KeyEvent} ev
|
||||
*/
|
||||
_onSwitchPmsPropertyClick: function (ev) {
|
||||
if (ev.type == 'keydown' && ev.which != $.ui.keyCode.ENTER && ev.which != $.ui.keyCode.SPACE) {
|
||||
_onSwitchPmsPropertyClick: function(ev) {
|
||||
if (
|
||||
ev.type == "keydown" &&
|
||||
ev.which != $.ui.keyCode.ENTER &&
|
||||
ev.which != $.ui.keyCode.SPACE
|
||||
) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var dropdownItem = $(ev.currentTarget).parent();
|
||||
var dropdownMenu = dropdownItem.parent();
|
||||
var pms_propertyID = dropdownItem.data('pms_property-id');
|
||||
var pms_propertyID = dropdownItem.data("pms_property-id");
|
||||
var allowed_pms_property_ids = this.allowed_pms_property_ids;
|
||||
if (dropdownItem.find('.fa-square-o').length) {
|
||||
if (dropdownItem.find(".fa-square-o").length) {
|
||||
// 1 enabled pms_property: Stay in single pms proeprty mode
|
||||
if (this.allowed_pms_property_ids.length === 1) {
|
||||
if (this.isMobile) {
|
||||
dropdownMenu = dropdownMenu.parent();
|
||||
}
|
||||
dropdownMenu.find('.fa-check-square').removeClass('fa-check-square').addClass('fa-square-o');
|
||||
dropdownItem.find('.fa-square-o').removeClass('fa-square-o').addClass('fa-check-square');
|
||||
dropdownMenu
|
||||
.find(".fa-check-square")
|
||||
.removeClass("fa-check-square")
|
||||
.addClass("fa-square-o");
|
||||
dropdownItem
|
||||
.find(".fa-square-o")
|
||||
.removeClass("fa-square-o")
|
||||
.addClass("fa-check-square");
|
||||
allowed_pms_property_ids = [pms_propertyID];
|
||||
} else { // Multi pms proeprty mode
|
||||
} else {
|
||||
// Multi pms proeprty mode
|
||||
allowed_pms_property_ids.push(pms_propertyID);
|
||||
dropdownItem.find('.fa-square-o').removeClass('fa-square-o').addClass('fa-check-square');
|
||||
dropdownItem
|
||||
.find(".fa-square-o")
|
||||
.removeClass("fa-square-o")
|
||||
.addClass("fa-check-square");
|
||||
}
|
||||
}
|
||||
$(ev.currentTarget).attr('aria-pressed', 'true');
|
||||
$(ev.currentTarget).attr("aria-pressed", "true");
|
||||
session.setPmsProperties(pms_propertyID, allowed_pms_property_ids);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent|KeyEvent} ev
|
||||
*/
|
||||
_onTogglePmsPropertyClick: function (ev) {
|
||||
if (ev.type == 'keydown' && ev.which != $.ui.keyCode.ENTER && ev.which != $.ui.keyCode.SPACE) {
|
||||
_onTogglePmsPropertyClick: function(ev) {
|
||||
if (
|
||||
ev.type == "keydown" &&
|
||||
ev.which != $.ui.keyCode.ENTER &&
|
||||
ev.which != $.ui.keyCode.SPACE
|
||||
) {
|
||||
return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var dropdownItem = $(ev.currentTarget).parent();
|
||||
var pms_propertyID = dropdownItem.data('pms_property-id');
|
||||
var pms_propertyID = dropdownItem.data("pms_property-id");
|
||||
var allowed_pms_property_ids = this.allowed_pms_property_ids;
|
||||
var current_pms_property_id = allowed_pms_property_ids[0];
|
||||
if (dropdownItem.find('.fa-square-o').length) {
|
||||
if (dropdownItem.find(".fa-square-o").length) {
|
||||
allowed_pms_property_ids.push(pms_propertyID);
|
||||
dropdownItem.find('.fa-square-o').removeClass('fa-square-o').addClass('fa-check-square');
|
||||
$(ev.currentTarget).attr('aria-checked', 'true');
|
||||
dropdownItem
|
||||
.find(".fa-square-o")
|
||||
.removeClass("fa-square-o")
|
||||
.addClass("fa-check-square");
|
||||
$(ev.currentTarget).attr("aria-checked", "true");
|
||||
} else {
|
||||
allowed_pms_property_ids.splice(allowed_pms_property_ids.indexOf(pms_propertyID), 1);
|
||||
dropdownItem.find('.fa-check-square').addClass('fa-square-o').removeClass('fa-check-square');
|
||||
$(ev.currentTarget).attr('aria-checked', 'false');
|
||||
allowed_pms_property_ids.splice(
|
||||
allowed_pms_property_ids.indexOf(pms_propertyID),
|
||||
1
|
||||
);
|
||||
dropdownItem
|
||||
.find(".fa-check-square")
|
||||
.addClass("fa-square-o")
|
||||
.removeClass("fa-check-square");
|
||||
$(ev.currentTarget).attr("aria-checked", "false");
|
||||
}
|
||||
session.setPmsProperties(current_pms_property_id, allowed_pms_property_ids);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
if (session.display_switch_pms_property_menu) {
|
||||
SystrayMenu.Items.push(SwitchPmsMenu);
|
||||
}
|
||||
|
||||
|
||||
return SwitchPmsMenu;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1,43 +1,76 @@
|
||||
<template>
|
||||
|
||||
<t t-name="SwitchPmsMenu">
|
||||
<li class="o_switch_company_menu">
|
||||
<a role="button" class="dropdown-toggle" data-toggle="dropdown" data-display="static" aria-expanded="false" href="#" title="Dropdown menu">
|
||||
<span t-attf-class="#{widget.isMobile ? 'fa fa-building-o' : 'oe_topbar_name'}">
|
||||
<t t-if="!widget.isMobile"><t t-esc="widget.current_pms_property_name"/></t>
|
||||
</span>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<t t-foreach="widget.user_pms_properties" t-as="pms_property">
|
||||
<div class="dropdown-item d-flex py-0 px-0" data-menu="pms_property" t-att-data-pms_property-id="pms_property[0]">
|
||||
<t t-set="is_allowed" t-value="widget.allowed_pms_property_ids.includes(pms_property[0])"/>
|
||||
<t t-set="is_current" t-value="pms_property[0] === widget.current_pms_property"/>
|
||||
<div role="menuitemcheckbox" t-att-aria-checked="is_allowed" t-att-aria-label="pms_property[1]" tabindex="0" class="ml-auto pl-3 pr-3 border border-top-0 border-left-0 border-bottom-0 pms_toggle_property o_py">
|
||||
<span style="height: 2rem;">
|
||||
<t t-name="SwitchPmsMenu">
|
||||
<li class="o_switch_company_menu">
|
||||
<a
|
||||
role="button"
|
||||
class="dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
data-display="static"
|
||||
aria-expanded="false"
|
||||
href="#"
|
||||
title="Dropdown menu"
|
||||
>
|
||||
<span
|
||||
t-attf-class="#{widget.isMobile ? 'fa fa-building-o' : 'oe_topbar_name'}"
|
||||
>
|
||||
<t t-if="!widget.isMobile">
|
||||
<t t-esc="widget.current_pms_property_name" />
|
||||
</t>
|
||||
</span>
|
||||
</a>
|
||||
<div class="dropdown-menu dropdown-menu-right" role="menu">
|
||||
<t t-foreach="widget.user_pms_properties" t-as="pms_property">
|
||||
<div
|
||||
class="dropdown-item d-flex py-0 px-0"
|
||||
data-menu="pms_property"
|
||||
t-att-data-pms_property-id="pms_property[0]"
|
||||
>
|
||||
<t
|
||||
t-set="is_allowed"
|
||||
t-value="widget.allowed_pms_property_ids.includes(pms_property[0])"
|
||||
/>
|
||||
<t
|
||||
t-set="is_current"
|
||||
t-value="pms_property[0] === widget.current_pms_property"
|
||||
/>
|
||||
<div
|
||||
role="menuitemcheckbox"
|
||||
t-att-aria-checked="is_allowed"
|
||||
t-att-aria-label="pms_property[1]"
|
||||
tabindex="0"
|
||||
class="ml-auto pl-3 pr-3 border border-top-0 border-left-0 border-bottom-0 pms_toggle_property o_py"
|
||||
>
|
||||
<span style="height: 2rem;">
|
||||
<t t-if="is_allowed">
|
||||
<i class="fa fa-fw fa-check-square pt-2" />
|
||||
</t>
|
||||
<t t-if="!is_allowed">
|
||||
<i class="fa fa-fw fa-square-o pt-2" />
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
role="button"
|
||||
t-att-aria-pressed="is_current"
|
||||
aria-label="Switch to this property"
|
||||
tabindex="0"
|
||||
class="d-flex flex-grow-1 align-items-center py-0 pms_log_into pl-3 o_py"
|
||||
t-att-style="is_current ? 'background-color: lightgrey;' : ''"
|
||||
>
|
||||
<t t-if="is_allowed">
|
||||
<i class="fa fa-fw fa-check-square pt-2"></i>
|
||||
<span class='mr-3 company_label'>
|
||||
<t t-esc="pms_property[1]" />
|
||||
</span>
|
||||
</t>
|
||||
<t t-if="!is_allowed">
|
||||
<i class="fa fa-fw fa-square-o pt-2"></i>
|
||||
<span class='mr-3 company_label text-muted'>
|
||||
<t t-esc="pms_property[1]" />
|
||||
</span>
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div role="button" t-att-aria-pressed="is_current" aria-label="Switch to this property" tabindex="0" class="d-flex flex-grow-1 align-items-center py-0 pms_log_into pl-3 o_py" t-att-style="is_current ? 'background-color: lightgrey;' : ''">
|
||||
<t t-if="is_allowed">
|
||||
<span class='mr-3 company_label'>
|
||||
<t t-esc="pms_property[1]"/>
|
||||
</span>
|
||||
</t>
|
||||
<t t-if="!is_allowed">
|
||||
<span class='mr-3 company_label text-muted'>
|
||||
<t t-esc="pms_property[1]"/>
|
||||
</span>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
</t>
|
||||
</div>
|
||||
</li>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
@@ -3,9 +3,15 @@
|
||||
<data>
|
||||
<template id="assets_backend" name="pms assets" inherit_id="web.assets_backend">
|
||||
<xpath expr="//script[last()]" position="after">
|
||||
<script type="text/javascript" src="/pms/static/src/js/session.js"></script>
|
||||
<script type="text/javascript" src="/pms/static/src/js/inherited_abstract_web_client.js"></script>
|
||||
<script type="text/javascript" src="/pms/static/src/js/widgets/switch_property_menu.js"></script>
|
||||
<script type="text/javascript" src="/pms/static/src/js/session.js" />
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/pms/static/src/js/inherited_abstract_web_client.js"
|
||||
/>
|
||||
<script
|
||||
type="text/javascript"
|
||||
src="/pms/static/src/js/widgets/switch_property_menu.js"
|
||||
/>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
|
||||
@@ -246,7 +246,6 @@
|
||||
class="label label-warning"
|
||||
attrs="{'invisible': [('overbooking', '=', False)]}"
|
||||
>OverBooking!</span>
|
||||
<field name="pms_property_id" invisible="0" />
|
||||
<h1>
|
||||
<field
|
||||
name="room_id"
|
||||
@@ -384,6 +383,8 @@
|
||||
string="Reservation Details"
|
||||
name="reservation_details"
|
||||
>
|
||||
<field name="pms_property_id" />
|
||||
<field name="allowed_room_ids" invisible="1" />
|
||||
<field
|
||||
name="pricelist_id"
|
||||
attrs="{'invisible': [('reservation_type','in',('out'))]}"
|
||||
@@ -406,6 +407,7 @@
|
||||
/>
|
||||
<field name="qty_invoiced" invisible="1" />
|
||||
<field name="qty_to_invoice" invisible="1" />
|
||||
<field name="allowed_room_ids" invisible="1" />
|
||||
<field
|
||||
name="room_type_id"
|
||||
on_change="1"
|
||||
@@ -742,6 +744,7 @@
|
||||
</bold>
|
||||
</div>
|
||||
<field name="shared_folio" invisible="1" />
|
||||
<field name="allowed_room_ids" invisible="1" />
|
||||
<sheet>
|
||||
<span
|
||||
class="label label-danger"
|
||||
@@ -850,6 +853,7 @@
|
||||
'exit_date': checkout,'reservation_id': id, 'hidden_checkin_partner': True, 'edit_checkin_partner': True }"
|
||||
attrs="{'invisible':['|','|', ('state','not in',('confirm','booking')),('checkin_partner_pending_count','=', 0),('parent_reservation','!=',False)]}"
|
||||
/>
|
||||
<field name="allowed_room_ids" invisible="1" />
|
||||
<field name="room_id" options="{'no_create': True,'no_open': True}" />
|
||||
<button
|
||||
type="action"
|
||||
|
||||
@@ -1,25 +1,5 @@
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Dario Lodeiros <>
|
||||
# Alexandre Díaz <dev@redneboa.es>
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
||||
|
||||
from . import folio_make_invoice_advance
|
||||
from . import massive_changes
|
||||
from . import split_reservation
|
||||
|
||||
@@ -591,10 +591,10 @@ class LineAdvancePaymentInv(models.TransientModel):
|
||||
)
|
||||
|
||||
def invoice_line_create(self, invoice_id, qty):
|
||||
""" Create an invoice line.
|
||||
:param invoice_id: integer
|
||||
:param qty: float quantity to invoice
|
||||
:returns recordset of account.move.line created
|
||||
"""Create an invoice line.
|
||||
:param invoice_id: integer
|
||||
:param qty: float quantity to invoice
|
||||
:returns recordset of account.move.line created
|
||||
"""
|
||||
self.ensure_one()
|
||||
invoice_lines = self.env["account.move.line"]
|
||||
|
||||
@@ -51,7 +51,7 @@ class MassiveChangesWizard(models.TransientModel):
|
||||
dsa = fields.Boolean("Saturday", default=True)
|
||||
dsu = fields.Boolean("Sunday", default=True)
|
||||
applied_on = fields.Selection(
|
||||
[("0", "Global"), ("1", "Room Type"),], string="Applied On", default="0"
|
||||
[("0", "Global"), ("1", "Room Type"),], string="Applied On", default="0",
|
||||
)
|
||||
|
||||
# Restriction fields
|
||||
|
||||
Reference in New Issue
Block a user