Merge branch 'commitsun/14.0' into 'OCA/14.0'

This commit is contained in:
Eric Antones
2021-05-26 16:23:50 +02:00
236 changed files with 31022 additions and 12051 deletions

View File

@@ -20,10 +20,11 @@ jobs:
include:
- stage: test
env:
- TESTS=1 ODOO_REPO="odoo/odoo" MAKEPOT="1"
- TESTS=1 ODOO_REPO="odoo/odoo" MAKEPOT="1" OPTIONS="--load
web,multi_pms_properties"
- stage: test
env:
- TESTS=1 ODOO_REPO="OCA/OCB"
- TESTS=1 ODOO_REPO="OCA/OCB" OPTIONS="--load web,multi_pms_properties"
env:
global:
- VERSION="14.0" TESTS="0" LINT_CHECK="0" MAKEPOT="0"

View File

@@ -20,6 +20,12 @@ Available addons
addon | version | summary
--- | --- | ---
[pms](pms/) | 14.0.1.0.0 | A property management system
[multi_pms_properties](multi_pms_properties/) | 14.0.1.0.0 | Multiproperty support
[pms_housekeeping](pms_housekeeping/) | 14.0.1.0.0 | Housekeeping feature
[pms_l10n_es](pms_l10n_es/) | 14.0.1.0.0 | Spanish localization support
[pms_rooming_xls](pms_rooming_xls/) | 14.0.1.0.0 | Management rooming
[//]: # (end addons)

View File

@@ -0,0 +1,97 @@
====================
multi_pms_properties
====================
.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! 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%2Fpms-lightgray.png?logo=github
:target: https://github.com/OCA/pms/tree/14.0/multi_pms_properties
:alt: OCA/pms
.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png
:target: https://translation.odoo-community.org/projects/pms-14-0/pms-14-0-multi_pms_properties
:alt: Translate me on Weblate
.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png
:target: https://runbot.odoo-community.org/runbot/293/14.0
:alt: Try me on Runbot
|badge1| |badge2| |badge3| |badge4| |badge5|
Technical addon to support multiproperty in property management system (PMS).
**Table of contents**
.. contents::
:local:
Installation
============
To install this module, you only need to add it to your addons, and load it as
a server-wide module.
This can be done with the ``server_wide_modules`` parameter in ``/etc/odoo.conf``
or with the ``--load`` command-line parameter
``server_wide_modules = "multi_pms_properties"``
Usage
=====
* Use the standard multicompany guidelines applied to pms.property:
``_check_pms_properties_auto like model attribute to autocheck on create/write``
``check_pms_properties like field attribute to check relational record properties consistence``
``This module not implement propety dependent fields``
Bug Tracker
===========
Bugs are tracked on `GitHub Issues <https://github.com/OCA/pms/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/pms/issues/new?body=module:%20multi_pms_properties%0Aversion:%2014.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
~~~~~~~
* Commit [Sun]
Contributors
~~~~~~~~~~~~
* `Commit [Sun] <https://www.commitsun.com>`:
* Dario Lodeiros
* Eric Antones
* Sara Lago
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/pms <https://github.com/OCA/pms/tree/14.0/multi_pms_properties>`_ project on GitHub.
You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

View File

@@ -0,0 +1,64 @@
# Copyright 2021 Dario Lodeiros
# Copyright 2021 Eric Antones
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import fields
from odoo.tools import config
from . import models
def _description_domain(self, env):
if self.check_company and not self.domain:
if self.company_dependent:
if self.comodel_name == "res.users":
# user needs access to current company (self.env.company)
return "[('company_ids', 'in', allowed_company_ids[0])]"
else:
return "[('company_id', 'in', [allowed_company_ids[0], False])]"
else:
# when using check_company=True on a field on 'res.company', the
# company_id comes from the id of the current record
cid = "id" if self.model_name == "res.company" else "company_id"
if self.comodel_name == "res.users":
# User allowed company ids = user.company_ids
return f"['|', (not {cid}, '=', True), ('company_ids', 'in', [{cid}])]"
else:
return f"[('company_id', 'in', [{cid}, False])]"
if self.check_pms_properties and not self.domain:
record = env[self.model_name]
# Skip company_id domain to avoid domain multiproperty error in inherited views
if (
self.check_pms_properties
and not self.domain
and self.name not in ["company_id"]
):
if self.model_name == "pms.property":
prop1 = "id"
prop2 = f"[{prop1}]"
elif "pms_property_id" in record._fields:
prop1 = "pms_property_id"
prop2 = f"[{prop1}]"
else:
prop1 = prop2 = "pms_property_ids"
coprop = (
"pms_property_id"
if "pms_property_id" in env[self.comodel_name]._fields
else "pms_property_ids"
)
return f"['|', '|', \
(not {prop1}, '=', True), \
('{coprop}', 'in', {prop2}), \
('{coprop}', '=', False)]"
return self.domain(env[self.model_name]) if callable(self.domain) else self.domain
if "multi_pms_properties" in config.get("server_wide_modules"):
_logger = logging.getLogger(__name__)
_logger.info("monkey patching fields._Relational")
fields._Relational.check_pms_properties = False
fields._Relational._description_domain = _description_domain

View File

@@ -0,0 +1,17 @@
# © 2013 Therp BV
# © 2014 ACSONE SA/NV
# Copyright 2018 Quartile Limited
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
{
"name": "multi_pms_properties",
"summary": "Multi Properties Manager",
"version": "14.0.1.0.0",
"website": "https://github.com/OCA/pms",
"author": "Commit [Sun], Odoo Community Association (OCA)",
"license": "AGPL-3",
"category": "Pms",
"depends": ["base"],
"auto_install": False,
"installable": True,
}

View File

@@ -0,0 +1,186 @@
# Copyright 2021 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, models
from odoo.exceptions import UserError
class BaseModel(models.AbstractModel):
_inherit = "base"
_check_pms_properties_auto = False
"""On write and create, call ``_check_pms_properties_auto`` to ensure properties
consistency on the relational fields having ``check_pms_properties=True``
as attribute.
"""
@api.model_create_multi
def create(self, vals_list):
records = super(BaseModel, self).create(vals_list)
if self._check_pms_properties_auto:
records._check_pms_properties()
return records
def write(self, vals):
res = super(BaseModel, self).write(vals)
check_pms_properties = False
for fname in vals:
field = self._fields.get(fname)
if (
fname == "pms_property_id"
or fname == "pms_property_ids"
or fname == "company_id"
or (field.relational and field.check_pms_properties)
):
check_pms_properties = True
if res and check_pms_properties and self._check_pms_properties_auto:
self._check_pms_properties()
return res
def _check_pms_properties(self, fnames=None):
"""Check the properties of the values of the given field names.
:param list fnames: names of relational fields to check
:raises UserError: if the `pms_properties` of the value of any field is not
in `[False, self.pms_property_id]` (or `self` if
:class:`~odoo.addons.base.models.pms_property`).
For :class:`~odoo.addons.base.models.res_users` relational fields,
verifies record company is in `company_ids` fields.
User with main pms property A, having access to pms property A and B, could be
assigned or linked to records in property B.
"""
if fnames is None:
fnames = self._fields
regular_fields = self._get_regular_fields(fnames)
if not regular_fields:
return
inconsistencies = self._check_inconsistencies(regular_fields)
if inconsistencies:
lines = [_("Incompatible properties on records:")]
property_msg = _(
"""- Record is properties %(pms_properties)r and %(field)r
(%(fname)s: %(values)s) belongs to another properties."""
)
record_msg = _(
"""- %(record)r belongs to properties %(pms_properties)r and
%(field)r (%(fname)s: %(values)s) belongs to another properties."""
)
for record, name, corecords in inconsistencies[:5]:
if record._name == "pms.property":
msg, pms_properties = property_msg, record
else:
msg, pms_properties = (
record_msg,
record.pms_property_id.name
if "pms_property_id" in record
else ", ".join(repr(p.name) for p in record.pms_property_ids),
)
field = self.env["ir.model.fields"]._get(self._name, name)
lines.append(
msg
% {
"record": record.display_name,
"pms_properties": pms_properties,
"field": field.field_description,
"fname": field.name,
"values": ", ".join(
repr(rec.display_name) for rec in corecords
),
}
)
raise UserError("\n".join(lines))
def _get_regular_fields(self, fnames):
regular_fields = []
for name in fnames:
field = self._fields[name]
if (
field.relational
and field.check_pms_properties
and (
"pms_property_id" in self.env[field.comodel_name]
or "pms_property_ids" in self.env[field.comodel_name]
)
):
regular_fields.append(name)
return regular_fields
def _check_inconsistencies(self, regular_fields):
inconsistencies = []
for record in self:
pms_properties = False
if record._name == "pms.property":
pms_properties = record
if "pms_property_id" in record:
pms_properties = record.pms_property_id
if "pms_property_ids" in record:
pms_properties = record.pms_property_ids
# Check the property & company consistence
if "company_id" in self._fields:
if record.company_id and pms_properties:
property_companies = pms_properties.mapped("company_id.id")
if (
len(property_companies) > 1
or record.company_id.id != property_companies[0]
):
raise UserError(
_(
"You cannot establish a company other than "
"the one with the established properties"
)
)
# Check verifies that all
# records linked via relation fields are compatible
# with the properties of the origin document,
for name in regular_fields:
field = self._fields[name]
co_pms_properties = False
corecord = record.sudo()[name]
# TODO:res.users management properties
if "pms_property_id" in corecord:
co_pms_properties = corecord.pms_property_id
if "pms_property_ids" in corecord:
co_pms_properties = corecord.pms_property_ids
if (
# There is an inconsistency if:
#
# - Record has properties and corecord too and
# there's no match between them:
# X Pms_room_class with Property1 cannot contain
# Pms_room with property2 X
#
# - Record has a relation one2many with corecord and
# corecord properties aren't included in record properties
# or what is the same, subtraction between corecord properties
# and record properties must be False:
# X Pricelist with Prop1 and Prop2 cannot contain
# Pricelist_item with Prop1 and Prop3 X
# X Pricelist with Prop1 and Prop2 cannot contain
# Pricelist_item with Prop1, Prop2 and Prop3 X
# -In case that record has a relation many2one
# with corecord the condition is the same as avobe
(
pms_properties
and co_pms_properties
and (not pms_properties & co_pms_properties)
)
or (
corecord
and field.type == "one2many"
and pms_properties
and (co_pms_properties - pms_properties)
)
or (
field.type == "many2one"
and co_pms_properties
and ((pms_properties - co_pms_properties) or not pms_properties)
)
):
inconsistencies.append((record, name, corecord))
return inconsistencies

View File

@@ -0,0 +1,5 @@
* `Commit [Sun] <https://www.commitsun.com>`:
* Dario Lodeiros
* Eric Antones
* Sara Lago

View File

@@ -0,0 +1 @@
Technical addon to support multiproperty in property management system (PMS).

View File

@@ -0,0 +1,7 @@
To install this module, you only need to add it to your addons, and load it as
a server-wide module.
This can be done with the ``server_wide_modules`` parameter in ``/etc/odoo.conf``
or with the ``--load`` command-line parameter
``server_wide_modules = "multi_pms_properties"``

View File

@@ -0,0 +1,5 @@
* Use the standard multicompany guidelines applied to pms.property:
``_check_pms_properties_auto like model attribute to autocheck on create/write``
``check_pms_properties like field attribute to check relational record properties consistence``
``This module not implement propety dependent fields``

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,444 @@
<?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: http://docutils.sourceforge.net/" />
<title>multi_pms_properties</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="multi-pms-properties">
<h1 class="title">multi_pms_properties</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/pms/tree/14.0/multi_pms_properties"><img alt="OCA/pms" src="https://img.shields.io/badge/github-OCA%2Fpms-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/pms-14-0/pms-14-0-multi_pms_properties"><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/293/14.0"><img alt="Try me on Runbot" src="https://img.shields.io/badge/runbot-Try%20me-875A7B.png" /></a></p>
<p>Technical addon to support multiproperty in property management system (PMS).</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="#usage" id="id2">Usage</a></li>
<li><a class="reference internal" href="#bug-tracker" id="id3">Bug Tracker</a></li>
<li><a class="reference internal" href="#credits" id="id4">Credits</a><ul>
<li><a class="reference internal" href="#authors" id="id5">Authors</a></li>
<li><a class="reference internal" href="#contributors" id="id6">Contributors</a></li>
<li><a class="reference internal" href="#maintainers" id="id7">Maintainers</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="installation">
<h1><a class="toc-backref" href="#id1">Installation</a></h1>
<p>To install this module, you only need to add it to your addons, and load it as
a server-wide module.</p>
<p>This can be done with the <tt class="docutils literal">server_wide_modules</tt> parameter in <tt class="docutils literal">/etc/odoo.conf</tt>
or with the <tt class="docutils literal"><span class="pre">--load</span></tt> command-line parameter</p>
<p><tt class="docutils literal">server_wide_modules = &quot;multi_pms_properties&quot;</tt></p>
</div>
<div class="section" id="usage">
<h1><a class="toc-backref" href="#id2">Usage</a></h1>
<ul>
<li><p class="first">Use the standard multicompany guidelines applied to pms.property:</p>
<p><tt class="docutils literal">_check_pms_properties_auto like model attribute to autocheck&nbsp; on create/write</tt>
<tt class="docutils literal">check_pms_properties like field attribute to check relational record properties consistence</tt>
<tt class="docutils literal">This module not implement propety dependent fields</tt></p>
</li>
</ul>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id3">Bug Tracker</a></h1>
<p>Bugs are tracked on <a class="reference external" href="https://github.com/OCA/pms/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/pms/issues/new?body=module:%20multi_pms_properties%0Aversion:%2014.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="#id4">Credits</a></h1>
<div class="section" id="authors">
<h2><a class="toc-backref" href="#id5">Authors</a></h2>
<ul class="simple">
<li>Commit [Sun]</li>
</ul>
</div>
<div class="section" id="contributors">
<h2><a class="toc-backref" href="#id6">Contributors</a></h2>
<ul class="simple">
<li><cite>Commit [Sun] &lt;https://www.commitsun.com&gt;</cite>:<ul>
<li>Dario Lodeiros</li>
<li>Eric Antones</li>
<li>Sara Lago</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">
<h2><a class="toc-backref" href="#id7">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/pms/tree/14.0/multi_pms_properties">OCA/pms</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

@@ -0,0 +1,4 @@
# Copyright 2021 Eric Antones
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import common
from . import test_multi_pms_properties

View File

@@ -0,0 +1,20 @@
# Copyright 2021 Eric Antones
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
def setup_test_model(env, model_clses):
for model_cls in model_clses:
model_cls._build_model(env.registry, env.cr)
env.registry.setup_models(env.cr)
env.registry.init_models(
env.cr,
[model_cls._name for model_cls in model_clses],
dict(env.context, update_custom_fields=True),
)
def teardown_test_model(env, model_clses):
for model_cls in model_clses:
del env.registry.models[model_cls._name]
env.registry.setup_models(env.cr)

View File

@@ -0,0 +1,17 @@
# Copyright 2021 Eric Antones
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class ParentTester(models.Model):
_name = "pms.parent.tester"
name = fields.Char(required=True)
class ChildTester(models.Model):
_name = "pms.child.tester"
name = fields.Char(required=True)
parent_id = fields.Many2one("pms.parent.tester", check_pms_properties=True)

View File

@@ -0,0 +1,41 @@
# Copyright 2021 Eric Antones
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo.tests import common
from .common import setup_test_model # , teardown_test_model
from .multi_pms_properties_tester import ChildTester, ParentTester
_logger = logging.getLogger(__name__)
@common.tagged("-at_install", "post_install")
class TestMultiPMSProperties(common.SavepointCase):
@classmethod
def setUpClass(cls):
super(TestMultiPMSProperties, cls).setUpClass()
model_classes = [ParentTester, ChildTester]
setup_test_model(cls.env, model_classes)
for mdl_cls in model_classes:
tester_model = cls.env["ir.model"].search([("model", "=", mdl_cls._name)])
# Access record
cls.env["ir.model.access"].create(
{
"name": "access.%s" % mdl_cls._name,
"model_id": tester_model.id,
"perm_read": 1,
"perm_write": 1,
"perm_create": 1,
"perm_unlink": 1,
}
)
# @classmethod
# def tearDownClass(cls):
# teardown_test_model(cls.env, [ParentTester])
# super(TestMultiPMSProperties, cls).tearDownClass()
# def test_exist_attribute(self):
# parent = self.env["pms.parent.tester"].create({"name": "parent test"})

View File

@@ -1 +1,2 @@
# See https://github.com/OCA/odoo-community.org/blob/master/website/Contribution/CONTRIBUTING.rst#oca_dependencies-txt
partner-contact
reporting-engine

View File

@@ -25,11 +25,11 @@ PMS (Property Management System)
|badge1| |badge2| |badge3| |badge4| |badge5|
This module is an all-in-one property management system (PMS) focused on medium-sized hotels
This module is an all-in-one property management system (PMS) focused on medium-sized properties
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.
You can manage properties with multi-property and multi-company support, including your rooms inventory,
reservations, check-in, daily reports, board services, rate and availability plans among other property functionalities.
.. IMPORTANT::
This is an alpha version, the data model and design can change at any time without warning.
@@ -44,20 +44,20 @@ reservations, check-in, daily reports, board services, rate and restriction plan
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.
This module depends on modules ``base``, ``mail``, ``sale`` and ``multi_pms_properties``.
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 PMS Management > Configuration > Properties > Your Property.
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.
To use this module, please, read the complete user guide at `<roomdoo.com>`_.
Bug Tracker
===========
@@ -75,18 +75,21 @@ Credits
Authors
~~~~~~~
* Dario Lodeiros
* Alexadre Diaz
* Pablo Quesada
* Jose Luis Algara
* Commit [Sun]
Contributors
~~~~~~~~~~~~
* Dario Lodeiros <dario@commitsun.com>
* Alexandre Díaz
* Pablo Quesada
* Jose Luis Algara
* `Commit [Sun] <https://www.commitsun.com>`:
* Dario Lodeiros
* Eric Antones
* Sara Lago
* Brais Abeijon
* Miguel Padin
Maintainers
~~~~~~~~~~~

View File

@@ -2,4 +2,5 @@
from . import models
from . import wizards
from .init_hook import post_init_hook
from . import controllers
from .init_hook import pre_init_hook

View File

@@ -8,11 +8,7 @@
"development_status": "Alpha",
"category": "Generic Modules/Property Management System",
"website": "https://github.com/OCA/pms",
"author": "Dario Lodeiros, "
"Alexadre Diaz, "
"Pablo Quesada, "
"Jose Luis Algara, "
"Odoo Community Association (OCA)",
"author": "Commit [Sun], Odoo Community Association (OCA)",
"license": "AGPL-3",
"application": True,
"installable": True,
@@ -23,6 +19,7 @@
# "partner_firstname",
# "email_template_qweb",
"sale",
"multi_pms_properties",
],
"data": [
"security/pms_security.xml",
@@ -33,29 +30,33 @@
"report/pms_folio.xml",
"report/pms_folio_templates.xml",
# "templates/pms_email_template.xml",
"views/general.xml",
"data/menus.xml",
"wizards/wizard_payment_folio.xml",
"wizards/folio_make_invoice_advance_views.xml",
"wizards/wizard_folio.xml",
"wizards/wizard_folio_changes.xml",
"views/pms_amenity_views.xml",
"views/pms_amenity_type_views.xml",
"views/pms_board_service_views.xml",
"views/pms_board_service_room_type_views.xml",
"views/pms_cancelation_rule_views.xml",
"views/pms_checkin_partner_views.xml",
"views/pms_floor_views.xml",
"views/pms_folio_views.xml",
"views/pms_ubication_views.xml",
"views/pms_property_views.xml",
"views/pms_reservation_views.xml",
"views/pms_service_views.xml",
"views/pms_service_line_views.xml",
"views/pms_folio_views.xml",
"views/pms_room_type_views.xml",
"views/pms_room_views.xml",
"views/pms_room_closure_reason_views.xml",
"views/account_payment_views.xml",
"views/account_move_views.xml",
"views/account_bank_statement_views.xml",
"views/res_users_views.xml",
"views/pms_room_type_class_views.xml",
"views/pms_room_type_restriction_views.xml",
"views/pms_room_type_restriction_item_views.xml",
"views/pms_service_views.xml",
"views/pms_service_line_views.xml",
"views/pms_availability_plan_views.xml",
"views/pms_availability_plan_rule_views.xml",
"views/pms_shared_room_views.xml",
"views/res_partner_views.xml",
"views/product_pricelist_views.xml",
@@ -63,8 +64,12 @@
"views/pms_sale_channel.xml",
"views/product_template_views.xml",
"views/webclient_templates.xml",
"views/ir_sequence_views.xml",
"wizards/wizard_reservation.xml",
"views/account_journal_views.xml",
"views/folio_portal_templates.xml",
"views/reservation_portal_templates.xml",
"wizards/wizard_split_join_swap_reservation.xml",
"wizards/wizard_massive_changes.xml",
"wizards/wizard_advanced_filters.xml",
],
"demo": [
"demo/pms_master_data.xml",
@@ -73,6 +78,7 @@
],
"qweb": [
"static/src/xml/pms_base_templates.xml",
"static/src/xml/reservation_group_button_views.xml",
],
"post_init_hook": "post_init_hook",
"pre_init_hook": "pre_init_hook",
}

View File

@@ -0,0 +1 @@
from . import pms_portal

View File

@@ -0,0 +1,216 @@
from odoo import _, http
from odoo.exceptions import AccessError, MissingError
from odoo.http import request
from odoo.addons.portal.controllers.portal import CustomerPortal, pager as portal_pager
class PortalFolio(CustomerPortal):
def _prepare_home_portal_values(self, counters):
partner = request.env.user.partner_id
values = super()._prepare_home_portal_values(counters)
Folio = request.env["pms.folio"]
if "folio_count" in counters:
values["folio_count"] = (
Folio.search_count(
[
("partner_id", "=", partner.id),
]
)
if Folio.check_access_rights("read", raise_exception=False)
else 0
)
return values
def _folio_get_page_view_values(self, folio, access_token, **kwargs):
values = {"folio": folio, "token": access_token}
return self._get_page_view_values(
folio, access_token, values, "my_folios_history", False, **kwargs
)
@http.route(
["/my/folios", "/my/folios/page/<int:page>"],
type="http",
auth="user",
website=True,
)
def portal_my_folios(
self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw
):
partner = request.env.user.partner_id
values = self._prepare_portal_layout_values()
PmsFolio = request.env["pms.folio"]
values["folios"] = PmsFolio.search(
[
("partner_id", "child_of", partner.id),
]
)
domain = [
("partner_id", "child_of", partner.id),
]
searchbar_sortings = {
"date": {"label": _("Order Date"), "folio": "date_order desc"},
"name": {"label": _("Reference"), "folio": "name"},
"stage": {"label": _("Stage"), "folio": "state"},
}
if not sortby:
sortby = "date"
sort_order = searchbar_sortings[sortby]["folio"]
if date_begin and date_end:
domain += [
("create_date", ">", date_begin),
("create_date", "<=", date_end),
]
folio_count = PmsFolio.search_count(domain)
pager = portal_pager(
url="/my/folios",
url_args={"date_begin": date_begin, "date_end": date_end, "sortby": sortby},
total=folio_count,
page=page,
step=self._items_per_page,
)
folios = PmsFolio.search(
domain, order=sort_order, limit=self._items_per_page, offset=pager["offset"]
)
request.session["my_folios_history"] = folios.ids[:100]
values.update(
{
"date": date_begin,
"folios": folios.sudo(),
"page_name": "folios",
"pager": pager,
"default_url": "/my/folios",
"searchbar_sortings": searchbar_sortings,
"sortby": sortby,
}
)
return request.render("pms.portal_my_folio", values)
@http.route(["/my/folios/<int:folio_id>"], type="http", auth="user", website=True)
def portal_my_folio_detail(
self, folio_id, access_token=None, report_type=None, download=False, **kw
):
try:
folio_sudo = self._document_check_access(
"pms.folio",
folio_id,
access_token=access_token,
)
except (AccessError, MissingError):
return request.redirect("/my")
if report_type in ("html", "pdf", "text"):
return self._show_report(
model=folio_sudo,
report_type=report_type,
report_ref="pms.action_report_folio",
download=download,
)
values = self._folio_get_page_view_values(folio_sudo, access_token, **kw)
return request.render("pms.folio_portal_template", values)
class PortalReservation(CustomerPortal):
def _prepare_home_portal_values(self, counters):
partner = request.env.user.partner_id
values = super()._prepare_home_portal_values(counters)
Reservation = request.env["pms.reservation"]
if "reservation_count" in counters:
values["reservation_count"] = (
Reservation.search_count(
[
("partner_id", "=", partner.id),
]
)
if Reservation.check_access_rights("read", raise_exception=False)
else 0
)
return values
def _reservation_get_page_view_values(self, reservation, access_token, **kwargs):
values = {"reservation": reservation, "token": access_token}
return self._get_page_view_values(
reservation,
access_token,
values,
"my_reservations_history",
False,
**kwargs
)
@http.route(
["/my/reservations", "/my/reservations/page/<int:page>"],
type="http",
auth="user",
website=True,
)
def portal_my_reservations(
self, page=1, date_begin=None, date_end=None, sortby=None, filterby=None, **kw
):
partner = request.env.user.partner_id
values = self._prepare_portal_layout_values()
Reservation = request.env["pms.reservation"]
values["reservations"] = Reservation.search(
[
("partner_id", "child_of", partner.id),
]
)
domain = [
("partner_id", "child_of", partner.id),
]
if date_begin and date_end:
domain += [
("create_date", ">", date_begin),
("create_date", "<=", date_end),
]
reservation_count = Reservation.search_count(domain)
pager = portal_pager(
url="/my/reservations",
url_args={"date_begin": date_begin, "date_end": date_end},
total=reservation_count,
page=page,
step=self._items_per_page,
)
reservations = Reservation.search(
domain, limit=self._items_per_page, offset=pager["offset"]
)
folios_dict = {}
for reservation in reservations:
folio = reservation.folio_id
folios_dict[folio] = ""
request.session["my_reservations_history"] = reservations.ids[:100]
values.update(
{
"date": date_begin,
"reservations": reservations.sudo(),
"page_name": "reservations",
"pager": pager,
"default_url": "/my/reservations",
"folios_dict": folios_dict,
"partner": partner,
}
)
return request.render("pms.portal_my_reservation", values)
@http.route(
["/my/reservations/<int:reservation_id>"],
type="http",
auth="user",
website=True,
)
def portal_my_reservation_detail(self, reservation_id, access_token=None, **kw):
try:
reservation_sudo = self._document_check_access(
"pms.reservation",
reservation_id,
access_token=access_token,
)
except (AccessError, MissingError):
return request.redirect("/my")
# for attachment in reservation_sudo.attachment_ids:
# attachment.generate_access_token()
values = self._reservation_get_page_view_values(
reservation_sudo, access_token, **kw
)
return request.render("pms.portal_my_reservation_detail", values)

View File

@@ -15,7 +15,7 @@
name="nextcall"
eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 09:00:00')"
/>
<field name="code">model.auto_no_show()</field>
<field name="code">model.auto_arrival_delayed()</field>
</record>
<!-- Set reservation like No Checout if checkout is not confirmed-->
<record model="ir.cron" id="nocheckout_reservations">
@@ -28,7 +28,7 @@
<field name="state">code</field>
<field name="model_id" ref="model_pms_reservation" />
<field name="nextcall" eval="DateTime.now()" />
<field name="code">model.auto_no_checkout()</field>
<field name="code">model.auto_departure_delayed()</field>
</record>
<!-- Scheduler For To Inform Guest About Reservation Before 24 Hours -->
<record model="ir.cron" id="autocheckout_reservations">

View File

@@ -5,33 +5,48 @@
name="PMS Management"
sequence="8"
web_icon="pms,static/description/icon.png"
groups="pms.group_pms_user,pms.group_pms_call"
/>
<menuitem
id="pms_configuration_menu"
name="Configuration"
id="menu_reservations"
name="Reservations"
parent="pms_management_menu"
sequence="5"
/>
<menuitem
id="pms_sales_menu"
name="Sales"
sequence="15"
parent="pms_management_menu"
/>
<menuitem
id="pms_contacts_menu"
name="Contacts"
sequence="20"
parent="pms_management_menu"
groups="pms.group_pms_user"
/>
<menuitem
id="pms_reports_menu"
name="Reports"
sequence="15"
id="revenue_management_menu"
name="Revenue Management"
sequence="25"
parent="pms_management_menu"
groups="pms.group_pms_user"
/>
<menuitem
id="menu_account_finance_xls_reports"
name="XLS Reports"
parent="pms.pms_reports_menu"
sequence="50"
id="pms_rooms_menu"
name="Rooms"
sequence="35"
parent="pms_management_menu"
/>
<menuitem
id="configuration_others"
id="pms_services_menu"
name="Services"
sequence="45"
parent="pms_management_menu"
/>
<menuitem
id="pms_configuration_menu"
name="Configuration"
parent="pms.pms_configuration_menu"
sequence="10"
groups="pms.group_pms_manager"
sequence="55"
parent="pms_management_menu"
/>
</odoo>

View File

@@ -2,14 +2,13 @@
<odoo>
<data noupdate="1">
<!-- Basic pms -->
<record id="main_pms_room_type_restriction" model="pms.room.type.restriction">
<field name="name">Restriction Plan</field>
<record id="main_pms_availability_plan" model="pms.availability.plan">
<field name="name">Availability Plan</field>
</record>
<record id="main_pms_property" model="pms.property">
<field name="name">My Property</field>
<field name="company_id" ref="base.main_company" />
<field name="default_pricelist_id" ref="product.list0" />
<field name="default_restriction_id" ref="main_pms_room_type_restriction" />
<field name="street">Rua Street Demo, s/n</field>
<field name="city">Commitsun city</field>
<field name="country_id" ref="base.es" />
@@ -18,6 +17,8 @@
<field name="email">commitsun@hootel.com</field>
<field name="website">https://www.commitsun.com</field>
<field name="folio_sequence_id" ref="pms.seq_pms_folio" />
<field name="checkin_sequence_id" ref="pms.seq_pms_checkin" />
<field name="reservation_sequence_id" ref="pms.seq_pms_reservation" />
</record>
<!-- pms.users -->
<record id="base.user_root" model="res.users">

View File

@@ -1,12 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="1">
<!-- Sequences for pms folio -->
<record model="ir.sequence" id="seq_pms_folio">
<field name="name">PMS Folio</field>
<field name="code">pms.folio</field>
<field name="prefix">F/</field>
<field name="padding">5</field>
<field name="prefix">F/%(y)s</field>
<field name="suffix">%(sec)s</field>
<field name="padding">4</field>
</record>
<record model="ir.sequence" id="seq_pms_reservation">
<field name="name">PMS Reservation</field>
<field name="code">pms.reservation</field>
<field name="prefix">R/%(y)s</field>
<field name="suffix">%(sec)s</field>
<field name="padding">4</field>
</record>
<record model="ir.sequence" id="seq_pms_checkin">
<field name="name">PMS Checkin</field>
<field name="code">pms.checkin.partner</field>
<field name="prefix">C/%(y)s</field>
<field name="suffix">%(sec)s</field>
<field name="padding">4</field>
</record>
</data>
</odoo>

View File

@@ -5,6 +5,8 @@
<!-- reservation of 1 economic room for 1 person -->
<record id="pms_folio_0" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_27" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -21,6 +23,8 @@
<!-- reservation of 1 triple room for 3 people on behalf on the company -->
<record id="pms_folio_1" model="pms.folio">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_admin" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -37,6 +41,8 @@
<!-- TODO: The third reservation is marked from State: Cancelled to Pending arrival at Folio creation -->
<record id="pms_folio_2" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_10" />
<field name="user_id" ref="base.user_admin" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -69,6 +75,8 @@
<!-- reservation of the conference room for 1 day on behalf of a company -->
<record id="pms_folio_3" model="pms.folio">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -84,7 +92,9 @@
<!-- out of service room -->
<record id="pms_folio_4" model="pms.folio">
<field name="partner_id" ref="main_pms_property" />
<field name="user_id" ref="base.user_admin" />
<field name="reservation_type">out</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -103,6 +113,8 @@
<!-- reservation of 1 double room for 1 day-->
<record id="pms_folio_5" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_15" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -120,6 +132,8 @@
<!-- reservation of 1 triple room for for 1 person and 1 day-->
<record id="pms_folio_6" model="pms.folio">
<field name="partner_id" ref="base.res_partner_4" />
<field name="user_id" ref="base.user_admin" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -136,6 +150,8 @@
<!-- reservation of all rooms for 1 day on behalf of a company -->
<record id="pms_folio_7" model="pms.folio">
<field name="partner_id" ref="base.res_partner_10" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -208,7 +224,9 @@
<!-- out of service single-101 room -->
<record id="pms_folio_8" model="pms.folio">
<field name="partner_id" ref="main_pms_property" />
<field name="user_id" ref="base.user_admin" />
<field name="reservation_type">out</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -227,6 +245,8 @@
<!--2 reservetions in diferent rooms and diferent days-->
<record id="pms_folio_9" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_33" />
<field name="user_id" ref="base.user_admin" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -251,6 +271,8 @@
<!--reservation of the conference room for 3 days-->
<record id="pms_folio_10" model="pms.folio">
<field name="partner_id" ref="base.res_partner_main2" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -266,6 +288,8 @@
<!--reservation of 2 single rooms and 1 triple room-->
<record id="pms_folio_11" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_10" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -298,6 +322,8 @@
<!--Reservation of economic room-->
<record id="pms_folio_12" model="pms.folio">
<field name="partner_id" ref="base.res_partner_1" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -313,7 +339,9 @@
<!--Reservation of the conference room with VIP privacy closure reason-->
<record id="pms_folio_13" model="pms.folio">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_admin" />
<field name="reservation_type">normal</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -330,6 +358,8 @@
<!--Reservation of triple room with 2 adults and 1 children-->
<record id="pms_folio_14" model="pms.folio">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -347,7 +377,9 @@
<!-- TODO: The reservation is marked from State: Cancelled to Pending arrival at Folio creation -->
<record id="pms_folio_15" model="pms.folio">
<field name="partner_id" ref="base.res_partner_18" />
<field name="user_id" ref="base.user_demo" />
<field name="reservation_type">normal</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -363,7 +395,9 @@
<!--Reservation of double room without default hours-->
<record id="pms_folio_16" model="pms.folio">
<field name="partner_id" ref="base.res_partner_18" />
<field name="user_id" ref="base.user_demo" />
<field name="reservation_type">normal</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -384,7 +418,9 @@
<!-- TODO: The reservation is marked from State: Pre-reservation to Pending arrival at Folio creation -->
<record id="pms_folio_17" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_32" />
<field name="user_id" ref="base.user_admin" />
<field name="reservation_type">normal</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -400,6 +436,8 @@
<!--Reservation of 2 double rooms and 1 triple room whith board service-->
<record id="pms_folio_18" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_30" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -436,6 +474,8 @@
<!--Reservation of economic room and 3 single rooms whith differents board services-->
<record id="pms_folio_19" model="pms.folio">
<field name="partner_id" ref="base.res_partner_18" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -479,6 +519,8 @@
<!--Reservation of 2 single rooms whith differents arrival and departure hours-->
<record id="pms_folio_20" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_25" />
<field name="user_id" ref="base.user_admin" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -507,6 +549,8 @@
<!--Reservation of 2 double rooms whith differents arrival and departure hours and one room whith board service-->
<record id="pms_folio_21" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_32" />
<field name="user_id" ref="base.user_admin" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -535,6 +579,8 @@
<!--Reservation of conference room whith lunch and dinner services-->
<record id="pms_folio_22" model="pms.folio">
<field name="partner_id" ref="base.res_partner_10" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -554,6 +600,8 @@
<!--Reservation of economic room for a week-->
<record id="pms_folio_23" model="pms.folio">
<field name="partner_id" ref="base.res_partner_4" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -570,6 +618,8 @@
<!--Reservation of 1 single room for a week-->
<record id="pms_folio_24" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_27" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -587,6 +637,8 @@
<!--Reservation of 1 single room for a week whith board service-->
<record id="pms_folio_25" model="pms.folio">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -604,6 +656,8 @@
<!--Reservation of 2 double rooms for a week whith diferent checkouts-->
<record id="pms_folio_26" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_27" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0),
@@ -628,6 +682,8 @@
<!--Reservation of the conference room with VIP privacy closure reason and board service-->
<record id="pms_folio_27" model="pms.folio">
<field name="partner_id" ref="base.res_partner_3" />
<field name="user_id" ref="base.user_admin" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -645,6 +701,8 @@
<!--Reservation of 1 triple room for 1 adult and 2 children whith board service-->
<record id="pms_folio_28" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_17" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -661,6 +719,8 @@
<!--Reservation of 1 triple room for 1 adult and 2 children whith specific arrival and departure hours and whith board service-->
<record id="pms_folio_29" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_25" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {
@@ -679,6 +739,8 @@
<!-- reservation of 1 triple room for 1 day-->
<record id="pms_folio_30" model="pms.folio">
<field name="partner_id" ref="base.res_partner_address_15" />
<field name="user_id" ref="base.user_demo" />
<field name="pms_property_id" ref="pms.main_pms_property" />
<field
name="reservation_ids"
eval="[(5, 0), (0, 0, {

View File

@@ -1,22 +1,125 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data noupdate="1">
<!-- Company -->
<record id="pms_company1" model="res.company">
<field name="name">Alda Company</field>
<field name="currency_id" ref="base.EUR" />
<field
name="favicon"
model="res.company"
eval="obj()._get_default_favicon(original=True)"
/>
</record>
<!--Availability Plan -->
<record id="demo_pms_availability" model="pms.availability.plan">
<field name="name">Availability Plan Demo</field>
</record>
<!-- Sequence -->
<record model="ir.sequence" id="seq_pms_folio2">
<field name="name">PMS Folio 2</field>
<field name="code">pms.folio</field>
<field name="prefix">F/%(y)s</field>
<field name="suffix">%(sec)s</field>
<field name="padding">4</field>
<field name="company_id" ref="pms_company1" />
</record>
<record model="ir.sequence" id="seq_pms_reservation2">
<field name="name">PMS Reservation 2</field>
<field name="code">pms.reservation</field>
<field name="prefix">R/%(y)s</field>
<field name="suffix">%(sec)s</field>
<field name="padding">4</field>
<field name="company_id" ref="pms_company1" />
</record>
<record model="ir.sequence" id="seq_pms_checkin2">
<field name="name">PMS Checkin 2</field>
<field name="code">pms.checkin.partner</field>
<field name="prefix">C/%(y)s</field>
<field name="suffix">%(sec)s</field>
<field name="padding">4</field>
<field name="company_id" ref="pms_company1" />
</record>
<!--Properties-->
<record id="demo_pms_property" model="pms.property">
<field name="name">San Carlos</field>
<field name="company_id" ref="base.main_company" />
<field name="default_pricelist_id" ref="product.list0" />
<field name="folio_sequence_id" ref="pms.seq_pms_folio" />
<field name="checkin_sequence_id" ref="pms.seq_pms_checkin" />
<field name="reservation_sequence_id" ref="pms.seq_pms_reservation" />
</record>
<record id="demo_pms_property2" model="pms.property">
<field name="name">Agalia</field>
<field name="company_id" ref="base.main_company" />
<field name="default_pricelist_id" ref="product.list0" />
<field name="folio_sequence_id" ref="pms.seq_pms_folio" />
<field name="checkin_sequence_id" ref="pms.seq_pms_checkin" />
<field name="reservation_sequence_id" ref="pms.seq_pms_reservation" />
</record>
<record id="demo_pms_property3" model="pms.property">
<field name="name">Pilgrim Leon</field>
<field name="company_id" ref="pms_company1" />
<field name="default_pricelist_id" ref="product.list0" />
<field name="folio_sequence_id" ref="pms.seq_pms_folio2" />
<field name="checkin_sequence_id" ref="pms.seq_pms_checkin2" />
<field name="reservation_sequence_id" ref="pms.seq_pms_reservation2" />
</record>
<!-- users -->
<record id="base.user_root" model="res.users">
<field name="company_ids" eval="[(4, ref('pms.pms_company1'))]" />
<field
name="pms_property_ids"
eval="[
(4, ref('demo_pms_property')),
(4, ref('demo_pms_property2')),
(4, ref('demo_pms_property3')),
]"
/>
</record>
<record id="base.user_admin" model="res.users">
<field name="company_ids" eval="[(4, ref('pms.pms_company1'))]" />
<field
name="pms_property_ids"
eval="[
(4, ref('demo_pms_property')),
(4, ref('demo_pms_property2')),
(4, ref('demo_pms_property3')),
]"
/>
</record>
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4,ref('pms.group_pms_user'))]" />
<field name="company_id" ref="base.main_company" />
<field name="company_ids" eval="[(4, ref('base.main_company'))]" />
<field
name="company_ids"
eval="[
(4, ref('base.main_company')),
(4, ref('pms.pms_company1')),
]"
/>
<field name="pms_property_id" ref="main_pms_property" />
<field name="pms_property_ids" eval="[(4, ref('main_pms_property'))]" />
<field
name="pms_property_ids"
eval="[
(4, ref('main_pms_property')),
(4, ref('demo_pms_property')),
(4, ref('demo_pms_property2')),
(4, ref('demo_pms_property3')),
]"
/>
</record>
<!-- pms.floor -->
<record id="pms_floor_0" model="pms.floor">
<!-- pms.ubication -->
<record id="pms_ubication_0" model="pms.ubication">
<field name="name">Ground Floor</field>
</record>
<record id="pms_floor_1" model="pms.floor">
<record id="pms_ubication_1" model="pms.ubication">
<field name="name">First Floor</field>
</record>
<record id="pms_floor_2" model="pms.floor">
<record id="pms_ubication_2" model="pms.ubication">
<field name="name">Second Floor</field>
</record>
<!-- pms.amenity.type -->
@@ -32,50 +135,52 @@
<!-- pms.amenity -->
<record id="pms_amenity_0" model="pms.amenity">
<field name="name">Shampoo and Soap</field>
<field name="room_amenity_type_id" ref="pms_amenity_type_0" />
<field name="pms_amenity_type_id" ref="pms_amenity_type_0" />
</record>
<record id="pms_amenity_1" model="pms.amenity">
<field name="name">High-quality Shampoo and Soap Essential Herbs</field>
<field name="room_amenity_type_id" ref="pms_amenity_type_0" />
<field name="pms_amenity_type_id" ref="pms_amenity_type_0" />
</record>
<record id="pms_amenity_2" model="pms.amenity">
<field name="name">Hair Dryer</field>
<field name="room_amenity_type_id" ref="pms_amenity_type_0" />
<field name="pms_amenity_type_id" ref="pms_amenity_type_0" />
</record>
<record id="pms_amenity_3" model="pms.amenity">
<field name="name">High speed Wired Internet access</field>
<field name="room_amenity_type_id" ref="pms_amenity_type_1" />
<field name="pms_amenity_type_id" ref="pms_amenity_type_1" />
</record>
<record id="pms_amenity_4" model="pms.amenity">
<field name="name">Wi-Fi</field>
<field name="room_amenity_type_id" ref="pms_amenity_type_1" />
<field name="pms_amenity_type_id" ref="pms_amenity_type_1" />
</record>
<record id="pms_amenity_5" model="pms.amenity">
<field name="name">Microwave oven</field>
<field name="room_amenity_type_id" ref="pms_amenity_type_2" />
<field name="pms_amenity_type_id" ref="pms_amenity_type_2" />
</record>
<record id="pms_amenity_6" model="pms.amenity">
<field name="name">Half-sized Refrigerator</field>
<field name="room_amenity_type_id" ref="pms_amenity_type_2" />
<field name="pms_amenity_type_id" ref="pms_amenity_type_2" />
</record>
<!-- pms.room.type.class -->
<record id="pms_room_type_class_0" model="pms.room.type.class">
<field name="name">Room</field>
<field name="default_code">RO</field>
</record>
<record id="pms_room_type_class_1" model="pms.room.type.class">
<field name="name">Conference</field>
<field name="default_code">CO</field>
</record>
<!-- pms.room.type -->
<record id="pms_room_type_0" model="pms.room.type">
<field name="name">Economic</field>
<field name="code_type">ECO</field>
<field name="default_code">ECO</field>
<field name="list_price">21.00</field>
<field name="class_id" ref="pms_room_type_class_0" />
<field name="room_amenity_ids" eval="[(4, ref('pms_amenity_0'))]" />
</record>
<record id="pms_room_type_1" model="pms.room.type">
<field name="name">Single</field>
<field name="code_type">SNG</field>
<field name="default_code">SNG</field>
<field name="list_price">20.00</field>
<field name="class_id" ref="pms_room_type_class_0" />
<field
@@ -85,7 +190,7 @@
</record>
<record id="pms_room_type_2" model="pms.room.type">
<field name="name">Double</field>
<field name="code_type">DBL</field>
<field name="default_code">DBL</field>
<field name="list_price">25.00</field>
<field name="class_id" ref="pms_room_type_class_0" />
<field
@@ -95,7 +200,7 @@
</record>
<record id="pms_room_type_3" model="pms.room.type">
<field name="name">Triple</field>
<field name="code_type">TRP</field>
<field name="default_code">TRP</field>
<field name="list_price">35.00</field>
<field name="class_id" ref="pms_room_type_class_0" />
<field
@@ -105,7 +210,7 @@
</record>
<record id="pms_room_type_4" model="pms.room.type">
<field name="name">Conference Room</field>
<field name="code_type">CFR</field>
<field name="default_code">CFR</field>
<field name="list_price">80.00</field>
<field name="class_id" ref="pms_room_type_class_1" />
<field
@@ -117,51 +222,59 @@
<record id="pms_room_0" model="pms.room">
<field name="name">Economic-101</field>
<field name="room_type_id" ref="pms_room_type_0" />
<field name="floor_id" ref="pms_floor_1" />
<field name="ubication_id" ref="pms_ubication_1" />
<field name="capacity">2</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_room_1" model="pms.room">
<field name="name">Single-101</field>
<field name="room_type_id" ref="pms_room_type_1" />
<field name="floor_id" ref="pms_floor_1" />
<field name="ubication_id" ref="pms_ubication_1" />
<field name="capacity">1</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_room_2" model="pms.room">
<field name="name">Single-102</field>
<field name="room_type_id" ref="pms_room_type_1" />
<field name="floor_id" ref="pms_floor_1" />
<field name="ubication_id" ref="pms_ubication_1" />
<field name="capacity">1</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_room_3" model="pms.room">
<field name="name">Single-103</field>
<field name="room_type_id" ref="pms_room_type_1" />
<field name="floor_id" ref="pms_floor_1" />
<field name="ubication_id" ref="pms_ubication_1" />
<field name="capacity">1</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_room_4" model="pms.room">
<field name="name">Double-201</field>
<field name="room_type_id" ref="pms_room_type_2" />
<field name="floor_id" ref="pms_floor_2" />
<field name="ubication_id" ref="pms_ubication_2" />
<field name="capacity">2</field>
<field name="extra_beds_allowed">1</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_room_5" model="pms.room">
<field name="name">Double-202</field>
<field name="room_type_id" ref="pms_room_type_2" />
<field name="floor_id" ref="pms_floor_2" />
<field name="ubication_id" ref="pms_ubication_2" />
<field name="capacity">2</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_room_6" model="pms.room">
<field name="name">Triple-203</field>
<field name="room_type_id" ref="pms_room_type_3" />
<field name="floor_id" ref="pms_floor_2" />
<field name="ubication_id" ref="pms_ubication_2" />
<field name="capacity">3</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_room_7" model="pms.room">
<field name="name">Open Talk Away Room</field>
<field name="room_type_id" ref="pms_room_type_4" />
<field name="floor_id" ref="pms_floor_0" />
<field name="capacity">1</field>
<field name="ubication_id" ref="pms_ubication_0" />
<field name="capacity">10</field>
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<!-- product.product for pms services -->
<record id="pms_service_0" model="product.product">
@@ -171,6 +284,7 @@
<field name="purchase_ok">False</field>
<field name="per_day">True</field>
<field name="per_person">True</field>
<field name="consumed_on">after</field>
</record>
<record id="pms_service_1" model="product.product">
<field name="name">Extra Bed</field>
@@ -181,7 +295,6 @@
<field name="per_person">False</field>
<field name="daily_limit">1</field>
<field name="is_extra_bed">True</field>
<field name="show_in_calendar">True</field>
</record>
<record id="pms_service_3" model="product.product">
<field name="name">Late Check-out</field>
@@ -218,16 +331,17 @@
<!-- pms.board.service -->
<record id="pms_board_service_0" model="pms.board.service">
<field name="name">BreakFast</field>
<field name="default_code">BB</field>
<field
name="board_service_line_ids"
eval="[(5, 0), (0, 0, {
'product_id': ref('pms_service_0'),
'amount': 3})]"
/>
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_1" model="pms.board.service">
<field name="name">Half Board</field>
<field name="default_code">HB</field>
<field
name="board_service_line_ids"
eval="[(5, 0), (0, 0, {
@@ -237,10 +351,10 @@
'amount': 8})
]"
/>
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_2" model="pms.board.service">
<field name="name">FullBoard</field>
<field name="default_code">FB</field>
<field
name="board_service_line_ids"
eval="[(5, 0), (0, 0, {
@@ -252,36 +366,29 @@
'amount': 8})
]"
/>
<field name="price_type">fixed</field>
</record>
<!-- pms.board.service.room.type -->
<!--Room 0 Economic-->
<record id="pms_board_service_room_0" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_0" />
<field name="pms_room_type_id" ref="pms_room_type_0" />
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_room_1" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_1" />
<field name="pms_room_type_id" ref="pms_room_type_0" />
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_room_2" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_1" />
<field name="pms_board_service_id" ref="pms_board_service_2" />
<field name="pms_room_type_id" ref="pms_room_type_0" />
<field name="pricelist_id" ref="product.list0" />
<field name="price_type">fixed</field>
</record>
<!--Room 3 Triple-->
<record id="pms_board_service_room_3" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_0" />
<field name="pms_room_type_id" ref="pms_room_type_3" />
<field name="price_type">fixed</field>
</record>
<record id="pms_board_service_room_4" model="pms.board.service.room.type">
<field name="pms_board_service_id" ref="pms_board_service_2" />
<field name="pms_room_type_id" ref="pms_room_type_3" />
<field name="price_type">fixed</field>
</record>
<!-- room.closure.reason -->
<record id="pms_room_closure_reason_0" model="room.closure.reason">
@@ -297,21 +404,11 @@
Used for closing of rooms for extra privacy.
</field>
</record>
<!-- Multi pms Demo -->
<record id="demo_pms_room_type_restriction" model="pms.room.type.restriction">
<field name="name">Restriction Plan Demo</field>
</record>
<record id="demo_pms_property" model="pms.property">
<field name="name">My pms Demo</field>
<field name="company_id" ref="base.main_company" />
<field name="default_pricelist_id" ref="product.list0" />
<field name="default_restriction_id" ref="demo_pms_room_type_restriction" />
</record>
<!-- pms.room.type -->
<record id="demo_pms_room_type_0" model="pms.room.type">
<field name="pms_property_ids" eval="[(4, ref('pms.demo_pms_property'))]" />
<field name="name">Prop. Demo Suite</field>
<field name="code_type">SUI</field>
<field name="default_code">SUI</field>
<field name="list_price">21.00</field>
<field name="class_id" ref="pms_room_type_class_0" />
<field name="room_amenity_ids" eval="[(4, ref('pms_amenity_0'))]" />
@@ -319,7 +416,7 @@
<record id="demo_pms_room_type_1" model="pms.room.type">
<field name="pms_property_ids" eval="[(4, ref('pms.demo_pms_property'))]" />
<field name="name">Prop. Demo Views</field>
<field name="code_type">VIE</field>
<field name="default_code">VIE</field>
<field name="list_price">20.00</field>
<field name="class_id" ref="pms_room_type_class_0" />
<field

View File

@@ -4,23 +4,28 @@
<!-- Economic -->
<record id="pms_reservation_0" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_27" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_0" />
<field name="adults">1</field>
<field name="checkin" eval="DateTime.today() + timedelta(8)" />
<field name="checkout" eval="DateTime.today() + timedelta(9)" />
<field name="board_service_room_id" ref="pms_board_service_room_2" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_1" model="pms.reservation">
<field name="partner_id" ref="main_pms_property" />
<field name="user_id" ref="base.user_demo" />
<field name="reservation_type">"out"</field>
<field name="room_type_id" ref="pms_room_type_0" />
<field name="checkin" eval="DateTime.today() + timedelta(-3)" />
<field name="checkout" eval="DateTime.today() + timedelta(-1)" />
<field name="closure_reason_id" ref="pms_room_closure_reason_1" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_2" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_28" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_0" />
<field name="adults">2</field>
@@ -41,34 +46,42 @@
<field name="checkin" eval="DateTime.today()" />
<field name="checkout" eval="DateTime.today() + timedelta(1)" />
<field name="board_service_room_id" ref="pms_board_service_room_1" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_3" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_13" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_0" />
<field name="adults">1</field>
<field name="checkin" eval="DateTime.today() + timedelta(1)" />
<field name="checkout" eval="DateTime.today() + timedelta(4)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_4" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_16" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_0" />
<field name="adults">2</field>
<field name="checkin" eval="DateTime.today() + timedelta(4)" />
<field name="checkout" eval="DateTime.today() + timedelta(5)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_5" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_33" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_0" />
<field name="adults">1</field>
<field name="checkin" eval="DateTime.today() + timedelta(5)" />
<field name="checkout" eval="DateTime.today() + timedelta(7)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<!-- Single -->
<record id="pms_reservation_6" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_27" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_1" />
<field name="adults">1</field>
@@ -84,9 +97,11 @@
<field name="checkin" eval="DateTime.today()" />
<field name="checkout" eval="DateTime.today() + timedelta(3)" />
<field name="board_service_room_id" ref="pms_board_service_room_2" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_7" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_10" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_1" />
<field name="adults">1</field>
@@ -101,62 +116,76 @@
<field name="checkin" eval="DateTime.today()" />
<field name="checkout" eval="DateTime.today() + timedelta(3)" />
<field name="board_service_room_id" ref="pms_board_service_room_0" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_8" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_13" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_1" />
<field name="adults">1</field>
<field name="checkin" eval="DateTime.today() + timedelta(7)" />
<field name="checkout" eval="DateTime.today() + timedelta(8)" />
<field name="board_service_room_id" ref="pms_board_service_room_1" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_9" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_15" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_1" />
<field name="adults">1</field>
<field name="checkin" eval="DateTime.today() + timedelta(9)" />
<field name="checkout" eval="DateTime.today() + timedelta(13)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_10" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_10" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_1" />
<field name="adults">1</field>
<field name="children">1</field>
<field name="checkin" eval="DateTime.today() + timedelta(13)" />
<field name="checkout" eval="DateTime.today() + timedelta(14)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_11" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_10" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_1" />
<field name="adults">1</field>
<field name="checkin" eval="DateTime.today()" />
<field name="checkout" eval="DateTime.today() + timedelta(4)" />
<field name="board_service_room_id" ref="pms_board_service_room_2" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_12" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_10" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_1" />
<field name="adults">1</field>
<field name="checkin" eval="DateTime.today() + timedelta(6)" />
<field name="checkout" eval="DateTime.today() + timedelta(10)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<!-- Double-->
<record id="pms_reservation_13" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">2</field>
<field name="checkin" eval="DateTime.today() + timedelta(11)" />
<field name="checkout" eval="DateTime.today() + timedelta(13)" />
<field name="board_service_room_id" ref="pms_board_service_room_0" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_14" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_33" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">2</field>
@@ -169,9 +198,11 @@
/>
<field name="checkin" eval="DateTime.today() + timedelta(6)" />
<field name="checkout" eval="DateTime.today() + timedelta(8)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_15" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_10" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">1</field>
@@ -179,26 +210,32 @@
<field name="checkin" eval="DateTime.today() + timedelta(10)" />
<field name="checkout" eval="DateTime.today() + timedelta(11)" />
<field name="board_service_room_id" ref="pms_board_service_room_3" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_16" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_13" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">2</field>
<field name="checkin" eval="DateTime.today() + timedelta(4)" />
<field name="checkout" eval="DateTime.today() + timedelta(6)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_17" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_15" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">2</field>
<field name="checkin" eval="DateTime.today()" />
<field name="checkout" eval="DateTime.today() + timedelta(4)" />
<field name="board_service_room_id" ref="pms_board_service_room_2" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_18" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">2</field>
@@ -211,54 +248,66 @@
/>
<field name="checkin" eval="DateTime.today() + timedelta(11)" />
<field name="checkout" eval="DateTime.today() + timedelta(13)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_19" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_32" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">2</field>
<field name="checkin" eval="DateTime.today() + timedelta(6)" />
<field name="checkout" eval="DateTime.today() + timedelta(8)" />
<field name="board_service_room_id" ref="pms_board_service_room_1" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_20" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_15" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">1</field>
<field name="children">1</field>
<field name="checkin" eval="DateTime.today() + timedelta(10)" />
<field name="checkout" eval="DateTime.today() + timedelta(11)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_21" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_3" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">2</field>
<field name="checkin" eval="DateTime.today() + timedelta(4)" />
<field name="checkout" eval="DateTime.today() + timedelta(6)" />
<field name="board_service_room_id" ref="pms_board_service_room_0" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_22" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_2" />
<field name="adults">2</field>
<field name="checkin" eval="DateTime.today()" />
<field name="checkout" eval="DateTime.today() + timedelta(4)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<!-- Triple -->
<record id="pms_reservation_23" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_4" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_3" />
<field name="adults">3</field>
<field name="checkin" eval="DateTime.today() + timedelta(11)" />
<field name="checkout" eval="DateTime.today() + timedelta(13)" />
<field name="board_service_room_id" ref="pms_board_service_room_2" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_24" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_3" />
<field name="adults">3</field>
@@ -272,9 +321,11 @@
/>
<field name="checkin" eval="DateTime.today() + timedelta(6)" />
<field name="checkout" eval="DateTime.today() + timedelta(8)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_25" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_28" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_3" />
<field name="adults">1</field>
@@ -288,9 +339,11 @@
<field name="checkin" eval="DateTime.today() + timedelta(10)" />
<field name="checkout" eval="DateTime.today() + timedelta(11)" />
<field name="board_service_room_id" ref="pms_board_service_room_3" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_26" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_16" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_3" />
<field name="adults">3</field>
@@ -304,9 +357,11 @@
/>
<field name="checkin" eval="DateTime.today() + timedelta(4)" />
<field name="checkout" eval="DateTime.today() + timedelta(6)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_27" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_12" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_3" />
<field name="adults">2</field>
@@ -328,28 +383,35 @@
<field name="checkin" eval="DateTime.today()" />
<field name="checkout" eval="DateTime.today() + timedelta(4)" />
<field name="board_service_room_id" ref="pms_board_service_room_2" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<!-- Open talk away -->
<record id="pms_reservation_28" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_27" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_4" />
<field name="checkin" eval="DateTime.today() + timedelta(10)" />
<field name="checkout" eval="DateTime.today() + timedelta(12)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_29" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_32" />
<field name="user_id" ref="base.user_demo" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_4" />
<field name="checkin" eval="DateTime.today() + timedelta(1)" />
<field name="checkout" eval="DateTime.today() + timedelta(4)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
<record id="pms_reservation_30" model="pms.reservation">
<field name="partner_id" ref="base.res_partner_address_33" />
<field name="user_id" ref="base.user_admin" />
<field name="pricelist_id" ref="product.list0" />
<field name="room_type_id" ref="pms_room_type_4" />
<field name="checkin" eval="DateTime.today() + timedelta(6)" />
<field name="checkout" eval="DateTime.today() + timedelta(8)" />
<field name="pms_property_id" ref="pms.main_pms_property" />
</record>
</data>
</odoo>

File diff suppressed because it is too large Load Diff

View File

@@ -2,9 +2,15 @@ from odoo import SUPERUSER_ID
from odoo.api import Environment
def post_init_hook(cr, _):
def pre_init_hook(cr):
with Environment.manage():
env = Environment(cr, SUPERUSER_ID, {})
ResConfig = env["res.config.settings"]
default_values = ResConfig.default_get(list(ResConfig.fields_get()))
default_values.update(
{"group_product_pricelist": True, "group_sale_pricelist": True}
)
ResConfig.sudo().create(default_values).execute()
env["ir.config_parameter"].sudo().set_param(
"product.product_pricelist_setting", "advanced"
)

View File

@@ -3,14 +3,13 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import ir_http
from . import ir_sequence
from . import ir_config_parameter
# from . import payment_return
from . import pms_board_service_room_type
from . import pms_property
from . import res_users
from . import pms_floor
from . import pms_ubication
from . import pms_folio
from . import pms_reservation
from . import pms_room
@@ -21,11 +20,11 @@ from . import pms_room_type
from . import pms_service
from . import account_move
from . import product_template
from . import product_product
from . import res_company
from . import account_payment
from . import pms_room_type_availability
from . import pms_room_type_restriction
from . import pms_room_type_restriction_item
from . import pms_availability_plan
from . import pms_availability_plan_rule
from . import pms_reservation_line
from . import pms_checkin_partner
from . import product_pricelist
@@ -42,3 +41,8 @@ from . import pms_board_service_room_type_line
from . import pms_board_service_line
from . import account_move_line
from . import pms_cancelation_rule
from . import folio_sale_line
from . import account_bank_statement_line
from . import account_bank_statement
from . import account_journal
from . import pms_availability

View File

@@ -0,0 +1,16 @@
from odoo import fields, models
class AccountBankStatement(models.Model):
_inherit = "account.bank.statement"
pms_property_id = fields.Many2one(
string="Property",
help="Properties with access to the element",
copy=False,
comodel_name="pms.property",
)
company_id = fields.Many2one(
string="Company",
help="The company for Account Bank Statement",
)

View File

@@ -0,0 +1,46 @@
from odoo import api, fields, models
class AccountBankStatementLine(models.Model):
_inherit = "account.bank.statement.line"
folio_ids = fields.Many2many(
string="Folios",
comodel_name="pms.folio",
ondelete="cascade",
relation="account_bank_statement_folio_rel",
column1="account_journal_id",
column2="folio_id",
)
reservation_ids = fields.Many2many(
string="Reservations",
help="Reservations in which the Account Bank Statement Lines are included",
comodel_name="pms.reservation",
ondelete="cascade",
relation="account_bank_statement_reservation_rel",
column1="account_bank_statement_id",
column2="reservation_id",
)
service_ids = fields.Many2many(
string="Services",
help="Services in which the Account Bank Statement Lines are included",
comodel_name="pms.service",
ondelete="cascade",
relation="account_bank_statement_service_rel",
column1="account_bank_statement_id",
column2="service_id",
)
@api.model
def _prepare_move_line_default_vals(self, counterpart_account_id=None):
line_vals_list = super(
AccountBankStatementLine, self
)._prepare_move_line_default_vals(counterpart_account_id)
if self.folio_ids:
for line in line_vals_list:
line.update(
{
"folio_ids": [(6, 0, self.folio_ids.ids)],
}
)
return line_vals_list

View File

@@ -0,0 +1,21 @@
from odoo import fields, models
class AccountJournal(models.Model):
_inherit = "account.journal"
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
ondelete="restrict",
relation="account_journal_pms_property_rel",
column1="account_journal_id",
column2="pms_property_id",
)
company_id = fields.Many2one(
string="Company",
help="The company for Account Jouarnal",
check_pms_properties=True,
)

View File

@@ -1,4 +1,3 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
import json
@@ -12,10 +11,19 @@ class AccountMove(models.Model):
# Field Declarations
folio_ids = fields.Many2many(
comodel_name="pms.folio", compute="_compute_folio_origin"
string="Folios",
help="Folios where the account move are included",
comodel_name="pms.folio",
compute="_compute_folio_origin",
relation="account_move_folio_ids_rel",
column1="account_move_id",
column2="folio_ids_id",
)
pms_property_id = fields.Many2one(
string="Property",
help="Property with access to the element",
comodel_name="pms.property",
)
pms_property_id = fields.Many2one("pms.property")
from_folio = fields.Boolean(compute="_compute_folio_origin")
outstanding_folios_debits_widget = fields.Text(
compute="_compute_get_outstanding_folios_JSON"
)
@@ -23,38 +31,13 @@ class AccountMove(models.Model):
compute="_compute_get_outstanding_folios_JSON"
)
# Compute and Search methods
def _compute_folio_origin(self):
for inv in self:
inv.from_folio = False
inv.folio_ids = False
folios = inv.mapped("invoice_line_ids.reservation_ids.folio_id")
folios |= inv.mapped("invoice_line_ids.service_ids.folio_id")
folios = inv.mapped("invoice_line_ids.folio_ids")
if folios:
inv.from_folio = True
inv.folio_ids = [(6, 0, folios.ids)]
# Action methods
def action_folio_payments(self):
self.ensure_one()
sales = self.mapped("invoice_line_ids.sale_line_ids.order_id")
folios = self.env["pms.folio"].search([("order_id.id", "in", sales.ids)])
payments_obj = self.env["account.payment"]
payments = payments_obj.search([("folio_id", "in", folios.ids)])
payment_ids = payments.mapped("id")
return {
"name": _("Payments"),
"view_type": "form",
"view_mode": "tree,form",
"res_model": "account.payment",
"target": "new",
"type": "ir.actions.act_window",
"domain": [("id", "in", payment_ids)],
}
# Business methods
def _compute_get_outstanding_folios_JSON(self):
self.ensure_one()
self.outstanding_folios_debits_widget = json.dumps(False)
@@ -124,3 +107,20 @@ class AccountMove(models.Model):
info["title"] = type_payment
self.outstanding_folios_debits_widget = json.dumps(info)
self.has_folio_outstanding = True
def action_folio_payments(self):
self.ensure_one()
sales = self.mapped("invoice_line_ids.sale_line_ids.order_id")
folios = self.env["pms.folio"].search([("order_id.id", "in", sales.ids)])
payments_obj = self.env["account.payment"]
payments = payments_obj.search([("folio_id", "in", folios.ids)])
payment_ids = payments.mapped("id")
return {
"name": _("Payments"),
"view_type": "form",
"view_mode": "tree,form",
"res_model": "account.payment",
"target": "new",
"type": "ir.actions.act_window",
"domain": [("id", "in", payment_ids)],
}

View File

@@ -1,37 +1,75 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
from odoo import fields, models
from odoo import api, fields, models
class AccountMoveLine(models.Model):
_inherit = "account.move.line"
# Fields declaration
reservation_ids = fields.Many2many(
"pms.reservation",
"reservation_move_rel",
"move_line_id",
"reservation_id",
string="Reservations",
readonly=True,
# TODO: REVIEW why not a Many2one?
folio_line_ids = fields.Many2many(
string="Folio Lines",
help="The folio lines in the account move lines",
copy=False,
comodel_name="folio.sale.line",
relation="folio_sale_line_invoice_rel",
column1="invoice_line_id",
column2="sale_line_id",
)
service_ids = fields.Many2many(
"pms.service",
"service_line_move_rel",
"move_line_id",
"service_id",
string="Services",
readonly=True,
copy=False,
folio_ids = fields.Many2many(
string="Folios",
comodel_name="pms.folio",
relation="payment_folio_rel",
column1="move_id",
column2="folio_id",
)
reservation_line_ids = fields.Many2many(
"pms.reservation.line",
"reservation_line_move_rel",
"move_line_id",
"reservation_line_id",
string="Reservation Lines",
readonly=True,
copy=False,
name_changed_by_user = fields.Boolean(
string="Custom label",
readonly=False,
default=False,
store=True,
compute="_compute_name_changed_by_user",
)
@api.depends("name")
def _compute_name_changed_by_user(self):
for record in self:
# if not record._context.get("auto_name"):
if not self._context.get("auto_name"):
record.name_changed_by_user = True
else:
record.name_changed_by_user = False
name = fields.Char(
compute="_compute_name",
store=True,
readonly=False,
)
@api.depends("quantity")
def _compute_name(self):
for record in self:
record.name = self.env["folio.sale.line"].generate_folio_sale_name(
record.folio_line_ids.reservation_id,
record.product_id,
record.folio_line_ids.service_id,
record.folio_line_ids.reservation_line_ids,
record.folio_line_ids.service_line_ids,
qty=record.quantity,
)
# TODO: check why this code doesn't work
# if not record.name_changed_by_user:
# record.with_context(auto_name=True).name = self
# .env["folio.sale.line"].generate_folio_sale_name(
# record.folio_line_ids.service_id,
# record.folio_line_ids.reservation_line_ids,
# record.product_id,
# qty=record.quantity)
# record.with_context(auto_name=True)
# ._compute_name_changed_by_user()
def _copy_data_extend_business_fields(self, values):
super(AccountMoveLine, self)._copy_data_extend_business_fields(values)
values["folio_line_ids"] = [(6, None, self.folio_line_ids.ids)]

View File

@@ -1,86 +1,17 @@
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import except_orm
from odoo import _, fields, models
class AccountPayment(models.Model):
_inherit = "account.payment"
# Fields declaration
folio_id = fields.Many2one("pms.folio", string="Folio Reference")
amount_total_folio = fields.Float(
compute="_compute_folio_amount",
store=True,
string="Total amount in folio",
folio_id = fields.Many2one(
string="Folio Reference",
help="Folio in account payment",
comodel_name="pms.folio",
)
save_amount = fields.Monetary(string="onchange_amount")
save_date = fields.Date()
save_journal_id = fields.Integer()
# Compute and Search methods
@api.depends("state")
def _compute_folio_amount(self):
# FIXME: Finalize method
res = []
fol = ()
for payment in self:
if payment.folio_id:
fol = payment.env["pms.folio"].search(
[("id", "=", payment.folio_id.id)]
)
else:
return
if not any(fol):
return
if len(fol) > 1:
raise except_orm(
_("Warning"),
_(
"This pay is related with \
more than one Reservation."
),
)
else:
fol.compute_amount()
return res
# Constraints and onchanges
# @api.onchange("amount", "payment_date", "journal_id")
# def onchange_amount(self):
# if self._origin:
# self.save_amount = self._origin.amount
# self.save_journal_id = self._origin.journal_id.id
# self.save_date = self._origin.payment_date
# Action methods
# def return_payment_folio(self):
# journal = self.journal_id
# partner = self.partner_id
# amount = self.amount
# reference = self.communication
# account_move_lines = self.move_line_ids.filtered(
# lambda x: (x.account_id.internal_type == "receivable")
# )
# return_line_vals = {
# "move_line_ids": [(6, False, [x.id for x in account_move_lines])],
# "partner_id": partner.id,
# "amount": amount,
# "reference": reference,
# }
# return_vals = {
# "journal_id": journal.id,
# "line_ids": [(0, 0, return_line_vals)],
# }
# return_pay = self.env["payment.return"].create(return_vals)
# if self.save_amount:
# self.amount = self.save_amount
# if self.save_date:
# self.payment_date = self.save_date
# if self.save_journal_id:
# self.journal_id = self.env["account.journal"].browse(self.save_journal_id)
# return_pay.action_confirm()
# Business methods

File diff suppressed because it is too large Load Diff

View File

@@ -27,10 +27,7 @@ class IrHttp(models.AbstractModel):
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,
"display_switch_pms_property_menu": len(user.pms_property_ids) > 1,
}
)
# TODO: This user context update should be placed in other function ¿?

View File

@@ -1,9 +0,0 @@
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class IrSequence(models.Model):
_inherit = "ir.sequence"
pms_property_id = fields.Many2one("pms.property", string="Property")

View File

@@ -7,12 +7,22 @@ class PaymentReturn(models.Model):
_inherit = "payment.return"
# Fields declaration
folio_id = fields.Many2one("pms.folio", string="Folio")
pms_property_id = fields.Many2one(
"pms.property", store=True, readonly=True, related="folio_id.pms_property_id"
folio_id = fields.Many2one(
string="Folio", help="Folio in payment return", comodel_name="pms.folio"
)
pms_property_id = fields.Many2one(
string="Property",
help="Property with access to the element",
store=True,
readonly=True,
comodel_name="pms.property",
related="folio_id.pms_property_id",
)
company_id = fields.Many2one(
string="Company",
help="The company for Payment Return",
check_pms_properties=True,
)
# Business methods
def action_confirm(self):
pay = super(PaymentReturn, self).action_confirm()

View File

@@ -6,15 +6,37 @@ from odoo import fields, models
class PmsRoomAmenity(models.Model):
_name = "pms.amenity"
_description = "Room amenities"
_description = "Room amenity"
_check_pms_properties_auto = True
# Fields declaration
name = fields.Char("Amenity Name", translate=True, required=True)
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
active = fields.Boolean(
string="Active",
help="Determines if amenity is active",
default=True,
)
name = fields.Char(
string="Amenity Name",
help="Amenity Name",
required=True,
translate=True,
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
ondelete="restrict",
relation="pms_amenity_pms_property_rel",
column1="amenity_id",
column2="pms_property_id",
check_pms_properties=True,
)
pms_amenity_type_id = fields.Many2one(
string="Amenity Category",
help="Segment the amenities by categories (multimedia, comfort, etc ...)",
comodel_name="pms.amenity.type",
check_pms_properties=True,
)
default_code = fields.Char(
string="Internal Reference", help="Internal unique identifier of the amenity"
)
room_amenity_type_id = fields.Many2one("pms.amenity.type", "Amenity Category")
default_code = fields.Char("Internal Reference")
active = fields.Boolean("Active", default=True)
# TODO: Constrain coherence pms_property_ids with amenity types pms_property_ids

View File

@@ -6,16 +6,35 @@ from odoo import fields, models
class PmsRoomAmenityType(models.Model):
_name = "pms.amenity.type"
_description = "Amenities Type"
_description = "Amenity Type"
_check_pms_properties_auto = True
# Fields declaration
name = fields.Char("Amenity Type Name", translate=True, required=True)
active = fields.Boolean(
string="Active",
help="Determines if amenity type is active",
default=True,
)
name = fields.Char(
string="Amenity Type Name",
help="Amenity Type Name",
required=True,
translate=True,
)
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
ondelete="restrict",
relation="pms_amenity_type_pms_property_rel",
column1="amenity_type_id",
column2="pms_property_id",
check_pms_properties=True,
)
room_amenity_ids = fields.One2many(
"pms.amenity", "room_amenity_type_id", "Amenities in this category"
pms_amenity_ids = fields.One2many(
string="Amenities In This Category",
help="Amenities included in this type",
comodel_name="pms.amenity",
inverse_name="pms_amenity_type_id",
check_pms_properties=True,
)
active = fields.Boolean("Active", default=True)
# TODO: Constrain coherence pms_property_ids with amenities pms_property_ids

View File

@@ -0,0 +1,103 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsAvailability(models.Model):
_name = "pms.availability"
_description = "Room type availability per day"
_check_pms_properties_auto = True
room_type_id = fields.Many2one(
string="Room Type",
help="Room type for which availability is indicated",
readonly=True,
required=True,
comodel_name="pms.room.type",
ondelete="cascade",
check_pms_properties=True,
)
date = fields.Date(
string="Date",
help="Date for which availability applies",
readonly=True,
required=True,
)
pms_property_id = fields.Many2one(
string="Property",
help="Property to which the availability is directed",
readonly=True,
required=True,
comodel_name="pms.property",
ondelete="restrict",
check_pms_properties=True,
)
reservation_line_ids = fields.One2many(
string="Reservation Lines",
help="They are the lines of the reservation into a reservation,"
"they corresponds to the nights",
readonly=True,
comodel_name="pms.reservation.line",
inverse_name="avail_id",
check_pms_properties=True,
)
real_avail = fields.Integer(
string="Real Avail",
help="",
store=True,
readonly=True,
compute="_compute_real_avail",
)
_sql_constraints = [
(
"room_type_registry_unique",
"unique(room_type_id, date, pms_property_id)",
"Only can exists one availability in the same \
day for the same room type!",
)
]
@api.depends(
"reservation_line_ids",
"reservation_line_ids.occupies_availability",
"room_type_id.total_rooms_count",
)
def _compute_real_avail(self):
for record in self:
Rooms = self.env["pms.room"]
RoomLines = self.env["pms.reservation.line"]
total_rooms = Rooms.search_count(
[
("room_type_id", "=", record.room_type_id.id),
("pms_property_id", "=", record.pms_property_id.id),
]
)
room_ids = record.room_type_id.mapped("room_ids.id")
rooms_not_avail = RoomLines.search_count(
[
("date", "=", record.date),
("room_id", "in", room_ids),
("pms_property_id", "=", record.pms_property_id.id),
("occupies_availability", "=", True),
# ("id", "not in", current_lines if current_lines else []),
]
)
record.real_avail = total_rooms - rooms_not_avail
@api.constrains(
"room_type_id",
"pms_property_id",
)
def _check_property_integrity(self):
for rec in self:
if rec.pms_property_id and rec.room_type_id:
if (
rec.room_type_id.pms_property_ids.ids
and rec.pms_property_id.id
not in rec.room_type_id.pms_property_ids.ids
):
raise ValidationError(
_("Property not allowed on availability day compute")
)

View File

@@ -0,0 +1,332 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import datetime
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
class PmsAvailabilityPlan(models.Model):
"""The room type availability is used as a daily availability plan for room types
and therefore is related only with one property."""
_name = "pms.availability.plan"
_description = "Reservation availability plan"
_check_pms_properties_auto = True
@api.model
def _get_default_pms_property(self):
return self.env.user.get_active_property_ids()[0] or None
name = fields.Char(
string="Availability Plan Name", help="Name of availability plan", required=True
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
ondelete="restrict",
relation="pms_availability_plan_pms_property_rel",
column1="availability_plan_id",
column2="pms_property_id",
check_pms_properties=True,
)
pms_pricelist_ids = fields.One2many(
string="Pricelists",
help="Pricelists of the availability plan ",
comodel_name="product.pricelist",
inverse_name="availability_plan_id",
check_pms_properties=True,
)
rule_ids = fields.One2many(
string="Availability Rules",
help="Rules in a availability plan",
comodel_name="pms.availability.plan.rule",
inverse_name="availability_plan_id",
check_pms_properties=True,
)
active = fields.Boolean(
string="Active",
help="If unchecked, it will allow you to hide the "
"Availability plan without removing it.",
default=True,
)
@classmethod
def any_rule_applies(cls, checkin, checkout, item):
reservation_len = (checkout - checkin).days
return any(
[
(0 < item.max_stay < reservation_len),
(0 < item.min_stay > reservation_len),
(0 < item.max_stay_arrival < reservation_len and checkin == item.date),
(0 < item.min_stay_arrival > reservation_len and checkin == item.date),
item.closed,
(item.closed_arrival and checkin == item.date),
(item.closed_departure and checkout == item.date),
(item.quota == 0 or item.max_avail == 0),
]
)
@api.model
def rooms_available(
self,
checkin,
checkout,
room_type_id=False,
current_lines=False,
pricelist_id=False,
pms_property_id=False,
):
if current_lines and not isinstance(current_lines, list):
current_lines = [current_lines]
free_rooms = self.get_real_free_rooms(
checkin, checkout, room_type_id, current_lines, pms_property_id
)
domain_rules = [
("date", ">=", checkin),
(
"date",
"<=",
checkout,
), # TODO: only closed_departure take account checkout date!
]
if pms_property_id:
domain_rules.append(("pms_property_id", "=", pms_property_id))
if room_type_id:
domain_rules.append(("room_type_id", "=", room_type_id))
if pricelist_id:
pricelist = self.env["product.pricelist"].browse(pricelist_id)
if pricelist and pricelist.availability_plan_id:
domain_rules.append(
("availability_plan_id", "=", pricelist.availability_plan_id.id)
)
rule_items = self.env["pms.availability.plan.rule"].search(domain_rules)
if len(rule_items) > 0:
room_types_to_remove = []
for item in rule_items:
if self.any_rule_applies(checkin, checkout, item):
room_types_to_remove.append(item.room_type_id.id)
free_rooms = free_rooms.filtered(
lambda x: x.room_type_id.id not in room_types_to_remove
)
elif not pricelist:
raise ValidationError(_("Pricelist not found"))
return free_rooms.sorted(key=lambda r: r.sequence)
def get_real_free_rooms(
self,
checkin,
checkout,
room_type_id=False,
current_lines=False,
pms_property_id=False,
):
Avail = self.env["pms.availability"]
if isinstance(checkin, str):
checkin = datetime.datetime.strptime(
checkin, DEFAULT_SERVER_DATE_FORMAT
).date()
if isinstance(checkout, str):
checkout = datetime.datetime.strptime(
checkout, DEFAULT_SERVER_DATE_FORMAT
).date()
domain = [
("date", ">=", checkin),
("date", "<=", checkout - datetime.timedelta(1)),
]
if not current_lines:
current_lines = []
rooms_not_avail = (
Avail.search(domain)
.reservation_line_ids.filtered(lambda l: l.id and l.id not in current_lines)
.room_id.ids
)
domain_rooms = []
if rooms_not_avail:
domain_rooms = [
("id", "not in", rooms_not_avail),
]
if pms_property_id:
domain_rooms.append(("pms_property_id", "=", pms_property_id))
if room_type_id:
domain_rooms.append(("room_type_id", "=", room_type_id))
return self.env["pms.room"].search(domain_rooms)
@api.model
def get_count_rooms_available(
self,
checkin,
checkout,
room_type_id,
pms_property_id,
current_lines=False,
pricelist_id=False,
):
if current_lines and not isinstance(current_lines, list):
current_lines = [current_lines]
avail = self.get_count_real_free_rooms(
checkin, checkout, room_type_id, pms_property_id, current_lines
)
domain_rules = [
("date", ">=", checkin),
(
"date",
"<=",
checkout,
), # TODO: only closed_departure take account checkout date!
("room_type_id", "=", room_type_id),
("pms_property_id", "=", pms_property_id),
]
if pricelist_id:
pricelist = self.env["product.pricelist"].browse(pricelist_id)
if pricelist and pricelist.availability_plan_id:
domain_rules.append(
("availability_plan_id", "=", pricelist.availability_plan_id.id)
)
rule_items = self.env["pms.availability.plan.rule"].search(domain_rules)
if len(rule_items) > 0:
for item in rule_items:
if self.any_rule_applies(checkin, checkout, item):
return 0
avail = min(rule_items.mapped("plan_avail"))
return avail
def get_count_real_free_rooms(
self,
checkin,
checkout,
room_type_id,
pms_property_id,
current_lines=False,
):
Avail = self.env["pms.availability"]
count_free_rooms = len(
self.env["pms.room.type"]
.browse(room_type_id)
.room_ids.filtered(lambda r: r.pms_property_id.id == pms_property_id)
)
if isinstance(checkin, str):
checkin = datetime.datetime.strptime(
checkin, DEFAULT_SERVER_DATE_FORMAT
).date()
if isinstance(checkout, str):
checkout = datetime.datetime.strptime(
checkout, DEFAULT_SERVER_DATE_FORMAT
).date()
for avail in Avail.search(
[
("date", ">=", checkin),
("date", "<=", checkout - datetime.timedelta(1)),
("room_type_id", "=", room_type_id),
("pms_property_id", "=", pms_property_id),
]
):
if avail.real_avail < count_free_rooms:
count_free_rooms = avail.real_avail
return count_free_rooms
@api.model
def splitted_availability(
self,
checkin,
checkout,
room_type_id=False,
current_lines=False,
pricelist=False,
pms_property_id=False,
):
if isinstance(checkin, str):
checkin = datetime.datetime.strptime(
checkin, DEFAULT_SERVER_DATE_FORMAT
).date()
if isinstance(checkout, str):
checkout = datetime.datetime.strptime(
checkout, DEFAULT_SERVER_DATE_FORMAT
).date()
for date_iterator in [
checkin + datetime.timedelta(days=x)
for x in range(0, (checkout - checkin).days)
]:
rooms_avail = self.rooms_available(
checkin=date_iterator,
checkout=date_iterator + datetime.timedelta(1),
room_type_id=room_type_id,
current_lines=current_lines,
pricelist_id=pricelist.id,
pms_property_id=pms_property_id,
)
if len(rooms_avail) < 1:
return False
return True
@api.model
def update_quota(self, pricelist_id, room_type_id, date, line):
if pricelist_id and room_type_id and date:
rule = self.env["pms.availability.plan.rule"].search(
[
("availability_plan_id.pms_pricelist_ids", "=", pricelist_id.id),
("room_type_id", "=", room_type_id.id),
("date", "=", date),
]
)
# applies a rule
if rule:
rule.ensure_one()
if rule and rule.quota != -1 and rule.quota > 0:
# the line has no rule item applied before
if not line.impacts_quota:
rule.quota -= 1
return rule.id
# the line has a rule item applied before
elif line.impacts_quota != rule.id:
# decrement quota on current rule item
rule.quota -= 1
# check old rule item
old_rule = self.env["pms.availability.plan.rule"].search(
[("id", "=", line.impacts_quota)]
)
# restore quota in old rule item
if old_rule:
old_rule.quota += 1
return rule.id
# in any case, check old rule item
if line.impacts_quota:
old_rule = self.env["pms.availability.plan.rule"].search(
[("id", "=", line.impacts_quota)]
)
# and restore quota in old rule item
if old_rule:
old_rule.quota += 1
return False
# Action methods
def open_massive_changes_wizard(self):
if self.ensure_one():
return {
"view_type": "form",
"view_mode": "form",
"name": "Massive changes on Availability Plan: " + self.name,
"res_model": "pms.massive.changes.wizard",
"target": "new",
"type": "ir.actions.act_window",
"context": {
"availability_plan_id": self.id,
},
}

View File

@@ -0,0 +1,192 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsAvailabilityPlanRule(models.Model):
_name = "pms.availability.plan.rule"
_description = "Reservation rule by day"
_check_pms_properties_auto = True
availability_plan_id = fields.Many2one(
string="Availability Plan",
help="The availability plan that include the Availabilty Rule",
index=True,
comodel_name="pms.availability.plan",
ondelete="cascade",
check_pms_properties=True,
)
room_type_id = fields.Many2one(
string="Room Type",
help="Room type for which availability rule is applied",
required=True,
comodel_name="pms.room.type",
ondelete="cascade",
check_pms_properties=True,
)
date = fields.Date(
string="Date",
help="Date for which availability rule applies",
)
min_stay = fields.Integer(
string="Min. Stay",
help="Minimum stay",
default=0,
)
min_stay_arrival = fields.Integer(
string="Min. Stay Arrival",
help="Minimum stay if checkin is today",
default=0,
)
max_stay = fields.Integer(
string="Max. Stay",
help="Maximum stay",
default=0,
)
max_stay_arrival = fields.Integer(
string="Max. Stay Arrival",
help="Maximum stay if checkin is today",
default=0,
)
closed = fields.Boolean(
string="Closed",
help="Indicate if property is closed or not",
default=False,
)
closed_departure = fields.Boolean(
string="Closed Departure",
help="",
default=False,
)
closed_arrival = fields.Boolean(
string="Closed Arrival",
help="",
default=False,
)
quota = fields.Integer(
string="Quota",
help="Generic Quota assigned.",
readonly=False,
store=True,
compute="_compute_quota",
)
max_avail = fields.Integer(
string="Max. Availability",
help="Maximum simultaneous availability on own Booking Engine",
readonly=False,
store=True,
compute="_compute_max_avail",
)
pms_property_id = fields.Many2one(
string="Property",
help="Properties with access to the element",
ondelete="restrict",
required=True,
comodel_name="pms.property",
check_pms_properties=True,
)
avail_id = fields.Many2one(
string="Avail record",
comodel_name="pms.availability",
compute="_compute_avail_id",
store=True,
readonly=False,
ondelete="restrict",
check_pms_properties=True,
)
real_avail = fields.Integer(
string="Real availability",
related="avail_id.real_avail",
store="True",
)
plan_avail = fields.Integer(
compute="_compute_plan_avail",
store="True",
)
_sql_constraints = [
(
"room_type_registry_unique",
"unique(availability_plan_id, room_type_id, date, pms_property_id)",
"Only can exists one availability rule in the same \
day for the same room type!",
)
]
@api.depends("room_type_id", "date", "pms_property_id")
def _compute_avail_id(self):
for record in self:
if record.room_type_id and record.pms_property_id and record.date:
avail = self.env["pms.availability"].search(
[
("date", "=", record.date),
("room_type_id", "=", record.room_type_id.id),
("pms_property_id", "=", record.pms_property_id.id),
]
)
if avail:
record.avail_id = avail.id
else:
record.avail_id = self.env["pms.availability"].create(
{
"date": record.date,
"room_type_id": record.room_type_id.id,
"pms_property_id": record.pms_property_id.id,
}
)
else:
record.avail_id = False
@api.depends("quota", "max_avail", "real_avail")
def _compute_plan_avail(self):
for record in self.filtered("real_avail"):
real_avail = record.real_avail
plan_avail = min(
[
record.max_avail if record.max_avail >= 0 else real_avail,
record.quota if record.quota >= 0 else real_avail,
real_avail,
]
)
if not record.plan_avail or record.plan_avail != plan_avail:
record.plan_avail = plan_avail
@api.depends("room_type_id")
def _compute_quota(self):
for record in self:
if not record.quota:
record.quota = record.room_type_id.default_quota
@api.depends("room_type_id")
def _compute_max_avail(self):
for record in self:
if not record.max_avail:
record.max_avail = record.room_type_id.default_max_avail
@api.constrains("min_stay", "min_stay_arrival", "max_stay", "max_stay_arrival")
def _check_min_max_stay(self):
for record in self:
if record.min_stay < 0:
raise ValidationError(_("Min. Stay can't be less than zero"))
elif record.min_stay_arrival < 0:
raise ValidationError(_("Min. Stay Arrival can't be less than zero"))
elif record.max_stay < 0:
raise ValidationError(_("Max. Stay can't be less than zero"))
elif record.max_stay_arrival < 0:
raise ValidationError(_("Max. Stay Arrival can't be less than zero"))
elif (
record.min_stay != 0
and record.max_stay != 0
and record.min_stay > record.max_stay
):
raise ValidationError(_("Max. Stay can't be less than Min. Stay"))
elif (
record.min_stay_arrival != 0
and record.max_stay_arrival != 0
and record.min_stay_arrival > record.max_stay_arrival
):
raise ValidationError(
_("Max. Stay Arrival can't be less than Min. Stay Arrival")
)

View File

@@ -1,34 +1,66 @@
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import api, fields, models
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsBoardService(models.Model):
_name = "pms.board.service"
_description = "Board Services"
_check_pms_properties_auto = True
# Fields declaration
name = fields.Char("Board Name", translate=True, size=64, required=True, index=True)
board_service_line_ids = fields.One2many(
"pms.board.service.line", "pms_board_service_id"
name = fields.Char(
string="Board Service Name",
help="Board Service Name",
required=True,
index=True,
size=64,
translate=True,
)
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
)
pms_board_service_room_type_ids = fields.One2many(
"pms.board.service.room.type", "pms_board_service_id"
)
price_type = fields.Selection(
[("fixed", "Fixed"), ("percent", "Percent")],
string="Type",
default="fixed",
default_code = fields.Char(
string="Board Service Code",
help="Unique Board Service identification code per property",
required=True,
)
board_service_line_ids = fields.One2many(
string="Board Service Lines",
help="Services included in this Board Service",
comodel_name="pms.board.service.line",
inverse_name="pms_board_service_id",
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
required=False,
ondelete="restrict",
comodel_name="pms.property",
relation="pms_board_service_pms_property_rel",
column1="board_service_id",
column2="pms_property_id",
check_pms_properties=True,
)
pms_board_service_room_type_ids = fields.One2many(
string="Board Services Room Type",
help="Board Services Room Type corresponding to this Board Service,"
"One board service for several room types",
comodel_name="pms.board.service.room.type",
inverse_name="pms_board_service_id",
)
amount = fields.Float(
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
string="Amount",
help="Price for this Board Service. "
"It corresponds to the sum of his board service lines",
store=True,
digits=("Product Price"),
compute="_compute_board_amount",
)
show_detail_report = fields.Boolean(
string="Show Detail Report",
help="True if you want that board service detail to be shown on the report",
)
# Compute and Search methods
@api.depends("board_service_line_ids.amount")
def _compute_board_amount(self):
for record in self:
@@ -36,3 +68,66 @@ class PmsBoardService(models.Model):
for service in record.board_service_line_ids:
total += service.amount
record.update({"amount": total})
@api.model
def get_unique_by_property_code(self, pms_property_id, default_code=None):
"""
:param pms_property_id: property ID
:param default_code: board service code (optional)
:return: - recordset of
- all the pms.board.service of the pms_property_id
if default_code not defined
- one or 0 pms.board.service if default_code defined
- ValidationError if more than one default_code found by
the same pms_property_id
"""
# TODO: similiar code as room.type -> unify
domain = []
if default_code:
domain += ["&", ("default_code", "=", default_code)]
domain += [
"|",
("pms_property_ids", "in", pms_property_id),
("pms_property_ids", "=", False),
]
records = self.search(domain)
res, res_priority = {}, {}
for rec in records:
res_priority.setdefault(rec.default_code, -1)
priority = rec.pms_property_ids and 1 or 0
if priority > res_priority[rec.default_code]:
res.setdefault(rec.default_code, rec.id)
res[rec.default_code], res_priority[rec.default_code] = rec.id, priority
elif priority == res_priority[rec.default_code]:
raise ValidationError(
_(
"Integrity error: There's multiple board services "
"with the same code %s and properties"
)
% rec.default_code
)
return self.browse(list(res.values()))
@api.constrains("default_code", "pms_property_ids")
def _check_code_property_uniqueness(self):
# TODO: similiar code as room.type -> unify
msg = _(
"Already exists another Board Service with the same code and properties"
)
for rec in self:
if not rec.pms_property_ids:
if self.search(
[
("id", "!=", rec.id),
("default_code", "=", rec.default_code),
("pms_property_ids", "=", False),
]
):
raise ValidationError(msg)
else:
for pms_property in rec.pms_property_ids:
other = rec.get_unique_by_property_code(
pms_property.id, rec.default_code
)
if other and other != rec:
raise ValidationError(msg)

View File

@@ -6,26 +6,77 @@ from odoo import api, fields, models
class PmsBoardServiceLine(models.Model):
_name = "pms.board.service.line"
_description = "Services on Board Service included"
_check_pms_properties_auto = True
pms_board_service_id = fields.Many2one(
string="Board Service",
help="Board Service in which this line is included",
required=True,
comodel_name="pms.board.service",
ondelete="cascade",
check_pms_properties=True,
)
product_id = fields.Many2one(
string="Product",
help="Product associated with this board service line",
required=True,
comodel_name="product.product",
check_pms_properties=True,
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
relation="pms_board_service_line_pms_property_rel",
column1="pms_board_service_line_id",
column2="pms_property_id",
store=True,
check_pms_properties=True,
)
amount = fields.Float(
string="Amount",
help="Price for this Board Service Line/Product",
default=lambda self: self._get_default_price(),
digits=("Product Price"),
)
# Default methods
def _get_default_price(self):
if self.product_id:
return self.product_id.list_price
# Fields declaration
pms_board_service_id = fields.Many2one(
"pms.board.service", "Board Service", ondelete="cascade", required=True
)
product_id = fields.Many2one("product.product", string="Product", required=True)
pms_property_ids = fields.Many2many(
"pms.property", related="pms_board_service_id.pms_property_ids"
)
amount = fields.Float(
"Amount", digits=("Product Price"), default=_get_default_price
)
# Constraints and onchanges
@api.onchange("product_id")
def onchange_product_id(self):
if self.product_id:
self.update({"amount": self.product_id.list_price})
@api.model
def create(self, vals):
properties = False
if "pms_board_service_id" in vals:
board_service = self.env["pms.board.service"].browse(
vals["pms_board_service_id"]
)
properties = board_service.pms_property_ids
if properties:
vals.update(
{
"pms_property_ids": properties,
}
)
return super(PmsBoardServiceLine, self).create(vals)
def write(self, vals):
properties = False
if "pms_board_service_id" in vals:
board_service = self.env["pms.board.service"].browse(
vals["pms_board_service_id"]
)
properties = board_service.pms_property_ids
if properties:
vals.update(
{
"pms_property_ids": properties,
}
)
return super(PmsBoardServiceLine, self).write(vals)

View File

@@ -10,57 +10,58 @@ class PmsBoardServiceRoomType(models.Model):
_rec_name = "pms_board_service_id"
_log_access = False
_description = "Board Service included in Room"
_check_pms_properties_auto = True
# Default Methods ang Gets
def name_get(self):
result = []
for res in self:
if res.pricelist_id:
name = u"{} ({})".format(
res.pms_board_service_id.name,
res.pricelist_id.name,
)
else:
name = u"{} ({})".format(res.pms_board_service_id.name, _("Generic"))
result.append((res.id, name))
return result
# Fields declaration
pms_board_service_id = fields.Many2one(
"pms.board.service",
string="Board Service",
index=True,
ondelete="cascade",
help="Board Service corresponding to this Board Service Room Type",
required=True,
index=True,
comodel_name="pms.board.service",
ondelete="cascade",
check_pms_properties=True,
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
required=False,
ondelete="restrict",
comodel_name="pms.property",
relation="pms_board_service_room_type_pms_property_rel",
column1="pms_board_service_room_type_id",
column2="pms_property_id",
check_pms_properties=True,
)
pms_room_type_id = fields.Many2one(
"pms.room.type",
string="Room Type",
index=True,
ondelete="cascade",
help="Room Type for which this Board Service is available",
required=True,
)
pricelist_id = fields.Many2one(
"product.pricelist", string="Pricelist", required=False
index=True,
comodel_name="pms.room.type",
ondelete="cascade",
check_pms_properties=True,
)
board_service_line_ids = fields.One2many(
"pms.board.service.room.type.line", "pms_board_service_room_type_id"
)
pms_property_id = fields.Many2one(
"pms.property",
)
price_type = fields.Selection(
[("fixed", "Fixed"), ("percent", "Percent")],
string="Type",
default="fixed",
string="Board Service Lines",
help="Services included in this Board Service",
comodel_name="pms.board.service.room.type.line",
inverse_name="pms_board_service_room_type_id",
required=True,
)
amount = fields.Float(
"Amount", digits=("Product Price"), compute="_compute_board_amount", store=True
string="Amount",
help="Price for this Board Service. "
"It corresponds to the sum of his board service lines",
store=True,
digits=("Product Price"),
compute="_compute_board_amount",
)
by_default = fields.Boolean(
string="Apply by Default",
help="Indicates if this board service is applied by default in the room type",
)
# Compute and Search methods
@api.depends("board_service_line_ids.amount")
def _compute_board_amount(self):
for record in self:
@@ -69,44 +70,22 @@ class PmsBoardServiceRoomType(models.Model):
total += service.amount
record.update({"amount": total})
# Constraints and onchanges
@api.constrains("pricelist_id")
def constrains_pricelist_id(self):
@api.constrains("by_default")
def constrains_duplicated_board_defaul(self):
for record in self:
if self.pricelist_id:
board_pricelist = self.env["pms.board.service.room.type"].search(
[
("pricelist_id", "=", record.pricelist_id.id),
("pms_room_type_id", "=", record.pms_room_type_id.id),
("pms_board_service_id", "=", record.pms_board_service_id.id),
("id", "!=", record.id),
]
)
if board_pricelist:
raise UserError(
_("This Board Service in this Room can't repeat pricelist")
)
else:
board_pricelist = self.env["pms.board.service.room.type"].search(
[
("pricelist_id", "=", False),
("pms_room_type_id", "=", record.pms_room_type_id.id),
("pms_board_service_id", "=", record.pms_board_service_id.id),
("id", "!=", record.id),
]
)
if board_pricelist:
raise UserError(
_(
"This Board Service in this Room \
can't repeat without pricelist"
default_boards = (
record.pms_room_type_id.board_service_room_type_ids.filtered(
"by_default"
)
)
# Action methods
# TODO Check properties (with different propertys is allowed)
if any(default_boards.filtered(lambda l: l.id != record.id)):
raise UserError(_("""Only can set one default board service"""))
def open_board_lines_form(self):
action = self.env.ref("pms.action_pms_board_service_room_type_view").read()[0]
action = (
self.env.ref("pms.action_pms_board_service_room_type_view").sudo().read()[0]
)
action["views"] = [
(self.env.ref("pms.pms_board_service_room_type_form").id, "form")
]
@@ -114,25 +93,42 @@ class PmsBoardServiceRoomType(models.Model):
action["target"] = "new"
return action
# ORM Overrides
def init(self):
self._cr.execute(
"SELECT indexname FROM pg_indexes WHERE indexname = %s",
("pms_board_service_id_pms_room_type_id_pricelist_id",),
("pms_board_service_id_pms_room_type_id",),
)
if not self._cr.fetchone():
self._cr.execute(
"CREATE INDEX pms_board_service_id_pms_room_type_id_pricelist_id \
"CREATE INDEX pms_board_service_id_pms_room_type_id \
ON pms_board_service_room_type_rel \
(pms_board_service_id, pms_room_type_id, pricelist_id)"
(pms_board_service_id, pms_room_type_id)"
)
@api.model
def create(self, vals):
properties = False
if "pms_board_service_id" in vals:
vals.update(
self.prepare_board_service_reservation_ids(vals["pms_board_service_id"])
)
board_service = self.env["pms.board.service"].browse(
vals["pms_board_service_id"]
)
properties = board_service.pms_property_ids
if "pms_room_type_id" in vals:
room_type = self.env["pms.room.type"].browse(vals["pms_room_type_id"])
properties = (
properties + room_type.pms_property_ids
if properties
else room_type.pms_property_ids
)
if properties:
vals.update(
{
"pms_property_ids": properties,
}
)
return super(PmsBoardServiceRoomType, self).create(vals)
def write(self, vals):
@@ -142,7 +138,6 @@ class PmsBoardServiceRoomType(models.Model):
)
return super(PmsBoardServiceRoomType, self).write(vals)
# Business methods
@api.model
def prepare_board_service_reservation_ids(self, board_service_id):
"""

View File

@@ -1,21 +1,73 @@
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
from odoo import api, fields, models
class PmsBoardServiceRoomTypeLine(models.Model):
_name = "pms.board.service.room.type.line"
_description = "Services on Board Service included in Room"
_check_pms_properties_auto = True
# Fields declaration
pms_board_service_room_type_id = fields.Many2one(
"pms.board.service.room.type",
"Board Service Room",
ondelete="cascade",
string="Board Service Room",
help="Board Service Room Type in which this line is included",
required=True,
comodel_name="pms.board.service.room.type",
ondelete="cascade",
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
relation="pms_board_service_room_type_line_pms_property_rel",
column1="pms_board_service_room_type_id",
column2="pms_property_id",
check_pms_properties=True,
)
product_id = fields.Many2one(
"product.product", "Product", required=True, readonly=True
string="Product",
help="Product associated with this board service room type line",
comodel_name="product.product",
readonly=True,
check_pms_properties=True,
)
# TODO def default_amount "amount of service"
amount = fields.Float("Amount", digits=("Product Price"), default=0.0)
amount = fields.Float(
string="Amount",
help="Price for this Board Service Room Type Line/Product",
default=0.0,
digits=("Product Price"),
)
@api.model
def create(self, vals):
properties = False
if "pms_board_service_room_type_id" in vals:
board_service = self.env["pms.board.service.room.type"].browse(
vals["pms_board_service_room_type_id"]
)
properties = board_service.pms_property_ids
if properties:
vals.update(
{
"pms_property_ids": properties,
}
)
return super(PmsBoardServiceRoomTypeLine, self).create(vals)
def write(self, vals):
properties = False
if "pms_board_service_room_type_id" in vals:
board_service = self.env["pms.board.service.room.type"].browse(
vals["pms_board_service_room_type_id"]
)
properties = board_service.pms_property_ids
if properties:
vals.update(
{
"pms_property_ids": properties,
}
)
return super(PmsBoardServiceRoomTypeLine, self).write(vals)

View File

@@ -4,36 +4,85 @@
from odoo import fields, models
# TODO: refactoring to cancellation.rule
class PmsCancelationRule(models.Model):
_name = "pms.cancelation.rule"
_description = "Cancelation Rules"
_check_pms_properties_auto = True
# Fields declaration
name = fields.Char("Amenity Name", translate=True, required=True)
name = fields.Char(
string="Cancelation Rule",
required=True,
translate=True,
)
pricelist_ids = fields.One2many(
"product.pricelist", "cancelation_rule_id", "Pricelist that use this rule"
string="Pricelist",
help="Pricelist that use this rule",
comodel_name="product.pricelist",
inverse_name="cancelation_rule_id",
check_pms_properties=True,
)
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
required=False,
comodel_name="pms.property",
relation="pms_cancelation_rule_pms_property_rel",
column1="pms_cancelation_rule_id",
column2="pms_property_id",
ondelete="restrict",
check_pms_properties=True,
)
active = fields.Boolean(
string="Active", help="Determines if cancelation rule is active", default=True
)
active = fields.Boolean("Active", default=True)
days_intime = fields.Integer(
"Days Late", help="Maximum number of days for free cancellation before Checkin"
string="Days Late",
help="Maximum number of days for free cancellation before Checkin",
)
penalty_late = fields.Integer(
string="% Penalty Late",
help="Percentage of the total price that partner has "
"to pay in case of late arrival",
default="100",
)
penalty_late = fields.Integer("% Penalty Late", default="100")
apply_on_late = fields.Selection(
[("first", "First Day"), ("all", "All Days"), ("days", "Specify days")],
"Late apply on",
string="Late apply on",
help="Days on which the cancelation rule applies when "
"the reason is late arrival. "
"Can be first, all days or specify the days.",
default="first",
selection=[
("first", "First Day"),
("all", "All Days"),
("days", "Specify days"),
],
)
days_late = fields.Integer(
string="Late first days",
help="Is number of days late in the cancelation rule "
"if the value of the apply_on_late field is specify days.",
default="2",
)
penalty_noshow = fields.Integer(
string="% Penalty No Show",
help="Percentage of the total price that partner has to pay in case of no show",
default="100",
)
days_late = fields.Integer("Late first days", default="2")
penalty_noshow = fields.Integer("% Penalty No Show", default="100")
apply_on_noshow = fields.Selection(
[("first", "First Day"), ("all", "All Days"), ("days", "Specify days")],
"No Show apply on",
string="No Show apply on",
help="Days on which the cancelation rule applies when"
" the reason is no show. Can be first, all days or specify the days.",
selection=[
("first", "First Day"),
("all", "All Days"),
("days", "Specify days"),
],
default="all",
)
days_noshow = fields.Integer("NoShow first days", default="2")
# TODO: Constrain coherence pms_property_ids pricelist and cancelation_rules
days_noshow = fields.Integer(
string="NoShow first days",
help="Is number of days no show in the cancelation rule "
"if the value of the apply_on_show field is specify days.",
default="2",
)

View File

@@ -2,6 +2,9 @@
# Copyright 2018 Alexandre Diaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import json
import re
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
@@ -9,43 +12,100 @@ from odoo.exceptions import ValidationError
class PmsCheckinPartner(models.Model):
_name = "pms.checkin.partner"
_description = "Partner Checkins"
_rec_name = "identifier"
_check_pms_properties_auto = True
@api.model
def _get_default_pms_property(self):
# TODO: Change by property env variable (like company)
return self.env.user.pms_property_id
# Fields declaration
identifier = fields.Char(
"Identifier",
compute="_compute_identifier",
readonly=False,
store=True,
string="Identifier",
help="Checkin Partner Id",
readonly=True,
index=True,
default=lambda self: _("New"),
)
partner_id = fields.Many2one(
"res.partner",
string="Partner",
help="Partner associated with checkin partner",
comodel_name="res.partner",
domain="[('is_company', '=', False)]",
)
reservation_id = fields.Many2one("pms.reservation")
reservation_id = fields.Many2one(
string="Reservation",
help="Reservation to which checkin partners belong",
comodel_name="pms.reservation",
check_pms_properties=True,
)
folio_id = fields.Many2one(
"pms.folio",
compute="_compute_folio_id",
string="Folio",
help="Folio to which reservation of checkin partner belongs",
store=True,
comodel_name="pms.folio",
compute="_compute_folio_id",
check_pms_properties=True,
)
pms_property_id = fields.Many2one(
"pms.property", default=_get_default_pms_property, required=True
string="Property",
help="Property to which the folio associated belongs",
readonly=True,
store=True,
comodel_name="pms.property",
related="folio_id.pms_property_id",
check_pms_properties=True,
)
name = fields.Char(
string="Name",
help="Checkin partner name",
readonly=False,
store=True,
compute="_compute_name",
)
email = fields.Char(
string="E-mail",
help="Checkin Partner Email",
readonly=False,
store=True,
compute="_compute_email",
)
mobile = fields.Char(
string="Mobile",
help="Checkin Partner Mobile",
compute="_compute_mobile",
store=True,
readonly=False,
)
image_128 = fields.Image(
string="Image",
help="Checkin Partner Image, it corresponds with Partner Image associated",
related="partner_id.image_128",
)
name = fields.Char("Name", related="partner_id.name")
email = fields.Char("E-mail", related="partner_id.email")
mobile = fields.Char("Mobile", related="partner_id.mobile")
image_128 = fields.Image(related="partner_id.image_128")
segmentation_ids = fields.Many2many(
string="Segmentation",
help="Segmentation tags to classify checkin partners",
related="reservation_id.segmentation_ids",
readonly=True,
)
arrival = fields.Datetime("Enter")
departure = fields.Datetime("Exit")
checkin = fields.Date(
string="Checkin",
help="Checkin date",
store=True,
related="reservation_id.checkin",
depends=["reservation_id.checkin"],
)
checkout = fields.Date(
string="Checkout",
help="Checkout date",
store=True,
related="reservation_id.checkout",
depends=["reservation_id.checkout"],
)
arrival = fields.Datetime("Enter", help="Checkin partner arrival date and time")
departure = fields.Datetime(
string="Exit", help="Checkin partner departure date and time"
)
state = fields.Selection(
string="State",
help="Status of the checkin partner regarding the reservation",
readonly=True,
store=True,
selection=[
("draft", "Unkown Guest"),
("precheckin", "Pending arrival"),
@@ -53,10 +113,7 @@ class PmsCheckinPartner(models.Model):
("done", "Out"),
("cancelled", "Cancelled"),
],
string="State",
compute="_compute_state",
store=True,
readonly=True,
)
# Compute
@@ -98,14 +155,28 @@ class PmsCheckinPartner(models.Model):
else:
record.state = "precheckin"
@api.model
def _checkin_mandatory_fields(self, depends=False):
# api.depends need "reservation_id.state" in de lambda function
if depends:
return ["reservation_id.state", "name"]
return ["name"]
@api.depends(
"partner_id",
"partner_id.name",
"reservation_id",
"reservation_id.preferred_room_id",
)
def _compute_name(self):
for record in self:
if not record.name:
record.name = record.partner_id.name
# Constraints and onchanges
@api.depends("partner_id", "partner_id.email")
def _compute_email(self):
for record in self:
if not record.email:
record.email = record.partner_id.email
@api.depends("partner_id", "partner_id.mobile")
def _compute_mobile(self):
for record in self:
if not record.mobile:
record.mobile = record.partner_id.mobile
@api.constrains("departure", "arrival")
def _check_departure(self):
@@ -129,23 +200,138 @@ class PmsCheckinPartner(models.Model):
_("This guest is already registered in the room")
)
# CRUD
@api.constrains("email")
def check_email_pattern(self):
for record in self:
if record.email:
if not re.search(
r"^[a-zA-Z0-9]([a-zA-z0-9\-\_]*[\.]?[a-zA-Z0-9\-\_]+)*"
r"@([a-zA-z0-9\-]+([\.][a-zA-Z0-9\-\_]+)?\.[a-zA-Z0-9]+)+$",
record.email,
):
raise ValidationError(_("'%s' is not a valid email", record.email))
@api.constrains("mobile")
def check_phone_pattern(self):
for record in self:
if record.mobile:
if not re.search(
r"^(\d{3}[\-\s]?\d{2}[\-\s]?\d{2}[\-\s]?\d{2}[\-\s]?|"
r"\d{3}[\-\s]?\d{3}[\-\s]?\d{3})$",
str(record.mobile),
):
raise ValidationError(_("'%s' is not a valid phone", record.mobile))
@api.model
def create(self, vals):
# The checkin records are created automatically from adult depends
# if you try to create one manually, we update one unassigned checkin
if not self._context.get("auto_create_checkin"):
reservation_id = vals.get("reservation_id")
if reservation_id:
reservation = self.env["pms.reservation"].browse(reservation_id)
else:
raise ValidationError(
_("Is mandatory indicate the reservation on the checkin")
)
draft_checkins = reservation.checkin_partner_ids.filtered(
lambda c: c.state == "draft"
)
if len(draft_checkins) > 0 and vals.get("partner_id"):
draft_checkins[0].sudo().unlink()
if len(reservation.checkin_partner_ids) < reservation.adults:
if vals.get("identifier", _("New")) == _("New") or "identifier" not in vals:
pms_property_id = (
self.env.user.get_active_property_ids()[0]
if "pms_property_id" not in vals
else vals["pms_property_id"]
)
pms_property = self.env["pms.property"].browse(pms_property_id)
vals["identifier"] = pms_property.folio_sequence_id._next_do()
return super(PmsCheckinPartner, self).create(vals)
if len(draft_checkins) > 0:
draft_checkins[0].write(vals)
return draft_checkins[0]
raise ValidationError(
_("Is not possible to create the proposed check-in in this reservation")
)
# Action methods
def write(self, vals):
res = super(PmsCheckinPartner, self).write(vals)
ResPartner = self.env["res.partner"]
if any(field in vals for field in ResPartner._get_key_fields()):
# Create Partner if get key field in the checkin
for record in self:
key = False
partner = False
if not record.partner_id:
partner_vals = {}
for field in self._checkin_partner_fields():
if getattr(record, field):
partner_vals[field] = getattr(record, field)
if field in ResPartner._get_key_fields() and partner_vals.get(
field
):
key = True
# REVIEW: if partner exist, we can merge?
partner = ResPartner.search(
[(field, "=", getattr(record, field))]
)
if key:
if not partner:
partner = ResPartner.create(partner_vals)
record.partner_id = partner
if any(field in vals for field in self._checkin_partner_fields()):
# Update partner when the checkin partner field is not set on the partner
for record in self:
if record.partner_id:
partner_vals = {}
for field in self._checkin_partner_fields():
if not getattr(record.partner_id, field):
partner_vals[field] = getattr(record, field)
record.partner_id.write(partner_vals)
return res
def unlink(self):
reservations = self.mapped("reservation_id")
res = super().unlink()
reservations._compute_checkin_partner_ids()
return res
@api.model
def _checkin_mandatory_fields(self, depends=False):
# api.depends need "reservation_id.state" in the lambda function
if depends:
return ["reservation_id.state", "name"]
return ["name"]
@api.model
def _checkin_partner_fields(self):
# api.depends need "reservation_id.state" in the lambda function
checkin_fields = self._checkin_mandatory_fields()
checkin_fields.extend(["mobile", "email"])
return checkin_fields
@api.model
def import_room_list_json(self, roomlist_json):
roomlist_json = json.loads(roomlist_json)
for checkin_dict in roomlist_json:
identifier = checkin_dict["identifier"]
reservation_id = checkin_dict["reservation_id"]
checkin = self.env["pms.checkin.partner"].search(
[("identifier", "=", identifier)]
)
reservation = self.env["pms.reservation"].browse(reservation_id)
if not checkin:
raise ValidationError(
_("%s not found in checkins (%s)"), identifier, reservation.name
)
checkin_vals = {}
for key, value in checkin_dict.items():
if key in ("reservation_id", "folio_id", "identifier"):
continue
checkin_vals[key] = value
checkin.write(checkin_vals)
def action_on_board(self):
for record in self:
@@ -153,12 +339,16 @@ class PmsCheckinPartner(models.Model):
raise ValidationError(_("It is not yet checkin day!"))
if record.reservation_id.checkout <= fields.Date.today():
raise ValidationError(_("Its too late to checkin"))
if any(
not getattr(record, field) for field in self._checkin_mandatory_fields()
):
raise ValidationError(_("Personal data is missing for check-in"))
vals = {
"state": "onboard",
"arrival": fields.Datetime.now(),
}
record.update(vals)
if record.reservation_id.left_for_checkin:
if record.reservation_id.allowed_checkin:
record.reservation_id.state = "onboard"
def action_done(self):

View File

@@ -1,17 +0,0 @@
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
class PmsFloor(models.Model):
_name = "pms.floor"
_description = "Ubication"
# Fields declaration
name = fields.Char(
"Ubication Name", translate=True, size=64, required=True, index=True
)
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
)
sequence = fields.Integer("Sequence")

File diff suppressed because it is too large Load Diff

View File

@@ -18,56 +18,77 @@ class PmsProperty(models.Model):
_inherits = {"res.partner": "partner_id"}
_check_company_auto = True
# Fields declaration
partner_id = fields.Many2one(
"res.partner", "Property", required=True, delegate=True, ondelete="cascade"
string="Property",
help="Current property",
comodel_name="res.partner",
required=True,
ondelete="cascade",
)
company_id = fields.Many2one(
"res.company",
required=True,
string="Company",
help="The company that owns or operates this property.",
comodel_name="res.company",
required=True,
check_pms_properties=True,
)
user_ids = fields.Many2many(
"res.users",
"pms_property_users_rel",
"pms_property_id",
"user_id",
string="Accepted Users",
help="Field related to res.users. Allowed users on the property",
comodel_name="res.users",
relation="pms_property_users_rel",
column1="pms_property_id",
column2="user_id",
)
room_ids = fields.One2many(
string="Rooms",
help="Rooms that a property has.",
comodel_name="pms.room",
inverse_name="pms_property_id",
)
room_ids = fields.One2many("pms.room", "pms_property_id", "Rooms")
default_pricelist_id = fields.Many2one(
"product.pricelist",
string="Product Pricelist",
required=True,
help="The default pricelist used in this property.",
)
default_restriction_id = fields.Many2one(
"pms.room.type.restriction",
"Restriction Plan",
comodel_name="product.pricelist",
required=True,
help="The default restriction plan used in this property.",
default=lambda self: self.env.ref("product.list0").id,
)
default_arrival_hour = fields.Char(
"Arrival Hour (GMT)", help="HH:mm Format", default="14:00"
string="Arrival Hour", help="HH:mm Format", default="14:00"
)
default_departure_hour = fields.Char(
"Departure Hour (GMT)", help="HH:mm Format", default="12:00"
string="Departure Hour", help="HH:mm Format", default="12:00"
)
default_cancel_policy_days = fields.Integer("Cancellation Days")
default_cancel_policy_percent = fields.Float("Percent to pay")
folio_sequence_id = fields.Many2one(
"ir.sequence", "Folio Sequence", check_company=True, copy=False
string="Folio Sequence",
help="The sequence that formed the name of the folio.",
check_company=True,
copy=False,
comodel_name="ir.sequence",
)
tz = fields.Selection(
_tz_get,
string="Timezone",
required=True,
default=lambda self: self.env.user.tz or "UTC",
help="This field is used in order to define \
in which timezone the arrival/departure will work.",
reservation_sequence_id = fields.Many2one(
string="Reservation Sequence",
help="The sequence that formed the name of the reservation.",
check_company=True,
copy=False,
comodel_name="ir.sequence",
)
checkin_sequence_id = fields.Many2one(
string="Checkin Sequence",
help="Field used to create the name of the checkin partner",
check_company=True,
copy=False,
comodel_name="ir.sequence",
)
tz = fields.Selection(
string="Timezone",
help="This field is used to determine de timezone of the property.",
required=True,
default=lambda self: self.env.user.tz or "UTC",
selection=_tz_get,
)
# Constraints and onchanges
@api.constrains("default_arrival_hour")
def _check_arrival_hour(self):
for record in self:
@@ -96,12 +117,75 @@ class PmsProperty(models.Model):
)
)
def date_property_timezone(self, date):
def date_property_timezone(self, dt):
self.ensure_one()
tz_property = self.tz
date = pytz.timezone(tz_property).localize(date)
date = date.replace(tzinfo=None)
date = pytz.timezone(self.env.user.tz).localize(date)
date = date.astimezone(pytz.utc)
date = date.replace(tzinfo=None)
return date
dt = pytz.timezone(tz_property).localize(dt)
dt = dt.replace(tzinfo=None)
dt = pytz.timezone(self.env.user.tz).localize(dt)
dt = dt.astimezone(pytz.utc)
dt = dt.replace(tzinfo=None)
return dt
def _get_payment_methods(self):
self.ensure_one()
payment_methods = self.env["account.journal"].search(
[
"&",
("type", "in", ["cash", "bank"]),
"|",
("pms_property_ids", "in", self.id),
"|",
"&",
("pms_property_ids", "=", False),
("company_id", "=", self.company_id.id),
"&",
("pms_property_ids", "=", False),
("company_id", "=", False),
]
)
return payment_methods
@api.model
def create(self, vals):
name = vals.get("name")
if "folio_sequence_id" not in vals or not vals.get("folio_sequence_id"):
folio_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Folio " + name,
"code": "pms.folio",
"prefix": "F/%(y)s",
"suffix": "%(sec)s",
"padding": 4,
"company_id": vals.get("company_id"),
}
)
vals.update({"folio_sequence_id": folio_sequence.id})
if "reservation_sequence_id" not in vals or not vals.get(
"reservation_sequence_id"
):
reservation_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Reservation " + name,
"code": "pms.reservation",
"prefix": "R/%(y)s",
"suffix": "%(sec)s",
"padding": 4,
"company_id": vals.get("company_id"),
}
)
vals.update({"reservation_sequence_id": reservation_sequence.id})
if "checkin_sequence_id" not in vals or not vals.get("checkin_sequence_id"):
checkin_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Checkin " + name,
"code": "pms.checkin.partner",
"prefix": "C/%(y)s",
"suffix": "%(sec)s",
"padding": 4,
"company_id": vals.get("company_id"),
}
)
vals.update({"checkin_sequence_id": checkin_sequence.id})
record = super(PmsProperty, self).create(vals)
return record

File diff suppressed because it is too large Load Diff

View File

@@ -14,71 +14,103 @@ class PmsReservationLine(models.Model):
_name = "pms.reservation.line"
_description = "Reservations by day"
_order = "date"
_check_company_auto = True
# Default Methods ang Gets
def name_get(self):
result = []
for res in self:
date = fields.Date.from_string(res.date)
name = u"{}/{}".format(date.day, date.month)
result.append((res.id, name))
return result
# Fields declaration
reservation_id = fields.Many2one(
"pms.reservation",
string="Reservation",
ondelete="cascade",
help="It is the reservation in a reservation line",
required=True,
copy=False,
comodel_name="pms.reservation",
ondelete="cascade",
check_pms_properties=True,
)
room_id = fields.Many2one(
"pms.room",
string="Room",
ondelete="restrict",
compute="_compute_room_id",
store=True,
help="The room of a reservation. ",
readonly=False,
store=True,
compute="_compute_room_id",
comodel_name="pms.room",
ondelete="restrict",
check_pms_properties=True,
)
move_line_ids = fields.Many2many(
"account.move.line",
"reservation_line_move_rel",
"reservation_line_id",
"move_line_id",
string="Invoice Lines",
sale_line_ids = fields.Many2many(
string="Sales Lines",
readonly=True,
copy=False,
comodel_name="folio.sale.line",
relation="reservation_line_sale_line_rel",
column1="reservation_line_id",
column2="sale_line_id",
check_pms_properties=True,
)
pms_property_id = fields.Many2one(
"pms.property",
store=True,
string="Property",
help="Property with access to the element;"
" if not set, all properties can access",
readonly=True,
store=True,
comodel_name="pms.property",
related="reservation_id.pms_property_id",
check_pms_properties=True,
)
date = fields.Date(
string="Date",
help="The date of the reservation in reservation line",
)
state = fields.Selection(
string="State",
help="State of the reservation line.",
related="reservation_id.state",
store=True,
)
date = fields.Date("Date")
state = fields.Selection(related="reservation_id.state")
price = fields.Float(
string="Price",
help="The price in a reservation line",
store=True,
readonly=False,
digits=("Product Price"),
compute="_compute_price",
store=True,
readonly=False,
)
cancel_discount = fields.Float(
string="Cancel Discount (%)",
digits=("Discount"),
default=0.0,
compute="_compute_cancel_discount",
store=True,
string="Cancelation Discount (%)",
help="",
readonly=False,
default=0.0,
store=True,
digits=("Discount"),
compute="_compute_cancel_discount",
)
avail_id = fields.Many2one(
string="Availability Day",
help="",
store=True,
comodel_name="pms.availability",
ondelete="restrict",
compute="_compute_avail_id",
check_pms_properties=True,
)
discount = fields.Float(
string="Discount (%)",
help="",
default=0.0,
digits=("Discount"),
)
discount = fields.Float(string="Discount (%)", digits=("Discount"), default=0.0)
occupies_availability = fields.Boolean(
string="Occupies",
compute="_compute_occupies_availability",
store=True,
help="This record is taken into account to calculate availability",
store=True,
compute="_compute_occupies_availability",
)
impacts_quota = fields.Integer(
string="Impacts quota",
help="This line has been taken into account in the avail quota",
readonly=False,
store=True,
compute="_compute_impact_quota",
)
_sql_constraints = [
@@ -90,66 +122,122 @@ class PmsReservationLine(models.Model):
),
]
# Compute and Search methods
@api.depends("reservation_id.room_type_id")
def name_get(self):
result = []
for res in self:
date = fields.Date.from_string(res.date)
name = u"{}/{}".format(date.day, date.month)
result.append((res.id, name))
return result
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)
@api.depends("reservation_id.room_type_id", "reservation_id.preferred_room_id")
def _compute_room_id(self):
for line in self.sorted(key=lambda r: (r.reservation_id, r.date)):
# if the reservation has a room type and no room id
if line.reservation_id.room_type_id and not line.room_id:
for line in self.filtered("reservation_id.room_type_id").sorted(
key=lambda r: (r.reservation_id, r.date)
):
reservation = line.reservation_id
if (
reservation.preferred_room_id
and reservation.preferred_room_id != line.room_id
) or (
(reservation.preferred_room_id or reservation.room_type_id)
and not line.room_id
):
free_room_select = True if reservation.preferred_room_id else False
# we get the rooms available for the entire stay
rooms_available = self.env[
"pms.room.type.availability"
].rooms_available(
rooms_available = self.env["pms.availability.plan"].rooms_available(
checkin=line.reservation_id.checkin,
checkout=line.reservation_id.checkout,
room_type_id=line.reservation_id.room_type_id.id,
current_lines=line._origin.reservation_id.reservation_line_ids.ids,
room_type_id=reservation.room_type_id.id
if not free_room_select
else False,
current_lines=line.reservation_id.reservation_line_ids.ids,
pricelist_id=line.reservation_id.pricelist_id.id,
pms_property_id=line.pms_property_id.id,
)
# if there is availability for the entire stay
if rooms_available:
# if the reservation has a preferred room
if line.reservation_id.preferred_room_id:
if reservation.preferred_room_id:
# if the preferred room is available
if line.reservation_id.preferred_room_id in rooms_available:
line.room_id = line.reservation_id.preferred_room_id
if reservation.preferred_room_id in rooms_available:
line.room_id = reservation.preferred_room_id
# if the preferred room is NOT available
else:
raise ValidationError(
_("%s: No room available.")
% (line.reservation_id.preferred_room_id.name)
% (reservation.preferred_room_id.name)
)
# otherwise we assign the first of those
# available for the entire stay
else:
line.room_id = rooms_available[0]
# check that the reservation cannot be allocated even by dividing it
elif not self.env["pms.availability.plan"].splitted_availability(
checkin=line.reservation_id.checkin,
checkout=line.reservation_id.checkout,
room_type_id=line.reservation_id.room_type_id.id,
current_lines=line._origin.reservation_id.reservation_line_ids.ids,
pricelist=line.reservation_id.pricelist_id,
pms_property_id=line.pms_property_id.id,
):
raise ValidationError(
_("%s: No room type available")
% (line.reservation_id.room_type_id.name)
)
# if there is no availability for the entire stay without
# changing rooms (we assume a split reservation)
# the reservation can be allocated into several rooms
else:
rooms_ranking = dict()
# we go through the rooms of the type
for room in self.env["pms.room"].search(
[("room_type_id", "=", line.reservation_id.room_type_id.id)]
[
("room_type_id", "=", reservation.room_type_id.id),
("pms_property_id", "=", reservation.pms_property_id.id),
]
):
# we iterate the dates from the date of the line to the checkout
for date_iterator in [
line.date + datetime.timedelta(days=x)
for x in range(
0, (line.reservation_id.checkout - line.date).days
)
for x in range(0, (reservation.checkout - line.date).days)
]:
# if the room is already assigned for
# a date we go to the next room
ids = line.reservation_id.reservation_line_ids.ids
ids = reservation.reservation_line_ids.ids
if (
self.env["pms.reservation.line"].search_count(
[
@@ -170,12 +258,8 @@ class PmsReservationLine(models.Model):
if room.id not in rooms_ranking
else rooms_ranking[room.id] + 1
)
if len(rooms_ranking) == 0:
raise ValidationError(
_("%s: No room type available")
% (line.reservation_id.room_type_id.name)
)
else:
if len(rooms_ranking) > 0:
# we get the best score in the ranking
best = max(rooms_ranking.values())
@@ -194,7 +278,7 @@ class PmsReservationLine(models.Model):
line_past_night = self.env["pms.reservation.line"].search(
[
("date", "=", date_last_night),
("reservation_id", "=", line.reservation_id.id),
("reservation_id", "=", reservation.id),
]
)
# if there is the night before and if the room
@@ -215,9 +299,19 @@ class PmsReservationLine(models.Model):
# no matter what it is
line.room_id = list(bests.keys())[0]
@api.depends("reservation_id.room_type_id", "reservation_id.pricelist_id")
def _compute_impact_quota(self):
for line in self:
reservation = line.reservation_id
line.impacts_quota = self.env["pms.availability.plan"].update_quota(
pricelist_id=reservation.pricelist_id,
room_type_id=reservation.room_type_id,
date=line.date,
line=line,
)
@api.depends(
"reservation_id",
"reservation_id.pricelist_id",
"reservation_id.room_type_id",
"reservation_id.reservation_type",
"reservation_id.pms_property_id",
@@ -231,7 +325,7 @@ class PmsReservationLine(models.Model):
or not reservation.pms_property_id
):
line.price = 0
elif line._recompute_price():
elif not line.price or self._context.get("force_recompute"):
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)
@@ -239,7 +333,8 @@ class PmsReservationLine(models.Model):
lang=partner.lang,
partner=partner.id,
quantity=1,
date=line.date,
date=line.reservation_id.date_order,
consumption_date=line.date,
pricelist=reservation.pricelist_id.id,
uom=product.uom_id.id,
property=reservation.pms_property_id.id,
@@ -251,8 +346,6 @@ class PmsReservationLine(models.Model):
line.reservation_id.company_id,
)
# TODO: Out of service 0 amount
else:
line.price = line._origin.price
@api.depends("reservation_id.state", "reservation_id.overbooking")
def _compute_occupies_availability(self):
@@ -265,38 +358,15 @@ class PmsReservationLine(models.Model):
else:
line.occupies_availability = True
def _recompute_price(self):
# REVIEW: Conditional to avoid overriding already calculated prices,
# I'm not sure it's the best way
self.ensure_one()
origin = self._origin.reservation_id
new = self.reservation_id
price_fields = [
"pricelist_id",
"room_type_id",
"reservation_type",
"pms_property_id",
]
if (
any(origin[field] != new[field] for field in price_fields)
or self._origin.price == 0
):
return True
return False
# 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
# TODO: Review cancel logic
# 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
@@ -344,6 +414,30 @@ class PmsReservationLine(models.Model):
# else:
# reservation.reservation_line_ids.update({"cancel_discount": 0})
@api.depends("room_id", "pms_property_id", "date", "occupies_availability")
def _compute_avail_id(self):
for record in self:
if record.room_id.room_type_id and record.date and record.pms_property_id:
avail = self.env["pms.availability"].search(
[
("date", "=", record.date),
("room_type_id", "=", record.room_id.room_type_id.id),
("pms_property_id", "=", record.pms_property_id.id),
]
)
if avail:
record.avail_id = avail.id
else:
record.avail_id = self.env["pms.availability"].create(
{
"date": record.date,
"room_type_id": record.room_id.room_type_id.id,
"pms_property_id": record.pms_property_id.id,
}
)
else:
record.avail_id = False
# Constraints and onchanges
@api.constrains("date")
def constrains_duplicated_date(self):
@@ -365,35 +459,6 @@ class PmsReservationLine(models.Model):
)
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)
@api.constrains("room_id")
def _check_adults(self):
for record in self.filtered("room_id"):

View File

@@ -15,46 +15,87 @@ class PmsRoom(models.Model):
_name = "pms.room"
_description = "Property Room"
_order = "sequence, room_type_id, name"
_check_pms_properties_auto = True
name = fields.Char(
string="Room Name",
help="Room Name",
required=True,
)
active = fields.Boolean(
string="Active", help="Determines if room is active", default=True
)
sequence = fields.Integer(
string="Sequence",
help="Field used to change the position of the rooms in tree view."
"Changing the position changes the sequence",
default=0,
)
pms_property_id = fields.Many2one(
string="Property",
help="Properties with access to the element;"
" if not set, all properties can access",
required=True,
default=lambda self: self.env.user.get_active_property_ids()[0],
comodel_name="pms.property",
ondelete="restrict",
)
room_type_id = fields.Many2one(
string="Property Room Type",
help="Unique room type for the rooms",
required=True,
comodel_name="pms.room.type",
ondelete="restrict",
check_pms_properties=True,
)
# TODO: design shared rooms
shared_room_id = fields.Many2one(
string="Shared Room",
help="The room can be sold by beds",
default=False,
comodel_name="pms.shared.room",
)
ubication_id = fields.Many2one(
string="Ubication",
help="At which ubication the room is located.",
comodel_name="pms.ubication",
check_pms_properties=True,
)
capacity = fields.Integer(
string="Capacity", help="The maximum number of people that can occupy a room"
)
extra_beds_allowed = fields.Integer(
string="Extra Beds Allowed",
help="Number of extra beds allowed in room",
required=True,
default="0",
)
description_sale = fields.Text(
string="Sale Description",
help="A description of the Product that you want to communicate to "
" your customers. This description will be copied to every Sales "
" Order, Delivery Order and Customer Invoice/Credit Note",
translate=True,
)
_sql_constraints = [
(
"room_property_unique",
"unique(name, pms_property_id)",
"you cannot have more than one room "
"with the same name in the same property",
)
]
# Defaults and Gets
def name_get(self):
result = []
for room in self:
name = room.name
if room.room_type_id:
name += " [%s]" % room.room_type_id.code_type
name += " [%s]" % room.room_type_id.default_code
result.append((room.id, name))
return result
# Fields declaration
name = fields.Char("Room Name", required=True)
pms_property_id = fields.Many2one(
"pms.property",
store=True,
readonly=True,
)
room_type_id = fields.Many2one(
"pms.room.type", "Property Room Type", required=True, ondelete="restrict"
)
shared_room_id = fields.Many2one("pms.shared.room", "Shared Room", default=False)
floor_id = fields.Many2one(
"pms.floor", "Ubication", help="At which floor the room is located."
)
capacity = fields.Integer("Capacity")
to_be_cleaned = fields.Boolean("To be Cleaned", default=False)
extra_beds_allowed = fields.Integer(
"Extra beds allowed", default="0", required=True
)
description_sale = fields.Text(
"Sale Description",
translate=True,
help="A description of the Product that you want to communicate to "
" your customers. This description will be copied to every Sales "
" Order, Delivery Order and Customer Invoice/Credit Note",
)
active = fields.Boolean("Active", default=True)
sequence = fields.Integer("Sequence", default=0)
# Constraints and onchanges
@api.constrains("capacity")
def _check_capacity(self):
@@ -70,6 +111,11 @@ class PmsRoom(models.Model):
# Business methods
def get_capacity(self, extra_bed=0):
if not self.shared_room_id:
return self.capacity + extra_bed
return self.capacity
for record in self:
if not record.shared_room_id:
if extra_bed > record.extra_beds_allowed:
raise ValidationError(
_("Extra beds can't be greater than allowed beds for this room")
)
return record.capacity + extra_bed
return record.capacity

View File

@@ -7,9 +7,24 @@ class RoomClosureReason(models.Model):
_name = "room.closure.reason"
_description = "Cause of out of service"
# Fields declaration
name = fields.Char("Name", translate=True, required=True)
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
name = fields.Char(
string="Name",
help="The name that identifies the room closure reason",
required=True,
translate=True,
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
relation="pms_room_closure_reason_pms_property_rel",
column1="room_closure_reason_type_id",
column2="pms_property_id",
ondelete="restrict",
)
description = fields.Text(
string="Description",
help="Explanation of the reason for closing a room",
translate=True,
)
description = fields.Text("Description", translate=True)

View File

@@ -1,5 +1,6 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# Copyright 2021 Eric Antones <eantones@nuobit.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
@@ -15,82 +16,180 @@ class PmsRoomType(models.Model):
_name = "pms.room.type"
_description = "Room Type"
_inherits = {"product.product": "product_id"}
_order = "sequence, code_type, name"
_order = "sequence,default_code,name"
_check_pms_properties_auto = True
# Fields declaration
sequence = fields.Integer(
string="Sequence",
help="Field used to change the position of the room types in tree view.",
default=0,
)
product_id = fields.Many2one(
"product.product",
"Product Room Type",
string="Product Room Type",
help="Product identifier associated with room type",
comodel_name="product.product",
required=True,
delegate=True,
ondelete="cascade",
)
pms_property_ids = fields.Many2many(
"pms.property",
"pms_property_room_type_rel",
"room_type_id",
"pms_property_id",
ondelete="restrict",
string="Properties",
room_ids = fields.One2many(
string="Rooms",
help="Rooms that belong to room type.",
comodel_name="pms.room",
inverse_name="room_type_id",
check_pms_properties=True,
)
class_id = fields.Many2one(
string="Property Type Class",
help="Class to which the room type belongs",
comodel_name="pms.room.type.class",
required=True,
check_pms_properties=True,
)
room_ids = fields.One2many("pms.room", "room_type_id", "Rooms")
class_id = fields.Many2one("pms.room.type.class", "Property Type Class")
board_service_room_type_ids = fields.One2many(
"pms.board.service.room.type", "pms_room_type_id", string="Board Services"
string="Board Services",
help="Board Service included in room type",
comodel_name="pms.board.service.room.type",
inverse_name="pms_room_type_id",
check_pms_properties=True,
)
room_amenity_ids = fields.Many2many(
"pms.amenity",
"pms_room_type_aminity_rel",
"room_type_ids",
"amenity_ids",
string="Room Type Amenities",
help="List of Amenities.",
help="List of amenities included in room type",
comodel_name="pms.amenity",
relation="pms_room_type_amenity_rel",
column1="room_type_id",
column2="amenity_id",
check_pms_properties=True,
)
code_type = fields.Char(
"Code",
default_code = fields.Char(
string="Code",
help="Identification code for a room type",
required=True,
)
shared_room = fields.Boolean(
"Shared Room", default=False, help="This room type is reservation by beds"
# TODO: Session review to define shared room and "sales rooms packs"
is_shared_room = fields.Boolean(
string="Shared Room",
help="This room type is reservation by beds",
default=False,
)
total_rooms_count = fields.Integer(
string="Total Rooms Count",
help="The number of rooms in a room type",
compute="_compute_total_rooms_count",
store=True,
)
total_rooms_count = fields.Integer(compute="_compute_total_rooms", store=True)
active = fields.Boolean("Active", default=True)
sequence = fields.Integer("Sequence", default=0)
default_max_avail = fields.Integer(
"Max. Availability",
default=-1,
string="Default Max. Availability",
help="Maximum simultaneous availability on own Booking Engine "
"given no availability rules. "
"Use `-1` for using maximum simultaneous availability.",
default=-1,
)
default_quota = fields.Integer(
"Default Quota",
default=-1,
string="Default Quota",
help="Quota assigned to the own Booking Engine given no availability rules. "
"Use `-1` for managing no quota.",
default=-1,
)
_sql_constraints = [
(
"code_type_pms_unique",
"unique(code_type)",
"Room Type Code must be unique",
),
]
def name_get(self):
result = []
for room_type in self:
name = room_type.name
if self._context.get("checkin") and self._context.get("checkout"):
avail = self.env["pms.availability.plan"].get_count_rooms_available(
checkin=self._context.get("checkin"),
checkout=self._context.get("checkout"),
room_type_id=room_type.id,
pms_property_id=self._context.get("pms_property_id") or False,
pricelist_id=self._context.get("pricelist_id") or False,
)
name += " (%s)" % avail
result.append((room_type.id, name))
return result
# Constraints and onchanges
@api.depends("room_ids", "room_ids.active")
def _compute_total_rooms(self):
def _compute_total_rooms_count(self):
for record in self:
record.total_rooms_count = len(record.room_ids)
@api.model
def get_room_types_by_property(self, pms_property_id, default_code=None):
"""
:param pms_property_id: property ID
:param default_code: room type code (optional)
:return: - recordset of
- all the pms.room.type of the pms_property_id
if default_code not defined
- one or 0 pms.room.type if default_code defined
- ValidationError if more than one default_code found by
the same pms_property_id
"""
domain = []
if default_code:
domain += ["&", ("default_code", "=", default_code)]
company_id = self.env["pms.property"].browse(pms_property_id).company_id.id
domain += [
"|",
("pms_property_ids", "in", pms_property_id),
"|",
"&",
("pms_property_ids", "=", False),
("company_id", "=", company_id),
"&",
("pms_property_ids", "=", False),
("company_id", "=", False),
]
records = self.search(domain)
res, res_priority = {}, {}
for rec in records:
res_priority.setdefault(rec.default_code, -1)
priority = (rec.pms_property_ids and 2) or (rec.company_id and 1 or 0)
if priority > res_priority[rec.default_code]:
res.setdefault(rec.default_code, rec.id)
res[rec.default_code], res_priority[rec.default_code] = rec.id, priority
elif priority == res_priority[rec.default_code]:
raise ValidationError(
_(
"Integrity error: There's multiple room types "
"with the same code %s and properties"
)
% rec.default_code
)
return self.browse(list(res.values()))
@api.constrains("default_code", "pms_property_ids", "company_id")
def _check_code_property_company_uniqueness(self):
msg = _("Already exists another room type with the same code and properties")
for rec in self:
if not rec.pms_property_ids:
if self.search(
[
("id", "!=", rec.id),
("default_code", "=", rec.default_code),
("pms_property_ids", "=", False),
("company_id", "=", rec.company_id.id),
]
):
raise ValidationError(msg)
else:
for pms_property in rec.pms_property_ids:
other = rec.get_room_types_by_property(
pms_property.id, rec.default_code
)
if other and other != rec:
raise ValidationError(msg)
# ORM Overrides
# TODO: Review Check product fields default values to room
@api.model
def create(self, vals):
""" Add room types as not purchase services. """
vals.update(
{
"purchase_ok": False,
"sale_ok": False,
"type": "service",
}
)
@@ -101,68 +200,7 @@ class PmsRoomType(models.Model):
record.product_id.unlink()
return super().unlink()
# Business methods
def get_capacity(self):
self.ensure_one()
capacities = self.room_ids.mapped("capacity")
return min(capacities) if any(capacities) else 0
@api.model
def get_rate_room_types(self, **kwargs):
"""
room_type_ids: Ids from room types to get rate, optional, if you
not use this param, the method return all room_types
from: Date from, mandatory
days: Number of days, mandatory
pricelist_id: Pricelist to use, optional
partner_id: Partner, optional
Return Dict Code Room Types: subdict with day, discount, price
"""
vals = {}
# room_type_ids = kwargs.get("room_type_ids", False)
# room_types = (
# self.env["pms.room.type"].browse(room_type_ids)
# if room_type_ids
# else self.env["pms.room.type"].search([])
# )
date_from = kwargs.get("date_from", False)
days = kwargs.get("days", False)
discount = kwargs.get("discount", False)
if not date_from or not days:
raise ValidationError(_("Date From and days are mandatory"))
partner_id = kwargs.get("partner_id", False)
# partner = self.env["res.partner"].browse(partner_id)
# pricelist_id = kwargs.get(
# "pricelist_id",
# partner.property_product_pricelist.id
# and partner.property_product_pricelist.id
# or self.env.user.pms_property_id.default_pricelist_id.id,
# )
vals.update(
{
"partner_id": partner_id if partner_id else False,
"discount": discount,
}
)
rate_vals = {}
# 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

@@ -1,92 +0,0 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# Copyright 2018 Pablo Quesada
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from datetime import timedelta
from odoo import api, fields, models
class PmsRoomTypeAvailability(models.Model):
_name = "pms.room.type.availability"
_description = "Availability"
_inherit = "mail.thread"
@api.model
def _default_max_avail(self):
return self.room_type_id.default_max_avail
@api.model
def _default_quota(self):
return self.room_type_id.default_quota
# Fields declaration
room_type_id = fields.Many2one(
"pms.room.type", "Room Type", required=True, ondelete="cascade"
)
date = fields.Date(
"Date",
required=True,
tracking=True,
)
quota = fields.Integer(
"Quota",
default=_default_quota,
tracking=True,
help="Generic Quota assigned.",
)
max_avail = fields.Integer(
"Max. Availability",
default=-1,
readonly=True,
tracking=True,
help="Maximum simultaneous availability on own Booking Engine.",
)
no_web = fields.Boolean(
"No Web",
default=False,
tracking=True,
help="Set zero availability to the own Booking Engine "
"even when the availability is positive,",
)
_sql_constraints = [
(
"unique_availability_room_type_rule_date",
"unique(room_type_id, date)",
"The availability rule for this date in this room type already exists, "
"modify it instead of trying to create a new one",
),
]
# Business Methods
@api.model
def rooms_available(
self, checkin, checkout, room_type_id=False, current_lines=False
):
domain = self._get_domain_reservations_occupation(
dfrom=checkin,
dto=checkout - timedelta(1),
current_lines=current_lines,
)
reservation_lines = self.env["pms.reservation.line"].search(domain)
reservations_rooms = reservation_lines.mapped("room_id.id")
free_rooms = self.env["pms.room"].search([("id", "not in", reservations_rooms)])
if room_type_id:
rooms_linked = (
self.env["pms.room.type"].search([("id", "=", room_type_id)]).room_ids
)
free_rooms = free_rooms & rooms_linked
return free_rooms.sorted(key=lambda r: r.sequence)
@api.model
def _get_domain_reservations_occupation(self, dfrom, dto, current_lines=False):
if current_lines and not isinstance(current_lines, list):
current_lines = [current_lines]
domain = [
("date", ">=", dfrom),
("date", "<=", dto),
("occupies_availability", "=", True),
("id", "not in", current_lines),
]
return domain

View File

@@ -1,7 +1,9 @@
# Copyright 2017 Alexandre Díaz
# Copyright 2017 Dario Lodeiros
# Copyright 2021 Eric Antones <eantones@nuobit.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import fields, models
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsRoomTypeClass(models.Model):
@@ -13,19 +15,108 @@ class PmsRoomTypeClass(models.Model):
_name = "pms.room.type.class"
_description = "Room Type Class"
_order = "sequence, name, code_class"
_order = "sequence, name, default_code"
_check_pms_properties_auto = True
# Fields declaration
name = fields.Char("Class Name", required=True, translate=True)
# Relationship between models
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
name = fields.Char(
string="Class Name",
help="Name of the room type class",
required=True,
translate=True,
)
active = fields.Boolean(
string="Active",
help="If unchecked, it will allow you to hide the room type",
default=True,
)
sequence = fields.Integer(
string="Sequence",
help="Field used to change the position of the room type classes in tree view.",
default=0,
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
relation="pms_room_type_class_property_rel",
column1="room_type_class_id",
column2="pms_property_id",
ondelete="restrict",
check_pms_properties=True,
)
room_type_ids = fields.One2many(
string="Types",
help="Room Types that belong to this Room Type Class",
comodel_name="pms.room.type",
inverse_name="class_id",
check_pms_properties=True,
)
default_code = fields.Char(
string="Code",
help="Room type class identification code",
required=True,
)
room_type_ids = fields.One2many("pms.room.type", "class_id", "Types")
code_class = fields.Char("Code")
active = fields.Boolean("Active", default=True)
sequence = fields.Integer("Sequence", default=0)
_sql_constraints = [
("code_class_unique", "unique(code_class)", "Room Class Code must be unique!")
@api.model
def get_unique_by_property_code(self, pms_property_id, default_code=None):
"""
:param pms_property_id: property ID
:param default_code: room type code (optional)
:return: - recordset of
- all the pms.room.type.class of the pms_property_id
if default_code not defined
- one or 0 pms.room.type.class if default_code defined
- ValidationError if more than one default_code found by
the same pms_property_id
"""
# TODO: similiar code as room.type -> unify
domain = []
if default_code:
domain += ["&", ("default_code", "=", default_code)]
domain += [
"|",
("pms_property_ids", "in", pms_property_id),
("pms_property_ids", "=", False),
]
records = self.search(domain)
res, res_priority = {}, {}
for rec in records:
res_priority.setdefault(rec.default_code, -1)
priority = rec.pms_property_ids and 1 or 0
if priority > res_priority[rec.default_code]:
res.setdefault(rec.default_code, rec.id)
res[rec.default_code], res_priority[rec.default_code] = rec.id, priority
elif priority == res_priority[rec.default_code]:
raise ValidationError(
_(
"Integrity error: There's multiple room types "
"with the same code %s and properties"
)
% rec.default_code
)
return self.browse(list(res.values()))
@api.constrains("default_code", "pms_property_ids")
def _check_code_property_uniqueness(self):
# TODO: similiar code as room.type -> unify
msg = _(
"Already exists another room type class with the same code and properties"
)
for rec in self:
if not rec.pms_property_ids:
if self.search(
[
("id", "!=", rec.id),
("default_code", "=", rec.default_code),
("pms_property_ids", "=", False),
]
):
raise ValidationError(msg)
else:
for pms_property in rec.pms_property_ids:
other = rec.get_unique_by_property_code(
pms_property.id, rec.default_code
)
if other and other != rec:
raise ValidationError(msg)

View File

@@ -1,37 +0,0 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
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."""
_name = "pms.room.type.restriction"
_description = "Reservation restriction plan"
# Default methods
@api.model
def _get_default_pms_property(self):
return self.env.user.pms_property_id or None
# Fields declaration
name = fields.Char("Restriction Plan Name", required=True)
pms_property_id = fields.Many2one(
"pms.property",
"Property",
ondelete="restrict",
default=_get_default_pms_property,
)
item_ids = fields.One2many(
"pms.room.type.restriction.item",
"restriction_id",
string="Restriction Items",
copy=True,
)
active = fields.Boolean(
"Active",
default=True,
help="If unchecked, it will allow you to hide the "
"restriction plan without removing it.",
)

View File

@@ -1,49 +0,0 @@
# Copyright 2017 Alexandre Díaz
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsRoomTypeRestrictionItem(models.Model):
_name = "pms.room.type.restriction.item"
_description = "Reservation restriction by day"
# Field Declarations
restriction_id = fields.Many2one(
"pms.room.type.restriction", "Restriction Plan", ondelete="cascade", index=True
)
room_type_id = fields.Many2one(
"pms.room.type", "Room Type", required=True, ondelete="cascade"
)
date = fields.Date("Date")
min_stay = fields.Integer("Min. Stay")
min_stay_arrival = fields.Integer("Min. Stay Arrival")
max_stay = fields.Integer("Max. Stay")
max_stay_arrival = fields.Integer("Max. Stay Arrival")
closed = fields.Boolean("Closed")
closed_departure = fields.Boolean("Closed Departure")
closed_arrival = fields.Boolean("Closed Arrival")
_sql_constraints = [
(
"room_type_registry_unique",
"unique(restriction_id, room_type_id, date)",
"Only can exists one restriction in the same \
day for the same room type!",
)
]
# Constraints and onchanges
@api.constrains("min_stay", "min_stay_arrival", "max_stay", "max_stay_arrival")
def _check_min_stay(self):
for record in self:
if record.min_stay < 0:
raise ValidationError(_("Min. Stay can't be less than zero"))
elif record.min_stay_arrival < 0:
raise ValidationError(_("Min. Stay Arrival can't be less than zero"))
elif record.max_stay < 0:
raise ValidationError(_("Max. Stay can't be less than zero"))
elif record.max_stay_arrival < 0:
raise ValidationError(_("Max. Stay Arrival can't be less than zero"))

View File

@@ -4,9 +4,37 @@ from odoo import fields, models
class PmsSaleChannel(models.Model):
_name = "pms.sale.channel"
_description = "Sales Channel"
_check_pms_properties_auto = True
# Fields declaration
name = fields.Text(string="Sale Channel Name")
name = fields.Text(string="Sale Channel Name", help="The name of the sale channel")
channel_type = fields.Selection(
[("direct", "Direct"), ("indirect", "Indirect")], string="Sale Channel Type"
string="Sale Channel Type",
help="Type of sale channel; it can be 'direct'(if there is"
"no intermediary) or 'indirect'(if there are"
"intermediaries between partner and property",
selection=[("direct", "Direct"), ("indirect", "Indirect")],
)
is_on_line = fields.Boolean(
string="On Line", help="Indicates if the sale channel is on-line"
)
product_pricelist_ids = fields.Many2many(
string="Pricelists",
help="Pricelists for a sale channel",
comodel_name="product.pricelist",
relation="pms_sale_channel_product_pricelist_rel",
column1="pms_sale_channel_id",
column2="product_pricelist_id",
check_pms_properties=True,
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
required=False,
ondelete="restrict",
comodel_name="pms.property",
relation="pms_sale_channel_pms_property_rel",
column1="pms_sale_channel_id",
column2="pms_property_id",
check_pms_properties=True,
)

View File

@@ -5,7 +5,6 @@ import logging
from datetime import timedelta
from odoo import _, api, fields, models
from odoo.tools import float_compare, float_is_zero
_logger = logging.getLogger(__name__)
@@ -13,156 +12,247 @@ _logger = logging.getLogger(__name__)
class PmsService(models.Model):
_name = "pms.service"
_description = "Services and its charges"
_check_pms_properties_auto = True
# Default methods
def name_get(self):
result = []
for rec in self:
name = []
name.append("{name}".format(name=rec.name))
if rec.reservation_id.name:
name.append("{name}".format(name=rec.reservation_id.name))
result.append((rec.id, ", ".join(name)))
return result
@api.model
def _default_reservation_id(self):
if self.env.context.get("reservation_ids"):
ids = [item[1] for item in self.env.context["reservation_ids"]]
return self.env["pms.reservation"].browse([(ids)], limit=1)
elif self.env.context.get("default_reservation_id"):
return self.env.context.get("default_reservation_id")
return False
@api.model
def _default_folio_id(self):
if "folio_id" in self._context:
return self._context["folio_id"]
return False
# Fields declaration
name = fields.Char(
"Service description",
compute="_compute_name",
store=True,
string="Service description",
help="Service description",
readonly=False,
store=True,
compute="_compute_name",
)
product_id = fields.Many2one(
"product.product", "Service", ondelete="restrict", required=True
string="Service",
help="Product associated with this service",
required=True,
comodel_name="product.product",
ondelete="restrict",
check_pms_properties=True,
)
folio_id = fields.Many2one(
"pms.folio", "Folio", ondelete="cascade", default=_default_folio_id
string="Folio",
help="Folio in which the service is included",
readonly=False,
store=True,
comodel_name="pms.folio",
compute="_compute_folio_id",
check_pms_properties=True,
)
sale_line_ids = fields.One2many(
string="Sale Lines",
help="",
copy=False,
comodel_name="folio.sale.line",
inverse_name="service_id",
check_pms_properties=True,
)
reservation_id = fields.Many2one(
"pms.reservation", "Room", default=_default_reservation_id
string="Room",
help="Reservation in which the service is included",
default=lambda self: self._default_reservation_id(),
comodel_name="pms.reservation",
ondelete="cascade",
check_pms_properties=True,
)
service_line_ids = fields.One2many(
"pms.service.line",
"service_id",
compute="_compute_service_line_ids",
store=True,
string="Service Lines",
help="Subservices included in this service",
readonly=False,
store=True,
comodel_name="pms.service.line",
inverse_name="service_id",
compute="_compute_service_line_ids",
check_pms_properties=True,
)
company_id = fields.Many2one(
related="folio_id.company_id", string="Company", store=True, readonly=True
string="Company",
help="Company to which the service belongs",
readonly=True,
store=True,
related="folio_id.company_id",
)
pms_property_id = fields.Many2one(
"pms.property", store=True, readonly=True, related="folio_id.pms_property_id"
string="Property",
help="Property to which the service belongs",
readonly=True,
store=True,
comodel_name="pms.property",
related="folio_id.pms_property_id",
check_pms_properties=True,
)
tax_ids = fields.Many2many(
"account.tax",
string="Taxes",
compute="_compute_tax_ids",
store=True,
help="Taxes applied in the service",
readonly=False,
store=True,
comodel_name="account.tax",
domain=["|", ("active", "=", False), ("active", "=", True)],
compute="_compute_tax_ids",
)
move_line_ids = fields.Many2many(
"account.move.line",
"service_line_move_rel",
"service_id",
"move_line_id",
string="move Lines",
copy=False,
analytic_tag_ids = fields.Many2many(
string="Analytic Tags",
help="",
comodel_name="account.analytic.tag",
)
analytic_tag_ids = fields.Many2many("account.analytic.tag", string="Analytic Tags")
currency_id = fields.Many2one(
related="folio_id.currency_id", store=True, string="Currency", readonly=True
)
sequence = fields.Integer(string="Sequence", default=10)
state = fields.Selection(related="folio_id.state")
per_day = fields.Boolean(related="product_id.per_day", related_sudo=True)
product_qty = fields.Integer(
"Quantity",
compute="_compute_product_qty",
string="Currency",
help="The currency used in relation to the folio",
readonly=True,
store=True,
readonly=False,
related="folio_id.currency_id",
)
sequence = fields.Integer(string="Sequence", help="", default=10)
state = fields.Selection(
string="State",
help="Service status, it corresponds with folio status",
related="folio_id.state",
)
per_day = fields.Boolean(
string="Per Day",
help="Indicates if service is sold by days",
related="product_id.per_day",
related_sudo=True,
)
product_qty = fields.Integer(
string="Quantity",
help="Number of services that were sold",
readonly=False,
store=True,
compute="_compute_product_qty",
)
is_board_service = fields.Boolean(
string="Is Board Service",
help="Indicates if the service is part of a board service",
)
is_board_service = fields.Boolean()
# Non-stored related field to allow portal user to
# see the image of the product he has ordered
product_image = fields.Binary(
"Product Image", related="product_id.image_1024", store=False, related_sudo=True
string="Product Image",
help="Image of the service",
store=False,
related="product_id.image_1024",
related_sudo=True,
)
invoice_status = fields.Selection(
[
string="Invoice Status",
help="State in which the service is with respect to invoices."
"It can be 'invoiced', 'to_invoice' or 'no'",
readonly=True,
default="no",
store=True,
compute="_compute_invoice_status",
selection=[
("invoiced", "Fully Invoiced"),
("to invoice", "To Invoice"),
("no", "Nothing to Invoice"),
],
string="Invoice Status",
compute="_compute_invoice_status",
store=True,
readonly=True,
default="no",
)
channel_type = fields.Selection(
[
string="Sales Channel",
help="sales channel through which the service was sold."
"It can be 'door', 'mail', 'phone', 'call' or 'web'",
selection=[
("door", "Door"),
("mail", "Mail"),
("phone", "Phone"),
("call", "Call Center"),
("web", "Web"),
],
string="Sales Channel",
)
price_unit = fields.Float(
"Unit Price",
digits=("Product Price"),
compute="_compute_price_unit",
store=True,
readonly=False,
)
discount = fields.Float(string="Discount (%)", digits=("Discount"), default=0.0)
qty_to_invoice = fields.Float(
compute="_compute_get_to_invoice_qty",
string="To Invoice",
store=True,
readonly=True,
digits=("Product Unit of Measure"),
)
qty_invoiced = fields.Float(
compute="_compute_get_invoice_qty",
string="Invoiced",
store=True,
readonly=True,
digits=("Product Unit of Measure"),
)
price_subtotal = fields.Monetary(
string="Subtotal", readonly=True, store=True, compute="_compute_amount_service"
string="Subtotal",
help="Subtotal price without taxes",
readonly=True,
store=True,
compute="_compute_amount_service",
)
price_total = fields.Monetary(
string="Total", readonly=True, store=True, compute="_compute_amount_service"
string="Total",
help="Total price without taxes",
readonly=True,
store=True,
compute="_compute_amount_service",
)
price_tax = fields.Float(
string="Taxes Amount",
help="Total of taxes in service",
readonly=True,
store=True,
compute="_compute_amount_service",
)
# Compute and Search methods
@api.depends("product_id")
def _compute_tax_ids(self):
for service in self:
service.tax_ids = service.product_id.taxes_id.filtered(
lambda r: not service.company_id or r.company_id == service.company_id
)
@api.depends("service_line_ids", "service_line_ids.day_qty")
def _compute_product_qty(self):
self.product_qty = 0
for service in self.filtered("service_line_ids"):
qty = sum(service.service_line_ids.mapped("day_qty"))
service.product_qty = qty
@api.depends("reservation_id", "reservation_id.folio_id")
def _compute_folio_id(self):
for record in self:
if record.reservation_id:
record.folio_id = record.reservation_id.folio_id
elif not record.folio_id:
record.folio_id = False
@api.depends(
"sale_line_ids",
"sale_line_ids.invoice_status",
)
def _compute_invoice_status(self):
"""
Compute the invoice status of a Reservation. Possible statuses:
Base on folio sale line invoice status
"""
for line in self:
states = list(set(line.sale_line_ids.mapped("invoice_status")))
if len(states) == 1:
line.invoice_status = states[0]
elif len(states) >= 1:
if "to_invoice" in states:
line.invoice_status = "to_invoice"
elif "invoiced" in states:
line.invoice_status = "invoiced"
else:
line.invoice_status = "no"
else:
line.invoice_status = "no"
@api.depends("service_line_ids.price_day_total")
def _compute_amount_service(self):
for service in self:
if service.service_line_ids:
service.update(
{
"price_tax": sum(
service.service_line_ids.mapped("price_day_tax")
),
"price_total": sum(
service.service_line_ids.mapped("price_day_total")
),
"price_subtotal": sum(
service.service_line_ids.mapped("price_day_subtotal")
),
}
)
else:
service.update(
{
"price_tax": 0,
"price_total": 0,
"price_subtotal": 0,
}
)
@api.depends("product_id")
def _compute_name(self):
self.name = False
@@ -188,7 +278,12 @@ class PmsService(models.Model):
name += "\n" + product.description_sale
service.name = name
@api.depends("reservation_id.checkin", "reservation_id.checkout", "product_id")
@api.depends(
"reservation_id.checkin",
"reservation_id.checkout",
"product_id",
"reservation_id.adults",
)
def _compute_service_line_ids(self):
for service in self:
if service.product_id:
@@ -205,13 +300,21 @@ class PmsService(models.Model):
if consumed_on == "after":
i += 1
idate = reservation.checkin + timedelta(days=i)
old_line = service._search_old_lines(idate)
if idate in [
line.date for line in service.service_line_ids
]:
# REVIEW: If the date is already
# cached (otherwise double the date)
pass
old_line = service.service_line_ids.filtered(
lambda r: r.date == idate
)
price_unit = service._get_price_unit_line(idate)
if old_line and old_line.auto_qty:
lines.append(
(
1,
old_line.id,
{
"day_qty": day_qty,
"auto_qty": True,
},
)
)
elif not old_line:
lines.append(
(
@@ -220,11 +323,11 @@ class PmsService(models.Model):
{
"date": idate,
"day_qty": day_qty,
"auto_qty": True,
"price_unit": price_unit,
},
)
)
else:
lines.append((4, old_line.id))
move_day = 0
if consumed_on == "after":
move_day = 1
@@ -245,12 +348,10 @@ class PmsService(models.Model):
]
)
)
_logger.info(service)
_logger.info(lines)
service.service_line_ids = lines
else:
# TODO: Review (business logic refact) no per_day logic service
if not service.service_line_ids:
price_unit = service._get_price_unit_line()
service.service_line_ids = [
(
0,
@@ -258,13 +359,13 @@ class PmsService(models.Model):
{
"date": fields.Date.today(),
"day_qty": day_qty,
"price_unit": price_unit,
},
)
]
else:
# TODO: Service without reservation(room) but with folio¿?
# example: tourist tour in group
if not service.service_line_ids:
price_unit = service._get_price_unit_line()
service.service_line_ids = [
(
0,
@@ -272,231 +373,37 @@ class PmsService(models.Model):
{
"date": fields.Date.today(),
"day_qty": day_qty,
"price_unit": price_unit,
},
)
]
else:
service.service_line_ids = False
def _search_old_lines(self, date):
self.ensure_one()
if isinstance(self._origin.id, int):
old_line = self._origin.service_line_ids.filtered(lambda r: r.date == date)
return old_line
# Default methods
def name_get(self):
result = []
for rec in self:
name = []
name.append("{name}".format(name=rec.name))
if rec.reservation_id.name:
name.append("{name}".format(name=rec.reservation_id.name))
result.append((rec.id, ", ".join(name)))
return result
@api.model
def _default_reservation_id(self):
if self.env.context.get("reservation_ids"):
ids = [item[1] for item in self.env.context["reservation_ids"]]
return self.env["pms.reservation"].browse([(ids)], limit=1)
elif self.env.context.get("default_reservation_id"):
return self.env.context.get("default_reservation_id")
return False
@api.depends("product_id")
def _compute_tax_ids(self):
for service in self:
service.tax_ids = service.product_id.taxes_id.filtered(
lambda r: not service.company_id or r.company_id == service.company_id
)
@api.depends("service_line_ids", "service_line_ids.day_qty")
def _compute_product_qty(self):
self.product_qty = 0
for service in self.filtered("service_line_ids"):
qty = sum(service.service_line_ids.mapped("day_qty"))
service.product_qty = qty
@api.depends(
"product_id",
"service_line_ids",
"reservation_id.pricelist_id",
"reservation_id.pms_property_id",
"pms_property_id",
)
def _compute_price_unit(self):
for service in self:
folio = service.folio_id
reservation = service.reservation_id
origin = reservation if reservation else folio
if origin:
if service._recompute_price():
partner = origin.partner_id
pricelist = origin.pricelist_id
if reservation and service.is_board_service:
board_room_type = reservation.board_service_room_id
if board_room_type.price_type == "fixed":
service.price_unit = (
self.env["pms.board.service.room.type.line"]
.search(
[
(
"pms_board_service_room_type_id",
"=",
board_room_type.id,
),
("product_id", "=", service.product_id.id),
]
)
.amount
)
else:
service.price_unit = (
reservation.price_total
* self.env["pms.board.service.room.type.line"]
.search(
[
(
"pms_board_service_room_type_id",
"=",
board_room_type.id,
),
("product_id", "=", service.product_id.id),
]
)
.amount
) / 100
else:
product = service.product_id.with_context(
lang=partner.lang,
partner=partner.id,
quantity=service.product_qty,
date=folio.date_order if folio else fields.Date.today(),
pricelist=pricelist.id,
uom=service.product_id.uom_id.id,
fiscal_position=False,
property=service.pms_property_id.id,
)
service.price_unit = self.env[
"account.tax"
]._fix_tax_included_price_company(
service._get_display_price(product),
product.taxes_id,
service.tax_ids,
origin.company_id,
)
else:
service.price_unit = service._origin.price_unit
else:
service.price_unit = 0
def _recompute_price(self):
# REVIEW: Conditional to avoid overriding already calculated prices,
# I'm not sure it's the best way
self.ensure_one()
# folio/reservation origin service
folio_origin = self._origin.folio_id
reservation_origin = self._origin.reservation_id
origin = reservation_origin if reservation_origin else folio_origin
# folio/reservation new service
folio_new = self.folio_id
reservation_new = self.reservation_id
new = reservation_new if reservation_new else folio_new
price_fields = [
"pricelist_id",
"reservation_type",
"pms_property_id",
]
if (
any(origin[field] != new[field] for field in price_fields)
or self._origin.price_unit == 0
):
return True
return False
@api.depends("qty_invoiced", "product_qty", "folio_id.state")
def _compute_get_to_invoice_qty(self):
"""
Compute the quantity to invoice. If the invoice policy is order,
the quantity to invoice is calculated from the ordered quantity.
Otherwise, the quantity delivered is used.
"""
for line in self:
if line.folio_id.state not in ["draft"]:
line.qty_to_invoice = line.product_qty - line.qty_invoiced
else:
line.qty_to_invoice = 0
@api.depends("move_line_ids.move_id.state", "move_line_ids.quantity")
def _compute_get_invoice_qty(self):
"""
Compute the quantity invoiced. If case of a refund,
the quantity invoiced is decreased. Note that this is the case only
if the refund is generated from the Folio and that is intentional: if
a refund made would automatically decrease the invoiced quantity,
then there is a risk of reinvoicing it automatically, which may
not be wanted at all. That's why the refund has to be
created from the Folio
"""
for line in self:
qty_invoiced = 0.0
for invoice_line in line.move_line_ids:
if invoice_line.move_id.state != "cancel":
if invoice_line.move_id.type == "out_invoice":
qty_invoiced += invoice_line.uom_id._compute_quantity(
invoice_line.quantity, line.product_id.uom_id
)
elif invoice_line.move_id.type == "out_refund":
qty_invoiced -= invoice_line.uom_id._compute_quantity(
invoice_line.quantity, line.product_id.uom_id
)
line.qty_invoiced = qty_invoiced
@api.depends("product_qty", "qty_to_invoice", "qty_invoiced")
def _compute_invoice_status(self):
"""
Compute the invoice status of a SO line. Possible statuses:
- no: if the SO is not in status 'sale' or 'done',
we consider that there is nothing to invoice.
This is also hte default value if the conditions of no other
status is met.
- to invoice: we refer to the quantity to invoice of the line.
Refer to method `_compute_get_to_invoice_qty()` for more information on
how this quantity is calculated.
- upselling: this is possible only for a product invoiced on ordered
quantities for which we delivered more than expected.
The could arise if, for example, a project took more time than
expected but we decided not to invoice the extra cost to the
client. This occurs onyl in state 'sale', so that when a Folio
is set to done, the upselling opportunity is removed from the list.
- invoiced: the quantity invoiced is larger or equal to the
quantity ordered.
"""
precision = self.env["decimal.precision"].precision_get(
"Product Unit of Measure"
)
for line in self:
state = line.folio_id.state or "draft"
if state == "draft":
line.invoice_status = "no"
elif not float_is_zero(line.qty_to_invoice, precision_digits=precision):
line.invoice_status = "to invoice"
elif (
float_compare(
line.qty_invoiced, line.product_qty, precision_digits=precision
)
>= 0
):
line.invoice_status = "invoiced"
else:
line.invoice_status = "no"
@api.depends("product_qty", "discount", "price_unit", "tax_ids")
def _compute_amount_service(self):
for service in self:
folio = service.folio_id
reservation = service.reservation_id
currency = folio.currency_id if folio else reservation.currency_id
product = service.product_id
price = service.price_unit * (1 - (service.discount or 0.0) * 0.01)
taxes = service.tax_ids.compute_all(
price, currency, service.product_qty, product=product
)
service.update(
{
"price_tax": sum(
t.get("amount", 0.0) for t in taxes.get("taxes", [])
),
"price_total": taxes["total_included"],
"price_subtotal": taxes["total_excluded"],
}
)
# Action methods
def open_service_ids(self):
action = self.env.ref("pms.action_pms_services_form").read()[0]
action = self.env.ref("pms.action_pms_services_form").sudo().read()[0]
action["views"] = [(self.env.ref("pms.pms_service_view_form").id, "form")]
action["res_id"] = self.id
action["target"] = "new"
@@ -522,20 +429,12 @@ class PmsService(models.Model):
reservation = self.reservation_id
origin = folio if folio else reservation
if origin.pricelist_id.discount_policy == "with_discount":
return product.with_context(pricelist=origin.pricelist_id.id).price
product_context = dict(
self.env.context,
partner_id=origin.partner_id.id,
date=folio.date_order if folio else fields.Date.today(),
uom=self.product_id.uom_id.id,
)
return product.price
final_price, rule_id = origin.pricelist_id.with_context(
product_context
).get_product_price_rule(
self.product_id, self.product_qty or 1.0, origin.partner_id
)
product._context
).get_product_price_rule(product, self.product_qty or 1.0, origin.partner_id)
base_price, currency_id = self.with_context(
product_context
product._context
)._get_real_price_currency(
product,
rule_id,
@@ -547,12 +446,74 @@ class PmsService(models.Model):
base_price = (
self.env["res.currency"]
.browse(currency_id)
.with_context(product_context)
.with_context(product._context)
.compute(base_price, origin.pricelist_id.currency_id)
)
# negative discounts (= surcharge) are included in the display price
return max(base_price, final_price)
def _get_real_price_currency(self, product, rule_id, qty, uom, pricelist_id):
"""Retrieve the price before applying the pricelist
:param obj product: object of current product record
:parem float qty: total quantity of product
:param tuple price_and_rule: tuple(price, suitable_rule)
coming from pricelist computation
:param obj uom: unit of measure of current order line
:param integer pricelist_id: pricelist id of sales order"""
PricelistItem = self.env["product.pricelist.item"]
field_name = "lst_price"
currency_id = None
product_currency = product.currency_id
if rule_id:
pricelist_item = PricelistItem.browse(rule_id)
if pricelist_item.pricelist_id.discount_policy == "without_discount":
while (
pricelist_item.base == "pricelist"
and pricelist_item.base_pricelist_id
and pricelist_item.base_pricelist_id.discount_policy
== "without_discount"
):
price, rule_id = pricelist_item.base_pricelist_id.with_context(
uom=uom.id
).get_product_price_rule(product, qty, self.order_id.partner_id)
pricelist_item = PricelistItem.browse(rule_id)
if pricelist_item.base == "standard_price":
field_name = "standard_price"
product_currency = product.cost_currency_id
elif (
pricelist_item.base == "pricelist" and pricelist_item.base_pricelist_id
):
field_name = "price"
product = product.with_context(
pricelist=pricelist_item.base_pricelist_id.id
)
product_currency = pricelist_item.base_pricelist_id.currency_id
currency_id = pricelist_item.pricelist_id.currency_id
if not currency_id:
currency_id = product_currency
cur_factor = 1.0
else:
if currency_id.id == product_currency.id:
cur_factor = 1.0
else:
cur_factor = currency_id._get_conversion_rate(
product_currency,
currency_id,
self.company_id or self.env.company,
self.folio_id.date_order or fields.Date.today(),
)
product_uom = self.env.context.get("uom") or product.uom_id.id
if uom and uom.id != product_uom:
# the unit price is in a different uom
uom_factor = uom._compute_price(1.0, product.uom_id)
else:
uom_factor = 1.0
return product[field_name] * uom_factor * cur_factor, currency_id
# Businness Methods
def _service_day_qty(self):
self.ensure_one()
@@ -563,3 +524,38 @@ class PmsService(models.Model):
if self.product_id.per_person:
qty = self.reservation_id.adults
return qty
def _get_price_unit_line(self, date=False):
self.ensure_one()
folio = self.folio_id
reservation = self.reservation_id
origin = reservation if reservation else folio
if origin:
partner = origin.partner_id
pricelist = origin.pricelist_id
board_room_type = False
product_context = dict(
self.env.context,
lang=partner.lang,
partner=partner.id,
quantity=self.product_qty,
date=folio.date_order if folio else fields.Date.today(),
pricelist=pricelist.id,
board_service=board_room_type.id if board_room_type else False,
uom=self.product_id.uom_id.id,
fiscal_position=False,
property=self.pms_property_id.id,
)
if date:
product_context["date_overnight"] = date
if reservation and self.is_board_service:
product_context["board_service"] = reservation.board_service_room_id.id
product = self.product_id.with_context(product_context)
return self.env["account.tax"]._fix_tax_included_price_company(
self._get_display_price(product),
product.taxes_id,
self.tax_ids,
origin.company_id,
)
else:
return 0

View File

@@ -9,53 +9,230 @@ class PmsServiceLine(models.Model):
_name = "pms.service.line"
_description = "Service by day"
_order = "date"
_rec_name = "service_id"
_check_pms_properties_auto = True
# Fields declaration
service_id = fields.Many2one(
"pms.service",
string="Service Room",
ondelete="cascade",
help="Service identifier",
required=True,
copy=False,
comodel_name="pms.service",
ondelete="cascade",
)
is_board_service = fields.Boolean(
string="Is Board Service",
help="Indicates if the service line is part of a board service",
store=True,
related="service_id.is_board_service",
)
product_id = fields.Many2one(
string="Product",
help="Product associated with this service line",
store=True,
related="service_id.product_id",
check_pms_properties=True,
)
product_id = fields.Many2one(related="service_id.product_id", store=True)
tax_ids = fields.Many2many(
"account.tax", string="Taxes", related="service_id.tax_ids", readonly="True"
string="Taxes",
help="Taxes applied in the service line",
readonly="True",
comodel_name="account.tax",
related="service_id.tax_ids",
)
pms_property_id = fields.Many2one(
"pms.property", store=True, readonly=True, related="service_id.pms_property_id"
string="Property",
help="Property to which the service belongs",
readonly=True,
store=True,
comodel_name="pms.property",
related="service_id.pms_property_id",
check_pms_properties=True,
)
date = fields.Date("Date")
day_qty = fields.Integer("Units")
price_total = fields.Float(
"Price Total", compute="_compute_price_total", store=True
date = fields.Date(
string="Date",
help="Sate on which the product is to be consumed",
)
day_qty = fields.Integer(
string="Units",
help="Amount to be consumed per day",
)
price_unit = fields.Float(
"Unit Price", related="service_id.price_unit", readonly=True, store=True
string="Unit Price",
help="Price per unit of service",
digits=("Product Price"),
)
room_id = fields.Many2one(
string="Room", related="service_id.reservation_id", readonly=True, store=True
price_day_subtotal = fields.Monetary(
string="Subtotal",
help="Subtotal price without taxes",
readonly=True,
store=True,
compute="_compute_day_amount_service",
)
price_day_total = fields.Monetary(
string="Total",
help="Total price without taxes",
readonly=True,
store=True,
compute="_compute_day_amount_service",
)
price_day_tax = fields.Float(
string="Taxes Amount",
help="",
readonly=True,
store=True,
compute="_compute_day_amount_service",
)
currency_id = fields.Many2one(
string="Currency",
help="The currency used in relation to the service where it's included",
readonly=True,
store=True,
related="service_id.currency_id",
)
reservation_id = fields.Many2one(
string="Reservation",
help="Room to which the services will be applied",
readonly=True,
store=True,
related="service_id.reservation_id",
check_pms_properties=True,
)
discount = fields.Float(
"Discount", related="service_id.discount", readonly=True, store=True
string="Discount (%)",
help="Discount in the price of the service.",
readonly=False,
store=True,
default=0.0,
digits=("Discount"),
compute="_compute_discount",
)
cancel_discount = fields.Float(
"Discount cancel", compute="_compute_cancel_discount"
string="Cancelation Discount",
help="",
compute="_compute_cancel_discount",
readonly=True,
store=True,
)
auto_qty = fields.Boolean(
string="Qty automated setted",
help="Show if the day qty was calculated automatically",
compute="_compute_auto_qty",
readonly=False,
store=True,
)
# Compute and Search methods
@api.depends("day_qty", "service_id.price_total")
def _compute_price_total(self):
@api.depends("day_qty", "discount", "price_unit", "tax_ids")
def _compute_day_amount_service(self):
for line in self:
amount_service = line.price_unit
if amount_service > 0:
currency = line.service_id.currency_id
product = line.product_id
price = amount_service * (1 - (line.discount or 0.0) * 0.01)
# REVIEW: line.day_qty is not the total qty (the total is on service_id)
taxes = line.tax_ids.compute_all(
price, currency, line.day_qty, product=product
)
line.update(
{
"price_day_tax": sum(
t.get("amount", 0.0) for t in taxes.get("taxes", [])
),
"price_day_total": taxes["total_included"],
"price_day_subtotal": taxes["total_excluded"],
}
)
else:
line.update(
{
"price_day_tax": 0,
"price_day_total": 0,
"price_day_subtotal": 0,
}
)
@api.depends("service_id.reservation_id", "service_id.reservation_id.discount")
def _compute_discount(self):
"""
Used to reports
On board service the line discount is always
equal to reservation line discount
"""
for record in self:
if record.service_id.product_qty != 0:
record.price_total = (
record.service_id.price_total * record.day_qty
) / record.service_id.product_qty
else:
record.price_total = 0
if record.is_board_service:
record.discount = (
record.service_id.reservation_id.reservation_line_ids.filtered(
lambda l: l.date == record.date
).discount
)
elif not record.discount:
record.discount = 0
# TODO: Refact method and allowed cancelled single days
@api.depends("service_id.reservation_id.cancelled_reason")
def _compute_cancel_discount(self):
for line in self:
line.cancel_discount = 0
# TODO: Review cancel logic
# reservation = line.reservation_id.reservation_id
# pricelist = reservation.pricelist_id
# if reservation.state == "cancelled":
# if (
# reservation.cancelled_reason
# and pricelist
# and pricelist.cancelation_rule_id
# ):
# date_start_dt = fields.Date.from_string(
# reservation.checkin
# )
# date_end_dt = fields.Date.from_string(
# 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.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})
@api.depends("day_qty")
def _compute_auto_qty(self):
"""
Set auto_qty = False if the service is no linked to room or
if the day_qty was set manually
(See autogeneration of service lines in
_compute_service_line_ids -pms.service-)
"""
self.auto_qty = False
# Constraints and onchanges
@api.constrains("day_qty")

View File

@@ -10,43 +10,63 @@ class PmsSharedRoom(models.Model):
_name = "pms.shared.room"
_description = "Shared Room"
_order = "room_type_id, name"
_check_pms_properties_auto = True
# Fields declaration
name = fields.Char("Room Name", required=True)
name = fields.Char(
string="Room Name", help="Name of the shared room", required=True
)
active = fields.Boolean(
string="Active", help="Determines if shared room is active", default=True
)
sequence = fields.Integer(
string="Sequence",
help="Field used to change the position of the shared rooms in tree view."
"Changing the position changes the sequence",
required=True,
)
room_type_id = fields.Many2one(
"pms.room.type",
"Room Type",
string="Room Type",
help="Room type which the shared room belongs",
comodel_name="pms.room.type",
required=True,
ondelete="restrict",
domain=[("shared_room", "=", True)],
)
# TODO: properties relation
pms_property_ids = fields.Many2many(
"pms.property",
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
relation="pms_shared_room_pms_property_rel",
column1="shared_room_id",
column2="pms_property_id",
check_pms_properties=True,
)
floor_id = fields.Many2one(
"pms.floor",
"Ubication",
ubication_id = fields.Many2one(
string="Ubication",
help="At which ubication the room is located.",
comodel_name="pms.ubication",
ondelete="restrict",
help="At which floor the room is located.",
)
bed_ids = fields.One2many(
"pms.room",
"shared_room_id",
string="Beds",
help="Beds in one room",
comodel_name="pms.room",
inverse_name="shared_room_id",
readonly=True,
)
active = fields.Boolean("Active", default=True)
sequence = fields.Integer("Sequence", required=True)
beds = fields.Integer("Beds")
beds = fields.Integer(
string="Number Of Beds", help="Number of beds in a shared room"
)
description_sale = fields.Text(
"Sale Description",
translate=True,
string="Sale Description",
help="A description of the Product that you want to communicate to "
" your customers. This description will be copied to every Sales "
" Order, Delivery Order and Customer Invoice/Credit Note",
translate=True,
)
# Constraints and onchanges
@api.constrains("beds")
def _constrain_beds(self):
self.ensure_one()
@@ -75,7 +95,7 @@ class PmsSharedRoom(models.Model):
"capacity": 1,
"room_type_id": self.room_type_id.id,
"sequence": self.sequence,
"floor_id": self.floor_id.id if self.floor_id else False,
"ubication_id": self.ubication_id.id if self.ubication_id else False,
"shared_room_id": self.id,
}
beds.append((0, False, bed_vals))
@@ -98,11 +118,11 @@ class PmsSharedRoom(models.Model):
}
)
@api.constrains("floor_id")
def _constrain_floor_id(self):
@api.constrains("ubication_id")
def _constrain_ubication_id(self):
self.bed_ids.write(
{
"floor_id": self.floor_id.id,
"ubication_id": self.ubication_id.id,
}
)

View File

@@ -0,0 +1,50 @@
# Copyright 2017 Dario Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class PmsUbication(models.Model):
_name = "pms.ubication"
_description = "Ubication"
_check_pms_properties_auto = True
name = fields.Char(
string="Ubication Name",
help="Ubication Name",
required=True,
translate=True,
)
sequence = fields.Integer(
string="Sequence",
help="Field used to change the position of the ubications in tree view."
"Changing the position changes the sequence",
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
relation="pms_ubication_pms_property_rel",
column1="ubication_type_id",
column2="pms_property_id",
ondelete="restrict",
check_pms_properties=True,
)
pms_room_ids = fields.One2many(
string="Rooms",
help="Rooms found in this location",
comodel_name="pms.room",
inverse_name="ubication_id",
check_pms_properties=True,
)
@api.constrains(
"pms_property_ids",
"pms_room_ids",
)
def _check_property_integrity(self):
for rec in self:
if rec.pms_property_ids and rec.pms_room_ids:
if rec.pms_room_ids.pms_property_id not in rec.pms_property_ids:
raise ValidationError(_("Property not allowed"))

View File

@@ -1,7 +1,11 @@
# Copyright 2017 Alexandre Díaz, Pablo Quesada, Darío Lodeiros
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import logging
from odoo import fields, models
_logger = logging.getLogger(__name__)
class ProductPricelist(models.Model):
"""Before creating a 'daily' pricelist, you need to consider the following:
@@ -10,17 +14,127 @@ class ProductPricelist(models.Model):
"""
_inherit = "product.pricelist"
_check_pms_properties_auto = True
# Fields declaration
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
required=False,
comodel_name="pms.property",
relation="product_pricelist_pms_property_rel",
column1="product_pricelist_id",
column2="pms_property_id",
ondelete="restrict",
check_pms_properties=True,
)
company_id = fields.Many2one(
string="Company",
help="Company to which the pricelist belongs",
check_pms_properties=True,
)
cancelation_rule_id = fields.Many2one(
"pms.cancelation.rule", string="Cancelation Policy"
string="Cancelation Policy",
help="Cancelation Policy included in the room",
comodel_name="pms.cancelation.rule",
check_pms_properties=True,
)
pricelist_type = fields.Selection(
[("daily", "Daily Plan")], string="Pricelist Type", default="daily"
string="Pricelist Type",
help="Pricelist types, it can be Daily Plan",
default="daily",
selection=[("daily", "Daily Plan")],
)
pms_sale_channel_ids = fields.Many2many(
string="Available Channels",
help="Sale channel for which the pricelist is included",
comodel_name="pms.sale.channel",
check_pms_properties=True,
)
availability_plan_id = fields.Many2one(
string="Availability Plan",
help="Availability Plan for which the pricelist is included",
comodel_name="pms.availability.plan",
ondelete="restrict",
check_pms_properties=True,
)
item_ids = fields.One2many(
string="Items",
help="Items for which the pricelist is made up",
check_pms_properties=True,
)
def _compute_price_rule_get_items(
self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
):
if (
"property" in self._context
and self._context["property"]
and self._context.get("consumption_date")
):
self.env.cr.execute(
"""
SELECT item.id
FROM product_pricelist_item item
LEFT JOIN product_category categ
ON item.categ_id = categ.id
LEFT JOIN product_pricelist_pms_property_rel cab
ON item.pricelist_id = cab.product_pricelist_id
LEFT JOIN product_pricelist_item_pms_property_rel lin
ON item.id = lin.product_pricelist_item_id
LEFT JOIN board_service_pricelist_item_rel board
ON item.id = board.pricelist_item_id
WHERE (lin.pms_property_id = %s OR lin.pms_property_id IS NULL)
AND (cab.pms_property_id = %s OR cab.pms_property_id IS NULL)
AND (item.product_tmpl_id IS NULL
OR item.product_tmpl_id = ANY(%s))
AND (item.product_id IS NULL OR item.product_id = ANY(%s))
AND (item.categ_id IS NULL OR item.categ_id = ANY(%s))
AND (item.pricelist_id = %s)
AND (item.date_start IS NULL OR item.date_start <=%s)
AND (item.date_end IS NULL OR item.date_end >=%s)
AND (item.date_start_overnight IS NULL
OR item.date_start_overnight <=%s)
AND (item.date_end_overnight IS NULL
OR item.date_end_overnight >=%s)
GROUP BY item.id
ORDER BY item.applied_on,
/* REVIEW: priotrity date sale / date overnight */
item.date_end - item.date_start ASC,
item.date_end_overnight - item.date_start_overnight ASC,
NULLIF((SELECT COUNT(1)
FROM product_pricelist_item_pms_property_rel l
WHERE item.id = l.product_pricelist_item_id)
+ (SELECT COUNT(1)
FROM product_pricelist_pms_property_rel c
WHERE item.pricelist_id = c.product_pricelist_id),0)
NULLS LAST,
item.id DESC;
""",
(
self._context["property"],
self._context["property"],
prod_tmpl_ids,
prod_ids,
categ_ids,
# on_board_service_bool,
# board_service_id,
self.id,
date,
date,
self._context["consumption_date"],
self._context["consumption_date"],
),
)
item_ids = [x[0] for x in self.env.cr.fetchall()]
items = self.env["product.pricelist.item"].browse(item_ids)
else:
items = super(ProductPricelist, self)._compute_price_rule_get_items(
products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
)
return items
# Constraints and onchanges
# @api.constrains("pricelist_type", "pms_property_ids")
@@ -52,23 +166,17 @@ class ProductPricelist(models.Model):
# )
# )
def _compute_price_rule_get_items(
self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
):
items = super(ProductPricelist, self)._compute_price_rule_get_items(
products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids
)
# Discard the rules with defined properties other than the context,
# and we reorder the rules to return the most concrete property rule first
if "property" in self._context:
items_filtered = items.filtered(
lambda i: not i.pms_property_ids
or self._context["property"] in i.pms_property_ids.ids
)
return items_filtered.sorted(
key=lambda s: (
(s.applied_on),
((s.date_end - s.date_start).days),
((not s.pms_property_ids, s), len(s.pms_property_ids)),
)
)
def open_massive_changes_wizard(self):
if self.ensure_one():
return {
"view_type": "form",
"view_mode": "form",
"name": "Massive changes on Pricelist: " + self.name,
"res_model": "pms.massive.changes.wizard",
"target": "new",
"type": "ir.actions.act_window",
"context": {
"pricelist_id": self.id,
},
}

View File

@@ -5,7 +5,53 @@ from odoo import fields, models
class ProductPricelistItem(models.Model):
_inherit = "product.pricelist.item"
_check_pms_properties_auto = True
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
comodel_name="pms.property",
relation="product_pricelist_item_pms_property_rel",
column1="product_pricelist_item_id",
column2="pms_property_id",
ondelete="restrict",
check_pms_properties=True,
)
date_start_overnight = fields.Date(
string="Start Date Overnight",
help="Start date to apply daily pricelist items",
)
date_end_overnight = fields.Date(
string="End Date Overnight",
help="End date to apply daily pricelist items",
)
on_board_service = fields.Boolean(
string="On Board Service",
help="Those included in Board Services",
)
board_service_room_type_ids = fields.Many2many(
string="Board Services",
help="""Specify a Board services on Room Types.""",
comodel_name="pms.board.service.room.type",
relation="board_service_pricelist_item_rel",
column1="pricelist_item_id",
column2="board_service_id",
ondelete="cascade",
check_pms_properties=True,
)
pricelist_id = fields.Many2one(
string="Pricelist",
help="Pricelist in which this item is included",
check_pms_properties=True,
)
product_id = fields.Many2one(
string="Product",
help="Product associated with the item",
check_pms_properties=True,
)
product_tmpl_id = fields.Many2one(
string="Product Template",
help="Product template associated with the item",
check_pms_properties=True,
)

View File

@@ -0,0 +1,43 @@
from odoo import api, fields, models
class ProductProduct(models.Model):
_inherit = "product.product"
board_price = fields.Float(
string="Board Service Price",
help="Get price on board service",
digits="Product Price",
compute="_compute_board_price",
)
@api.depends_context("consumption_date")
def _compute_product_price(self):
super(ProductProduct, self)._compute_product_price()
def _compute_board_price(self):
for record in self:
if self._context.get("board_service"):
record.board_price = (
self.env["pms.board.service.room.type.line"]
.search(
[
(
"pms_board_service_room_type_id",
"=",
self._context.get("board_service"),
),
("product_id", "=", record.id),
]
)
.amount
)
else:
record.board_price = False
def price_compute(self, price_type, uom=False, currency=False, company=None):
if self._context.get("board_service"):
price_type = "board_price"
return super(ProductProduct, self).price_compute(
price_type, uom, currency, company
)

View File

@@ -8,19 +8,39 @@ class ProductTemplate(models.Model):
_inherit = "product.template"
pms_property_ids = fields.Many2many(
"pms.property", string="Properties", required=False, ondelete="restrict"
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
required=False,
comodel_name="pms.property",
relation="product_template_pms_property_rel",
column1="product_tmpl_id",
column2="pms_property_id",
ondelete="restrict",
check_pms_properties=True,
)
company_id = fields.Many2one(
check_pms_properties=True,
)
per_day = fields.Boolean(
string="Unit increment per day",
help="Indicates that the product is sold by days",
)
per_person = fields.Boolean(
string="Unit increment per person",
help="Indicates that the product is sold per person",
)
per_day = fields.Boolean("Unit increment per day")
per_person = fields.Boolean("Unit increment per person")
consumed_on = fields.Selection(
[("before", "Before night"), ("after", "After night")],
"Consumed",
string="Consumed",
help="Indicates when the product is consumed",
selection=[("before", "Before night"), ("after", "After night")],
default="before",
)
daily_limit = fields.Integer("Daily limit")
is_extra_bed = fields.Boolean("Is extra bed", default=False)
show_in_calendar = fields.Boolean(
"Show in Calendar",
default=False,
help="Specifies if the product is shown in the calendar information.",
daily_limit = fields.Integer(
string="Daily limit", help="Indicates how much products can consumed in one day"
)
is_extra_bed = fields.Boolean(
string="Is extra bed",
help="Indicates if that product is a extra bed, add +1 capacity in the room",
default=False,
)

View File

@@ -7,20 +7,9 @@ from odoo import fields, models
class ResCompany(models.Model):
_inherit = "res.company"
# Fields declaration
pms_property_ids = fields.One2many("pms.property", "company_id", "Properties")
# TODO: need extra explanation or remove otherwise
# additional_hours = fields.Integer('Additional Hours',
# help="Provide the min hours value for \
# check in, checkout days, whatever \
# the hours will be provided here based \
# on that extra days will be \
# calculated.")
# TODO: move the text to the default template for confirmed reservations
# cardex_warning = fields.Text(
# 'Warning in Cardex',
# default="Time to access rooms: 14: 00h. Departure time: \
# 12: 00h. If the accommodation is not left at that time, \
# the establishment will charge a day's stay according to \
# current rate that day",
# help="Notice under the signature on the traveler's ticket.")
pms_property_ids = fields.One2many(
string="Properties",
help="Properties with access to the element",
comodel_name="pms.property",
inverse_name="company_id",
)

View File

@@ -11,35 +11,60 @@ _logger = logging.getLogger(__name__)
class ResPartner(models.Model):
_inherit = "res.partner"
# Fields declaration
main_partner_id = fields.Many2one(
"res.partner", string="Destination Partner fusion"
)
reservations_count = fields.Integer(
"Reservations", compute="_compute_reservations_count"
string="Reservations",
help="Number of reservations of the partner",
compute="_compute_reservations_count",
)
folios_count = fields.Integer(
string="Folios",
help="Number of folios of the partner",
compute="_compute_folios_count",
)
is_agency = fields.Boolean(
string="Is Agency", help="Indicates if the partner is an agency"
)
folios_count = fields.Integer("Folios", compute="_compute_folios_count")
unconfirmed = fields.Boolean("Unconfirmed", default=True)
is_agency = fields.Boolean("Is Agency")
sale_channel_id = fields.Many2one(
"pms.sale.channel",
string="Sale Channel",
ondelete="restrict",
help="The sale channel of the partner",
comodel_name="pms.sale.channel",
domain=[("channel_type", "=", "indirect")],
ondelete="restrict",
)
default_commission = fields.Integer(string="Commission", help="Default commission")
apply_pricelist = fields.Boolean(
string="Apply Pricelist",
help="Indicates if agency pricelist is applied to his reservations",
)
invoice_to_agency = fields.Boolean(
string="Invoice Agency",
help="Indicates if agency invoices partner",
)
pms_property_ids = fields.Many2many(
string="Properties",
help="Properties with access to the element;"
" if not set, all properties can access",
required=False,
comodel_name="pms.property",
relation="res_partner_pms_property_rel",
column1="res_partner_id",
column2="pms_property_id",
ondelete="restrict",
check_pms_properties=True,
)
company_id = fields.Many2one(
check_pms_properties=True,
)
default_commission = fields.Integer("Commission")
apply_pricelist = fields.Boolean("Apply Pricelist")
invoice_agency = fields.Boolean("Invoice Agency")
# Compute and Search methods
def _compute_reservations_count(self):
# TODO: recuperar las reservas de los folios del partner
pms_reservation_obj = self.env["pms.reservation"]
for record in self:
record.reservations_count = pms_reservation_obj.search_count(
[
(
"partner_id.id",
"=",
"child_of",
record.id if isinstance(record.id, int) else False,
)
]
@@ -58,17 +83,14 @@ class ResPartner(models.Model):
]
)
# ORM Overrides
@api.model
def name_search(self, name, args=None, operator="ilike", limit=100):
if not args:
args = []
domain = [
"|",
"|",
("phone", operator, name),
("mobile", operator, name),
("email", operator, name),
]
partners = self.search(
domain + args,
@@ -93,3 +115,34 @@ class ResPartner(models.Model):
raise models.ValidationError(_("Sale Channel must be entered"))
if not record.is_agency and record.sale_channel_id:
record.sale_channel_id = None
# REVIEW: problems with odoo demo data
# @api.constrains("mobile", "email")
# def _check_duplicated(self):
# for record in self:
# partner, field = record._search_duplicated()
# if partner:
# raise models.ValidationError(
# _(
# "Partner %s found with same %s (%s)",
# partner.name,
# partner._fields[field].string,
# getattr(record, field),
# )
# )
def _search_duplicated(self):
self.ensure_one()
partner = False
for field in self._get_key_fields():
if getattr(self, field):
partner = self.search(
[(field, "=", getattr(self, field)), ("id", "!=", self.id)]
)
if partner:
field = field
return partner, field
@api.model
def _get_key_fields(self):
return []

View File

@@ -1,33 +1,28 @@
# Copyright 2019 Pablo Quesada
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import _, api, fields, models
from odoo.exceptions import AccessError
from odoo.exceptions import ValidationError
from odoo.http import request
class ResUsers(models.Model):
_inherit = "res.users"
# Default Methods ang Gets
@api.model
def _get_default_pms_property(self):
return self.env.user.pms_property_id
# Fields declaration
pms_property_id = fields.Many2one(
"pms.property",
string="Property",
default=_get_default_pms_property,
help="The property this user is currently working for.",
string="Default Property",
help="The property that is selected within " "those allowed for the user",
comodel_name="pms.property",
domain="[('id','in',pms_property_ids)]",
context={"user_preference": True},
)
pms_property_ids = fields.Many2many(
"pms.property",
"pms_property_users_rel",
"user_id",
"pms_property_id",
string="Properties",
default=_get_default_pms_property,
help="The properties allowed for this user",
comodel_name="pms.property",
relation="pms_property_users_rel",
column1="user_id",
column2="pms_property_id",
domain="[('company_id','in',company_ids)]",
)
@api.model
@@ -39,7 +34,24 @@ class ResUsers(models.Model):
active_property_ids = list(
map(int, request.httprequest.cookies.get("pms_pids", "").split(","))
)
if any(pid not in user_property_ids for pid in active_property_ids):
raise AccessError(_("Access to unauthorized or invalid properties."))
active_property_ids = [
pid for pid in active_property_ids if pid in user_property_ids
]
return self.env["pms.property"].browse(active_property_ids).ids
return user_property_ids
@api.constrains("pms_property_id", "pms_property_ids")
def _check_property_in_allowed_properties(self):
if any(user.pms_property_id not in user.pms_property_ids for user in self):
raise ValidationError(
_("The chosen property is not in the allowed properties for this user")
)
@api.constrains("pms_property_ids", "company_id")
def _check_company_in_property_ids(self):
for record in self:
for pms_property in record.pms_property_ids:
if pms_property.company_id not in record.company_ids:
raise ValidationError(
_("Some properties do not belong to the allowed companies")
)

View File

@@ -1,3 +1,3 @@
You will find the hotel settings in Settings > Users & Companies > Hotels > Your Hotel.
You will find the hotel settings in PMS Management > Configuration > Properties > Your Property.
This module required additional configuration for company, accounting, invoicing and user privileges.

View File

@@ -1,4 +1,10 @@
* Dario Lodeiros <dario@commitsun.com>
* Alexandre Díaz
* Pablo Quesada
* Jose Luis Algara
* `Commit [Sun] <https://www.commitsun.com>`:
* Dario Lodeiros
* Eric Antones
* Sara Lago
* Brais Abeijon
* Miguel Padin

View File

@@ -1,5 +1,5 @@
This module is an all-in-one property management system (PMS) focused on medium-sized hotels
This module is an all-in-one property management system (PMS) focused on medium-sized properties
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.
You can manage properties with multi-property and multi-company support, including your rooms inventory,
reservations, check-in, daily reports, board services, rate and availability plans among other property functionalities.

View File

@@ -1,2 +1,2 @@
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.
This module depends on modules ``base``, ``mail``, ``sale`` and ``multi_pms_properties``.
Ensure yourself to have all them in your addons list.

View File

@@ -1 +1 @@
To use this module, please, read the complete user guide at https://roomdoo.com.
To use this module, please, read the complete user guide at `<roomdoo.com>`_.

View File

@@ -1,15 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<template id="report_folio_document">
<template id="report_folio_document">
<t t-call="web.external_layout">
<t t-set="doc" t-value="doc.with_context({'lang':doc.partner_id.lang})" />
<div class="page">
<div class="oe_structure" />
<t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)" />
<t t-set="address">
<div class="row">
<div class="col-xs-6">
<t t-if="doc.partner_invoice_id != doc.partner_id">
<t
t-if="len(doc.partner_invoice_ids)==1 and doc.partner_id != doc.partner_invoice_ids[0]"
>
<div
t-field="doc.partner_invoice_id"
t-field="doc.partner_invoice_ids[0]"
t-options='{"widget": "contact", "fields": ["address", "name", "phone"], "no_marker": True, "phone_icons": True}'
/>
</t>
@@ -26,155 +27,186 @@
</p>
</div>
</div>
<h2>
</t>
<div class="page">
<div class="oe_structure" />
<h2 class="mt16">
<span t-if="doc.state not in ['draft','sent']">Order #</span>
<span t-if="doc.state in ['draft','sent']">Quotation #</span>
<span t-field="doc.name" />
</h2>
<div class="row mt32 mb32" id="informations">
<div t-if="doc.client_order_ref" class="col-xs-3">
<div t-if="doc.client_order_ref" class="col-auto col-3 mw-100 mb-2">
<strong>Your Reference:</strong>
<p t-field="doc.client_order_ref" />
<p class="m-0" t-field="doc.client_order_ref" />
</div>
<div
t-if="doc.confirmation_date and doc.state not in ['draft','sent']"
class="col-xs-3"
t-if="doc.date_order and doc.state not in ['draft','sent']"
class="col-auto col-3 mw-100 mb-2"
>
<strong>Date Ordered:</strong>
<p t-field="doc.confirmation_date" />
<strong>Order Date:</strong>
<p class="m-0" t-field="doc.date_order" />
</div>
<div
t-if="doc.date_order and doc.state in ['draft','sent']"
class="col-xs-3"
class="col-auto col-3 mw-100 mb-2"
>
<strong>Quotation Date:</strong>
<p t-field="doc.date_order" />
<p
class="m-0"
t-field="doc.date_order"
t-options='{"widget": "date"}'
/>
</div>
<div t-if="doc.user_id.name" class="col-xs-3">
<div t-if="doc.user_id.name" class="col-auto col-3 mw-100 mb-2">
<strong>Salesperson:</strong>
<p t-field="doc.user_id" />
</div>
<div
name="payment_term"
t-if="doc.payment_term_id"
class="col-xs-3"
>
<strong>Payment Terms:</strong>
<p t-field="doc.payment_term_id" />
<p class="m-0" t-field="doc.user_id" />
</div>
</div>
<!-- Is there a discount on at least one line? -->
<t
t-set="display_discount"
t-value="any([l.discount for l in doc.reservation_ids])"
t-value="any(l.discount for l in doc.sale_line_ids)"
/>
<table class="table table-condensed">
<thead>
<table class="table table-sm o_main_table">
<!-- In case we want to repeat the header, remove "display: table-row-group" -->
<thead style="display: table-row-group">
<tr>
<th>Description</th>
<th class="text-right">Quantity</th>
<th name="th_description" class="text-left">Description</th>
<th name="th_quantity" class="text-right">Quantity</th>
<th name="th_priceunit" class="text-right">Unit Price</th>
<th
name="th_discount"
t-if="display_discount"
class="text-right"
groups="sale.group_discount_per_so_line"
>
Disc.(%)
<span>Disc.%</span>
</th>
<th class="text-right">Taxes</th>
<th
class="text-right"
groups="sale.group_show_price_subtotal"
>
Amount
</th>
<th
class="text-right price_tax_included"
groups="sale.group_show_price_total"
>
Total Price
<th name="th_taxes" class="text-right">Taxes</th>
<th name="th_subtotal" class="text-right">
<span
groups="account.group_show_line_subtotals_tax_excluded"
>Amount</span>
<span
groups="account.group_show_line_subtotals_tax_included"
>Total Price</span>
</th>
</tr>
</thead>
<tbody class="sale_tbody">
<!-- Lines associated -->
<t t-foreach="doc.reservation_ids" t-as="l">
<t t-if="l.price_total > 0">
<tr>
<td>
<span t-field="l.name" />
</td>
<td class="text-right">
<span t-field="l.nights" />
</td>
<td
t-if="display_discount"
class="text-right"
groups="sale.group_discount_per_so_line"
<t t-set="current_subtotal" t-value="0" />
<t t-foreach="doc.sale_line_ids" t-as="line">
<t
t-set="current_subtotal"
t-value="current_subtotal + line.price_subtotal"
groups="account.group_show_line_subtotals_tax_excluded"
/>
<t
t-set="current_subtotal"
t-value="current_subtotal + line.price_total"
groups="account.group_show_line_subtotals_tax_included"
/>
<tr
t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''"
>
<span t-field="l.discount" />
</td>
<td class="text-right">
<t t-if="not line.display_type">
<t t-set="price" t-value="line.price_unit" />
<t t-if="line.reservation_id">
<t
t-set="print_board_service"
t-value="line.reservation_id.board_service_room_id.pms_board_service_id.show_detail_report"
/>
<t t-if="not print_board_service">
<t
t-foreach="line.reservation_id.service_ids"
t-as="service"
>
<t t-if="service.is_board_service">
<t
t-set="price"
t-value="service.product_qty/line.price_total*(1-(service.reservation_id.discount or 0.0)*0.01) + price"
/>
</t>
</t>
</t>
</t>
<t
t-if="not(not print_board_service and line.service_id.is_board_service)"
>
<td name="td_name"><span
t-field="line.name"
/></td>
<td name="td_quantity" class="text-right">
<span t-field="line.product_uom_qty" />
<span
t-esc="', '.join(map(lambda x: (x.description or x.name), l.tax_ids))"
t-field="line.product_uom"
groups="uom.group_uom"
/>
</td>
<td
class="text-right"
groups="sale.group_show_price_subtotal"
>
<td name="td_priceunit" class="text-right">
<span
t-field="l.price_subtotal"
t-esc="price"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
</td>
<td t-if="display_discount" class="text-right">
<span t-field="line.discount" />
</td>
<td name="td_taxes" class="text-right">
<span
t-esc="', '.join(map(lambda x: (x.description or x.name), line.tax_ids))"
/>
</td>
<td
class="text-right"
groups="sale.group_show_price_total"
name="td_subtotal"
class="text-right o_price_total"
>
<span
t-field="l.price_total"
t-esc="price*(1-(line.discount or 0.0)*0.01)* line.product_uom_qty"
groups="account.group_show_line_subtotals_tax_excluded"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
<span
t-esc="price *(1-(line.discount or 0.0)*0.01)* line.product_uom_qty"
groups="account.group_show_line_subtotals_tax_included"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
</td>
</t>
</t>
<t t-if="line.display_type == 'line_section'">
<td name="td_section_line" colspan="99">
<span t-field="line.name" />
</td>
<t t-set="current_section" t-value="line" />
<t t-set="current_subtotal" t-value="0" />
</t>
<t t-if="line.display_type == 'line_note'">
<td name="td_note_line" colspan="99">
<span t-field="line.name" />
</td>
</t>
</tr>
</t>
</t>
<t t-foreach="doc.service_ids" t-as="l">
<t t-if="l.price_total > 0">
<tr>
<td>
<span t-field="l.name" />
</td>
<td class="text-right">
<span t-field="l.product_qty" />
</td>
<td
t-if="display_discount"
class="text-right"
groups="sale.group_discount_per_so_line"
<t
t-if="current_section and (line_last or doc.sale_line_ids[line_index+1].display_type == 'line_section')"
>
<span t-field="l.discount" />
</td>
<td class="text-right">
<tr class="is-subtotal text-right">
<td name="td_section_subtotal" colspan="99">
<strong class="mr16">Subtotal</strong>
<span
t-esc="', '.join(map(lambda x: (x.description or x.name), l.tax_ids))"
/>
</td>
<td
class="text-right"
groups="sale.group_show_price_subtotal"
>
<span
t-field="l.price_subtotal"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
</td>
<td
class="text-right"
groups="sale.group_show_price_total"
>
<span
t-field="l.price_total"
t-esc="current_subtotal"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
</td>
@@ -183,46 +215,39 @@
</t>
</tbody>
</table>
<div class="clearfix">
<div class="row" name="total">
<div class="col-xs-4 pull-right">
<table
class="table table-condensed"
style="min-width: 200px;max-width: 350px;"
<div class="clearfix" name="so_total_summary">
<div id="total" class="row" name="total">
<div
t-attf-class="#{'col-4' if report_type != 'html' else 'col-sm-7 col-md-5'} ml-auto"
>
<tr
class="border-black"
style="border-bottom:1px solid #dddddd;"
>
<td>
<strong>Subtotal</strong>
</td>
<td class="text-right">
<span
t-field="doc.amount_untaxed"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
<table class="table table-sm">
<tr class="border-black o_subtotal" style="">
<td name="td_amount_untaxed_label"><strong
>Subtotal</strong></td>
<td name="td_amount_untaxed" class="text-right">
<span t-field="doc.amount_untaxed" />
</td>
</tr>
-
<t
t-foreach="doc._get_tax_amount_by_group()"
t-as="amount_by_group"
>
<tr style="border-bottom:1px solid #dddddd;">
<tr style="">
<t
t-if="amount_by_group[3] == 1 and doc.amount_untaxed == amount_by_group[2]"
>
<td>
<td name="td_amount_by_group_label_3">
<span t-esc="amount_by_group[0]" />
<span>&amp;nbsp;<span>
on
</span>&amp;nbsp;<t
<span>&amp;nbsp;<span>on</span>&amp;nbsp;<t
t-esc="amount_by_group[2]"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/></span>
</td>
<td class="text-right">
<td
name="td_amount_by_group_3"
class="text-right o_price_total"
>
<span
t-esc="amount_by_group[1]"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
@@ -230,10 +255,13 @@
</td>
</t>
<t t-else="">
<td>
<td name="td_amount_by_group_label">
<span t-esc="amount_by_group[0]" />
</td>
<td class="text-right">
<td
name="td_amount_by_group"
class="text-right o_price_total"
>
<span
t-esc="amount_by_group[1]"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
@@ -242,91 +270,42 @@
</t>
</tr>
</t>
<tr class="border-black">
<td>
<strong>Total</strong>
</td>
<td class="text-right">
<span
t-field="doc.amount_total"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
</td>
</tr>
<tr class="border-black">
<td>
<strong>Pending Payment</strong>
</td>
<td class="text-right">
<span
t-field="doc.pending_amount"
t-options='{"widget": "monetary", "display_currency": doc.pricelist_id.currency_id}'
/>
<tr class="border-black o_total">
<td name="td_amount_total_label"><strong
>Total</strong></td>
<td name="td_amount_total" class="text-right">
<span t-field="doc.amount_total" />
</td>
</tr>
</table>
</div>
</div>
</div>
<div>
<span t-if="doc.payment_ids">
<table style="width:80%;">
<thead>
<tr>
<th>Payment Ref.</th>
<th>Payment Date</th>
<th>Payment Method</th>
<th>Paid Amount</th>
</tr>
</thead>
<tbody>
<tr t-foreach="doc.payment_ids" t-as="l">
<td t-esc="l.name" />
<td t-esc="l.payment_date" />
<td t-esc="l.journal_id.name" />
<td t-esc="l.amount" />
</tr>
</tbody>
</table>
</span>
<!-- <span t-if="doc.return_ids">
<table style="width:80%;">
<thead>
<tr>
<th>Return Ref.</th>
<th>Return Date</th>
<th>Return Method</th>
<th>Return Amount</th>
</tr>
</thead>
<tbody>
<tr t-foreach="doc.return_ids" t-as="r">
<td t-esc="r.name" />
<td t-esc="r.date" />
<td t-esc="r.journal_id.name" />
<t
t-set="total_amount"
t-value="sum(l.amount for l in r.line_ids)"
/>
<td t-esc="total_amount" />
</tr>
</tbody>
</table>
</span> -->
</div>
<div class="oe_structure" />
<p t-field="doc.note" />
<p t-if="doc.payment_term_id.note">
<span t-field="doc.payment_term_id.note" />
</p>
<div class="oe_structure" />
<p
id="fiscal_position_remark"
t-if="doc.fiscal_position_id and doc.fiscal_position_id.sudo().note"
>
<strong>Fiscal Position Remark:</strong>
<span t-field="doc.fiscal_position_id.sudo().note" />
</p>
</div>
</t>
</template>
<template id="report_folio">
</template>
<template id="report_folio">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="pms.report_folio_document" t-lang="doc.partner_id.lang" />
</t>
</t>
</template>
</odoo>

View File

@@ -1,18 +1,18 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
user_access_pms_floor,user_access_pms_floor,model_pms_floor,pms.group_pms_user,1,0,0,0
user_access_pms_ubication,user_access_pms_ubication,model_pms_ubication,pms.group_pms_user,1,0,0,0
user_access_pms_amenity,user_access_pms_amenity,model_pms_amenity,pms.group_pms_user,1,0,0,0
user_access_pms_amenity_type,user_access_pms_amenity_type,model_pms_amenity_type,pms.group_pms_user,1,0,0,0
user_access_pms_service,user_access_pms_service,model_pms_service,pms.group_pms_user,1,1,1,1
user_access_pms_room_type_restriction,user_access_pms_room_type_restriction,model_pms_room_type_restriction,pms.group_pms_user,1,0,0,0
user_access_pms_reservation_line,user_access_pms_reservation_line,model_pms_reservation_line,pms.group_pms_user,1,1,1,1
user_access_room_closure_reason,user_access_room_closure_reason,model_room_closure_reason,pms.group_pms_user,1,0,0,0
user_access_pms_service_line,user_access_pms_service_line,model_pms_service_line,pms.group_pms_user,1,1,1,1
user_access_pms_board_service,user_access_pms_board_service,model_pms_board_service,pms.group_pms_user,1,0,0,0
user_access_pms_checkin_partner,user_access_pms_checkin_partner,model_pms_checkin_partner,pms.group_pms_user,1,1,1,0
user_access_pms_checkin_partner,user_access_pms_checkin_partner,model_pms_checkin_partner,pms.group_pms_user,1,1,1,1
user_access_pms_room_type_class,user_access_pms_room_type_class,model_pms_room_type_class,pms.group_pms_user,1,0,0,0
user_access_pms_room,user_access_pms_room,model_pms_room,pms.group_pms_user,1,0,0,0
user_access_shared_pms_room,user_access_pms_shared_room,model_pms_shared_room,pms.group_pms_user,1,0,0,0
user_access_pms_room_type_restriction_item,user_access_pms_room_type_restriction_item,model_pms_room_type_restriction_item,pms.group_pms_user,1,0,0,0
user_access_pms_availability_plan_rule,user_access_pms_availability_plan_rule,model_pms_availability_plan_rule,pms.group_pms_user,1,0,0,0
user_access_pms_availability,user_access_pms_availability,model_pms_availability,pms.group_pms_user,1,1,1,0
user_access_pms_reservation,user_access_pms_reservation,model_pms_reservation,pms.group_pms_user,1,1,1,1
user_access_pms_folio,user_access_pms_folio,model_pms_folio,pms.group_pms_user,1,1,1,1
user_access_pms_room_type,user_access_pms_room_type,model_pms_room_type,pms.group_pms_user,1,0,0,0
@@ -23,23 +23,23 @@ user_access_account_partial_reconcile,user_access_account_partial_reconcile,acco
user_access_pms_cancelation_rule,user_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_user,1,0,0,0
user_access_account_full_reconcile,user_access_account_full_reconcile,account.model_account_full_reconcile,pms.group_pms_user,1,1,1,1
user_access_property,user_access_property,model_pms_property,pms.group_pms_user,1,0,0,0
user_access_availability,user_access_availability,model_pms_room_type_availability,pms.group_pms_user,1,0,0,0
user_access_availability,user_access_availability,model_pms_availability_plan,pms.group_pms_user,1,0,0,0
user_access_pms_sale_channel,user_access_pms_sale_channel,model_pms_sale_channel,pms.group_pms_user,1,0,0,0
manager_access_pms_floor,manager_access_pms_floor,model_pms_floor,pms.group_pms_manager,1,1,1,1
manager_access_pms_ubication,manager_access_pms_ubication,model_pms_ubication,pms.group_pms_manager,1,1,1,1
manager_access_pms_amenity,manager_access_pms_amenity,model_pms_amenity,pms.group_pms_manager,1,1,1,1
manager_access_pms_amenity_type,manager_access_pms_amenity_type,model_pms_amenity_type,pms.group_pms_manager,1,1,1,1
manager_access_pms_service,manager_access_pms_service,model_pms_service,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type_restriction,manager_access_pms_room_type_restriction,model_pms_room_type_restriction,pms.group_pms_manager,1,1,1,1
manager_access_pms_reservation_line,manager_access_pms_reservation_line,model_pms_reservation_line,pms.group_pms_manager,1,1,1,1
manager_access_room_closure_reason,manager_access_room_closure_reason,model_room_closure_reason,pms.group_pms_manager,1,1,1,1
manager_access_pms_service_line,manager_access_pms_service_line,model_pms_service_line,pms.group_pms_manager,1,1,1,1
manager_access_pms_board_service,manager_access_pms_board_service,model_pms_board_service,pms.group_pms_manager,1,1,1,1
manager_access_pms_checkin_partner,manager_access_pms_checkin_partner,model_pms_checkin_partner,pms.group_pms_manager,1,1,1,0
manager_access_pms_checkin_partner,manager_access_pms_checkin_partner,model_pms_checkin_partner,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type_class,manager_access_pms_room_type_class,model_pms_room_type_class,pms.group_pms_manager,1,1,1,1
manager_access_pms_room,manager_access_pms_room,model_pms_room,pms.group_pms_manager,1,1,1,1
manager_access_pms_shared_room,manager_access_pms_shared_room,model_pms_shared_room,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type_restriction_item,manager_access_pms_room_type_restriction_item,model_pms_room_type_restriction_item,pms.group_pms_manager,1,1,1,1
manager_access_pms_availability_plan_rule,manager_access_pms_availability_plan_rule,model_pms_availability_plan_rule,pms.group_pms_manager,1,1,1,1
manager_access_pms_reservation,manager_access_pms_reservation,model_pms_reservation,pms.group_pms_manager,1,1,1,1
manager_access_pms_availability,manager_access_pms_availability,model_pms_availability,pms.group_pms_manager,1,1,1,0
manager_access_pms_folio,manager_access_pms_folio,model_pms_folio,pms.group_pms_manager,1,1,1,1
manager_access_pms_room_type,manager_access_pms_room_type,model_pms_room_type,pms.group_pms_manager,1,1,1,1
manager_access_pms_board_service_room_type,manager_access_pms_board_service_room_type,model_pms_board_service_room_type,pms.group_pms_manager,1,1,1,1
@@ -47,6 +47,18 @@ manager_access_pms_board_service_room_type_line,manager_access_pms_board_service
manager_access_pms_board_service_line,manager_access_pms_board_service_line,model_pms_board_service_line,pms.group_pms_manager,1,1,1,1
manager_access_property,manager_access_property,model_pms_property,pms.group_pms_manager,1,1,1,1
manager_access_pms_cancelation_rule,manager_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_manager,1,1,1,1
manager_access_availability,manager_access_availability,model_pms_room_type_availability,pms.group_pms_manager,1,1,1,1
manager_access_availability,manager_access_availability,model_pms_availability_plan,pms.group_pms_manager,1,1,1,1
manager_access_pms_sale_channel,manager_access_pms_sale_channel,model_pms_sale_channel,pms.group_pms_manager,1,1,1,1
user_access_pms_reservation_wizard,user_access_pms_reservation_wizard,model_pms_reservation_wizard,pms.group_pms_user,1,1,1,1
user_access_pms_reservation_split_join_swap_wizard,user_access_pms_reservation_split_join_swap_wizard,model_pms_reservation_split_join_swap_wizard,pms.group_pms_user,1,1,1,1
user_access_pms_wizard_reservation_lines_split,user_access_pms_wizard_reservation_lines_split,model_pms_wizard_reservation_lines_split,pms.group_pms_user,1,1,1,1
user_access_pms_massive_changes_wizard,user_access_pms_massive_changes_wizard,model_pms_massive_changes_wizard,pms.group_pms_user,1,1,1,1
user_access_pms_advanced_filters_wizard,user_access_pms_advanced_filters_wizard,model_pms_advanced_filters_wizard,pms.group_pms_user,1,1,1,1
user_access_pms_folio_wizard,user_access_pms_folio_wizard,model_pms_folio_wizard,pms.group_pms_user,1,1,1,1
user_access_pms_folio_availability_wizard,user_access_pms_folio_availability_wizard,model_pms_folio_availability_wizard,pms.group_pms_user,1,1,1,1
user_access_pms_num_rooms_selection,user_access_pms_num_rooms_selection,model_pms_num_rooms_selection,pms.group_pms_user,1,1,1,1
user_access_pms_folio_sale_line,user_access_pms_folio_sale_line,model_folio_sale_line,pms.group_pms_user,1,0,0,0
user_access_folio_make_invoice_advance,user_access_folio_make_invoice_advance,model_folio_advance_payment_inv,pms.group_pms_user,1,1,1,1
user_access_wizard_payment_folio,user_access_wizard_payment_folio,model_wizard_payment_folio,pms.group_pms_user,1,1,1,1
user_access_wizard_folio_changes,user_access_wizard_folio_changes,model_wizard_folio_changes,pms.group_pms_user,1,1,1,1
user_access_pms_folio_portal,user_access_pms_folio_portal,model_pms_folio,base.group_portal,1,0,0,0
user_access_pms_reservation_portal,user_access_pms_reservation_portal,model_pms_reservation,base.group_portal,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 user_access_pms_floor user_access_pms_ubication user_access_pms_floor user_access_pms_ubication model_pms_floor model_pms_ubication pms.group_pms_user 1 0 0 0
3 user_access_pms_amenity user_access_pms_amenity model_pms_amenity pms.group_pms_user 1 0 0 0
4 user_access_pms_amenity_type user_access_pms_amenity_type model_pms_amenity_type pms.group_pms_user 1 0 0 0
5 user_access_pms_service user_access_pms_service model_pms_service pms.group_pms_user 1 1 1 1
user_access_pms_room_type_restriction user_access_pms_room_type_restriction model_pms_room_type_restriction pms.group_pms_user 1 0 0 0
6 user_access_pms_reservation_line user_access_pms_reservation_line model_pms_reservation_line pms.group_pms_user 1 1 1 1
7 user_access_room_closure_reason user_access_room_closure_reason model_room_closure_reason pms.group_pms_user 1 0 0 0
8 user_access_pms_service_line user_access_pms_service_line model_pms_service_line pms.group_pms_user 1 1 1 1
9 user_access_pms_board_service user_access_pms_board_service model_pms_board_service pms.group_pms_user 1 0 0 0
10 user_access_pms_checkin_partner user_access_pms_checkin_partner model_pms_checkin_partner pms.group_pms_user 1 1 1 0 1
11 user_access_pms_room_type_class user_access_pms_room_type_class model_pms_room_type_class pms.group_pms_user 1 0 0 0
12 user_access_pms_room user_access_pms_room model_pms_room pms.group_pms_user 1 0 0 0
13 user_access_shared_pms_room user_access_pms_shared_room model_pms_shared_room pms.group_pms_user 1 0 0 0
14 user_access_pms_room_type_restriction_item user_access_pms_availability_plan_rule user_access_pms_room_type_restriction_item user_access_pms_availability_plan_rule model_pms_room_type_restriction_item model_pms_availability_plan_rule pms.group_pms_user 1 0 0 0
15 user_access_pms_availability user_access_pms_availability model_pms_availability pms.group_pms_user 1 1 1 0
16 user_access_pms_reservation user_access_pms_reservation model_pms_reservation pms.group_pms_user 1 1 1 1
17 user_access_pms_folio user_access_pms_folio model_pms_folio pms.group_pms_user 1 1 1 1
18 user_access_pms_room_type user_access_pms_room_type model_pms_room_type pms.group_pms_user 1 0 0 0
23 user_access_pms_cancelation_rule user_access_pms_cancelation_rule model_pms_cancelation_rule pms.group_pms_user 1 0 0 0
24 user_access_account_full_reconcile user_access_account_full_reconcile account.model_account_full_reconcile pms.group_pms_user 1 1 1 1
25 user_access_property user_access_property model_pms_property pms.group_pms_user 1 0 0 0
26 user_access_availability user_access_availability model_pms_room_type_availability model_pms_availability_plan pms.group_pms_user 1 0 0 0
27 user_access_pms_sale_channel user_access_pms_sale_channel model_pms_sale_channel pms.group_pms_user 1 0 0 0
28 manager_access_pms_floor manager_access_pms_ubication manager_access_pms_floor manager_access_pms_ubication model_pms_floor model_pms_ubication pms.group_pms_manager 1 1 1 1
29 manager_access_pms_amenity manager_access_pms_amenity model_pms_amenity pms.group_pms_manager 1 1 1 1
30 manager_access_pms_amenity_type manager_access_pms_amenity_type model_pms_amenity_type pms.group_pms_manager 1 1 1 1
31 manager_access_pms_service manager_access_pms_service model_pms_service pms.group_pms_manager 1 1 1 1
manager_access_pms_room_type_restriction manager_access_pms_room_type_restriction model_pms_room_type_restriction pms.group_pms_manager 1 1 1 1
32 manager_access_pms_reservation_line manager_access_pms_reservation_line model_pms_reservation_line pms.group_pms_manager 1 1 1 1
33 manager_access_room_closure_reason manager_access_room_closure_reason model_room_closure_reason pms.group_pms_manager 1 1 1 1
34 manager_access_pms_service_line manager_access_pms_service_line model_pms_service_line pms.group_pms_manager 1 1 1 1
35 manager_access_pms_board_service manager_access_pms_board_service model_pms_board_service pms.group_pms_manager 1 1 1 1
36 manager_access_pms_checkin_partner manager_access_pms_checkin_partner model_pms_checkin_partner pms.group_pms_manager 1 1 1 0 1
37 manager_access_pms_room_type_class manager_access_pms_room_type_class model_pms_room_type_class pms.group_pms_manager 1 1 1 1
38 manager_access_pms_room manager_access_pms_room model_pms_room pms.group_pms_manager 1 1 1 1
39 manager_access_pms_shared_room manager_access_pms_shared_room model_pms_shared_room pms.group_pms_manager 1 1 1 1
40 manager_access_pms_room_type_restriction_item manager_access_pms_availability_plan_rule manager_access_pms_room_type_restriction_item manager_access_pms_availability_plan_rule model_pms_room_type_restriction_item model_pms_availability_plan_rule pms.group_pms_manager 1 1 1 1
41 manager_access_pms_reservation manager_access_pms_reservation model_pms_reservation pms.group_pms_manager 1 1 1 1
42 manager_access_pms_availability manager_access_pms_availability model_pms_availability pms.group_pms_manager 1 1 1 0
43 manager_access_pms_folio manager_access_pms_folio model_pms_folio pms.group_pms_manager 1 1 1 1
44 manager_access_pms_room_type manager_access_pms_room_type model_pms_room_type pms.group_pms_manager 1 1 1 1
45 manager_access_pms_board_service_room_type manager_access_pms_board_service_room_type model_pms_board_service_room_type pms.group_pms_manager 1 1 1 1
47 manager_access_pms_board_service_line manager_access_pms_board_service_line model_pms_board_service_line pms.group_pms_manager 1 1 1 1
48 manager_access_property manager_access_property model_pms_property pms.group_pms_manager 1 1 1 1
49 manager_access_pms_cancelation_rule manager_access_pms_cancelation_rule model_pms_cancelation_rule pms.group_pms_manager 1 1 1 1
50 manager_access_availability manager_access_availability model_pms_room_type_availability model_pms_availability_plan pms.group_pms_manager 1 1 1 1
51 manager_access_pms_sale_channel manager_access_pms_sale_channel model_pms_sale_channel pms.group_pms_manager 1 1 1 1
52 user_access_pms_reservation_wizard user_access_pms_reservation_split_join_swap_wizard user_access_pms_reservation_wizard user_access_pms_reservation_split_join_swap_wizard model_pms_reservation_wizard model_pms_reservation_split_join_swap_wizard pms.group_pms_user 1 1 1 1
53 user_access_pms_wizard_reservation_lines_split user_access_pms_wizard_reservation_lines_split model_pms_wizard_reservation_lines_split pms.group_pms_user 1 1 1 1
54 user_access_pms_massive_changes_wizard user_access_pms_massive_changes_wizard model_pms_massive_changes_wizard pms.group_pms_user 1 1 1 1
55 user_access_pms_advanced_filters_wizard user_access_pms_advanced_filters_wizard model_pms_advanced_filters_wizard pms.group_pms_user 1 1 1 1
56 user_access_pms_folio_wizard user_access_pms_folio_wizard model_pms_folio_wizard pms.group_pms_user 1 1 1 1
57 user_access_pms_folio_availability_wizard user_access_pms_folio_availability_wizard model_pms_folio_availability_wizard pms.group_pms_user 1 1 1 1
58 user_access_pms_num_rooms_selection user_access_pms_num_rooms_selection model_pms_num_rooms_selection pms.group_pms_user 1 1 1 1
59 user_access_pms_folio_sale_line user_access_pms_folio_sale_line model_folio_sale_line pms.group_pms_user 1 0 0 0
60 user_access_folio_make_invoice_advance user_access_folio_make_invoice_advance model_folio_advance_payment_inv pms.group_pms_user 1 1 1 1
61 user_access_wizard_payment_folio user_access_wizard_payment_folio model_wizard_payment_folio pms.group_pms_user 1 1 1 1
62 user_access_wizard_folio_changes user_access_wizard_folio_changes model_wizard_folio_changes pms.group_pms_user 1 1 1 1
63 user_access_pms_folio_portal user_access_pms_folio_portal model_pms_folio base.group_portal 1 0 0 0
64 user_access_pms_reservation_portal user_access_pms_reservation_portal model_pms_reservation base.group_portal 1 0 0 0

View File

@@ -35,7 +35,7 @@
</record>
<!-- Property Rules -->
<record id="pms_folio_property_rule" model="ir.rule">
<field name="name">PMS Folio Company Rule</field>
<field name="name">PMS Folio Property Rule</field>
<field name="model_id" ref="model_pms_folio" />
<field name="global" eval="True" />
<field name="domain_force">
@@ -44,7 +44,7 @@
</field>
</record>
<record id="pms_reservation_property_rule" model="ir.rule">
<field name="name">PMS Reservation Company Rule</field>
<field name="name">PMS Reservation Property Rule</field>
<field name="model_id" ref="model_pms_reservation" />
<field name="global" eval="True" />
<field name="domain_force">
@@ -52,5 +52,190 @@
user.get_active_property_ids())]
</field>
</record>
<record id="pms_amenity_property_rule" model="ir.rule">
<field name="name">PMS Amenity Property Rule</field>
<field name="model_id" ref="model_pms_amenity" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_amenity_type_property_rule" model="ir.rule">
<field name="name">PMS Amenity Type Property Rule</field>
<field name="model_id" ref="model_pms_amenity_type" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_board_service_property_rule" model="ir.rule">
<field name="name">PMS Board Service Property Rule</field>
<field name="model_id" ref="model_pms_board_service" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_board_service_line_property_rule" model="ir.rule">
<field name="name">PMS Board Service Line Property Rule</field>
<field name="model_id" ref="model_pms_board_service_line" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_board_service_room_type_property_rule" model="ir.rule">
<field name="name">PMS Board Service Room Type Property Rule</field>
<field name="model_id" ref="model_pms_board_service_room_type" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_cancelation_rule_property_rule" model="ir.rule">
<field name="name">PMS Cancelation Rule Property Rule</field>
<field name="model_id" ref="model_pms_cancelation_rule" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_ubication_property_rule" model="ir.rule">
<field name="name">PMS Ubication Property Rule</field>
<field name="model_id" ref="model_pms_ubication" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_reservation_line_property_rule" model="ir.rule">
<field name="name">PMS Reservation Line Property Rule</field>
<field name="model_id" ref="model_pms_reservation_line" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_id','=',False),('pms_property_id', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_clousure_reason_property_rule" model="ir.rule">
<field name="name">PMS Clousure Reason Property Rule</field>
<field name="model_id" ref="model_room_closure_reason" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_room_type_property_rule" model="ir.rule">
<field name="name">PMS Room Type Property Rule</field>
<field name="model_id" ref="model_pms_room_type" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_availability_plan_property_rule" model="ir.rule">
<field name="name">PMS Room Type Availability Plan Property Rule</field>
<field name="model_id" ref="model_pms_availability_plan" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_availability_plan_rule_property_rule" model="ir.rule">
<field name="name">PMS Room Type Availability Rule Property Rule</field>
<field name="model_id" ref="model_pms_availability_plan_rule" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_id','=',False),('pms_property_id', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_service_property_rule" model="ir.rule">
<field name="name">PMS Service Property Rule</field>
<field name="model_id" ref="model_pms_service" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_id','=',False),('pms_property_id', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_service_line_property_rule" model="ir.rule">
<field name="name">PMS Service Line Property Rule</field>
<field name="model_id" ref="model_pms_service_line" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_id','=',False),('pms_property_id', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_product_pricelist_property_rule" model="ir.rule">
<field name="name">PMS Product Pricelist Property Rule</field>
<field name="model_id" ref="model_product_pricelist" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_product_pricelist_item_property_rule" model="ir.rule">
<field name="name">PMS Room Type Class Property Rule</field>
<field name="model_id" ref="model_pms_availability_plan" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_res_users_property_rule" model="ir.rule">
<field name="name">PMS Users Property Rule</field>
<field name="model_id" ref="model_res_users" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_account_move_property_rule" model="ir.rule">
<field name="name">PMS Account Move Property Rule</field>
<field name="model_id" ref="model_account_move" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_id','=',False),('pms_property_id', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_account_journal_property_rule" model="ir.rule">
<field name="name">PMS Account Journal Property Rule</field>
<field name="model_id" ref="model_account_journal" />
<field name="global" eval="True" />
<field name="domain_force">
['|',('pms_property_ids','=',False),('pms_property_ids', 'in',
user.get_active_property_ids())]
</field>
</record>
<record id="pms_folio_rule_portal" model="ir.rule">
<field name="name">Portal Personal Folios</field>
<field name="model_id" ref="model_pms_folio" />
<field name="domain_force">[]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]" />
<field name="perm_read" eval="True" />
</record>
<record id="pms_reservation_rule_portal" model="ir.rule">
<field name="name">Portal Personal Reservation</field>
<field name="model_id" ref="model_pms_reservation" />
<field name="domain_force">[]</field>
<field name="groups" eval="[(4, ref('base.group_portal'))]" />
<field name="perm_read" eval="True" />
</record>
</data>
</odoo>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -3,7 +3,7 @@
<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/" />
<meta name="generator" content="Docutils: http://docutils.sourceforge.net/" />
<title>PMS (Property Management System)</title>
<style type="text/css">
@@ -368,10 +368,10 @@ ul.auto-toc {
!! changes will be overwritten. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -->
<p><a class="reference external" href="https://odoo-community.org/page/development-status"><img alt="Alpha" src="https://img.shields.io/badge/maturity-Alpha-red.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/pms/tree/14.0/pms"><img alt="OCA/pms" src="https://img.shields.io/badge/github-OCA%2Fpms-lightgray.png?logo=github" /></a> <a class="reference external" href="https://translation.odoo-community.org/projects/pms-14-0/pms-14-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/293/14.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
<p>This module is an all-in-one property management system (PMS) focused on medium-sized properties
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>You can manage properties with multi-property and multi-company support, including your rooms inventory,
reservations, check-in, daily reports, board services, rate and availability plans among other property functionalities.</p>
<div class="admonition important">
<p class="first admonition-title">Important</p>
<p class="last">This is an alpha version, the data model and design can change at any time without warning.
@@ -395,17 +395,17 @@ Only for development or testing purpose, do not use in production.
</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>
<p>This module depends on modules <tt class="docutils literal">base</tt>, <tt class="docutils literal">mail</tt>, <tt class="docutils literal">sale</tt> and <tt class="docutils literal">multi_pms_properties</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>You will find the hotel settings in PMS Management &gt; Configuration &gt; Properties &gt; Your Property.</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>
<p>To use this module, please, read the complete user guide at <a class="reference external" href="roomdoo.com">roomdoo.com</a>.</p>
</div>
<div class="section" id="bug-tracker">
<h1><a class="toc-backref" href="#id4">Bug Tracker</a></h1>
@@ -420,19 +420,23 @@ If you spotted it first, help us smashing it by providing a detailed and welcome
<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>
<li>Commit [Sun]</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>
<li><cite>Commit [Sun] &lt;https://www.commitsun.com&gt;</cite>:<ul>
<li>Dario Lodeiros</li>
<li>Eric Antones</li>
<li>Sara Lago</li>
<li>Brais Abeijon</li>
<li>Miguel Padin</li>
</ul>
</li>
</ul>
</div>
<div class="section" id="maintainers">

View File

@@ -11,15 +11,14 @@ odoo.define("pms.AbstractWebClient", function (require) {
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
state.pms_pids = utils.get_cookie("pms_pids")
? utils.get_cookie("pms_pids")
: String(current_pms_property_id);
}
var statePmsPropertyIDS = _.map(state.pms_pids.split(","), function (
pms_pid
) {
return parseInt(pms_pid);
return parseInt(pms_pid, 10);
});
var userPmsPropertyIDS = _.map(
session.user_pms_properties.allowed_pms_properties,

View File

@@ -0,0 +1,41 @@
odoo.define("booking.engine.tree", function (require) {
"use strict";
var ListController = require("web.ListController");
var ListView = require("web.ListView");
var viewRegistry = require("web.view_registry");
function renderBookingEngineButton() {
if (this.$buttons) {
var self = this;
this.$buttons.on("click", ".o_button_booking_engine", function () {
self.do_action({
name: "Booking Engine",
type: "ir.actions.act_window",
res_model: "pms.folio.wizard",
target: "new",
views: [[false, "form"]],
context: {is_modal: true},
});
});
}
}
var BookingEngineRequestListController = ListController.extend({
start: function () {
return this._super.apply(this, arguments);
},
buttons_template: "BookingEngineRequestListView.buttons",
renderButtons: function () {
this._super.apply(this, arguments);
renderBookingEngineButton.apply(this, arguments);
},
});
var BookingEngineRequestListView = ListView.extend({
config: _.extend({}, ListView.prototype.config, {
Controller: BookingEngineRequestListController,
}),
});
viewRegistry.add("pms_booking_engine_request_tree", BookingEngineRequestListView);
});

View File

@@ -3,9 +3,8 @@ odoo.define("pms.session", function (require) {
var Session = require("web.Session");
var utils = require("web.utils");
var modules = odoo._modules;
var inherited_Session = Session.extend({
Session.include({
// TODO: require test and debug
setPmsProperties: function (pms_main_property_id, pms_property_ids) {
var hash = $.bbq.getState();
@@ -24,12 +23,4 @@ odoo.define("pms.session", function (require) {
location.reload();
},
});
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,32 +0,0 @@
odoo.define("pms.ListController", function (require) {
"use strict";
/*
* Pms
* GNU Public License
* Alexandre Díaz <dev@redneboa.es>
*/
var ListController = require("web.ListController");
var Core = require("web.core");
var _t = Core._t;
ListController.include({
renderButtons: function () {
this._super.apply(this, arguments); // Sets this.$buttons
var self = this;
if (this.modelName === "pms.reservation") {
this.$buttons.append(
"<button class='btn btn-sm oe_open_reservation_wizard oe_highlight' type='button'>" +
_t("Open Wizard") +
"</button>"
);
this.$buttons
.find(".oe_open_reservation_wizard")
.on("click", function () {
self.do_action("pms.open_wizard_reservations");
});
}
},
});
});

View File

@@ -8,13 +8,10 @@ odoo.define("web.SwitchPmsMenu", function (require) {
*/
var config = require("web.config");
// Var core = require("web.core");
var session = require("pms.session");
var session = require("web.session");
var SystrayMenu = require("web.SystrayMenu");
var Widget = require("web.Widget");
// Var _t = core._t;
var SwitchPmsMenu = Widget.extend({
template: "SwitchPmsMenu",
events: {
@@ -50,7 +47,7 @@ odoo.define("web.SwitchPmsMenu", function (require) {
)
.split(",")
.map(function (id) {
return parseInt(id);
return parseInt(id, 10);
});
this.user_pms_properties =
session.user_pms_properties.allowed_pms_properties;
@@ -74,9 +71,9 @@ odoo.define("web.SwitchPmsMenu", function (require) {
*/
_onSwitchPmsPropertyClick: function (ev) {
if (
ev.type == "keydown" &&
ev.which != $.ui.keyCode.ENTER &&
ev.which != $.ui.keyCode.SPACE
ev.type === "keydown" &&
ev.which !== $.ui.keyCode.ENTER &&
ev.which !== $.ui.keyCode.SPACE
) {
return;
}
@@ -124,9 +121,9 @@ odoo.define("web.SwitchPmsMenu", function (require) {
*/
_onTogglePmsPropertyClick: function (ev) {
if (
ev.type == "keydown" &&
ev.which != $.ui.keyCode.ENTER &&
ev.which != $.ui.keyCode.SPACE
ev.type === "keydown" &&
ev.which !== $.ui.keyCode.ENTER &&
ev.which !== $.ui.keyCode.SPACE
) {
return;
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates>
<t t-extend="ListView.buttons" t-name="BookingEngineRequestListView.buttons">
<t t-jquery="button.o_list_button_add" t-operation="after">
<button type="button" class="btn btn-primary o_button_booking_engine">
Booking Engine
</button>
</t>
</t>
</templates>

View File

@@ -25,3 +25,18 @@ from . import test_pms_pricelist_priority
from . import test_pms_checkin_partner
from . import test_pms_sale_channel
from . import test_pms_folio
from . import test_pms_availability_plan_rules
from . import test_pms_room_type
from . import test_pms_room_type_class
from . import test_pms_board_service
from . import test_pms_wizard_massive_changes
from . import test_pms_wizard_folio
from . import test_pms_res_users
from . import test_pms_amenity
from . import test_pms_room
from . import test_pms_board_service_line
from . import test_pms_board_service_room_type
from . import test_pms_board_service_room_type_line
from . import test_pms_folio_invoice
from . import test_pms_folio_sale_line
from . import test_pms_wizard_split_join_swap_reservation

View File

@@ -1,63 +1,29 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
# 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/>.
#
##############################################################################
import logging
from odoo.tests import common
_logger = logging.getLogger(__name__)
class TestHotel(common.SavepointCase):
@classmethod
def _init_mock_hotel(cls):
return True
@classmethod
def setUpClass(cls):
super(TestHotel, cls).setUpClass()
cls._init_mock_hotel()
# Create Tests Records
cls.main_hotel_property = cls.env.ref("pms.main_pms_property")
cls.demo_hotel_property = cls.env.ref("pms.demo_pms_property")
cls.room_type_0 = cls.env.ref("pms.pms_room_type_0")
cls.room_type_1 = cls.env.ref("pms.pms_room_type_1")
cls.room_type_2 = cls.env.ref("pms.pms_room_type_2")
cls.room_type_3 = cls.env.ref("pms.pms_room_type_3")
cls.demo_room_type_0 = cls.env.ref("pms.demo_pms_room_type_0")
cls.demo_room_type_1 = cls.env.ref("pms.demo_pms_room_type_1")
cls.room_0 = cls.env.ref("pms.pms_room_0")
cls.room_1 = cls.env.ref("pms.pms_room_1")
cls.room_2 = cls.env.ref("pms.pms_room_2")
cls.room_3 = cls.env.ref("pms.pms_room_3")
cls.room_4 = cls.env.ref("pms.pms_room_4")
cls.room_5 = cls.env.ref("pms.pms_room_5")
cls.room_6 = cls.env.ref("pms.pms_room_6")
cls.list0 = cls.env.ref("product.list0")
cls.list1 = cls.env["product.pricelist"].create(
{"name": "Test Pricelist", "pricelist_type": ""}
class TestPms(common.SavepointCase):
def setUp(self):
super().setUp()
self.pricelist1 = self.env["product.pricelist"].create(
{
"name": "Pricelist 1",
}
)
self.company1 = self.env["res.company"].create(
{
"name": "Company 1",
}
)
self.pms_property1 = self.env["pms.property"].create(
{
"name": "Property 1",
"company_id": self.company1.id,
"default_pricelist_id": self.pricelist1.id,
}
)
self.room_type_class1 = self.env["pms.room.type.class"].create(
{
"name": "Room Type Class 1",
"default_code": "RTC1",
}
)

View File

@@ -0,0 +1,71 @@
from odoo.exceptions import UserError
from .common import TestPms
class TestPmsAmenity(TestPms):
def setUp(self):
super().setUp()
# Create two properties
# +-----------+-----------+
# | Properties |
# +-----------+-----------+
# | Property2 - Property3 |
# +-----------+-----------+
self.pms_property2 = self.env["pms.property"].create(
{
"name": "Pms_property_test2",
"company_id": self.company1.id,
"default_pricelist_id": self.pricelist1.id,
}
)
self.pms_property3 = self.env["pms.property"].create(
{
"name": "Pms_property_test3",
"company_id": self.company1.id,
"default_pricelist_id": self.pricelist1.id,
}
)
def test_property_not_allowed(self):
# Creation of a Amenity with Properties incompatible with it Amenity Type
# +-----------------------------------+-----------------------------------+
# | Amenity Type (TestAmenityType1) | Amenity (TestAmenity1) |
# +-----------------------------------+-----------------------------------+
# | Property1 - Property2 | Property1 - Property2 - Property3 |
# +-----------------------------------+-----------------------------------+
# ARRANGE
AmenityType = self.env["pms.amenity.type"]
Amenity = self.env["pms.amenity"]
amenity_type1 = AmenityType.create(
{
"name": "TestAmenityType1",
"pms_property_ids": [
(4, self.pms_property1.id),
(4, self.pms_property2.id),
],
}
)
# ACT & ASSERT
with self.assertRaises(UserError), self.cr.savepoint():
Amenity.create(
{
"name": "TestAmenity1",
"pms_amenity_type_id": amenity_type1.id,
"pms_property_ids": [
(
6,
0,
[
self.pms_property1.id,
self.pms_property2.id,
self.pms_property3.id,
],
)
],
}
)

View File

@@ -0,0 +1,814 @@
import datetime
from freezegun import freeze_time
from odoo import fields
from odoo.exceptions import UserError, ValidationError
from odoo.tests import common
@freeze_time("1980-01-01")
class TestPmsRoomTypeAvailabilityRules(common.SavepointCase):
def create_common_scenario(self):
self.test_pricelist2 = self.env["product.pricelist"].create(
{
"name": "test pricelist 2",
}
)
self.test_property1 = self.env["pms.property"].create(
{
"name": "Property 1",
"company_id": self.env.ref("base.main_company").id,
"default_pricelist_id": self.test_pricelist2.id,
}
)
self.test_property2 = self.env["pms.property"].create(
{
"name": "Property 2",
"company_id": self.env.ref("base.main_company").id,
"default_pricelist_id": self.test_pricelist2.id,
}
)
self.test_pricelist1 = self.env["product.pricelist"].create(
{
"name": "test pricelist 1",
"pms_property_ids": [
(4, self.test_property1.id),
(4, self.test_property2.id),
],
}
)
# pms.availability.plan
self.test_room_type_availability1 = self.env["pms.availability.plan"].create(
{
"name": "Availability plan for TEST",
"pms_pricelist_ids": [(6, 0, [self.test_pricelist1.id])],
}
)
# SEQUENCES
self.folio_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Folio",
"code": "pms.folio",
"padding": 4,
"company_id": self.env.ref("base.main_company").id,
}
)
self.reservation_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Reservation",
"code": "pms.reservation",
"padding": 4,
"company_id": self.env.ref("base.main_company").id,
}
)
self.checkin_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Checkin",
"code": "pms.checkin.partner",
"padding": 4,
"company_id": self.env.ref("base.main_company").id,
}
)
# pms.property
self.test_property = self.env["pms.property"].create(
{
"name": "MY PMS TEST",
"company_id": self.env.ref("base.main_company").id,
"default_pricelist_id": self.test_pricelist1.id,
"folio_sequence_id": self.folio_sequence.id,
"reservation_sequence_id": self.reservation_sequence.id,
"checkin_sequence_id": self.checkin_sequence.id,
}
)
# pms.room.type.class
self.test_room_type_class = self.env["pms.room.type.class"].create(
{"name": "Room", "default_code": "ROOM"}
)
# pms.room.type
self.test_room_type_single = self.env["pms.room.type"].create(
{
"pms_property_ids": [self.test_property.id],
"name": "Single Test",
"default_code": "SNG_Test",
"class_id": self.test_room_type_class.id,
}
)
# pms.room.type
self.test_room_type_double = self.env["pms.room.type"].create(
{
"pms_property_ids": [
(4, self.test_property.id),
],
"name": "Double Test",
"default_code": "DBL_Test",
"class_id": self.test_room_type_class.id,
}
)
# pms.room
self.test_room1_double = self.env["pms.room"].create(
{
"pms_property_id": self.test_property.id,
"name": "Double 201 test",
"room_type_id": self.test_room_type_double.id,
"capacity": 2,
}
)
# pms.room
self.test_room2_double = self.env["pms.room"].create(
{
"pms_property_id": self.test_property.id,
"name": "Double 202 test",
"room_type_id": self.test_room_type_double.id,
"capacity": 2,
}
)
# pms.room
# self.test_room3_double = self.env["pms.room"].create(
# {
# "pms_property_id": self.test_property.id,
# "name": "Double 203 test",
# "room_type_id": self.test_room_type_double.id,
# "capacity": 2,
# }
# )
# # pms.room
# self.test_room4_double = self.env["pms.room"].create(
# {
# "pms_property_id": self.test_property.id,
# "name": "Double 204 test",
# "room_type_id": self.test_room_type_double.id,
# "capacity": 2,
# }
# )
# pms.room
self.test_room1_single = self.env["pms.room"].create(
{
"pms_property_id": self.test_property.id,
"name": "Single 101 test",
"room_type_id": self.test_room_type_single.id,
"capacity": 1,
}
)
# pms.room
self.test_room2_single = self.env["pms.room"].create(
{
"pms_property_id": self.test_property.id,
"name": "Single 102 test",
"room_type_id": self.test_room_type_single.id,
"capacity": 1,
}
)
# partner
self.partner1 = self.env["res.partner"].create({"name": "Charles"})
def create_scenario_multiproperty(self):
self.create_common_scenario()
self.test_property3 = self.env["pms.property"].create(
{
"name": "Property 3",
"company_id": self.env.ref("base.main_company").id,
"default_pricelist_id": self.test_pricelist2.id,
"folio_sequence_id": self.folio_sequence.id,
"reservation_sequence_id": self.reservation_sequence.id,
"checkin_sequence_id": self.checkin_sequence.id,
}
)
self.availability_multiproperty = self.env["pms.availability.plan"].create(
{
"name": "Availability plan for TEST",
"pms_pricelist_ids": [(6, 0, [self.test_pricelist1.id])],
"pms_property_ids": [
(4, self.test_property1.id),
(4, self.test_property2.id),
],
}
)
def test_availability_rooms_all(self):
# TEST CASE
# get availability withouth rules
# ARRANGE
self.create_common_scenario()
checkin = fields.date.today()
checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date()
test_rooms_double_rooms = self.env["pms.room"].search(
[("pms_property_id", "=", self.test_property.id)]
)
# ACT
result = self.env["pms.availability.plan"].rooms_available(
checkin=checkin,
checkout=checkout,
)
# ASSERT
obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms)
self.assertTrue(
obtained,
"Availability should contain the test rooms"
"because there's no availability rules for them.",
)
def test_availability_rooms_all_lines(self):
# TEST CASE
# get availability withouth rules
# given reservation lines to not consider
# ARRANGE
self.create_common_scenario()
checkin = fields.date.today()
checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date()
test_rooms_double_rooms = self.env["pms.room"].search(
[("pms_property_id", "=", self.test_property.id)]
)
test_reservation = self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": checkin,
"checkout": checkout,
"partner_id": self.partner1.id,
}
)
# ACT
result = self.env["pms.availability.plan"].rooms_available(
checkin=checkin,
checkout=checkout,
current_lines=test_reservation.reservation_line_ids.ids,
)
# ASSERT
obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms)
self.assertTrue(
obtained,
"Availability should contain the test rooms"
"because there's no availability rules for them.",
)
def test_availability_rooms_room_type(self):
# TEST CASE
# get availability withouth rules
# given a room type
# ARRANGE
self.create_common_scenario()
test_rooms_double_rooms = self.env["pms.room"].search(
[
("pms_property_id", "=", self.test_property.id),
("room_type_id", "=", self.test_room_type_double.id),
]
)
# ACT
result = self.env["pms.availability.plan"].rooms_available(
checkin=fields.date.today(),
checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(),
room_type_id=self.test_room_type_double.id,
)
# ASSERT
obtained = all(elem.id in result.ids for elem in test_rooms_double_rooms)
self.assertTrue(
obtained,
"Availability should contain the test rooms"
"because there's no availability rules for them.",
)
def test_availability_closed_no_room_type(self):
# TEST CASE:
# coverage for 2 points:
# 1. without room type, availability rules associated
# with the pricelist are applied
# 2. availability rule "closed" is taken into account
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability_rule1 = self.env[
"pms.availability.plan.rule"
].create(
{
"availability_plan_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True, # <- (1/2)
"pms_property_id": self.test_property.id,
}
)
# ACT
result = self.env["pms.availability.plan"].rooms_available(
checkin=fields.date.today(),
checkout=(fields.datetime.today() + datetime.timedelta(days=4)).date(),
# room_type_id=False, # <- (2/2)
pricelist_id=self.test_pricelist1.id,
)
# ASSERT
self.assertNotIn(
self.test_room_type_double,
result.mapped("room_type_id"),
"Availability should not contain rooms of a type "
"which its availability rules applies",
)
def test_availability_rules(self):
# TEST CASE
# the availability should take into acount availability rules:
# closed_arrival, closed_departure, min_stay, max_stay,
# min_stay_arrival, max_stay_arrival
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability_rule1 = self.env[
"pms.availability.plan.rule"
].create(
{
"availability_plan_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": (fields.datetime.today() + datetime.timedelta(days=0)).date(),
"pms_property_id": self.test_property.id,
}
)
checkin = fields.date.today()
checkout = (fields.datetime.today() + datetime.timedelta(days=4)).date()
test_cases = [
{
"closed": False,
"closed_arrival": True,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": True,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkout,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 5,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 2,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 5,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 3,
"quota": -1,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": 0,
"max_avail": -1,
"date": checkin,
},
{
"closed": False,
"closed_arrival": False,
"closed_departure": False,
"min_stay": 0,
"max_stay": 0,
"min_stay_arrival": 0,
"max_stay_arrival": 0,
"quota": -1,
"max_avail": 0,
"date": checkin,
},
]
for test_case in test_cases:
with self.subTest(k=test_case):
# ACT
self.test_room_type_availability_rule1.write(test_case)
result = self.env["pms.availability.plan"].rooms_available(
checkin=checkin,
checkout=checkout,
room_type_id=self.test_room_type_double.id,
pricelist_id=self.test_pricelist1.id,
)
# ASSERT
self.assertNotIn(
self.test_room_type_double,
result.mapped("room_type_id"),
"Availability should not contain rooms of a type "
"which its availability rules applies",
)
@freeze_time("1980-11-01")
def test_rule_on_create_reservation(self):
# TEST CASE
# an availability rule should be applied that would prevent the
# creation of reservations
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability_rule1 = self.env[
"pms.availability.plan.rule"
].create(
{
"availability_plan_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True,
"pms_property_id": self.test_property.id,
}
)
checkin = datetime.datetime.now()
checkout = datetime.datetime.now() + datetime.timedelta(days=4)
# ACT & ASSERT
with self.assertRaises(
ValidationError,
msg="Availability rules should be applied that would"
" prevent the creation of the reservation.",
):
self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": checkin,
"checkout": checkout,
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
"partner_id": self.partner1.id,
}
)
@freeze_time("1980-11-01")
def test_rules_on_create_splitted_reservation(self):
# TEST CASE
# an availability rule should be applied that would prevent the
# creation of reservations including splitted reservations.
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability_rule1 = self.env[
"pms.availability.plan.rule"
].create(
{
"availability_plan_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True,
"pms_property_id": self.test_property.id,
}
)
checkin_test = datetime.datetime.now()
checkout_test = datetime.datetime.now() + datetime.timedelta(days=4)
self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": datetime.datetime.now(),
"checkout": datetime.datetime.now() + datetime.timedelta(days=2),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"preferred_room_id": self.test_room1_double.id,
"partner_id": self.partner1.id,
}
)
self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": datetime.datetime.now() + datetime.timedelta(days=2),
"checkout": datetime.datetime.now() + datetime.timedelta(days=4),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"preferred_room_id": self.test_room2_double.id,
"partner_id": self.partner1.id,
}
)
# ACT & ASSERT
with self.assertRaises(
ValidationError,
msg="Availability rule should be applied that would"
" prevent the creation of splitted reservation.",
):
self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": checkin_test,
"checkout": checkout_test,
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
"partner_id": self.partner1.id,
}
)
@freeze_time("1980-11-01")
def test_rule_update_quota_on_create_reservation(self):
# TEST CASE
# quota rule is changed after creating a reservation
# with pricelist linked to a availability plan that applies
# ARRANGE
self.create_common_scenario()
self.test_room_type_availability_rule1 = self.env[
"pms.availability.plan.rule"
].create(
{
"availability_plan_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": datetime.date.today(),
"quota": 1,
"pms_property_id": self.test_property.id,
}
)
self.test_pricelist1.pms_property_ids = [
(4, self.test_property1.id),
(4, self.test_property2.id),
(4, self.test_property.id),
]
r1 = self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": datetime.date.today(),
"checkout": datetime.date.today() + datetime.timedelta(days=1),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
"partner_id": self.partner1.id,
}
)
r1.flush()
with self.assertRaises(
ValidationError,
msg="The quota shouldnt be enough to create a new reservation",
):
self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": datetime.date.today(),
"checkout": datetime.date.today() + datetime.timedelta(days=1),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
"partner_id": self.partner1.id,
}
)
@freeze_time("1980-11-01")
def test_rule_update_quota_on_update_reservation(self):
# TEST CASE
# quota rule is restored after creating a reservation
# with pricelist linked to a availability rule that applies
# and then modify the pricelist of the reservation and
# no rules applies
# ARRANGE
self.create_common_scenario()
test_quota = 2
test_pricelist2 = self.env["product.pricelist"].create(
{
"name": "test pricelist 2",
}
)
self.test_pricelist1.pms_property_ids = [
(4, self.test_property1.id),
(4, self.test_property2.id),
(4, self.test_property.id),
]
rule = self.env["pms.availability.plan.rule"].create(
{
"availability_plan_id": self.test_room_type_availability1.id,
"room_type_id": self.test_room_type_double.id,
"date": datetime.date.today(),
"quota": test_quota,
"pms_property_id": self.test_property.id,
}
)
reservation = self.env["pms.reservation"].create(
{
"pms_property_id": self.test_property.id,
"checkin": datetime.date.today(),
"checkout": datetime.date.today() + datetime.timedelta(days=1),
"adults": 2,
"room_type_id": self.test_room_type_double.id,
"pricelist_id": self.test_pricelist1.id,
"partner_id": self.partner1.id,
}
)
# ACT
reservation.pricelist_id = test_pricelist2.id
reservation.flush()
self.assertEqual(
test_quota,
rule.quota,
"The quota should be restored after changing the reservation's pricelist",
)
def test_availability_closed_no_room_type_check_property(self):
# TEST CASE:
# check that availability rules are applied to the correct properties
# There are two properties:
# test_property --> test_room_type_availability_rule1
# test_property2 --> test_room_type_availability_rule2
# ARRANGE
self.create_scenario_multiproperty()
self.test_room_type_special = self.env["pms.room.type"].create(
{
"pms_property_ids": [
(4, self.test_property1.id),
(4, self.test_property2.id),
],
"name": "Special Room Test",
"default_code": "SP_Test",
"class_id": self.test_room_type_class.id,
}
)
self.test_room1 = self.env["pms.room"].create(
{
"pms_property_id": self.test_property1.id,
"name": "Double 201 test",
"room_type_id": self.test_room_type_special.id,
"capacity": 2,
}
)
# pms.room
self.test_room2 = self.env["pms.room"].create(
{
"pms_property_id": self.test_property2.id,
"name": "Double 202 test",
"room_type_id": self.test_room_type_special.id,
"capacity": 2,
}
)
self.test_room_type_availability_rule1 = self.env[
"pms.availability.plan.rule"
].create(
{
"availability_plan_id": self.availability_multiproperty.id,
"room_type_id": self.test_room_type_special.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True,
"pms_property_id": self.test_property1.id,
}
)
self.test_room_type_availability_rule2 = self.env[
"pms.availability.plan.rule"
].create(
{
"availability_plan_id": self.availability_multiproperty.id,
"room_type_id": self.test_room_type_special.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"pms_property_id": self.test_property2.id,
}
)
# check that for that date test_property1 doesnt have rooms available
# (of that type:test_room_type_double),
# instead, property2 has test_room_type_double available
properties = [
{"property": self.test_property1.id, "value": False},
{"property": self.test_property2.id, "value": True},
]
for p in properties:
with self.subTest(k=p):
# ACT
rooms_avail = self.env["pms.availability.plan"].rooms_available(
checkin=fields.date.today(),
checkout=(
fields.datetime.today() + datetime.timedelta(days=2)
).date(),
room_type_id=self.test_room_type_special.id,
pricelist_id=self.test_pricelist1.id,
pms_property_id=p["property"],
)
# ASSERT
self.assertEqual(
len(rooms_avail) > 0, p["value"], "Availability is not correct"
)
def test_check_property_availability_room_type(self):
# TEST CASE:
# check integrity between availability properties and room_type properties
# ARRANGE
self.create_scenario_multiproperty()
# create new room_type
self.test_room_type_special = self.env["pms.room.type"].create(
{
"pms_property_ids": [
(4, self.test_property1.id),
(4, self.test_property3.id),
],
"name": "Special Room Test",
"default_code": "SP_Test",
"class_id": self.test_room_type_class.id,
}
)
# ACT
self.availability_example = self.env["pms.availability.plan"].create(
{
"name": "Availability plan for TEST",
"pms_pricelist_ids": [(6, 0, [self.test_pricelist1.id])],
"pms_property_ids": [
(4, self.test_property1.id),
(4, self.test_property2.id),
],
}
)
self.availability_rule1 = self.env["pms.availability.plan.rule"].create(
{
"availability_plan_id": self.availability_example.id,
"room_type_id": self.test_room_type_special.id,
"date": (fields.datetime.today() + datetime.timedelta(days=2)).date(),
"closed": True,
"pms_property_id": self.test_property1.id,
}
)
# Test cases when creating a availability_rule
# Allowed properties:
# Room Type(test_room_type_special) -->TEST_PROPERTY1 TEST_PROPERTY3
# Availability Plan(availability_example)-->TEST_PROPERTY1 TEST_PROPERTY2
# Both cases throw an exception:
# 1:Rule for property2,
# it is allowed in availability_plan but not in room_type
# 2:Rule for property3,
# it is allowed in room_type, but not in availability_plan
test_cases = [
{
"pms_property_id": self.test_property2.id,
},
{
"pms_property_id": self.test_property3.id,
},
]
# ASSERT
for test_case in test_cases:
with self.subTest(k=test_case):
with self.assertRaises(UserError):
self.availability_rule1.pms_property_id = test_case[
"pms_property_id"
]

View File

@@ -0,0 +1,423 @@
# Copyright 2021 Eric Antones <eantones@nuobit.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo.exceptions import ValidationError
from .common import TestPms
class TestBoardService(TestPms):
def setUp(self):
super().setUp()
self.company2 = self.env["res.company"].create(
{
"name": "Company 2",
}
)
self.pms_property3 = self.env["pms.property"].create(
{
"name": "Property 3",
"company_id": self.company2.id,
"default_pricelist_id": self.pricelist1.id,
}
)
# external integrity
def test_external_case_01(self):
"""
PRE: - board service bs1 exists
- board_service1 has code c1
- board_service1 has pms_property1
- pms_property1 has company company1
ACT: - create a new board_service2
- board_service2 has code c1
- board_service2 has pms_property1
- pms_property1 has company company1
POST: - Integrity error: the room type already exists
- board_service2 not created
"""
# ARRANGE
# board_service1
self.env["pms.board.service"].create(
{
"name": "Board service bs1",
"default_code": "c1",
"pms_property_ids": [(6, 0, [self.pms_property1.id])],
}
)
# ACT & ASSERT
with self.assertRaises(
ValidationError, msg="The board service has been created and it shouldn't"
):
# board_service2
self.env["pms.board.service"].create(
{
"name": "Board service bs2",
"default_code": "c1",
"pms_property_ids": [(6, 0, [self.pms_property1.id])],
}
)
def test_external_case_02(self):
"""
PRE: - board service bs1 exists
- board_service1 has code c1
- board_service1 has property pms_property1
- pms_property1 has company company1
ACT: - create a new board_service2
- board_service2 has code c1
- board_service2 has property pms_property1, pms_property2,
pms_property3
- pms_property1, pms_property2 has company company1
- pms_property3 has company company2
POST: - Integrity error: the board service already exists
- board_service2 not created
"""
# ARRANGE
self.pms_property2 = self.env["pms.property"].create(
{
"name": "Property 2",
"company_id": self.company1.id,
"default_pricelist_id": self.pricelist1.id,
}
)
# board_service1
self.env["pms.board.service"].create(
{
"name": "Board service 1",
"default_code": "c1",
"pms_property_ids": [(6, 0, [self.pms_property1.id])],
}
)
# ACT & ASSERT
with self.assertRaises(
ValidationError, msg="The board service has been created and it shouldn't"
):
# board_service2
self.env["pms.board.service"].create(
{
"name": "Board service bs2",
"default_code": "c1",
"pms_property_ids": [
(
6,
0,
[
self.pms_property1.id,
self.pms_property2.id,
self.pms_property3.id,
],
)
],
}
)
def test_single_case_01(self):
"""
PRE: - board service bs1 exists
- board_service1 has code c1
- board_service1 has 2 properties pms_property1 and pms_property2
- pms_property_1 and pms_property2 have the same company company1
ACT: - search board service with code c1 and pms_property1
- pms_property1 has company company1
POST: - only board_service1 board service found
"""
# ARRANGE
board_service1 = self.env["pms.board.service"].create(
{
"name": "Board service 1",
"default_code": "c1",
"pms_property_ids": [
(6, 0, [self.pms_property1.id, self.pms_property3.id])
],
}
)
# ACT
board_services = self.env["pms.board.service"].get_unique_by_property_code(
self.pms_property1.id, "c1"
)
# ASSERT
self.assertEqual(
board_services.id,
board_service1.id,
"Expected board service not found",
)
def test_single_case_02(self):
"""
PRE: - board service bs1 exists
- board_service1 has code c1
- board_service1 has 2 properties pms_property1 and pms_property3
- pms_property1 and pms_property2 have different companies
- pms_property1 have company company1 and pms_property3 have company2
ACT: - search board service with code c1 and property pms_property1
- pms_property1 has company company1
POST: - only board_service1 room type found
"""
# ARRANGE
bs1 = self.env["pms.board.service"].create(
{
"name": "Board service 1",
"default_code": "c1",
"pms_property_ids": [
(6, 0, [self.pms_property1.id, self.pms_property3.id])
],
}
)
# ACT
board_services = self.env["pms.board.service"].get_unique_by_property_code(
self.pms_property1.id, "c1"
)
# ASSERT
self.assertEqual(board_services.id, bs1.id, "Expected board service not found")
def test_single_case_03(self):
"""
PRE: - board_service1 exists
- board_service1 has code c1
- board_service1 with 2 properties pms_property1 and pms_property2
- pms_property1 and pms_property2 have same company company1
ACT: - search board service with code c1 and property pms_property3
- pms_property3 have company company2
POST: - no room type found
"""
# ARRANGE
self.pms_property2 = self.env["pms.property"].create(
{
"name": "Property 2",
"company_id": self.company1.id,
"default_pricelist_id": self.pricelist1.id,
}
)
# board_service1
self.env["pms.board.service"].create(
{
"name": "Board service 1",
"default_code": "c1",
"pms_property_ids": [
(6, 0, [self.pms_property1.id, self.pms_property2.id])
],
}
)
# ACT
board_services = self.env["pms.board.service"].get_unique_by_property_code(
self.pms_property3.id, "c1"
)
# ASSERT
self.assertFalse(
board_services, "Board service found but it should not have found any"
)
def test_single_case_04(self):
"""
PRE: - board_service1 exists
- board_service1 has code c1
- board_service1 properties are null
ACT: - search board service with code c1 and property pms_property1
- pms_property1 have company company1
POST: - only board_service1 board service found
"""
# ARRANGE
board_service1 = self.env["pms.board.service"].create(
{
"name": "Board service 1",
"default_code": "c1",
"pms_property_ids": False,
}
)
# ACT
board_services = self.env["pms.board.service"].get_unique_by_property_code(
self.pms_property1.id, "c1"
)
# ASSERT
self.assertEqual(
board_services.id,
board_service1.id,
"Expected board service not found",
)
# tests with more than one board service
def test_multiple_case_01(self):
"""
PRE: - board_service1 exists
- board_service1 has code c1
- board_service1 has 2 properties pms_property1 and pms_property2
- pms_property1 and pms_property2 have the same company company1
- board service board_service2 exists
- board_service2 has code c1
- board_service2 has no properties
ACT: - search board service with code c1 and property pms_property1
- pms_property1 have company company1
POST: - only board_service1 board service found
"""
# ARRANGE
board_service1 = self.env["pms.board.service"].create(
{
"name": "Board service 1",
"default_code": "c1",
"pms_property_ids": [
(6, 0, [self.pms_property1.id, self.pms_property3.id])
],
}
)
# board_service2
self.env["pms.board.service"].create(
{
"name": "Board service bs2",
"default_code": "c1",
"pms_property_ids": False,
}
)
# ACT
board_services = self.env["pms.board.service"].get_unique_by_property_code(
self.pms_property1.id, "c1"
)
# ASSERT
self.assertEqual(
board_services.id,
board_service1.id,
"Expected board service not found",
)
def test_multiple_case_02(self):
"""
PRE: - board_service1 exists
- board_service1 has code c1
- board_service1 has property pms_property1
- pms_property1 have the company company1
- board service board_service2 exists
- board_service2 has code c1
- board_service2 has no properties
ACT: - search board service with code c1 and pms_property2
- pms_property2 have company company1
POST: - only board_service1 board service found
"""
# ARRANGE
self.pms_property2 = self.env["pms.property"].create(
{
"name": "Property 2",
"company_id": self.company1.id,
"default_pricelist_id": self.pricelist1.id,
}
)
# board_service1
self.env["pms.board.service"].create(
{
"name": "Board service 1",
"default_code": "c1",
"pms_property_ids": [(6, 0, [self.pms_property1.id])],
}
)
board_service2 = self.env["pms.board.service"].create(
{
"name": "Board service bs2",
"default_code": "c1",
"pms_property_ids": False,
}
)
# ACT
board_services = self.env["pms.board.service"].get_unique_by_property_code(
self.pms_property2.id, "c1"
)
# ASSERT
self.assertEqual(
board_services.id,
board_service2.id,
"Expected board service not found",
)
def test_multiple_case_03(self):
"""
PRE: - board_service1 exists
- board_service1 has code c1
- board_service1 has property pms_property1
- pms_property1 have the company company1
- board service board_service2 exists
- board_service2 has code c1
- board_service2 has no properties
ACT: - search board service with code c1 and property pms_property3
- pms_property3 have company company2
POST: - only board_service2 board service found
"""
# ARRANGE
# board_service1
self.env["pms.board.service"].create(
{
"name": "Board service bs1",
"default_code": "c1",
"pms_property_ids": [(6, 0, [self.pms_property1.id])],
}
)
board_service2 = self.env["pms.board.service"].create(
{
"name": "Board service bs2",
"default_code": "c1",
"pms_property_ids": False,
}
)
# ACT
board_services = self.env["pms.board.service"].get_unique_by_property_code(
self.pms_property3.id, "c1"
)
# ASSERT
self.assertEqual(
board_services.id,
board_service2.id,
"Expected board service not found",
)
def test_multiple_case_04(self):
"""
PRE: - board_service1 exists
- board_service1 has code c1
- board_service1 has property pms_property1
- pms_property1 have the company company1
- room type board_service2 exists
- board_service2 has code c1
- board_service2 has no properties
ACT: - search board service with code c1 and property pms_property3
- pms_property3 have company company2
POST: - r2 board service found
"""
# ARRANGE
# board_service1
self.env["pms.board.service"].create(
{
"name": "Board service 1",
"default_code": "c1",
"pms_property_ids": [(6, 0, [self.pms_property1.id])],
}
)
board_service2 = self.env["pms.board.service"].create(
{
"name": "Board service bs2",
"default_code": "c1",
"pms_property_ids": False,
}
)
# ACT
board_services = self.env["pms.board.service"].get_unique_by_property_code(
self.pms_property3.id, "c1"
)
# ASSERT
self.assertEqual(
board_services.id, board_service2.id, "Expected room type not found"
)

View File

@@ -0,0 +1,75 @@
from odoo.exceptions import UserError
from odoo.tests import common
class TestPmsBoardService(common.SavepointCase):
def test_property_integrity(self):
self.company1 = self.env["res.company"].create(
{
"name": "Pms_Company_Test",
}
)
self.folio_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Folio",
"code": "pms.folio",
"padding": 4,
"company_id": self.company1.id,
}
)
self.reservation_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Reservation",
"code": "pms.reservation",
"padding": 4,
"company_id": self.company1.id,
}
)
self.checkin_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Checkin",
"code": "pms.checkin.partner",
"padding": 4,
"company_id": self.company1.id,
}
)
self.property1 = self.env["pms.property"].create(
{
"name": "Pms_property_test1",
"company_id": self.company1.id,
"default_pricelist_id": self.env.ref("product.list0").id,
"folio_sequence_id": self.folio_sequence.id,
"reservation_sequence_id": self.reservation_sequence.id,
"checkin_sequence_id": self.checkin_sequence.id,
}
)
self.property2 = self.env["pms.property"].create(
{
"name": "Pms_property_test2",
"company_id": self.company1.id,
"default_pricelist_id": self.env.ref("product.list0").id,
"folio_sequence_id": self.folio_sequence.id,
"reservation_sequence_id": self.reservation_sequence.id,
"checkin_sequence_id": self.checkin_sequence.id,
}
)
self.product = self.env["product.product"].create(
{"name": "Product", "pms_property_ids": self.property1}
)
self.board_service = self.env["pms.board.service"].create(
{
"name": "Board Service",
"default_code": "CB",
}
)
with self.assertRaises(UserError):
board_service_line = self.board_service_line = self.env[
"pms.board.service.line"
].create(
{
"product_id": self.product.id,
"pms_board_service_id": self.board_service.id,
}
)
board_service_line.pms_property_ids = [self.property2.id]

View File

@@ -0,0 +1,69 @@
from odoo.tests import common
class TestPmsBoardServiceRoomType(common.SavepointCase):
def _create_common_scenario(self):
self.company1 = self.env["res.company"].create(
{
"name": "Pms_Company_Test",
}
)
self.folio_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Folio",
"code": "pms.folio",
"padding": 4,
"company_id": self.company1.id,
}
)
self.reservation_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Reservation",
"code": "pms.reservation",
"padding": 4,
"company_id": self.company1.id,
}
)
self.checkin_sequence = self.env["ir.sequence"].create(
{
"name": "PMS Checkin",
"code": "pms.checkin.partner",
"padding": 4,
"company_id": self.company1.id,
}
)
self.property1 = self.env["pms.property"].create(
{
"name": "Pms_property_test1",
"company_id": self.company1.id,
"default_pricelist_id": self.env.ref("product.list0").id,
"folio_sequence_id": self.folio_sequence.id,
"reservation_sequence_id": self.reservation_sequence.id,
"checkin_sequence_id": self.checkin_sequence.id,
}
)
self.property2 = self.env["pms.property"].create(
{
"name": "Pms_property_test2",
"company_id": self.company1.id,
"default_pricelist_id": self.env.ref("product.list0").id,
"folio_sequence_id": self.folio_sequence.id,
"reservation_sequence_id": self.reservation_sequence.id,
"checkin_sequence_id": self.checkin_sequence.id,
}
)
self.board_service = self.env["pms.board.service"].create(
{
"name": "Board Service",
}
)
self.room_type_class = self.env["pms.room.type.class"].create(
{"name": "Room Type Class", "default_code": "SIN1"}
)
self.room_type = self.env["pms.room.type"].create(
{
"name": "Room Type",
"default_code": "Type1",
"class_id": self.room_type_class.id,
}
)

Some files were not shown because too many files have changed in this diff Show More