[IMP] Refact onchanges/compute on availability and prices process

This commit is contained in:
Darío Lodeiros
2020-09-01 23:01:35 +02:00
parent b273926cdc
commit 3df6a3a198
30 changed files with 1181 additions and 637 deletions

View File

@@ -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.

View File

@@ -1,3 +1,4 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import wizard

View File

@@ -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,

View File

@@ -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

View File

@@ -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")

View File

@@ -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"))

View File

@@ -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):

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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

View File

@@ -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...

View File

@@ -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"

View File

@@ -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.

View File

@@ -1 +1,4 @@
.. authors are taken from the __manifest__.py file
* Dario Lodeiros <dario@commitsun.com>
* Alexandre Díaz
* Pablo Quesada
* Jose Luis Algara

View File

@@ -1,2 +0,0 @@
.. [ This file is optional and contains additional credits, other than
authors, contributors, and maintainers. ]

View File

@@ -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.

View File

@@ -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.

View File

@@ -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. ]
- [ ]

View File

@@ -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.

View 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 propertys 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 &gt; Users &amp; Companies &gt; Hotels &gt; 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 &lt;<a class="reference external" href="mailto:dario&#64;commitsun.com">dario&#64;commitsun.com</a>&gt;</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>

View File

@@ -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);
},
});
});
});

View File

@@ -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;
});
});

View File

@@ -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;
});
});

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"

View File

@@ -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

View File

@@ -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"]

View File

@@ -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