mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
@@ -42,6 +42,7 @@
|
||||
"report/pms_folio.xml",
|
||||
"report/pms_folio_templates.xml",
|
||||
"report/traveller_report_action.xml",
|
||||
"report/invoice.xml",
|
||||
# "templates/pms_email_template.xml",
|
||||
"data/menus.xml",
|
||||
"wizards/wizard_payment_folio.xml",
|
||||
@@ -49,6 +50,7 @@
|
||||
"wizards/pms_booking_engine_views.xml",
|
||||
"wizards/wizard_folio_changes.xml",
|
||||
"wizards/wizard_several_partners.xml",
|
||||
"wizards/pms_booking_duplicate_views.xml",
|
||||
"views/pms_amenity_views.xml",
|
||||
"views/pms_amenity_type_views.xml",
|
||||
"views/pms_board_service_views.xml",
|
||||
@@ -88,6 +90,7 @@
|
||||
"views/precheckin_portal_templates.xml",
|
||||
"wizards/wizard_massive_changes.xml",
|
||||
"wizards/wizard_advanced_filters.xml",
|
||||
"views/res_partner_id_category.xml",
|
||||
"views/payment_transaction_views.xml",
|
||||
"views/account_move_line_views.xml",
|
||||
],
|
||||
|
||||
@@ -363,6 +363,10 @@ class PortalPrecheckin(CustomerPortal):
|
||||
error, error_message = self.form_validate(kw, None)
|
||||
if not kw.get("first") and not kw.get("back") and not error:
|
||||
kw.update({"checkin_partner_id": checkin_partner_id})
|
||||
if kw.get("residence_state_id") == "placeholder":
|
||||
kw["residence_state_id"] = False
|
||||
if kw.get("residence_country_id") == "placeholder":
|
||||
kw["residence_country_id"] = False
|
||||
request.env["pms.checkin.partner"]._save_data_from_portal(kw)
|
||||
if error:
|
||||
checkin_pos = checkin_pos - 1
|
||||
|
||||
@@ -101,5 +101,21 @@
|
||||
<field name="nextcall" eval="DateTime.now()" />
|
||||
<field name="code">model.send_cancelation_mail()</field>
|
||||
</record>
|
||||
<record model="ir.cron" id="autoinvoicing_folios">
|
||||
<field name="name">Auto Invoicing Folios</field>
|
||||
<field name="interval_number">5</field>
|
||||
<field name="user_id" ref="base.user_root" />
|
||||
<field name="active" eval="False" />
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False" />
|
||||
<field name="state">code</field>
|
||||
<field name="model_id" ref="model_pms_property" />
|
||||
<field
|
||||
name="nextcall"
|
||||
eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d 04:30:00')"
|
||||
/>
|
||||
<field name="code">model.autoinvoicing()</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
@@ -20,6 +20,13 @@
|
||||
<field name="checkin_sequence_id" ref="pms.seq_pms_checkin" />
|
||||
<field name="reservation_sequence_id" ref="pms.seq_pms_reservation" />
|
||||
</record>
|
||||
<!-- default partner simplified invoices -->
|
||||
<record id="various_pms_partner" model="res.partner">
|
||||
<field name="name">Various Clients</field>
|
||||
<field
|
||||
name="comment"
|
||||
>Contact used for simplified invoices where no customer is available</field>
|
||||
</record>
|
||||
<!-- pms.users -->
|
||||
<record id="base.user_root" model="res.users">
|
||||
<field name="company_id" ref="base.main_company" />
|
||||
|
||||
@@ -2920,11 +2920,6 @@ msgstr ""
|
||||
msgid "Customer Ref"
|
||||
msgstr ""
|
||||
|
||||
#. module: pms
|
||||
#: model:ir.model.fields,field_description:pms.field_pms_folio__client_order_ref
|
||||
msgid "Customer Reference"
|
||||
msgstr ""
|
||||
|
||||
#. module: pms
|
||||
#: model:ir.model.fields,field_description:pms.field_folio_advance_payment_inv__deposit_taxes_id
|
||||
#: model:ir.model.fields,field_description:pms.field_pms_room_type__taxes_id
|
||||
|
||||
@@ -46,3 +46,4 @@ from . import pms_availability
|
||||
from . import res_partner_id_number
|
||||
from . import pms_automated_mails
|
||||
from . import payment_transaction
|
||||
from . import res_partner_id_category
|
||||
|
||||
@@ -34,40 +34,11 @@ class AccountBankStatement(models.Model):
|
||||
)
|
||||
super(AccountBankStatement, self).button_post()
|
||||
for line in lines_of_moves_to_post:
|
||||
folio_ids = line.folio_ids.ids
|
||||
if folio_ids:
|
||||
to_reconcile_ids = self.env["account.move.line"].search(
|
||||
[
|
||||
("move_id.folio_ids", "in", folio_ids),
|
||||
("reconciled", "=", False),
|
||||
"|",
|
||||
(
|
||||
"account_id",
|
||||
"=",
|
||||
self.journal_id.payment_debit_account_id.id,
|
||||
),
|
||||
(
|
||||
"account_id",
|
||||
"=",
|
||||
self.journal_id.payment_credit_account_id.id,
|
||||
),
|
||||
("journal_id", "=", self.journal_id.id),
|
||||
]
|
||||
)
|
||||
if to_reconcile_ids:
|
||||
statement_move_line = line.move_id.line_ids.filtered(
|
||||
lambda line: line.account_id.reconcile
|
||||
)
|
||||
payment_lines = self.env["account.move.line"].browse(
|
||||
to_reconcile_ids.ids
|
||||
)
|
||||
# We try to reconcile by amount
|
||||
payment_line = False
|
||||
for record in payment_lines:
|
||||
payment_line = (
|
||||
record if abs(record.balance) == line.amount else False
|
||||
)
|
||||
if payment_line and statement_move_line:
|
||||
statement_move_line.account_id = payment_line.account_id
|
||||
lines_to_reconcile = payment_line + statement_move_line
|
||||
lines_to_reconcile.reconcile()
|
||||
payment_move_line = line._get_payment_move_lines_to_reconcile()
|
||||
statement_move_line = line.move_id.line_ids.filtered(
|
||||
lambda line: line.account_id.reconcile
|
||||
)
|
||||
if payment_move_line and statement_move_line:
|
||||
statement_move_line.account_id = payment_move_line.account_id
|
||||
lines_to_reconcile = payment_move_line + statement_move_line
|
||||
lines_to_reconcile.reconcile()
|
||||
|
||||
@@ -48,3 +48,33 @@ class AccountBankStatementLine(models.Model):
|
||||
}
|
||||
)
|
||||
return line_vals_list
|
||||
|
||||
def _get_payment_move_lines_to_reconcile(self):
|
||||
self.ensure_one()
|
||||
payment_move_line = False
|
||||
folio_ids = self.folio_ids and self.folio_ids.ids or False
|
||||
domain = [("move_id.folio_ids", "in", folio_ids)] if folio_ids else []
|
||||
domain.extend(
|
||||
[
|
||||
("move_id.ref", "=", self.payment_ref),
|
||||
("date", "=", self.date),
|
||||
("reconciled", "=", False),
|
||||
"|",
|
||||
(
|
||||
"account_id",
|
||||
"=",
|
||||
self.journal_id.payment_debit_account_id.id,
|
||||
),
|
||||
(
|
||||
"account_id",
|
||||
"=",
|
||||
self.journal_id.payment_credit_account_id.id,
|
||||
),
|
||||
("journal_id", "=", self.journal_id.id),
|
||||
]
|
||||
)
|
||||
to_reconcile_move_lines = self.env["account.move.line"].search(domain)
|
||||
# We try to reconcile by amount
|
||||
for record in to_reconcile_move_lines:
|
||||
payment_move_line = record if record.balance == self.amount else False
|
||||
return payment_move_line
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from odoo import fields, models
|
||||
from odoo import _, api, fields, models
|
||||
|
||||
|
||||
class AccountJournal(models.Model):
|
||||
@@ -20,3 +20,51 @@ class AccountJournal(models.Model):
|
||||
string="For manual payments",
|
||||
help="Use to pay for reservations",
|
||||
)
|
||||
is_simplified_invoice = fields.Boolean(
|
||||
string="Simplified invoice",
|
||||
help="Use to simplified invoice",
|
||||
compute="_compute_is_simplified_invoice",
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
|
||||
@api.depends("pms_property_ids", "pms_property_ids.journal_simplified_invoice_id")
|
||||
def _compute_is_simplified_invoice(self):
|
||||
self.is_simplified_invoice = False
|
||||
for journal in self:
|
||||
if journal.id in journal.pms_property_ids.mapped(
|
||||
"journal_simplified_invoice_id.id"
|
||||
):
|
||||
journal.is_simplified_invoice = True
|
||||
|
||||
@api.constrains("is_simplified_invoice")
|
||||
def _check_pms_properties_simplified_invoice(self):
|
||||
for journal in self:
|
||||
if (
|
||||
journal.is_simplified_invoice
|
||||
and journal.id
|
||||
in journal.pms_property_ids.mapped("journal_normal_invoice_id.id")
|
||||
):
|
||||
raise models.ValidationError(
|
||||
_(
|
||||
"The journal %s is used for normal invoices in the properties: %s"
|
||||
% (
|
||||
journal.name,
|
||||
", ".join(journal.pms_property_ids.mapped("name")),
|
||||
)
|
||||
)
|
||||
)
|
||||
if (
|
||||
not journal.is_simplified_invoice
|
||||
and journal.id
|
||||
in journal.pms_property_ids.mapped("journal_simplified_invoice_id.id")
|
||||
):
|
||||
raise models.ValidationError(
|
||||
_(
|
||||
"The journal %s is used for simplified invoices in the properties: %s"
|
||||
% (
|
||||
journal.name,
|
||||
", ".join(journal.pms_property_ids.mapped("name")),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import itertools as it
|
||||
import json
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
@@ -32,6 +32,20 @@ class AccountMove(models.Model):
|
||||
# check_pms_properties=True,
|
||||
)
|
||||
# journal_id = fields.Many2one(check_pms_properties=True)
|
||||
is_simplified_invoice = fields.Boolean(
|
||||
help="Technical field to know if the invoice is simplified",
|
||||
related="journal_id.is_simplified_invoice",
|
||||
store=True,
|
||||
)
|
||||
origin_agency_id = fields.Many2one(
|
||||
string="Origin Agency",
|
||||
help="The agency where the folio account move originates",
|
||||
comodel_name="res.partner",
|
||||
domain="[('is_agency', '=', True)]",
|
||||
compute="_compute_origin_agency_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
@api.onchange("pms_property_id")
|
||||
def _onchange_pms_property_id(self):
|
||||
@@ -64,6 +78,19 @@ class AccountMove(models.Model):
|
||||
move.folio_ids = False
|
||||
move.folio_ids = move.mapped("line_ids.folio_ids.id")
|
||||
|
||||
@api.depends("line_ids", "line_ids.origin_agency_id")
|
||||
def _compute_origin_agency_id(self):
|
||||
"""
|
||||
Compute the origin agency of the account move
|
||||
if the move has multiple agencies in origin,
|
||||
the first one is returned (REVIEW: is this correct?)
|
||||
"""
|
||||
self.origin_agency_id = False
|
||||
for move in self:
|
||||
agencies = move.mapped("line_ids.origin_agency_id")
|
||||
if agencies:
|
||||
move.origin_agency_id = agencies[0]
|
||||
|
||||
def _compute_payments_widget_to_reconcile_info(self):
|
||||
for move in self:
|
||||
if not move.line_ids.folio_line_ids:
|
||||
@@ -84,22 +111,6 @@ class AccountMove(models.Model):
|
||||
in ("receivable", "payable")
|
||||
)
|
||||
|
||||
domain = [
|
||||
("account_id", "in", pay_term_lines.account_id.ids),
|
||||
("parent_state", "=", "posted"),
|
||||
("reconciled", "=", False),
|
||||
"|",
|
||||
("amount_residual", "!=", 0.0),
|
||||
("amount_residual_currency", "!=", 0.0),
|
||||
"|",
|
||||
(
|
||||
"folio_ids",
|
||||
"in",
|
||||
move.line_ids.mapped("folio_line_ids.folio_id.id"),
|
||||
),
|
||||
("partner_id", "=", move.commercial_partner_id.id),
|
||||
]
|
||||
|
||||
payments_widget_vals = {
|
||||
"outstanding": True,
|
||||
"content": [],
|
||||
@@ -107,13 +118,31 @@ class AccountMove(models.Model):
|
||||
}
|
||||
|
||||
if move.is_inbound():
|
||||
domain.append(("balance", "<", 0.0))
|
||||
domain = [("balance", "<", 0.0)]
|
||||
payments_widget_vals["title"] = _("Outstanding credits")
|
||||
else:
|
||||
domain.append(("balance", ">", 0.0))
|
||||
domain = [("balance", ">", 0.0)]
|
||||
payments_widget_vals["title"] = _("Outstanding debits")
|
||||
for line in self.env["account.move.line"].search(domain):
|
||||
|
||||
domain.extend(
|
||||
[
|
||||
("account_id", "in", pay_term_lines.account_id.ids),
|
||||
("parent_state", "=", "posted"),
|
||||
("reconciled", "=", False),
|
||||
"|",
|
||||
("amount_residual", "!=", 0.0),
|
||||
("amount_residual_currency", "!=", 0.0),
|
||||
"|",
|
||||
(
|
||||
"folio_ids",
|
||||
"in",
|
||||
move.line_ids.mapped("folio_line_ids.folio_id.id"),
|
||||
),
|
||||
("partner_id", "=", move.commercial_partner_id.id),
|
||||
]
|
||||
)
|
||||
|
||||
for line in self.env["account.move.line"].search(domain):
|
||||
if line.currency_id == move.currency_id:
|
||||
# Same foreign currency.
|
||||
amount = abs(line.amount_residual_currency)
|
||||
@@ -157,13 +186,18 @@ class AccountMove(models.Model):
|
||||
default_pms_property_id is set in context
|
||||
"""
|
||||
journal = super(AccountMove, self)._search_default_journal(journal_types)
|
||||
if self._context.get("default_pms_property_id"):
|
||||
property_id = self._context.get("default_pms_property_id")
|
||||
pms_property = self.env["pms.property"].browse(property_id)
|
||||
company_id = self._context.get("default_company_id", self.env.company.id)
|
||||
company = self.env["res.company"].browse(company_id)
|
||||
pms_property_id = self.pms_property_id.id or (
|
||||
self.env.user.get_active_property_ids
|
||||
and self.env.user.get_active_property_ids()[0]
|
||||
)
|
||||
pms_property = self.env["pms.property"].browse(pms_property_id)
|
||||
if pms_property:
|
||||
domain = [
|
||||
("company_id", "=", pms_property.company_id.id),
|
||||
("type", "in", journal_types),
|
||||
("pms_property_ids", "in", property_id),
|
||||
("pms_property_ids", "in", pms_property.id),
|
||||
]
|
||||
journal = self.env["account.journal"].search(domain, limit=1)
|
||||
if not journal:
|
||||
@@ -173,16 +207,41 @@ class AccountMove(models.Model):
|
||||
("pms_property_ids", "=", False),
|
||||
]
|
||||
journal = self.env["account.journal"].search(domain, limit=1)
|
||||
if not journal:
|
||||
else:
|
||||
domain = [
|
||||
("company_id", "=", company_id),
|
||||
("type", "in", journal_types),
|
||||
("pms_property_ids", "=", False),
|
||||
]
|
||||
journal = self.env["account.journal"].search(domain, limit=1)
|
||||
if not journal:
|
||||
if pms_property:
|
||||
error_msg = _(
|
||||
"""No journal could be found in property %(property_name)s
|
||||
for any of those types: %(journal_types)s""",
|
||||
property_name=pms_property.display_name,
|
||||
journal_types=", ".join(journal_types),
|
||||
)
|
||||
raise UserError(error_msg)
|
||||
else:
|
||||
error_msg = _(
|
||||
"""No journal could be found in company %(company_name)s
|
||||
for any of those types: %(journal_types)s""",
|
||||
company_name=company.display_name,
|
||||
journal_types=", ".join(journal_types),
|
||||
)
|
||||
raise UserError(error_msg)
|
||||
return journal
|
||||
|
||||
@api.depends("pms_property_id")
|
||||
def _compute_suitable_journal_ids(self):
|
||||
super(AccountMove, self)._compute_suitable_journal_ids()
|
||||
for move in self:
|
||||
if move.pms_property_id:
|
||||
move.suitable_journal_ids = move.suitable_journal_ids.filtered(
|
||||
lambda j: not j.pms_property_ids
|
||||
or move.pms_property_id.id in j.pms_property_ids.ids
|
||||
)
|
||||
|
||||
def _autoreconcile_folio_payments(self):
|
||||
"""
|
||||
Reconcile payments with the invoice
|
||||
@@ -235,6 +294,8 @@ class AccountMove(models.Model):
|
||||
"""
|
||||
Overwrite the original method to add the folio_ids to the invoice
|
||||
"""
|
||||
for record in self:
|
||||
record._check_pms_valid_invoice(record)
|
||||
res = super(AccountMove, self)._post(soft)
|
||||
self._autoreconcile_folio_payments()
|
||||
return res
|
||||
@@ -252,3 +313,39 @@ class AccountMove(models.Model):
|
||||
lambda p: p.id in [item.id for item in combi]
|
||||
)
|
||||
return []
|
||||
|
||||
@api.model
|
||||
def _check_pms_valid_invoice(self, move):
|
||||
"""
|
||||
Check invoice and receipts legal status
|
||||
"""
|
||||
if (
|
||||
move.is_invoice(include_receipts=True)
|
||||
and not move.journal_id.is_simplified_invoice
|
||||
and (
|
||||
not move.partner_id or not move.partner_id._check_enought_invoice_data()
|
||||
)
|
||||
):
|
||||
raise UserError(
|
||||
_(
|
||||
"You cannot validate this invoice. Please check the "
|
||||
" partner has the complete information required."
|
||||
)
|
||||
)
|
||||
if move.journal_id.is_simplified_invoice:
|
||||
move._check_simplified_restrictions()
|
||||
return True
|
||||
|
||||
def _check_simplified_restrictions(self):
|
||||
self.ensure_one()
|
||||
if (
|
||||
self.pms_property_id
|
||||
and self.amount_total > self.pms_property_id.max_amount_simplified_invoice
|
||||
):
|
||||
mens = _(
|
||||
"The total amount of the simplified invoice is higher than the "
|
||||
"maximum amount allowed for simplified invoices."
|
||||
)
|
||||
self.folio_ids.message_post(body=mens)
|
||||
raise ValidationError(mens)
|
||||
return True
|
||||
|
||||
@@ -10,6 +10,11 @@ class AccountMoveLine(models.Model):
|
||||
|
||||
# Fields declaration
|
||||
# TODO: REVIEW why not a Many2one?
|
||||
name = fields.Char(
|
||||
compute="_compute_name",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
folio_line_ids = fields.Many2many(
|
||||
string="Folio Lines",
|
||||
help="The folio lines in the account move lines",
|
||||
@@ -41,47 +46,16 @@ class AccountMoveLine(models.Model):
|
||||
readonly=False,
|
||||
check_pms_properties=True,
|
||||
)
|
||||
move_id = fields.Many2one(check_pms_properties=True)
|
||||
|
||||
@api.depends("move_id")
|
||||
def _compute_pms_property_id(self):
|
||||
for rec in self:
|
||||
if rec.move_id and rec.move_id.pms_property_id:
|
||||
rec.pms_property_id = rec.move_id.pms_property_id
|
||||
elif not rec.pms_property_id:
|
||||
rec.pms_property_id = False
|
||||
|
||||
@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",
|
||||
origin_agency_id = fields.Many2one(
|
||||
string="Origin Agency",
|
||||
help="The agency where the folio account move originates",
|
||||
comodel_name="res.partner",
|
||||
domain="[('is_agency', '=', True)]",
|
||||
compute="_compute_origin_agency_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"folio_line_ids",
|
||||
"payment_id",
|
||||
"payment_id.folio_ids",
|
||||
"statement_line_id",
|
||||
"statement_line_id.folio_ids",
|
||||
)
|
||||
def _compute_folio_ids(self):
|
||||
if self.folio_line_ids:
|
||||
self.folio_ids = self.folio_line_ids.mapped("folio_id")
|
||||
elif self.payment_id:
|
||||
self.folio_ids = self.payment_id.folio_ids
|
||||
elif self.statement_line_id:
|
||||
self.folio_ids = self.statement_line_id.folio_ids
|
||||
else:
|
||||
self.folio_ids = False
|
||||
move_id = fields.Many2one(check_pms_properties=True)
|
||||
|
||||
@api.depends("quantity")
|
||||
def _compute_name(self):
|
||||
@@ -104,3 +78,51 @@ class AccountMoveLine(models.Model):
|
||||
# qty=record.quantity)
|
||||
# record.with_context(auto_name=True)
|
||||
# ._compute_name_changed_by_user()
|
||||
|
||||
@api.depends("move_id")
|
||||
def _compute_pms_property_id(self):
|
||||
for rec in self:
|
||||
if rec.move_id and rec.move_id.pms_property_id:
|
||||
rec.pms_property_id = rec.move_id.pms_property_id
|
||||
elif not rec.pms_property_id:
|
||||
rec.pms_property_id = False
|
||||
|
||||
@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
|
||||
|
||||
@api.depends(
|
||||
"folio_line_ids",
|
||||
"payment_id",
|
||||
"payment_id.folio_ids",
|
||||
"statement_line_id",
|
||||
"statement_line_id.folio_ids",
|
||||
)
|
||||
def _compute_folio_ids(self):
|
||||
if self.folio_line_ids:
|
||||
self.folio_ids = self.folio_line_ids.mapped("folio_id")
|
||||
elif self.payment_id:
|
||||
self.folio_ids = self.payment_id.folio_ids
|
||||
elif self.statement_line_id:
|
||||
self.folio_ids = self.statement_line_id.folio_ids
|
||||
else:
|
||||
self.folio_ids = False
|
||||
|
||||
@api.depends("folio_line_ids")
|
||||
def _compute_origin_agency_id(self):
|
||||
"""
|
||||
Compute the origin agency of the account move line,
|
||||
if the line has multiple agencies in origin,
|
||||
(p.e. nights with different agencies in origin),
|
||||
the first one is returned (REVIEW: is this correct?)
|
||||
"""
|
||||
self.origin_agency_id = False
|
||||
for line in self:
|
||||
agencies = line.mapped("folio_line_ids.origin_agency_id")
|
||||
if agencies:
|
||||
line.origin_agency_id = agencies[0]
|
||||
|
||||
@@ -17,6 +17,46 @@ class AccountPayment(models.Model):
|
||||
column1="payment_id",
|
||||
column2="folio_id",
|
||||
)
|
||||
origin_agency_id = fields.Many2one(
|
||||
string="Origin Agency",
|
||||
help="The agency where the folio account move originates",
|
||||
comodel_name="res.partner",
|
||||
domain="[('is_agency', '=', True)]",
|
||||
compute="_compute_origin_agency_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
origin_reference = fields.Char(
|
||||
string="Origin Reference",
|
||||
help="The reference of the payment origin",
|
||||
)
|
||||
|
||||
@api.depends("reconciled_invoice_ids", "reconciled_bill_ids")
|
||||
def _compute_origin_agency_id(self):
|
||||
"""
|
||||
Compute the origin agency of the sale line,
|
||||
if the line has multiple agencies in origin,
|
||||
(p.e. nights with different agencies in origin),
|
||||
the first one is returned (REVIEW: is this correct?)
|
||||
"""
|
||||
for rec in self:
|
||||
inv_agency_ids = rec.reconciled_invoice_ids.mapped(
|
||||
"line_ids.folio_line_ids.origin_agency_id.id"
|
||||
)
|
||||
bill_agency_ids = rec.reconciled_bill_ids.mapped(
|
||||
"line_ids.folio_line_ids.origin_agency_id.id"
|
||||
)
|
||||
agency_ids = list(set(inv_agency_ids + bill_agency_ids))
|
||||
if agency_ids:
|
||||
rec.write({"origin_agency_id": agency_ids[0]})
|
||||
elif (
|
||||
not rec.reconciled_invoice_ids
|
||||
and not rec.reconciled_bill_ids
|
||||
and rec.folio_ids
|
||||
):
|
||||
rec.origin_agency_id = rec.origin_agency_id
|
||||
else:
|
||||
rec.origin_agency_id = False
|
||||
|
||||
@api.depends("reconciled_invoice_ids", "reconciled_bill_ids")
|
||||
def _compute_folio_ids(self):
|
||||
|
||||
@@ -264,6 +264,15 @@ class FolioSaleLine(models.Model):
|
||||
store=True,
|
||||
related="folio_id.partner_id",
|
||||
)
|
||||
origin_agency_id = fields.Many2one(
|
||||
string="Origin Agency",
|
||||
help="The agency where the folio sale line originates",
|
||||
comodel_name="res.partner",
|
||||
domain="[('is_agency', '=', True)]",
|
||||
compute="_compute_origin_agency_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
analytic_tag_ids = fields.Many2many(
|
||||
string="Analytic Tags",
|
||||
comodel_name="account.analytic.tag",
|
||||
@@ -320,6 +329,14 @@ class FolioSaleLine(models.Model):
|
||||
compute="_compute_date_order",
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"reservation_line_ids",
|
||||
"reservation_id.agency_id",
|
||||
)
|
||||
def _compute_origin_agency_id(self):
|
||||
for rec in self:
|
||||
rec.origin_agency_id = rec.folio_id.agency_id
|
||||
|
||||
@api.depends("qty_to_invoice")
|
||||
def _compute_service_order(self):
|
||||
for record in self:
|
||||
@@ -395,7 +412,7 @@ class FolioSaleLine(models.Model):
|
||||
invoice_date = (
|
||||
invoice_line.move_id.invoice_date or fields.Date.today()
|
||||
)
|
||||
if invoice_line.move_id.move_type == "out_invoice":
|
||||
if invoice_line.move_id.move_type in ["out_invoice", "out_receipt"]:
|
||||
amount_invoiced += invoice_line.currency_id._convert(
|
||||
invoice_line.price_subtotal,
|
||||
line.currency_id,
|
||||
@@ -506,7 +523,7 @@ class FolioSaleLine(models.Model):
|
||||
"Product Unit of Measure"
|
||||
)
|
||||
for line in self:
|
||||
if line.state == "draft":
|
||||
if line.state == "draft" or line.price_total == 0.0:
|
||||
line.invoice_status = "no"
|
||||
# REVIEW: if qty_to_invoice < 0 (invoice qty > sale qty),
|
||||
# why status to_invoice?? this behavior is copied from sale order
|
||||
@@ -625,7 +642,7 @@ class FolioSaleLine(models.Model):
|
||||
Otherwise, the quantity delivered is used.
|
||||
"""
|
||||
for line in self:
|
||||
if line.folio_id.state not in ["draft"]:
|
||||
if line.folio_id.state not in ["draft"] and line.price_total > 0.0:
|
||||
line.qty_to_invoice = line.product_uom_qty - line.qty_invoiced
|
||||
else:
|
||||
line.qty_to_invoice = 0
|
||||
@@ -650,7 +667,7 @@ class FolioSaleLine(models.Model):
|
||||
qty_invoiced = 0.0
|
||||
for invoice_line in line.invoice_lines:
|
||||
if invoice_line.move_id.state != "cancel":
|
||||
if invoice_line.move_id.move_type == "out_invoice":
|
||||
if invoice_line.move_id.move_type in ["out_invoice", "out_receipt"]:
|
||||
qty_invoiced += invoice_line.product_uom_id._compute_quantity(
|
||||
invoice_line.quantity, line.product_uom
|
||||
)
|
||||
|
||||
@@ -81,13 +81,16 @@ class PmsAvailabilityPlan(models.Model):
|
||||
)
|
||||
|
||||
@api.model
|
||||
def update_quota(self, pricelist_id, room_type_id, date, impacts_quota_id=False):
|
||||
def update_quota(
|
||||
self, pricelist_id, room_type_id, date, pms_property_id, impacts_quota_id=False
|
||||
):
|
||||
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),
|
||||
("availability_plan_id.pms_pricelist_ids", "=", pricelist_id),
|
||||
("room_type_id", "=", room_type_id),
|
||||
("date", "=", date),
|
||||
("pms_property_id", "=", pms_property_id),
|
||||
]
|
||||
)
|
||||
# applies a rule
|
||||
|
||||
@@ -76,6 +76,13 @@ class PmsCheckinPartner(models.Model):
|
||||
store=True,
|
||||
compute="_compute_mobile",
|
||||
)
|
||||
phone = fields.Char(
|
||||
string="Phone",
|
||||
help="Checkin Partner Phone",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_phone",
|
||||
)
|
||||
image_128 = fields.Image(
|
||||
string="Image",
|
||||
help="Checkin Partner Image, it corresponds with Partner Image associated",
|
||||
@@ -136,18 +143,52 @@ class PmsCheckinPartner(models.Model):
|
||||
compute="_compute_nationality_id",
|
||||
comodel_name="res.country",
|
||||
)
|
||||
# TODO: Use new partner contact "other or "private" with
|
||||
# personal contact address complete??
|
||||
# to avoid user country_id on companies contacts.
|
||||
# View to res.partner state_id inherit
|
||||
state_id = fields.Many2one(
|
||||
string="Country State",
|
||||
help="host state",
|
||||
residence_street = fields.Char(
|
||||
string="Street",
|
||||
help="Street of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_state_id",
|
||||
compute="_compute_residence_street",
|
||||
)
|
||||
residence_street2 = fields.Char(
|
||||
string="Street2",
|
||||
help="Second street of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_street2",
|
||||
)
|
||||
residence_zip = fields.Char(
|
||||
string="Zip",
|
||||
help="Zip of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_zip",
|
||||
change_default=True,
|
||||
)
|
||||
residence_city = fields.Char(
|
||||
string="City",
|
||||
help="City of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_city",
|
||||
)
|
||||
residence_country_id = fields.Many2one(
|
||||
string="Country of residence",
|
||||
help="Country of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_country_id",
|
||||
comodel_name="res.country",
|
||||
)
|
||||
residence_state_id = fields.Many2one(
|
||||
string="State of residence",
|
||||
help="State of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_state_id",
|
||||
comodel_name="res.country.state",
|
||||
)
|
||||
|
||||
firstname = fields.Char(
|
||||
string="First Name",
|
||||
help="host firstname",
|
||||
@@ -224,6 +265,10 @@ class PmsCheckinPartner(models.Model):
|
||||
inverse_name="checkin_partner_possible_customer_id",
|
||||
)
|
||||
|
||||
partner_relationship = fields.Char(
|
||||
string="Partner relationship", help="Family relationship between travelers"
|
||||
)
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_document_number(self):
|
||||
for record in self:
|
||||
@@ -304,12 +349,55 @@ class PmsCheckinPartner(models.Model):
|
||||
record.nationality_id = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_state_id(self):
|
||||
def _compute_residence_street(self):
|
||||
for record in self:
|
||||
if not record.state_id and record.partner_id.state_id:
|
||||
record.state_id = record.partner_id.state_id
|
||||
elif not record.state_id:
|
||||
record.state_id = False
|
||||
if not record.residence_street and record.partner_id.residence_street:
|
||||
record.residence_street = record.partner_id.residence_street
|
||||
elif not record.residence_street:
|
||||
record.residence_street = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_residence_street2(self):
|
||||
for record in self:
|
||||
if not record.residence_street2 and record.partner_id.residence_street2:
|
||||
record.residence_street2 = record.partner_id.residence_street2
|
||||
elif not record.residence_street2:
|
||||
record.residence_street2 = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_residence_zip(self):
|
||||
for record in self:
|
||||
if not record.residence_zip and record.partner_id.residence_zip:
|
||||
record.residence_zip = record.partner_id.residence_zip
|
||||
elif not record.residence_zip:
|
||||
record.residence_zip = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_residence_city(self):
|
||||
for record in self:
|
||||
if not record.residence_city and record.partner_id.residence_city:
|
||||
record.residence_city = record.partner_id.residence_city
|
||||
elif not record.residence_city:
|
||||
record.residence_city = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_residence_country_id(self):
|
||||
for record in self:
|
||||
if (
|
||||
not record.residence_country_id
|
||||
and record.partner_id.residence_country_id
|
||||
):
|
||||
record.residence_country_id = record.partner_id.residence_country_id
|
||||
elif not record.residence_country_id:
|
||||
record.residence_country_id = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_residence_state_id(self):
|
||||
for record in self:
|
||||
if not record.residence_state_id and record.partner_id.residence_state_id:
|
||||
record.residence_state_id = record.partner_id.residence_state_id
|
||||
elif not record.residence_state_id:
|
||||
record.residence_state_id = False
|
||||
|
||||
@api.depends("reservation_id", "reservation_id.folio_id")
|
||||
def _compute_folio_id(self):
|
||||
@@ -348,14 +436,26 @@ class PmsCheckinPartner(models.Model):
|
||||
@api.depends("partner_id")
|
||||
def _compute_email(self):
|
||||
for record in self:
|
||||
if not record.email or record.partner_id.email:
|
||||
if not record.email and record.partner_id.email:
|
||||
record.email = record.partner_id.email
|
||||
elif not record.email:
|
||||
record.email = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_mobile(self):
|
||||
for record in self:
|
||||
if not record.mobile or record.partner_id.mobile:
|
||||
if not record.mobile and record.partner_id.mobile:
|
||||
record.mobile = record.partner_id.mobile
|
||||
elif not record.mobile:
|
||||
record.mobile = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_phone(self):
|
||||
for record in self:
|
||||
if not record.phone and record.partner_id.phone:
|
||||
record.phone = record.partner_id.phone
|
||||
elif not record.phone:
|
||||
record.phone = False
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_document_id(self):
|
||||
@@ -756,10 +856,10 @@ class PmsCheckinPartner(models.Model):
|
||||
if not values.get("document_type"):
|
||||
values.update({"document_type": False})
|
||||
if values.get("state"):
|
||||
state_id = self.env["res.country.state"].search(
|
||||
residence_state_id = self.env["res.country.state"].search(
|
||||
[("id", "=", values.get("state"))]
|
||||
)
|
||||
values.update({"state_id": state_id})
|
||||
values.update({"residence_state_id": residence_state_id})
|
||||
values.pop("state")
|
||||
if values.get("document_expedition_date"):
|
||||
doc_type = values.get("document_type")
|
||||
|
||||
@@ -4,8 +4,11 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from itertools import groupby
|
||||
|
||||
from dateutil import relativedelta
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import AccessError, UserError, ValidationError
|
||||
from odoo.tools import float_compare, float_is_zero
|
||||
@@ -32,6 +35,10 @@ class PmsFolio(models.Model):
|
||||
index=True,
|
||||
default=lambda self: _("New"),
|
||||
)
|
||||
external_reference = fields.Char(
|
||||
string="External Reference",
|
||||
help="Reference of this folio in an external system",
|
||||
)
|
||||
pms_property_id = fields.Many2one(
|
||||
string="Property",
|
||||
help="The property for folios",
|
||||
@@ -293,7 +300,6 @@ class PmsFolio(models.Model):
|
||||
comodel_name="res.partner.category",
|
||||
ondelete="restrict",
|
||||
)
|
||||
client_order_ref = fields.Char(string="Customer Reference", help="", copy=False)
|
||||
reservation_type = fields.Selection(
|
||||
string="Type",
|
||||
help="The type of the reservation. "
|
||||
@@ -505,6 +511,11 @@ class PmsFolio(models.Model):
|
||||
store=True,
|
||||
compute="_compute_last_checkout",
|
||||
)
|
||||
autoinvoice_date = fields.Date(
|
||||
string="Autoinvoice Date",
|
||||
compute="_compute_autoinvoice_date",
|
||||
store=True,
|
||||
)
|
||||
|
||||
def name_get(self):
|
||||
result = []
|
||||
@@ -539,72 +550,138 @@ class PmsFolio(models.Model):
|
||||
)
|
||||
invoice_vals_list = []
|
||||
invoice_item_sequence = 0
|
||||
for order in self:
|
||||
order = order.with_company(order.company_id)
|
||||
current_section_vals = None
|
||||
down_payments = order.env["folio.sale.line"]
|
||||
|
||||
# Invoice values.
|
||||
invoice_vals = order._prepare_invoice(partner_invoice_id=partner_invoice_id)
|
||||
|
||||
# Invoice line values (keep only necessary sections).
|
||||
invoice_lines_vals = []
|
||||
for line in order.sale_line_ids.filtered(
|
||||
for folio in self:
|
||||
folio_lines_to_invoice = folio.sale_line_ids.filtered(
|
||||
lambda l: l.id in list(lines_to_invoice.keys())
|
||||
):
|
||||
if line.display_type == "line_section":
|
||||
current_section_vals = line._prepare_invoice_line(
|
||||
sequence=invoice_item_sequence + 1
|
||||
)
|
||||
continue
|
||||
if line.display_type != "line_note" and float_is_zero(
|
||||
line.qty_to_invoice, precision_digits=precision
|
||||
):
|
||||
continue
|
||||
if (
|
||||
line.qty_to_invoice > 0
|
||||
or (line.qty_to_invoice < 0 and final)
|
||||
or line.display_type == "line_note"
|
||||
):
|
||||
if line.is_downpayment:
|
||||
down_payments += line
|
||||
continue
|
||||
if current_section_vals:
|
||||
invoice_item_sequence += 1
|
||||
invoice_lines_vals.append(current_section_vals)
|
||||
current_section_vals = None
|
||||
invoice_item_sequence += 1
|
||||
prepared_line = line._prepare_invoice_line(
|
||||
sequence=invoice_item_sequence, qty=lines_to_invoice[line.id]
|
||||
)
|
||||
invoice_lines_vals.append(prepared_line)
|
||||
)
|
||||
folio_partner_invoice_id = partner_invoice_id
|
||||
if not folio_partner_invoice_id:
|
||||
folio_partner_invoice_id = folio._get_default_partner_invoice_id()
|
||||
|
||||
# If down payments are present in SO, group them under common section
|
||||
if down_payments:
|
||||
invoice_item_sequence += 1
|
||||
down_payments_section = order._prepare_down_payment_section_line(
|
||||
sequence=invoice_item_sequence
|
||||
groups_invoice_lines = folio._get_groups_invoice_lines(
|
||||
lines_to_invoice=folio_lines_to_invoice,
|
||||
partner_invoice_id=folio_partner_invoice_id,
|
||||
)
|
||||
for group in groups_invoice_lines:
|
||||
folio = folio.with_company(folio.company_id)
|
||||
down_payments = folio.env["folio.sale.line"]
|
||||
|
||||
# Invoice values.
|
||||
invoice_vals = folio._prepare_invoice(
|
||||
partner_invoice_id=group["partner_id"]
|
||||
)
|
||||
invoice_lines_vals.append(down_payments_section)
|
||||
for down_payment in down_payments:
|
||||
# Invoice line values (keep only necessary sections).
|
||||
current_section_vals = None
|
||||
invoice_lines_vals = []
|
||||
for line in group["lines"]:
|
||||
if line.display_type == "line_section":
|
||||
current_section_vals = line._prepare_invoice_line(
|
||||
sequence=invoice_item_sequence + 1
|
||||
)
|
||||
continue
|
||||
if line.display_type != "line_note" and float_is_zero(
|
||||
line.qty_to_invoice, precision_digits=precision
|
||||
):
|
||||
continue
|
||||
if (
|
||||
line.qty_to_invoice > 0
|
||||
or (line.qty_to_invoice < 0 and final)
|
||||
or line.display_type == "line_note"
|
||||
):
|
||||
if line.is_downpayment:
|
||||
down_payments += line
|
||||
continue
|
||||
if current_section_vals:
|
||||
invoice_item_sequence += 1
|
||||
invoice_lines_vals.append(current_section_vals)
|
||||
current_section_vals = None
|
||||
invoice_item_sequence += 1
|
||||
prepared_line = line._prepare_invoice_line(
|
||||
sequence=invoice_item_sequence,
|
||||
qty=lines_to_invoice[line.id],
|
||||
)
|
||||
invoice_lines_vals.append(prepared_line)
|
||||
|
||||
# If down payments are present in SO, group them under common section
|
||||
if down_payments:
|
||||
invoice_item_sequence += 1
|
||||
invoice_down_payment_vals = down_payment._prepare_invoice_line(
|
||||
down_payments_section = folio._prepare_down_payment_section_line(
|
||||
sequence=invoice_item_sequence
|
||||
)
|
||||
invoice_lines_vals.append(invoice_down_payment_vals)
|
||||
invoice_lines_vals.append(down_payments_section)
|
||||
for down_payment in down_payments:
|
||||
invoice_item_sequence += 1
|
||||
invoice_down_payment_vals = down_payment._prepare_invoice_line(
|
||||
sequence=invoice_item_sequence
|
||||
)
|
||||
invoice_lines_vals.append(invoice_down_payment_vals)
|
||||
|
||||
if not any(
|
||||
new_line["display_type"] is False for new_line in invoice_lines_vals
|
||||
):
|
||||
raise self._nothing_to_invoice_error()
|
||||
if not any(
|
||||
new_line["display_type"] is False for new_line in invoice_lines_vals
|
||||
):
|
||||
raise self._nothing_to_invoice_error()
|
||||
invoice_vals["invoice_line_ids"] = [
|
||||
(0, 0, invoice_line_id) for invoice_line_id in invoice_lines_vals
|
||||
]
|
||||
|
||||
invoice_vals["invoice_line_ids"] = [
|
||||
(0, 0, invoice_line_id) for invoice_line_id in invoice_lines_vals
|
||||
]
|
||||
|
||||
invoice_vals_list.append(invoice_vals)
|
||||
invoice_vals_list.append(invoice_vals)
|
||||
return invoice_vals_list
|
||||
|
||||
def _get_groups_invoice_lines(self, lines_to_invoice, partner_invoice_id):
|
||||
self.ensure_one()
|
||||
target_lines = lines_to_invoice
|
||||
if self._context.get("lines_auto_add") and partner_invoice_id:
|
||||
folio_partner_invoice = self.env["res.partner"].browse(partner_invoice_id)
|
||||
if folio_partner_invoice.default_invoice_lines == "overnights":
|
||||
target_lines = target_lines.filtered(
|
||||
lambda r: r.is_board_service
|
||||
or (r.reservation_line_ids and r.reservation_id.overnight_room)
|
||||
)
|
||||
elif folio_partner_invoice.default_invoice_lines == "reservations":
|
||||
target_lines = target_lines.filtered(
|
||||
lambda r: r.is_board_service or r.reservation_line_ids
|
||||
)
|
||||
elif folio_partner_invoice.default_invoice_lines == "services":
|
||||
target_lines = target_lines.filtered(
|
||||
lambda r: not r.is_board_service or r.service_line_ids
|
||||
)
|
||||
groups_invoice_lines = [
|
||||
{
|
||||
"partner_id": partner_invoice_id,
|
||||
"lines": target_lines,
|
||||
}
|
||||
]
|
||||
if (
|
||||
self.autoinvoice_date
|
||||
and self.autoinvoice_date <= fields.Date.today()
|
||||
and len(target_lines) < len(lines_to_invoice)
|
||||
):
|
||||
other_partner_to_invoice = self.partner_invoice_ids.filtered(
|
||||
lambda p: p.id != partner_invoice_id
|
||||
)
|
||||
if not other_partner_to_invoice:
|
||||
other_partner_to_invoice = self.env.ref("pms.various_pms_partner")
|
||||
groups_invoice_lines.append(
|
||||
{
|
||||
"partner_id": other_partner_to_invoice.id,
|
||||
"lines": lines_to_invoice - target_lines,
|
||||
}
|
||||
)
|
||||
return groups_invoice_lines
|
||||
|
||||
def _get_default_partner_invoice_id(self):
|
||||
self.ensure_one()
|
||||
folio_partner_invoice_id = False
|
||||
if self.partner_id and self.partner_id.vat:
|
||||
folio_partner_invoice_id = self.partner_id.id
|
||||
if not folio_partner_invoice_id:
|
||||
folio_partner_invoice_id = (
|
||||
self.partner_invoice_ids[0].id if self.partner_invoice_ids else False
|
||||
)
|
||||
if not folio_partner_invoice_id:
|
||||
folio_partner_invoice_id = self.env.ref("pms.various_pms_partner").id
|
||||
return folio_partner_invoice_id
|
||||
|
||||
def _get_tax_amount_by_group(self):
|
||||
self.ensure_one()
|
||||
res = {}
|
||||
@@ -627,6 +704,42 @@ class PmsFolio(models.Model):
|
||||
]
|
||||
return res
|
||||
|
||||
@api.depends("partner_id", "invoice_status", "last_checkout", "partner_invoice_ids")
|
||||
def _compute_autoinvoice_date(self):
|
||||
self.autoinvoice_date = False
|
||||
for record in self.filtered(lambda r: r.invoice_status == "to_invoice"):
|
||||
record.autoinvoice_date = record._get_to_invoice_date()
|
||||
|
||||
def _get_to_invoice_date(self):
|
||||
self.ensure_one()
|
||||
partner = self.partner_id
|
||||
invoicing_policy = (
|
||||
self.pms_property_id.default_invoicing_policy
|
||||
if not partner or partner.invoicing_policy == "property"
|
||||
else partner.invoicing_policy
|
||||
)
|
||||
if invoicing_policy == "manual":
|
||||
return False
|
||||
if invoicing_policy == "checkout":
|
||||
margin_days = (
|
||||
self.pms_property_id.margin_days_autoinvoice
|
||||
if not partner or partner.invoicing_policy == "property"
|
||||
else partner.margin_days_autoinvoice
|
||||
)
|
||||
return self.last_checkout + timedelta(days=margin_days)
|
||||
if invoicing_policy == "month_day":
|
||||
month_day = (
|
||||
self.pms_property_id.invoicing_month_day
|
||||
if not partner or partner.invoicing_policy == "property"
|
||||
else partner.invoicing_month_day
|
||||
)
|
||||
if self.last_checkout.day <= month_day:
|
||||
self.autoinvoice_date = self.last_checkout.replace(day=month_day)
|
||||
else:
|
||||
self.autoinvoice_date = (
|
||||
self.last_checkout + relativedelta.relativedelta(months=1)
|
||||
).replace(day=month_day)
|
||||
|
||||
@api.depends("reservation_ids", "reservation_ids.state")
|
||||
def _compute_number_of_rooms(self):
|
||||
for folio in self:
|
||||
@@ -782,7 +895,8 @@ class PmsFolio(models.Model):
|
||||
# directly linked to the SO.
|
||||
for order in self:
|
||||
invoices = order.sale_line_ids.invoice_lines.move_id.filtered(
|
||||
lambda r: r.move_type in ("out_invoice", "out_refund")
|
||||
lambda r: r.move_type
|
||||
in ("out_invoice", "out_refund", "out_receipt", "in_receipt")
|
||||
)
|
||||
order.move_ids = invoices
|
||||
order.invoice_count = len(invoices)
|
||||
@@ -814,9 +928,10 @@ class PmsFolio(models.Model):
|
||||
- to_invoice: if any SO line is 'to_invoice', the whole SO is 'to_invoice'
|
||||
- invoiced: if all SO lines are invoiced, the SO is invoiced.
|
||||
"""
|
||||
unconfirmed_orders = self.filtered(lambda so: so.state in ["draft"])
|
||||
unconfirmed_orders = self.filtered(lambda folio: folio.state in ["draft"])
|
||||
unconfirmed_orders.invoice_status = "no"
|
||||
confirmed_orders = self - unconfirmed_orders
|
||||
zero_orders = self.filtered(lambda folio: folio.amount_total == 0)
|
||||
confirmed_orders = self - unconfirmed_orders - zero_orders
|
||||
if not confirmed_orders:
|
||||
return
|
||||
line_invoice_status_all = [
|
||||
@@ -1105,7 +1220,7 @@ class PmsFolio(models.Model):
|
||||
JOIN account_move_line aml ON aml.id = soli_rel.invoice_line_id
|
||||
JOIN account_move am ON am.id = aml.move_id
|
||||
WHERE
|
||||
am.move_type in ('out_invoice', 'out_refund') AND
|
||||
am.move_type in ('out_invoice', 'out_refund', 'in_receipt') AND
|
||||
am.id = ANY(%s)
|
||||
""",
|
||||
(list(value),),
|
||||
@@ -1117,7 +1232,7 @@ class PmsFolio(models.Model):
|
||||
(
|
||||
"sale_line_ids.invoice_lines.move_id.move_type",
|
||||
"in",
|
||||
("out_invoice", "out_refund"),
|
||||
("out_invoice", "out_refund", "in_receipt"),
|
||||
),
|
||||
("sale_line_ids.invoice_lines.move_id", operator, value),
|
||||
]
|
||||
@@ -1507,6 +1622,7 @@ class PmsFolio(models.Model):
|
||||
return self.env["account.move"]
|
||||
# 1) Create invoices.
|
||||
if not lines_to_invoice:
|
||||
self = self.with_context(lines_auto_add=True)
|
||||
lines_to_invoice = dict()
|
||||
for line in self.sale_line_ids:
|
||||
lines_to_invoice[line.id] = (
|
||||
@@ -1517,45 +1633,12 @@ class PmsFolio(models.Model):
|
||||
lines_to_invoice=lines_to_invoice,
|
||||
partner_invoice_id=partner_invoice_id,
|
||||
)
|
||||
|
||||
if not invoice_vals_list:
|
||||
raise self._nothing_to_invoice_error()
|
||||
|
||||
# 2) Manage 'grouped' parameter: group by (partner_id, currency_id).
|
||||
if not grouped:
|
||||
new_invoice_vals_list = []
|
||||
invoice_grouping_keys = self._get_invoice_grouping_keys()
|
||||
for _grouping_keys, invoices in groupby(
|
||||
invoice_vals_list,
|
||||
key=lambda x: [
|
||||
x.get(grouping_key) for grouping_key in invoice_grouping_keys
|
||||
],
|
||||
):
|
||||
origins = set()
|
||||
payment_refs = set()
|
||||
refs = set()
|
||||
ref_invoice_vals = None
|
||||
for invoice_vals in invoices:
|
||||
if not ref_invoice_vals:
|
||||
ref_invoice_vals = invoice_vals
|
||||
else:
|
||||
ref_invoice_vals["invoice_line_ids"] += invoice_vals[
|
||||
"invoice_line_ids"
|
||||
]
|
||||
origins.add(invoice_vals["invoice_origin"])
|
||||
payment_refs.add(invoice_vals["payment_reference"])
|
||||
refs.add(invoice_vals["ref"])
|
||||
ref_invoice_vals.update(
|
||||
{
|
||||
"ref": ", ".join(refs)[:2000],
|
||||
"invoice_origin": ", ".join(origins),
|
||||
"payment_reference": len(payment_refs) == 1
|
||||
and payment_refs.pop()
|
||||
or False,
|
||||
}
|
||||
)
|
||||
new_invoice_vals_list.append(ref_invoice_vals)
|
||||
invoice_vals_list = new_invoice_vals_list
|
||||
invoice_vals_list = self._get_group_vals_list(invoice_vals_list)
|
||||
|
||||
# 3) Create invoices.
|
||||
|
||||
@@ -1595,12 +1678,7 @@ class PmsFolio(models.Model):
|
||||
# a salesperson must be able to generate an invoice from a
|
||||
# sale order without "billing" access rights.
|
||||
# However, he should not be able to create an invoice from scratch.
|
||||
moves = (
|
||||
self.env["account.move"]
|
||||
.sudo()
|
||||
.with_context(default_move_type="out_invoice", auto_name=True)
|
||||
.create(invoice_vals_list)
|
||||
)
|
||||
moves = self._create_account_moves(invoice_vals_list)
|
||||
|
||||
# 4) Some moves might actually be refunds: convert
|
||||
# them if the total amount is negative
|
||||
@@ -1622,6 +1700,54 @@ class PmsFolio(models.Model):
|
||||
)
|
||||
return moves
|
||||
|
||||
def _create_account_moves(self, invoice_vals_list):
|
||||
moves = self.env["account.move"]
|
||||
for invoice_vals in invoice_vals_list:
|
||||
if invoice_vals["move_type"] == "out_invoice":
|
||||
move = (
|
||||
self.env["account.move"]
|
||||
.sudo()
|
||||
.with_context(default_move_type="out_invoice", auto_name=True)
|
||||
.create(invoice_vals)
|
||||
)
|
||||
moves += move
|
||||
return moves
|
||||
|
||||
def _get_group_vals_list(self, invoice_vals_list):
|
||||
new_invoice_vals_list = []
|
||||
invoice_grouping_keys = self._get_invoice_grouping_keys()
|
||||
for _grouping_keys, invoices in groupby(
|
||||
invoice_vals_list,
|
||||
key=lambda x: [
|
||||
x.get(grouping_key) for grouping_key in invoice_grouping_keys
|
||||
],
|
||||
):
|
||||
origins = set()
|
||||
payment_refs = set()
|
||||
refs = set()
|
||||
ref_invoice_vals = None
|
||||
for invoice_vals in invoices:
|
||||
if not ref_invoice_vals:
|
||||
ref_invoice_vals = invoice_vals
|
||||
else:
|
||||
ref_invoice_vals["invoice_line_ids"] += invoice_vals[
|
||||
"invoice_line_ids"
|
||||
]
|
||||
origins.add(invoice_vals["invoice_origin"])
|
||||
payment_refs.add(invoice_vals["payment_reference"])
|
||||
refs.add(invoice_vals["ref"])
|
||||
ref_invoice_vals.update(
|
||||
{
|
||||
"ref": ", ".join(refs)[:2000],
|
||||
"invoice_origin": ", ".join(origins),
|
||||
"payment_reference": len(payment_refs) == 1
|
||||
and payment_refs.pop()
|
||||
or False,
|
||||
}
|
||||
)
|
||||
new_invoice_vals_list.append(ref_invoice_vals)
|
||||
return new_invoice_vals_list
|
||||
|
||||
def _prepare_invoice(self, partner_invoice_id=False):
|
||||
"""
|
||||
Prepare the dict of values to create the new invoice for a folio.
|
||||
@@ -1629,28 +1755,25 @@ class PmsFolio(models.Model):
|
||||
(making sure to call super() to establish a clean extension chain).
|
||||
"""
|
||||
self.ensure_one()
|
||||
journal = (
|
||||
self.env["account.move"]
|
||||
.with_context(
|
||||
default_move_type="out_invoice",
|
||||
default_company_id=self.company_id.id,
|
||||
default_pms_property_id=self.pms_property_id.id,
|
||||
journal = self._get_folio_default_journal(partner_invoice_id)
|
||||
if not journal:
|
||||
journal = (
|
||||
self.env["account.move"]
|
||||
.with_context(
|
||||
default_move_type="out_invoice",
|
||||
default_company_id=self.company_id.id,
|
||||
default_pms_property_id=self.pms_property_id.id,
|
||||
)
|
||||
._get_default_journal()
|
||||
)
|
||||
._get_default_journal()
|
||||
)
|
||||
if not journal:
|
||||
raise UserError(
|
||||
_("Please define an accounting sales journal for the company %s (%s).")
|
||||
% (self.company_id.name, self.company_id.id)
|
||||
)
|
||||
|
||||
if not partner_invoice_id:
|
||||
partner_invoice_id = (
|
||||
self.partner_invoice_ids[0].id if self.partner_invoice_ids else False
|
||||
)
|
||||
|
||||
invoice_vals = {
|
||||
"ref": self.client_order_ref or "",
|
||||
"ref": self.name or "",
|
||||
"move_type": "out_invoice",
|
||||
"narration": self.note,
|
||||
"currency_id": self.pricelist_id.currency_id.id,
|
||||
@@ -1663,14 +1786,25 @@ class PmsFolio(models.Model):
|
||||
"journal_id": journal.id, # company comes from the journal
|
||||
"invoice_origin": self.name,
|
||||
"invoice_payment_term_id": self.payment_term_id.id,
|
||||
"payment_reference": self.reference,
|
||||
"transaction_ids": [(6, 0, self.transaction_ids.ids)],
|
||||
"folio_ids": [(6, 0, [self.id])],
|
||||
"invoice_line_ids": [],
|
||||
"company_id": self.company_id.id,
|
||||
"payment_reference": self.external_reference or self.reference,
|
||||
}
|
||||
return invoice_vals
|
||||
|
||||
def _get_folio_default_journal(self, partner_invoice_id):
|
||||
self.ensure_one()
|
||||
pms_property = self.pms_property_id
|
||||
partner = self.env["res.partner"].browse(partner_invoice_id)
|
||||
if not partner or (
|
||||
not partner._check_enought_invoice_data()
|
||||
and self._context.get("autoinvoice")
|
||||
):
|
||||
return pms_property.journal_simplified_invoice_id
|
||||
return pms_property.journal_normal_invoice_id
|
||||
|
||||
def do_payment(
|
||||
self,
|
||||
journal,
|
||||
@@ -1691,18 +1825,31 @@ class PmsFolio(models.Model):
|
||||
"""
|
||||
if not pay_type:
|
||||
pay_type = journal.type
|
||||
reference = folio.name
|
||||
if folio.external_reference:
|
||||
reference += " - " + folio.external_reference
|
||||
vals = {
|
||||
"journal_id": journal.id,
|
||||
"partner_id": partner.id,
|
||||
"amount": amount,
|
||||
"date": date or fields.Date.today(),
|
||||
"ref": folio.name,
|
||||
"ref": reference,
|
||||
"folio_ids": [(6, 0, [folio.id])],
|
||||
"payment_type": "inbound",
|
||||
"partner_type": "customer",
|
||||
"state": "draft",
|
||||
"origin_reference": folio.external_reference,
|
||||
}
|
||||
pay = self.env["account.payment"].create(vals)
|
||||
pay.message_post_with_view(
|
||||
"mail.message_origin_link",
|
||||
values={
|
||||
"self": pay,
|
||||
"origin": folio,
|
||||
},
|
||||
subtype_id=self.env.ref("mail.mt_note").id,
|
||||
)
|
||||
|
||||
pay.action_post()
|
||||
|
||||
# Review: force to autoreconcile payment with invoices already created
|
||||
@@ -1762,19 +1909,29 @@ class PmsFolio(models.Model):
|
||||
"""
|
||||
if not pay_type:
|
||||
pay_type = journal.type
|
||||
|
||||
reference = folio.name
|
||||
if folio.external_reference:
|
||||
reference += " - " + folio.external_reference
|
||||
vals = {
|
||||
"journal_id": journal.id,
|
||||
"partner_id": partner.id,
|
||||
"amount": amount if amount > 0 else -amount,
|
||||
"date": date or fields.Date.today(),
|
||||
"ref": folio.name,
|
||||
"ref": reference,
|
||||
"folio_ids": [(6, 0, [folio.id])],
|
||||
"payment_type": "outbound",
|
||||
"partner_type": "customer",
|
||||
"state": "draft",
|
||||
}
|
||||
pay = self.env["account.payment"].create(vals)
|
||||
pay.message_post_with_view(
|
||||
"mail.message_origin_link",
|
||||
values={
|
||||
"self": pay,
|
||||
"origin": folio,
|
||||
},
|
||||
subtype_id=self.env.ref("mail.mt_note").id,
|
||||
)
|
||||
pay.action_post()
|
||||
|
||||
# Automatic register refund in cash register
|
||||
@@ -2176,7 +2333,6 @@ class PmsFolio(models.Model):
|
||||
"partner_id": record.partner_id.id,
|
||||
"name": record.document_number,
|
||||
"category_id": record.document_type.id,
|
||||
"valid_from": record.document_expedition_date,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -142,6 +142,55 @@ class PmsProperty(models.Model):
|
||||
is_modified_auto_mail = fields.Boolean(string="Auto Send Modification Mail")
|
||||
is_canceled_auto_mail = fields.Boolean(string="Auto Send Cancellation Mail")
|
||||
|
||||
default_invoicing_policy = fields.Selection(
|
||||
string="Default Invoicing Policy",
|
||||
selection=[
|
||||
("manual", "Manual"),
|
||||
("checkout", "Checkout"),
|
||||
("month_day", "Month Day Invoice"),
|
||||
],
|
||||
default="manual",
|
||||
)
|
||||
|
||||
margin_days_autoinvoice = fields.Integer(
|
||||
string="Margin Days",
|
||||
help="Days from Checkout to generate the invoice",
|
||||
)
|
||||
|
||||
invoicing_month_day = fields.Integer(
|
||||
string="Invoicing Month Day",
|
||||
help="The day of the month to invoice",
|
||||
)
|
||||
|
||||
journal_simplified_invoice_id = fields.Many2one(
|
||||
string="Simplified Invoice Journal",
|
||||
comodel_name="account.journal",
|
||||
domain=[
|
||||
("type", "=", "sale"),
|
||||
],
|
||||
help="Journal used to create the simplified invoice",
|
||||
check_company=True,
|
||||
check_pms_properties=True,
|
||||
)
|
||||
|
||||
journal_normal_invoice_id = fields.Many2one(
|
||||
string="Normal Invoice Journal",
|
||||
comodel_name="account.journal",
|
||||
domain=[
|
||||
("type", "=", "sale"),
|
||||
("is_simplified_invoice", "=", False),
|
||||
],
|
||||
help="Journal used to create the normal invoice",
|
||||
check_company=True,
|
||||
check_pms_properties=True,
|
||||
)
|
||||
|
||||
max_amount_simplified_invoice = fields.Float(
|
||||
string="Max Amount Simplified Invoice",
|
||||
help="Maximum amount to create the simplified invoice",
|
||||
default=400.0,
|
||||
)
|
||||
|
||||
@api.depends_context(
|
||||
"checkin",
|
||||
"checkout",
|
||||
@@ -551,5 +600,37 @@ class PmsProperty(models.Model):
|
||||
"closed": True,
|
||||
}
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def autoinvoicing(self):
|
||||
"""
|
||||
This method is used to autoinvoicing the folios
|
||||
"""
|
||||
folios = self.env["pms.folio"].search(
|
||||
[
|
||||
("autoinvoice_date", "=", fields.date.today()),
|
||||
]
|
||||
)
|
||||
if folios:
|
||||
invoices = folios.with_context(autoinvoice=True)._create_invoices(
|
||||
grouped=True,
|
||||
)
|
||||
if invoices:
|
||||
invoices.action_post()
|
||||
return True
|
||||
|
||||
@api.constrains("journal_normal_invoice_id")
|
||||
def _check_journal_normal_invoice(self):
|
||||
for pms_property in self.filtered("journal_normal_invoice_id"):
|
||||
if pms_property.journal_normal_invoice_id.is_simplified_invoice:
|
||||
raise ValidationError(
|
||||
_("Journal %s is not allowed to be used for normal invoices")
|
||||
% pms_property.journal_normal_invoice_id.name
|
||||
)
|
||||
|
||||
@api.constrains("journal_simplified_invoice_id")
|
||||
def _check_journal_simplified_invoice(self):
|
||||
for pms_property in self.filtered("journal_simplified_invoice_id"):
|
||||
if not pms_property.journal_simplified_invoice_id.is_simplified_invoice:
|
||||
pms_property.journal_simplified_invoice_id.is_simplified_invoice = True
|
||||
|
||||
@@ -1597,13 +1597,33 @@ class PmsReservation(models.Model):
|
||||
)
|
||||
|
||||
@api.constrains("reservation_line_ids")
|
||||
def check_consecutive_dates(self):
|
||||
def checkin_checkout_consecutive_dates(self):
|
||||
"""
|
||||
simply convert date objects to integers using the .toordinal() method
|
||||
of datetime objects. The difference between the maximum and minimum value
|
||||
of the set of ordinal dates is one more than the length of the set
|
||||
"""
|
||||
for record in self:
|
||||
if min(record.reservation_line_ids.mapped("date")) != record.checkin:
|
||||
raise UserError(
|
||||
_(
|
||||
"""
|
||||
Compute error: The first room line date should
|
||||
be the same as the checkin date!
|
||||
"""
|
||||
)
|
||||
)
|
||||
if max(
|
||||
record.reservation_line_ids.mapped("date")
|
||||
) != record.checkout - datetime.timedelta(days=1):
|
||||
raise UserError(
|
||||
_(
|
||||
"""
|
||||
Compute error: The last room line date should
|
||||
be the previous day of the checkout date!
|
||||
"""
|
||||
)
|
||||
)
|
||||
if record.reservation_line_ids and len(record.reservation_line_ids) > 1:
|
||||
dates = record.reservation_line_ids.mapped("date")
|
||||
date_ints = {d.toordinal() for d in dates}
|
||||
@@ -1874,8 +1894,23 @@ class PmsReservation(models.Model):
|
||||
record = super(PmsReservation, self).create(vals)
|
||||
if record.preconfirm and record.state == "draft":
|
||||
record.confirm()
|
||||
|
||||
record._check_services(vals)
|
||||
|
||||
return record
|
||||
|
||||
def write(self, vals):
|
||||
asset = super(PmsReservation, self).write(vals)
|
||||
self._check_services(vals)
|
||||
return asset
|
||||
|
||||
def _check_services(self, vals):
|
||||
# If we create a reservation with board service and other service at the same time,
|
||||
# compute_service_ids dont run (compute with readonly to False),
|
||||
# and we must force it to compute the services linked with the board service:
|
||||
if "board_service_room_id" in vals and "service_ids" in vals:
|
||||
self._compute_service_ids()
|
||||
|
||||
def update_prices(self):
|
||||
self.ensure_one()
|
||||
for line in self.reservation_line_ids:
|
||||
|
||||
@@ -346,9 +346,10 @@ class PmsReservationLine(models.Model):
|
||||
else:
|
||||
impacts_quota = line.impacts_quota
|
||||
line.impacts_quota = self.env["pms.availability.plan"].update_quota(
|
||||
pricelist_id=reservation.pricelist_id,
|
||||
room_type_id=reservation.room_type_id,
|
||||
pricelist_id=reservation.pricelist_id.id,
|
||||
room_type_id=reservation.room_type_id.id,
|
||||
date=line.date,
|
||||
pms_property_id=reservation.pms_property_id.id,
|
||||
impacts_quota_id=impacts_quota,
|
||||
)
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ class ResCompany(models.Model):
|
||||
inverse_name="company_id",
|
||||
)
|
||||
|
||||
url_advert = fields.Char(string="Url Advert", help="Url to identify the ad")
|
||||
|
||||
privacy_policy = fields.Text(
|
||||
string="Privacy Policy",
|
||||
help="Authorization by the user for the" "manage of their personal data",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import logging
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -42,7 +43,7 @@ class ResPartner(models.Model):
|
||||
)
|
||||
pms_property_ids = fields.Many2many(
|
||||
string="Properties",
|
||||
help="Properties with access to the element;"
|
||||
help="Properties with access to the element"
|
||||
" if not set, all properties can access",
|
||||
required=False,
|
||||
comodel_name="pms.property",
|
||||
@@ -85,15 +86,6 @@ class ResPartner(models.Model):
|
||||
store=True,
|
||||
compute="_compute_nationality_id",
|
||||
)
|
||||
# TODO: Use new partner contact "other or "private" with
|
||||
# personal contact address complete??
|
||||
# to avoid user country_id on companies contacts.
|
||||
# view to checkin partner state_id field
|
||||
state_id = fields.Many2one(
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_state_id",
|
||||
)
|
||||
email = fields.Char(
|
||||
readonly=False,
|
||||
store=True,
|
||||
@@ -104,6 +96,11 @@ class ResPartner(models.Model):
|
||||
store=True,
|
||||
compute="_compute_mobile",
|
||||
)
|
||||
phone = fields.Char(
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_phone",
|
||||
)
|
||||
firstname = fields.Char(
|
||||
readonly=False,
|
||||
store=True,
|
||||
@@ -120,6 +117,11 @@ class ResPartner(models.Model):
|
||||
store=True,
|
||||
compute="_compute_lastname2",
|
||||
)
|
||||
vat = fields.Char(
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_vat",
|
||||
)
|
||||
comment = fields.Text(
|
||||
tracking=True,
|
||||
)
|
||||
@@ -133,6 +135,109 @@ class ResPartner(models.Model):
|
||||
string="Possible Customer In Checkin Partner",
|
||||
comodel_name="pms.checkin.partner",
|
||||
)
|
||||
invoicing_policy = fields.Selection(
|
||||
string="Invoicing Policy",
|
||||
help="""The invoicing policy of the partner,
|
||||
set Property to user the policy configured in the Property""",
|
||||
selection=[
|
||||
("property", "Property Policy Invoice"),
|
||||
("manual", "Manual"),
|
||||
("checkout", "From Checkout"),
|
||||
("month_day", "Month Day Invoice"),
|
||||
],
|
||||
default="property",
|
||||
)
|
||||
invoicing_month_day = fields.Integer(
|
||||
string="Invoicing Month Day",
|
||||
help="The day of the month to invoice",
|
||||
)
|
||||
margin_days_autoinvoice = fields.Integer(
|
||||
string="Days from Checkout",
|
||||
help="Days from Checkout to generate the invoice",
|
||||
)
|
||||
default_invoice_lines = fields.Selection(
|
||||
string="Invoice...",
|
||||
help="""Use to preconfigure the sale lines to autoinvoice
|
||||
for this partner. All (invoice reservations and services),
|
||||
Only overnights to invoice only the reservations
|
||||
with overnight and board services(exclude parkings, salon, etc...),
|
||||
All reservations to include all reservations,
|
||||
and Services only include services not boards""",
|
||||
selection=[
|
||||
("all", "All"),
|
||||
("overnights", "Only Overnights"),
|
||||
("reservations", "All reservations"),
|
||||
("services", "Services"),
|
||||
],
|
||||
default="all",
|
||||
)
|
||||
vat_document_type = fields.Selection(
|
||||
string="Document Type",
|
||||
help="""The vat document type of the partner,
|
||||
set if is a fiscal document, passport, etc...""",
|
||||
selection=lambda self: self._selection_vat_document_type(),
|
||||
compute="_compute_vat_document_type",
|
||||
store=True,
|
||||
)
|
||||
residence_street = fields.Char(
|
||||
string="Street of residence",
|
||||
help="Street of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_street",
|
||||
)
|
||||
residence_street2 = fields.Char(
|
||||
string="Second street of residence",
|
||||
help="Second street of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_street2",
|
||||
)
|
||||
residence_zip = fields.Char(
|
||||
string="Zip of residence",
|
||||
help="Zip of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_zip",
|
||||
change_default=True,
|
||||
)
|
||||
residence_city = fields.Char(
|
||||
string="city of residence",
|
||||
help="City of the guest's residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_city",
|
||||
)
|
||||
residence_country_id = fields.Many2one(
|
||||
string="Country of residence",
|
||||
help="Partner country of residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_country_id",
|
||||
comodel_name="res.country",
|
||||
)
|
||||
residence_state_id = fields.Many2one(
|
||||
string="State of residence",
|
||||
help="Partner state of residence",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_residence_state_id",
|
||||
comodel_name="res.country.state",
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _selection_vat_document_type(self):
|
||||
vat_document_types = [
|
||||
("vat", _("VAT")),
|
||||
]
|
||||
document_categories = self.env["res.partner.id_category"].search(
|
||||
[
|
||||
("is_vat_equivalent", "=", False),
|
||||
]
|
||||
)
|
||||
for doc_type in document_categories:
|
||||
vat_document_types.append((doc_type.name, doc_type.name))
|
||||
return vat_document_types
|
||||
|
||||
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.gender")
|
||||
def _compute_gender(self):
|
||||
@@ -188,24 +293,145 @@ class ResPartner(models.Model):
|
||||
elif not record.nationality_id:
|
||||
record.nationality_id = False
|
||||
|
||||
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.state_id")
|
||||
def _compute_state_id(self):
|
||||
if hasattr(super(), "_compute_state_id"):
|
||||
super()._compute_state_id()
|
||||
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.phone")
|
||||
def _compute_phone(self):
|
||||
if hasattr(super(), "_compute_phone"):
|
||||
super()._compute_phone()
|
||||
for record in self:
|
||||
if not record.state_id and record.pms_checkin_partner_ids:
|
||||
state_id = list(
|
||||
if not record.phone and record.pms_checkin_partner_ids:
|
||||
phone = list(
|
||||
filter(None, set(record.pms_checkin_partner_ids.mapped("phone")))
|
||||
)
|
||||
if len(phone) == 1:
|
||||
record.phone = phone[0]
|
||||
else:
|
||||
record.phone = False
|
||||
elif not record.phone:
|
||||
record.phone = False
|
||||
|
||||
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_street")
|
||||
def _compute_residence_street(self):
|
||||
if hasattr(super(), "_compute_residence_street"):
|
||||
super()._compute_residence_street()
|
||||
for record in self:
|
||||
if not record.residence_street and record.pms_checkin_partner_ids:
|
||||
residence_street = list(
|
||||
filter(
|
||||
None,
|
||||
set(record.pms_checkin_partner_ids.mapped("state_id")),
|
||||
set(record.pms_checkin_partner_ids.mapped("residence_street")),
|
||||
)
|
||||
)
|
||||
if len(state_id) == 1:
|
||||
record.state_id = state_id[0]
|
||||
if len(residence_street) == 1:
|
||||
record.residence_street = residence_street[0]
|
||||
else:
|
||||
record.state_id = False
|
||||
elif not record.state_id:
|
||||
record.state_id = False
|
||||
record.residence_street = False
|
||||
elif not record.residence_street:
|
||||
record.residence_street = False
|
||||
|
||||
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_street2")
|
||||
def _compute_residence_street2(self):
|
||||
if hasattr(super(), "_compute_residence_street2"):
|
||||
super()._compute_residence_street2()
|
||||
for record in self:
|
||||
if not record.residence_street2 and record.pms_checkin_partner_ids:
|
||||
residence_street2 = list(
|
||||
filter(
|
||||
None,
|
||||
set(record.pms_checkin_partner_ids.mapped("residence_street2")),
|
||||
)
|
||||
)
|
||||
if len(residence_street2) == 1:
|
||||
record.residence_street2 = residence_street2[0]
|
||||
else:
|
||||
record.residence_street2 = False
|
||||
elif not record.residence_street2:
|
||||
record.residence_street2 = False
|
||||
|
||||
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_zip")
|
||||
def _compute_residence_zip(self):
|
||||
if hasattr(super(), "_compute_residence_zip"):
|
||||
super()._compute_residence_zip()
|
||||
for record in self:
|
||||
if not record.residence_zip and record.pms_checkin_partner_ids:
|
||||
residence_zip = list(
|
||||
filter(
|
||||
None,
|
||||
set(record.pms_checkin_partner_ids.mapped("residence_zip")),
|
||||
)
|
||||
)
|
||||
if len(residence_zip) == 1:
|
||||
record.residence_zip = residence_zip[0]
|
||||
else:
|
||||
record.residence_zip = False
|
||||
elif not record.residence_zip:
|
||||
record.residence_zip = False
|
||||
|
||||
@api.depends("pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_city")
|
||||
def _compute_residence_city(self):
|
||||
if hasattr(super(), "_compute_residence_city"):
|
||||
super()._compute_residence_city()
|
||||
for record in self:
|
||||
if not record.residence_city and record.pms_checkin_partner_ids:
|
||||
residence_city = list(
|
||||
filter(
|
||||
None,
|
||||
set(record.pms_checkin_partner_ids.mapped("residence_city")),
|
||||
)
|
||||
)
|
||||
if len(residence_city) == 1:
|
||||
record.residence_city = residence_city[0]
|
||||
else:
|
||||
record.residence_city = False
|
||||
elif not record.residence_city:
|
||||
record.residence_city = False
|
||||
|
||||
@api.depends(
|
||||
"pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_country_id"
|
||||
)
|
||||
def _compute_residence_country_id(self):
|
||||
if hasattr(super(), "_compute_residence_country_id"):
|
||||
super()._compute_residence_country_id()
|
||||
for record in self:
|
||||
if not record.residence_country_id and record.pms_checkin_partner_ids:
|
||||
residence_country_id = list(
|
||||
filter(
|
||||
None,
|
||||
set(
|
||||
record.pms_checkin_partner_ids.mapped(
|
||||
"residence_country_id"
|
||||
)
|
||||
),
|
||||
)
|
||||
)
|
||||
if len(residence_country_id) == 1:
|
||||
record.residence_country_id = residence_country_id[0]
|
||||
else:
|
||||
record.residence_country_id = False
|
||||
elif not record.residence_country_id:
|
||||
record.residence_country_id = False
|
||||
|
||||
@api.depends(
|
||||
"pms_checkin_partner_ids", "pms_checkin_partner_ids.residence_state_id"
|
||||
)
|
||||
def _compute_residence_state_id(self):
|
||||
if hasattr(super(), "_compute_residence_state_id"):
|
||||
super()._compute_residence_state_id()
|
||||
for record in self:
|
||||
if not record.residence_state_id and record.pms_checkin_partner_ids:
|
||||
residence_state_id = list(
|
||||
filter(
|
||||
None,
|
||||
set(
|
||||
record.pms_checkin_partner_ids.mapped("residence_state_id")
|
||||
),
|
||||
)
|
||||
)
|
||||
if len(residence_state_id) == 1:
|
||||
record.residence_state_id = residence_state_id[0]
|
||||
else:
|
||||
record.residence_state_id = False
|
||||
elif not record.residence_state_id:
|
||||
record.residence_state_id = False
|
||||
|
||||
@api.depends(
|
||||
"pms_checkin_partner_ids",
|
||||
@@ -327,6 +553,17 @@ class ResPartner(models.Model):
|
||||
elif not record.lastname2:
|
||||
record.lastname2 = False
|
||||
|
||||
@api.depends("id_numbers", "id_numbers.name")
|
||||
def _compute_vat(self):
|
||||
if hasattr(super(), "_compute_vat"):
|
||||
super()._compute_vat()
|
||||
for record in self:
|
||||
if not record.vat and record.id_numbers:
|
||||
vat = list(filter(None, set(record.id_numbers.mapped("name"))))
|
||||
record.vat = vat[0]
|
||||
elif not record.vat:
|
||||
record.vat = False
|
||||
|
||||
def _compute_reservations_count(self):
|
||||
# Return reservation with partner included in reservation and/or checkin
|
||||
pms_reservation_obj = self.env["pms.reservation"]
|
||||
@@ -348,6 +585,27 @@ class ResPartner(models.Model):
|
||||
]
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"vat", "id_numbers", "id_numbers.category_id", "id_numbers.vat_syncronized"
|
||||
)
|
||||
def _compute_vat_document_type(self):
|
||||
self.vat_document_type = False
|
||||
for record in self.filtered("vat"):
|
||||
document = record.id_numbers.filtered("vat_syncronized")
|
||||
if document:
|
||||
if len(document) > 1:
|
||||
raise ValidationError(
|
||||
_("There is more than one document with vat syncronized")
|
||||
)
|
||||
if record.vat:
|
||||
record.vat_document_type = (
|
||||
document.category_id.name
|
||||
if not document.category_id.is_vat_equivalent
|
||||
else "vat"
|
||||
)
|
||||
else:
|
||||
record.vat_document_type = "vat"
|
||||
|
||||
def action_partner_reservations(self):
|
||||
self.ensure_one()
|
||||
checkin_reservation_ids = (
|
||||
@@ -499,3 +757,29 @@ class ResPartner(models.Model):
|
||||
key_fields = super(ResPartner, self)._get_key_fields()
|
||||
key_fields.extend(["document_number"])
|
||||
return key_fields
|
||||
|
||||
def _check_enought_invoice_data(self):
|
||||
self.ensure_one()
|
||||
if self.vat and self.country_id and self.city and self.street:
|
||||
return True
|
||||
return False
|
||||
|
||||
@api.constrains("vat_document_type")
|
||||
def check_vat(self):
|
||||
"""
|
||||
Inherit constrain to allow set vat in
|
||||
document ids like passport, etc...
|
||||
"""
|
||||
for partner in self:
|
||||
if not partner.vat_document_type or partner.vat_document_type != "vat":
|
||||
continue
|
||||
else:
|
||||
super(ResPartner, partner).check_vat()
|
||||
|
||||
def unlink(self):
|
||||
dummy, various_partner_id = self.env["ir.model.data"].get_object_reference(
|
||||
"pms", "various_pms_partner"
|
||||
)
|
||||
if various_partner_id in self.ids:
|
||||
raise ValidationError(_("The partner 'Various Clients' cannot be deleted"))
|
||||
return super().unlink()
|
||||
|
||||
11
pms/models/res_partner_id_category.py
Normal file
11
pms/models/res_partner_id_category.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartnerIdCategory(models.Model):
|
||||
_inherit = "res.partner.id_category"
|
||||
|
||||
is_vat_equivalent = fields.Boolean(
|
||||
string="Is VAT Equivalent",
|
||||
help="If true, this document type is check by vat number",
|
||||
default=False,
|
||||
)
|
||||
@@ -19,6 +19,11 @@ class ResPartnerIdNumber(models.Model):
|
||||
store=True,
|
||||
compute="_compute_valid_from",
|
||||
)
|
||||
vat_syncronized = fields.Boolean(
|
||||
help="Technical field to know if vat partner is syncronized with this document",
|
||||
compute="_compute_vat_syncronized",
|
||||
store=True,
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
"partner_id", "partner_id.pms_checkin_partner_ids.document_expedition_date"
|
||||
@@ -53,3 +58,15 @@ class ResPartnerIdNumber(models.Model):
|
||||
)
|
||||
if len(id_number) > 1:
|
||||
raise ValidationError(_("Partner already has this document type"))
|
||||
|
||||
@api.depends("partner_id", "partner_id.vat", "name")
|
||||
def _compute_vat_syncronized(self):
|
||||
self.vat_syncronized = False
|
||||
for record in self:
|
||||
if record.partner_id and record.partner_id.vat and record.name:
|
||||
if record.name.upper() == record.partner_id.vat.upper():
|
||||
record.vat_syncronized = True
|
||||
elif not record.partner_id.vat and record.name:
|
||||
record.vat_syncronized = True
|
||||
else:
|
||||
record.vat_syncronized = False
|
||||
|
||||
31
pms/report/invoice.xml
Normal file
31
pms/report/invoice.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<template id="report_invoice_document" inherit_id="account.report_invoice_document">
|
||||
<xpath expr="//div[@class='page']//h2" position="replace">
|
||||
<h2>
|
||||
<span
|
||||
t-if="o.move_type == 'out_invoice' and o.is_simplified_invoice and o.state == 'posted'"
|
||||
>Simplified Invoice</span>
|
||||
<span
|
||||
t-if="o.move_type == 'out_invoice' and o.is_simplified_invoice and o.state == 'draft'"
|
||||
>Draft Simplified Invoice</span>
|
||||
<span
|
||||
t-if="o.move_type == 'out_invoice' and o.is_simplified_invoice and o.state == 'cancel'"
|
||||
>Cancelled Simplified Invoice</span>
|
||||
<span
|
||||
t-if="o.move_type == 'out_invoice' and not o.is_simplified_invoice and o.state == 'posted'"
|
||||
>Invoice</span>
|
||||
<span
|
||||
t-if="o.move_type == 'out_invoice' and not o.is_simplified_invoice and o.state == 'draft'"
|
||||
>Draft Invoice</span>
|
||||
<span
|
||||
t-if="o.move_type == 'out_invoice' and not o.is_simplified_invoice and o.state == 'cancel'"
|
||||
>Cancelled Invoice</span>
|
||||
<span t-if="o.move_type == 'out_refund'">Credit Note</span>
|
||||
<span t-if="o.move_type == 'in_refund'">Vendor Credit Note</span>
|
||||
<span t-if="o.move_type == 'in_invoice'">Vendor Bill</span>
|
||||
<span t-if="o.name != '/'" t-field="o.name" />
|
||||
</h2>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -43,9 +43,9 @@
|
||||
</h2>
|
||||
|
||||
<div class="row mt32 mb32" id="informations">
|
||||
<div t-if="doc.client_order_ref" class="col-auto col-3 mw-100 mb-2">
|
||||
<div t-if="doc.name" class="col-auto col-3 mw-100 mb-2">
|
||||
<strong>Your Reference:</strong>
|
||||
<p class="m-0" t-field="doc.client_order_ref" />
|
||||
<p class="m-0" t-field="doc.name" />
|
||||
</div>
|
||||
<div
|
||||
t-if="doc.date_order and doc.state not in ['draft','sent']"
|
||||
|
||||
@@ -64,3 +64,5 @@ user_access_pms_automated_mails,user_access_pms_automated_mails,model_pms_automa
|
||||
access_pms_several_partners_wizard,access_pms_several_partners_wizard,model_pms_several_partners_wizard,base.group_user,1,1,1,1
|
||||
user_access_res_partner_portal,user_access_res_partner_portal,model_res_partner,base.group_portal,1,1,1,1
|
||||
user_access_pms_precheckin_portal,user_access_pms_precheckin_portal,model_pms_checkin_partner,base.group_portal,1,1,1,1
|
||||
user_access_pms_booking_duplicate,user_access_pms_booking_duplicate,model_pms_booking_duplicate,pms.group_pms_user,1,1,1,1
|
||||
user_access_pms_reservation_duplicate,user_access_pms_reservation_duplicate,model_pms_reservation_duplicate,pms.group_pms_user,1,1,1,1
|
||||
|
||||
|
@@ -1201,7 +1201,6 @@ class TestPmsCheckinPartner(TestPms):
|
||||
is = 20 years old and document_date = today + 1 year. The expected
|
||||
expedition date has to be doc_date - 5 years
|
||||
"""
|
||||
doc_type_id = self.env["res.partner.id_category"].search([("code", "=", "D")])
|
||||
doc_date = fields.date.today() + datetime.timedelta(days=366)
|
||||
doc_date_str = str(doc_date)
|
||||
|
||||
@@ -1213,7 +1212,7 @@ class TestPmsCheckinPartner(TestPms):
|
||||
expected_exp_date = doc_date - datetime.timedelta(days=1826.25)
|
||||
expedition_date = (
|
||||
self.checkin1.calculate_doc_type_expedition_date_from_validity_date(
|
||||
doc_type_id, doc_date_str, birthdate_str
|
||||
self.id_category, doc_date_str, birthdate_str
|
||||
)
|
||||
)
|
||||
date_expedition_date = datetime.date(
|
||||
@@ -1238,7 +1237,6 @@ class TestPmsCheckinPartner(TestPms):
|
||||
is = 40 years old and document_date = today + 1 year. The expected
|
||||
expedition date has to be doc_date - 10 years
|
||||
"""
|
||||
doc_type_id = self.env["res.partner.id_category"].search([("code", "=", "D")])
|
||||
doc_date = fields.date.today() + datetime.timedelta(days=366)
|
||||
doc_date_str = str(doc_date)
|
||||
|
||||
@@ -1250,7 +1248,7 @@ class TestPmsCheckinPartner(TestPms):
|
||||
expected_exp_date = doc_date - datetime.timedelta(days=3652.5)
|
||||
expedition_date = (
|
||||
self.checkin1.calculate_doc_type_expedition_date_from_validity_date(
|
||||
doc_type_id, doc_date_str, birthdate_str
|
||||
self.id_category, doc_date_str, birthdate_str
|
||||
)
|
||||
)
|
||||
date_expedition_date = datetime.date(
|
||||
@@ -1275,7 +1273,6 @@ class TestPmsCheckinPartner(TestPms):
|
||||
is = 20 years old and document_date = today + 1 year. The expected
|
||||
expedition date has to be doc_date - 5 years
|
||||
"""
|
||||
doc_type_id = self.env["res.partner.id_category"].search([("code", "=", "P")])
|
||||
doc_date = fields.date.today() + datetime.timedelta(days=366)
|
||||
doc_date_str = str(doc_date)
|
||||
|
||||
@@ -1287,7 +1284,7 @@ class TestPmsCheckinPartner(TestPms):
|
||||
expected_exp_date = doc_date - datetime.timedelta(days=1826.25)
|
||||
expedition_date = (
|
||||
self.checkin1.calculate_doc_type_expedition_date_from_validity_date(
|
||||
doc_type_id, doc_date_str, birthdate_str
|
||||
self.id_category, doc_date_str, birthdate_str
|
||||
)
|
||||
)
|
||||
date_expedition_date = datetime.date(
|
||||
@@ -1436,7 +1433,7 @@ class TestPmsCheckinPartner(TestPms):
|
||||
"firstname": "Serafín",
|
||||
"lastname": "Rivas",
|
||||
"lastname2": "Gonzalez",
|
||||
"document_type": self.id_category.name,
|
||||
"document_type": self.id_category.code,
|
||||
"document_number": "18038946T",
|
||||
"document_expedition_date": "2015-10-07",
|
||||
"birthdate_date": "1983-10-05",
|
||||
@@ -1464,3 +1461,65 @@ class TestPmsCheckinPartner(TestPms):
|
||||
checkin_partner_vals[key],
|
||||
"The value of " + key + " is not correctly established",
|
||||
)
|
||||
|
||||
def test_compute_partner_fields(self):
|
||||
"""
|
||||
Check that the computes of the checkin_partner fields related to your partner correctly
|
||||
add these fields to the checkin_partner.
|
||||
---------------------------------------
|
||||
A reservation is created with an adult (checkin_partner) ql which is saved in the
|
||||
checkin_partner_id variable, a partner is also created with all the fields that are
|
||||
related to the checkin_partner fields. The partner is added to the partner_id field
|
||||
of the checkin_partner and, through subtests, it is verified that the fields of the
|
||||
partner and the associated checkin_partner match.
|
||||
"""
|
||||
self.reservation = self.env["pms.reservation"].create(
|
||||
{
|
||||
"checkin": datetime.date.today() + datetime.timedelta(days=1),
|
||||
"checkout": datetime.date.today() + datetime.timedelta(days=2),
|
||||
"room_type_id": self.room_type1.id,
|
||||
"partner_id": self.host1.id,
|
||||
"adults": 1,
|
||||
"pms_property_id": self.pms_property1.id,
|
||||
}
|
||||
)
|
||||
checkin_partner_id = self.reservation.checkin_partner_ids[0]
|
||||
nationality_id = self.env["res.country"].browse(1)
|
||||
state_id = self.env["res.country.state"].browse(1)
|
||||
partner_vals = {
|
||||
"firstname": "Paz",
|
||||
"lastname": "Valenzuela",
|
||||
"lastname2": "Soto",
|
||||
"email": "paz@example.com",
|
||||
"birthdate_date": datetime.date(1980, 10, 5),
|
||||
"gender": "female",
|
||||
"mobile": "666555444",
|
||||
"phone": "123456789",
|
||||
"nationality_id": nationality_id.id,
|
||||
"residence_street": "Calle 123",
|
||||
"residence_street2": "Avda. Constitución 123",
|
||||
"residence_zip": "15700",
|
||||
"residence_city": "City Residence",
|
||||
"residence_country_id": nationality_id.id,
|
||||
"residence_state_id": state_id.id,
|
||||
# "pms_checkin_partner_ids": checkin_partner_id,
|
||||
}
|
||||
self.partner_id = self.env["res.partner"].create(partner_vals)
|
||||
|
||||
partner_vals.update(
|
||||
{
|
||||
"nationality_id": nationality_id,
|
||||
"residence_country_id": nationality_id,
|
||||
"residence_state_id": state_id,
|
||||
}
|
||||
)
|
||||
|
||||
checkin_partner_id.partner_id = self.partner_id.id
|
||||
for key in partner_vals:
|
||||
if key != "pms_checkin_partner_ids":
|
||||
with self.subTest(k=key):
|
||||
self.assertEqual(
|
||||
self.reservation.checkin_partner_ids[0][key],
|
||||
self.partner_id[key],
|
||||
"The value of " + key + " is not correctly established",
|
||||
)
|
||||
|
||||
@@ -10,12 +10,24 @@ class TestPmsFolioInvoice(TestPms):
|
||||
self.room_type_availability = self.env["pms.availability.plan"].create(
|
||||
{"name": "Availability plan for TEST"}
|
||||
)
|
||||
|
||||
# journal to simplified invoices
|
||||
self.simplified_journal = self.env["account.journal"].create(
|
||||
{
|
||||
"name": "Simplified journal",
|
||||
"code": "SMP",
|
||||
"type": "sale",
|
||||
"company_id": self.env.ref("base.main_company").id,
|
||||
}
|
||||
)
|
||||
|
||||
# create a property
|
||||
self.property = self.env["pms.property"].create(
|
||||
{
|
||||
"name": "MY PMS TEST",
|
||||
"company_id": self.env.ref("base.main_company").id,
|
||||
"default_pricelist_id": self.pricelist1.id,
|
||||
"journal_simplified_invoice_id": self.simplified_journal.id,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -62,6 +74,11 @@ class TestPmsFolioInvoice(TestPms):
|
||||
self.partner_id = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "Miguel",
|
||||
"vat": "ES123456789",
|
||||
"country_id": self.env.ref("base.es").id,
|
||||
"city": "Madrid",
|
||||
"zip": "28013",
|
||||
"street": "Calle de la calle",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -147,7 +164,7 @@ class TestPmsFolioInvoice(TestPms):
|
||||
] = 1
|
||||
r1.folio_id._create_invoices(
|
||||
lines_to_invoice=dict_lines,
|
||||
partner_invoice_id=self.env.ref("base.res_partner_1"),
|
||||
partner_invoice_id=self.env.ref("base.res_partner_1").id,
|
||||
)
|
||||
|
||||
# test does not work without invalidating cache
|
||||
@@ -165,7 +182,7 @@ class TestPmsFolioInvoice(TestPms):
|
||||
] = 2
|
||||
r1.folio_id._create_invoices(
|
||||
lines_to_invoice=dict_lines,
|
||||
partner_invoice_id=self.env.ref("base.res_partner_12"),
|
||||
partner_invoice_id=self.env.ref("base.res_partner_12").id,
|
||||
)
|
||||
self.assertNotEqual(
|
||||
r1.folio_id.move_ids.mapped("partner_id")[0],
|
||||
@@ -298,13 +315,6 @@ class TestPmsFolioInvoice(TestPms):
|
||||
{"name": "Test Product 1", "per_day": True, "list_price": 10}
|
||||
)
|
||||
|
||||
self.service1 = self.env["pms.service"].create(
|
||||
{
|
||||
"is_board_service": False,
|
||||
"product_id": self.product1.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.reservation1 = self.env["pms.reservation"].create(
|
||||
{
|
||||
"pms_property_id": self.property.id,
|
||||
@@ -313,13 +323,21 @@ class TestPmsFolioInvoice(TestPms):
|
||||
"adults": 2,
|
||||
"room_type_id": self.room_type_double.id,
|
||||
"partner_id": self.partner_id.id,
|
||||
"service_ids": [(6, 0, [self.service1.id])],
|
||||
}
|
||||
)
|
||||
|
||||
self.service1 = self.env["pms.service"].create(
|
||||
{
|
||||
"is_board_service": False,
|
||||
"product_id": self.product1.id,
|
||||
"reservation_id": self.reservation1.id,
|
||||
}
|
||||
)
|
||||
|
||||
dict_lines = dict()
|
||||
dict_lines[
|
||||
self.reservation1.folio_id.sale_line_ids.filtered("service_id")[0].id
|
||||
] = 1
|
||||
] = 3
|
||||
self.reservation1.folio_id._create_invoices(lines_to_invoice=dict_lines)
|
||||
self.assertEqual(
|
||||
self.reservation1.folio_id.sale_line_ids.filtered("service_id")[
|
||||
@@ -339,13 +357,6 @@ class TestPmsFolioInvoice(TestPms):
|
||||
{"name": "Test Product 1", "per_day": True, "list_price": 10}
|
||||
)
|
||||
|
||||
self.service1 = self.env["pms.service"].create(
|
||||
{
|
||||
"is_board_service": False,
|
||||
"product_id": self.product1.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.reservation1 = self.env["pms.reservation"].create(
|
||||
{
|
||||
"pms_property_id": self.property.id,
|
||||
@@ -354,9 +365,17 @@ class TestPmsFolioInvoice(TestPms):
|
||||
"adults": 2,
|
||||
"room_type_id": self.room_type_double.id,
|
||||
"partner_id": self.partner_id.id,
|
||||
"service_ids": [(6, 0, [self.service1.id])],
|
||||
}
|
||||
)
|
||||
|
||||
self.service1 = self.env["pms.service"].create(
|
||||
{
|
||||
"is_board_service": False,
|
||||
"product_id": self.product1.id,
|
||||
"reservation_id": self.reservation1.id,
|
||||
}
|
||||
)
|
||||
|
||||
dict_lines = dict()
|
||||
service_lines = self.reservation1.folio_id.sale_line_ids.filtered("service_id")
|
||||
for line in service_lines:
|
||||
@@ -381,13 +400,6 @@ class TestPmsFolioInvoice(TestPms):
|
||||
{"name": "Test Product 1", "per_day": True, "list_price": 10}
|
||||
)
|
||||
|
||||
self.service1 = self.env["pms.service"].create(
|
||||
{
|
||||
"is_board_service": False,
|
||||
"product_id": self.product1.id,
|
||||
}
|
||||
)
|
||||
|
||||
self.reservation1 = self.env["pms.reservation"].create(
|
||||
{
|
||||
"pms_property_id": self.property.id,
|
||||
@@ -396,9 +408,17 @@ class TestPmsFolioInvoice(TestPms):
|
||||
"adults": 2,
|
||||
"room_type_id": self.room_type_double.id,
|
||||
"partner_id": self.partner_id.id,
|
||||
"service_ids": [(6, 0, [self.service1.id])],
|
||||
}
|
||||
)
|
||||
|
||||
self.service1 = self.env["pms.service"].create(
|
||||
{
|
||||
"is_board_service": False,
|
||||
"product_id": self.product1.id,
|
||||
"reservation_id": self.reservation1.id,
|
||||
}
|
||||
)
|
||||
|
||||
expected_qty_to_invoice = sum(
|
||||
self.reservation1.folio_id.sale_line_ids.filtered("service_id").mapped(
|
||||
"qty_to_invoice"
|
||||
@@ -579,14 +599,157 @@ class TestPmsFolioInvoice(TestPms):
|
||||
"The quantity of board services to be invoice is wrong",
|
||||
)
|
||||
|
||||
def test_autoinvoice_folio_checkout_property_policy(self):
|
||||
"""
|
||||
Test create and invoice the cron by property preconfig automation
|
||||
--------------------------------------
|
||||
Set property default_invoicing_policy to checkout with 0 days with
|
||||
margin, and check that the folio autoinvoice date is set to last checkout
|
||||
folio date
|
||||
"""
|
||||
# ARRANGE
|
||||
self.property.default_invoicing_policy = "checkout"
|
||||
self.property.margin_days_autoinvoice = 0
|
||||
|
||||
# ACT
|
||||
self.reservation1 = self.env["pms.reservation"].create(
|
||||
{
|
||||
"pms_property_id": self.property.id,
|
||||
"checkin": datetime.date.today(),
|
||||
"checkout": datetime.date.today() + datetime.timedelta(days=3),
|
||||
"adults": 2,
|
||||
"room_type_id": self.room_type_double.id,
|
||||
"partner_id": self.partner_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
# ASSERT
|
||||
self.assertEqual(
|
||||
datetime.date.today() + datetime.timedelta(days=3),
|
||||
self.reservation1.folio_id.autoinvoice_date,
|
||||
"The autoinvoice date in folio with property checkout policy is wrong",
|
||||
)
|
||||
|
||||
def test_autoinvoice_folio_checkout_partner_policy(self):
|
||||
"""
|
||||
Test create and invoice the cron by partner preconfig automation
|
||||
--------------------------------------
|
||||
Set partner invoicing_policy to checkout with 2 days with
|
||||
margin, and check that the folio autoinvoice date is set to last checkout
|
||||
folio date + 2 days
|
||||
"""
|
||||
# ARRANGE
|
||||
self.partner_id.invoicing_policy = "checkout"
|
||||
self.partner_id.margin_days_autoinvoice = 2
|
||||
|
||||
# ACT
|
||||
self.reservation1 = self.env["pms.reservation"].create(
|
||||
{
|
||||
"pms_property_id": self.property.id,
|
||||
"checkin": datetime.date.today(),
|
||||
"checkout": datetime.date.today() + datetime.timedelta(days=3),
|
||||
"adults": 2,
|
||||
"room_type_id": self.room_type_double.id,
|
||||
"partner_id": self.partner_id.id,
|
||||
}
|
||||
)
|
||||
|
||||
# ASSERT
|
||||
self.assertEqual(
|
||||
datetime.date.today() + datetime.timedelta(days=5),
|
||||
self.reservation1.folio_id.autoinvoice_date,
|
||||
"The autoinvoice date in folio with property checkout policy is wrong",
|
||||
)
|
||||
|
||||
def test_autoinvoice_folio_overnights_partner_policy(self):
|
||||
"""
|
||||
Test create and invoice the cron by partner preconfig automation
|
||||
with only overnights reservations (included board services)
|
||||
--------------------------------------
|
||||
Set partner invoicing_policy to checkout, create a reservation
|
||||
with room, board service and normal service, run autoinvoicing
|
||||
method and check that only room and board service was invoiced
|
||||
in partner1,
|
||||
|
||||
"""
|
||||
# ARRANGE
|
||||
self.partner_id.invoicing_policy = "checkout"
|
||||
self.partner_id.margin_days_autoinvoice = 0
|
||||
self.partner_id.default_invoice_lines = "overnights"
|
||||
self.product1 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Test Product 1",
|
||||
}
|
||||
)
|
||||
|
||||
self.product2 = self.env["product.product"].create(
|
||||
{
|
||||
"name": "Test Product 2",
|
||||
"lst_price": 100,
|
||||
}
|
||||
)
|
||||
|
||||
self.board_service1 = self.env["pms.board.service"].create(
|
||||
{
|
||||
"name": "Test Board Service 1",
|
||||
"default_code": "CB1",
|
||||
"amount": 10,
|
||||
}
|
||||
)
|
||||
|
||||
self.board_service_line1 = self.env["pms.board.service.line"].create(
|
||||
{
|
||||
"product_id": self.product1.id,
|
||||
"pms_board_service_id": self.board_service1.id,
|
||||
"amount": 10,
|
||||
}
|
||||
)
|
||||
|
||||
self.board_service_room_type1 = self.env["pms.board.service.room.type"].create(
|
||||
{
|
||||
"pms_room_type_id": self.room_type_double.id,
|
||||
"pms_board_service_id": self.board_service1.id,
|
||||
}
|
||||
)
|
||||
# ACT
|
||||
self.reservation1 = self.env["pms.reservation"].create(
|
||||
{
|
||||
"pms_property_id": self.property.id,
|
||||
"checkin": datetime.date.today() - datetime.timedelta(days=3),
|
||||
"checkout": datetime.date.today(),
|
||||
"adults": 2,
|
||||
"room_type_id": self.room_type_double.id,
|
||||
"partner_id": self.partner_id.id,
|
||||
"board_service_room_id": self.board_service_room_type1.id,
|
||||
}
|
||||
)
|
||||
self.service = self.env["pms.service"].create(
|
||||
{
|
||||
"is_board_service": False,
|
||||
"product_id": self.product2.id,
|
||||
"reservation_id": self.reservation1.id,
|
||||
}
|
||||
)
|
||||
self.property.autoinvoicing()
|
||||
|
||||
# ASSERT
|
||||
overnight_sale_lines = self.reservation1.folio_id.sale_line_ids.filtered(
|
||||
lambda line: line.reservation_line_ids or line.is_board_service
|
||||
)
|
||||
partner_invoice = self.reservation1.folio_id.move_ids.filtered(
|
||||
lambda inv: inv.partner_id == self.partner_id
|
||||
)
|
||||
self.assertEqual(
|
||||
partner_invoice.mapped("line_ids.folio_line_ids.id"),
|
||||
overnight_sale_lines.ids,
|
||||
"Billed services and overnights invoicing wrong compute",
|
||||
)
|
||||
|
||||
def _test_invoice_line_group_by_room_type_sections(self):
|
||||
"""Test create and invoice from the Folio, and check qty invoice/to invoice,
|
||||
and the grouped invoice lines by room type, by one
|
||||
line by unit prices/qty with nights"""
|
||||
|
||||
def _test_autoinvoice_folio(self):
|
||||
"""Test create and invoice the cron by partner preconfig automation"""
|
||||
|
||||
def _test_downpayment(self):
|
||||
"""Test invoice qith a way of downpaument and check dowpayment's
|
||||
folio line is created and also check a total amount of invoice is
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
<field name="model">account.bank.statement</field>
|
||||
<field name="inherit_id" ref="account.view_bank_statement_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='ref']" position="after">
|
||||
|
||||
<xpath expr="//field[@name='company_id']" position="after">
|
||||
<field name="pms_property_id" />
|
||||
<field name="pms_property_id" invisible="1" />
|
||||
<field name="folio_ids" widget="many2many_tags" optional="hidden" />
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='ref']" position="after">
|
||||
|
||||
@@ -5,17 +5,41 @@
|
||||
<field name="inherit_id" ref="account.view_move_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_date']" position="after">
|
||||
<field name="folio_ids" widget="many2many_tags" />
|
||||
<field name="pms_property_id" />
|
||||
<field
|
||||
name="folio_ids"
|
||||
widget="many2many_tags"
|
||||
readonly="1"
|
||||
attrs="{'invisible':[('move_type','not in',('out_invoice','out_refund','out_receipt'))]}"
|
||||
/>
|
||||
<field
|
||||
name="pms_property_id"
|
||||
domain="[('company_id','=',company_id)]"
|
||||
/>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//field[@name='quantity']" position="before">
|
||||
<xpath expr="//field[@name='invoice_user_id']" position="before">
|
||||
<field name="origin_agency_id" />
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//field[@name='invoice_line_ids']/tree/field[@name='quantity']"
|
||||
position="before"
|
||||
>
|
||||
<field name="name_changed_by_user" invisible="1" />
|
||||
<field
|
||||
name="pms_property_id"
|
||||
attrs="{'column_invisible':[('parent.pms_property_id','!=',False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//notebook//field[@name='line_ids']/tree/field[@name='date_maturity']"
|
||||
position="after"
|
||||
>
|
||||
<field name="name_changed_by_user" invisible="1" />
|
||||
<field
|
||||
name="pms_property_id"
|
||||
attrs="{'column_invisible':[('parent.pms_property_id','!=',False)]}"
|
||||
optional="hide"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -25,6 +49,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='company_id']" position="after">
|
||||
<field name="pms_property_id" optional="show" />
|
||||
<field name="origin_agency_id" optional="show" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -42,6 +67,11 @@
|
||||
string="Property"
|
||||
context="{'group_by':'pms_property_id'}"
|
||||
/>
|
||||
<filter
|
||||
name="origin_agency_id"
|
||||
string="Originating agency"
|
||||
context="{'group_by':'origin_agency_id'}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
@@ -59,6 +89,11 @@
|
||||
string="Property"
|
||||
context="{'group_by':'pms_property_id'}"
|
||||
/>
|
||||
<filter
|
||||
name="by_origin_agency"
|
||||
string="Originating agency"
|
||||
context="{'group_by':'origin_agency_id'}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -5,7 +5,56 @@
|
||||
<field name="inherit_id" ref="account.view_account_payment_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='date']" position="after">
|
||||
<field name="folio_ids" />
|
||||
<field
|
||||
name="folio_ids"
|
||||
widget="many2many_tags"
|
||||
attrs="{'invisible':[('partner_type','!=','customer')]}"
|
||||
/>
|
||||
<field
|
||||
name="origin_agency_id"
|
||||
attrs="{'invisible':[('origin_agency_id','=',False)]}"
|
||||
/>
|
||||
<field
|
||||
name="origin_reference"
|
||||
attrs="{'invisible':[('origin_agency_id','=',False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_payment_tree" model="ir.ui.view">
|
||||
<field name="model">account.payment</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_tree" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='company_id']" position="before">
|
||||
<field name="folio_ids" optional="hidden" />
|
||||
<field name="pms_property_id" optional="hidden" />
|
||||
<field name="origin_agency_id" optional="hidden" />
|
||||
<field name="origin_reference" optional="hidden" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_account_payment_search" model="ir.ui.view">
|
||||
<field name="model">account.payment</field>
|
||||
<field name="inherit_id" ref="account.view_account_payment_search" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='journal_id']" position="after">
|
||||
<field name="pms_property_id" />
|
||||
<field name="origin_agency_id" />
|
||||
<field name="origin_reference" />
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='company']" position="after">
|
||||
<filter
|
||||
name="by_property"
|
||||
string="Property"
|
||||
context="{'group_by':'pms_property_id'}"
|
||||
/>
|
||||
<filter
|
||||
name="by_origin_agency"
|
||||
string="Originating agency"
|
||||
context="{'group_by':'origin_agency_id'}"
|
||||
/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -67,11 +67,46 @@
|
||||
<field name="document_number" />
|
||||
<field name="document_expedition_date" />
|
||||
<field name="nationality_id" />
|
||||
<field name="state_id" />
|
||||
<field name="email" />
|
||||
<field name="mobile" />
|
||||
<field name="phone" />
|
||||
<field name="document_id" invisible="1" />
|
||||
<field name="pms_property_id" invisible="1" />
|
||||
<label for="residence_street" string="Residence Address" />
|
||||
<div class="o_address_format">
|
||||
<field
|
||||
name="residence_street"
|
||||
placeholder="Street..."
|
||||
class="o_address_street"
|
||||
/>
|
||||
<field
|
||||
name="residence_street2"
|
||||
placeholder="Street 2..."
|
||||
class="o_address_street"
|
||||
/>
|
||||
<field
|
||||
name="residence_city"
|
||||
placeholder="City"
|
||||
class="o_address_city"
|
||||
/>
|
||||
<field
|
||||
name="residence_state_id"
|
||||
class="o_address_state"
|
||||
placeholder="State"
|
||||
options="{'no_open': True, 'no_quick_create': True}"
|
||||
/>
|
||||
<field
|
||||
name="residence_zip"
|
||||
placeholder="ZIP"
|
||||
class="o_address_zip"
|
||||
/>
|
||||
<field
|
||||
name="residence_country_id"
|
||||
placeholder="Country"
|
||||
class="o_address_country"
|
||||
options='{"no_open": True, "no_create": True}'
|
||||
/>
|
||||
</div>
|
||||
<field name="arrival" />
|
||||
<field name="departure" />
|
||||
</group>
|
||||
@@ -79,6 +114,7 @@
|
||||
<field name="reservation_id" />
|
||||
<field name="folio_id" />
|
||||
<field name="identifier" />
|
||||
<field name="partner_relationship" />
|
||||
<field name="pms_property_id" invisible="1" />
|
||||
</group>
|
||||
</group>
|
||||
@@ -126,7 +162,7 @@
|
||||
<field name="document_number" />
|
||||
<field name="document_expedition_date" />
|
||||
<field name="nationality_id" />
|
||||
<field name="state_id" />
|
||||
<field name="residence_state_id" />
|
||||
<field name="mobile" />
|
||||
<field name="email" />
|
||||
<field name="arrival" />
|
||||
@@ -172,7 +208,7 @@
|
||||
<field name="document_number" />
|
||||
<field name="document_expedition_date" />
|
||||
<field name="nationality_id" />
|
||||
<field name="state_id" />
|
||||
<field name="residence_state_id" />
|
||||
<field name="mobile" />
|
||||
<field name="email" />
|
||||
<field name="arrival" invisible="1" />
|
||||
@@ -215,7 +251,7 @@
|
||||
<field name="document_number" />
|
||||
<field name="document_expedition_date" />
|
||||
<field name="nationality_id" />
|
||||
<field name="state_id" />
|
||||
<field name="residence_state_id" />
|
||||
<field name="mobile" />
|
||||
<field name="email" />
|
||||
<field name="arrival" />
|
||||
@@ -254,7 +290,7 @@
|
||||
<field name="document_number" />
|
||||
<field name="document_expedition_date" />
|
||||
<field name="nationality_id" />
|
||||
<field name="state_id" />
|
||||
<field name="residence_state_id" />
|
||||
<field name="email" />
|
||||
<field name="mobile" />
|
||||
<field name="arrival" />
|
||||
|
||||
@@ -176,6 +176,15 @@
|
||||
>
|
||||
<span class="o_stat_text">New Booking Group</span>
|
||||
</button>
|
||||
<button
|
||||
type="action"
|
||||
class="oe_stat_button"
|
||||
name="%(action_booking_duplicate)d"
|
||||
icon="fa-clone"
|
||||
context="{'default_reference_folio_id': id}"
|
||||
>
|
||||
<span class="o_stat_text">Duplicate Booking</span>
|
||||
</button>
|
||||
<button
|
||||
name="preview_folio"
|
||||
type="object"
|
||||
@@ -213,16 +222,48 @@
|
||||
bg_color="bg-dark"
|
||||
attrs="{'invisible': [('reservation_type', 'not in', 'out')]}"
|
||||
/>
|
||||
<h2>
|
||||
<field name="name" />
|
||||
</h2>
|
||||
<group col="8">
|
||||
<group>
|
||||
<group>
|
||||
<h2>
|
||||
<field name="name" />
|
||||
</h2>
|
||||
</group>
|
||||
<group
|
||||
colspan="2"
|
||||
col="3"
|
||||
string="General Info"
|
||||
name="contact_details"
|
||||
class="oe_subtotal_footer oe_right oe_inline"
|
||||
name="folio_total"
|
||||
attrs="{'invisible':[('reservation_type', '!=', 'normal')]}"
|
||||
>
|
||||
<field
|
||||
name="amount_untaxed"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
/>
|
||||
<field
|
||||
name="amount_tax"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
/>
|
||||
<div
|
||||
class="oe_subtotal_footer_separator oe_inline o_td_label"
|
||||
>
|
||||
<label for="amount_total" />
|
||||
</div>
|
||||
<field
|
||||
name="amount_total"
|
||||
nolabel="1"
|
||||
class="oe_subtotal_footer_separator"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
/>
|
||||
<field
|
||||
name="commission"
|
||||
widget='monetary'
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<group string="General Info" name="contact_details">
|
||||
<field
|
||||
name="document_type"
|
||||
attrs="{'invisible':[('reservation_type','in',('out'))]}"
|
||||
@@ -269,13 +310,8 @@
|
||||
/>
|
||||
<field name="internal_comment" />
|
||||
</group>
|
||||
<group
|
||||
colspan="2"
|
||||
col="3"
|
||||
string="Sale Details"
|
||||
name="sale_details"
|
||||
>
|
||||
<field name="pms_property_id" invisible="0" />
|
||||
<group string="Sale Details" name="sale_details">
|
||||
<field name="pms_property_id" />
|
||||
<field
|
||||
name="pricelist_id"
|
||||
attrs="{'invisible': [('reservation_type', 'not in', 'normal')]}"
|
||||
@@ -293,45 +329,15 @@
|
||||
name="agency_id"
|
||||
attrs="{'invisible': [('reservation_type', 'not in', 'normal')]}"
|
||||
/>
|
||||
<field
|
||||
name="external_reference"
|
||||
attrs="{'invisible': [('agency_id', '=', False)]}"
|
||||
/>
|
||||
<field
|
||||
name="channel_type_id"
|
||||
attrs="{'readonly':[('agency_id','!=', False)], 'invisible':[('reservation_type', 'not in', 'normal')]}"
|
||||
/>
|
||||
</group>
|
||||
<group
|
||||
class="oe_subtotal_footer oe_right"
|
||||
colspan="2"
|
||||
name="folio_total"
|
||||
attrs="{'invisible':[('reservation_type', '!=', 'normal')]}"
|
||||
>
|
||||
<field
|
||||
name="amount_untaxed"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
/>
|
||||
<field
|
||||
name="amount_tax"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
/>
|
||||
<div
|
||||
class="oe_subtotal_footer_separator oe_inline o_td_label"
|
||||
>
|
||||
<label for="amount_total" />
|
||||
</div>
|
||||
<field
|
||||
name="amount_total"
|
||||
nolabel="1"
|
||||
class="oe_subtotal_footer_separator"
|
||||
widget="monetary"
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
/>
|
||||
<field
|
||||
name="commission"
|
||||
widget='monetary'
|
||||
options="{'currency_field': 'currency_id'}"
|
||||
/>
|
||||
</group>
|
||||
<div class="oe_clear" />
|
||||
|
||||
</group>
|
||||
@@ -562,7 +568,6 @@
|
||||
<page string="Other data">
|
||||
<group>
|
||||
<field name="user_id" />
|
||||
<field name="client_order_ref" />
|
||||
</group>
|
||||
</page>
|
||||
<!-- <page string="Foreign Exchange" name="foreign exchange" invisible="1">
|
||||
|
||||
@@ -85,6 +85,32 @@
|
||||
<field name="room_ids" />
|
||||
</group>
|
||||
</page>
|
||||
<page string="Invoicing" name="property_invoicing">
|
||||
<group>
|
||||
<field name="default_invoicing_policy" />
|
||||
<field
|
||||
name="margin_days_autoinvoice"
|
||||
attrs="{'invisible': [('default_invoicing_policy', '=', 'manual')]}"
|
||||
/>
|
||||
<field
|
||||
name="journal_normal_invoice_id"
|
||||
attrs="{'required': [('default_invoicing_policy', '!=', 'manual')]}"
|
||||
domain="[
|
||||
('type', '=', 'sale'),
|
||||
('company_id','=',company_id),
|
||||
]"
|
||||
/>
|
||||
<field
|
||||
name="journal_simplified_invoice_id"
|
||||
attrs="{'required': [('default_invoicing_policy', '!=', 'manual')]}"
|
||||
domain="[
|
||||
('type', '=', 'sale'),
|
||||
('company_id','=',company_id),
|
||||
]"
|
||||
/>
|
||||
<field name="max_amount_simplified_invoice" />
|
||||
</group>
|
||||
</page>
|
||||
<page string="Email Configuration">
|
||||
<group>
|
||||
<div class="row">
|
||||
|
||||
@@ -126,7 +126,10 @@
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-12 col-md-6">
|
||||
<div
|
||||
name="document_type_div"
|
||||
t-attf-class="form-group col-12 col-md-6"
|
||||
>
|
||||
<label
|
||||
class="col-form-label"
|
||||
for="document_type"
|
||||
@@ -156,7 +159,10 @@
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-attf-class="col-12 col-md-6">
|
||||
<div
|
||||
name="document_number_div"
|
||||
t-attf-class="col-12 col-md-6"
|
||||
>
|
||||
<label
|
||||
class="col-form-label"
|
||||
for="document_number"
|
||||
@@ -236,24 +242,6 @@
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group col-12 col-md-6">
|
||||
<label
|
||||
class="col-form-label"
|
||||
for="state"
|
||||
>Country State</label>
|
||||
<select class="form-control" id="state" name='state'>
|
||||
<option value="">Select an option</option>
|
||||
<t t-foreach="state_ids" t-as='state'>
|
||||
<option
|
||||
t-att-value="state.id"
|
||||
t-att-country_id="state.country_id.id"
|
||||
t-att-selected="state.id == checkin_partner_id.state_id.id"
|
||||
>
|
||||
<t t-esc="state.name" />
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-12 col-md-6 pt-5">
|
||||
<label
|
||||
id="label_mobile"
|
||||
@@ -274,6 +262,25 @@
|
||||
</t>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-12 col-md-6 pt-md-5">
|
||||
<label
|
||||
id="label_phone"
|
||||
class="col-form-label"
|
||||
for="phone"
|
||||
>Phone</label>
|
||||
<input
|
||||
type="phone"
|
||||
name="phone"
|
||||
t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}"
|
||||
t-att-value="checkin_partner_id.phone"
|
||||
/>
|
||||
<t t-if="error_message">
|
||||
<span
|
||||
class="text-danger"
|
||||
t-esc="error_message.get('phone')"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-12 col-md-6">
|
||||
<label
|
||||
id="label_email"
|
||||
class="col-form-label"
|
||||
@@ -292,6 +299,104 @@
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-12 col-md-6 ">
|
||||
<label
|
||||
id="label_relationship"
|
||||
class="col-form-label"
|
||||
for="partner_relationship"
|
||||
>Partner Relationship</label>
|
||||
<input
|
||||
type="text"
|
||||
name="partner_relationship"
|
||||
t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}"
|
||||
t-att-value="checkin_partner_id.partner_relationship"
|
||||
/>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-md-12 pt-md-5">
|
||||
<label
|
||||
class="col-form-label"
|
||||
for="residence_street"
|
||||
>Residence Address</label>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Street"
|
||||
name="residence_street"
|
||||
t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}"
|
||||
t-att-value="checkin_partner_id.residence_street"
|
||||
/>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-md-12">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Second Street"
|
||||
name="residence_street2"
|
||||
t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}"
|
||||
t-att-value="checkin_partner_id.residence_street2"
|
||||
/>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-md-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="City"
|
||||
name="residence_city"
|
||||
t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}"
|
||||
t-att-value="checkin_partner_id.residence_city"
|
||||
/>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-md-4">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Zip"
|
||||
name="residence_zip"
|
||||
t-attf-class="form-control #{error.get('email') and 'is-invalid' or ''}"
|
||||
t-att-value="checkin_partner_id.residence_zip"
|
||||
/>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-md-4">
|
||||
<select
|
||||
class="form-control"
|
||||
id="residence-country"
|
||||
name='residence_country_id'
|
||||
onclick="changeCountryFormClass()"
|
||||
>
|
||||
<option
|
||||
id="country_placeholder"
|
||||
hidden="1"
|
||||
value="placeholder"
|
||||
>Country</option>
|
||||
<t t-foreach="country_ids" t-as='country_id'>
|
||||
<option
|
||||
t-att-value="country_id.id"
|
||||
t-att-selected="country_id.id == checkin_partner_id.residence_country_id.id if checkin_partner_id.residence_country_id.id else placeholder"
|
||||
>
|
||||
<t t-esc='country_id.name' />
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-md-12 pb-md-3">
|
||||
<select
|
||||
class="form-control"
|
||||
id="residence-state"
|
||||
name='residence_state_id'
|
||||
onclick="changeFormClass()"
|
||||
>
|
||||
<option
|
||||
id="placeholder"
|
||||
hidden="1"
|
||||
value="placeholder"
|
||||
>State</option>
|
||||
<t t-foreach="state_ids" t-as='state'>
|
||||
<option
|
||||
t-att-value="state.id"
|
||||
t-att-country_id="state.country_id.id"
|
||||
t-att-selected="state.id == checkin_partner_id.residence_state_id.id if checkin_partner_id.residence_state_id.id else placeholder"
|
||||
>
|
||||
<t t-esc="state.name" />
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
</div>
|
||||
<t t-if="checkin_pos >= 0">
|
||||
<div class="col-4">
|
||||
<t t-if="not error">
|
||||
@@ -410,8 +515,69 @@
|
||||
from {top: 100px; opacity: 1;}
|
||||
to {top: 0; opacity: 0;}
|
||||
}
|
||||
|
||||
.placeholder-class{
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: calc(1.5em + 0.75rem + 2px);
|
||||
padding: 0.375rem 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: grey;
|
||||
background-color: #FFFFFF;
|
||||
background-clip: padding-box;
|
||||
border: 1px solid #CED4DA;
|
||||
border-radius: 0.25rem;
|
||||
transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
var placeholder_country = document.getElementById("country_placeholder")
|
||||
function changeCountryFormClass() {
|
||||
let select_value = placeholder_country.parentNode.value
|
||||
if (placeholder_country.parentNode.value == 'placeholder'){
|
||||
placeholder_country.parentNode.classList.add('placeholder-class')
|
||||
}else{
|
||||
placeholder_country.parentNode.classList.remove('placeholder-class')
|
||||
}
|
||||
};
|
||||
this.changeCountryFormClass()
|
||||
|
||||
var placeholder = document.getElementById("placeholder")
|
||||
function changeFormClass() {
|
||||
let select_value = placeholder.parentNode.value
|
||||
if (placeholder.parentNode.value == 'placeholder'){
|
||||
placeholder.parentNode.classList.remove('form-control')
|
||||
placeholder.parentNode.classList.add('placeholder-class')
|
||||
}else{
|
||||
placeholder.parentNode.classList.remove('placeholder-class')
|
||||
placeholder.parentNode.classList.add('form-control')
|
||||
}
|
||||
};
|
||||
this.changeFormClass()
|
||||
|
||||
let select_residence_country = document.getElementById('residence-country')
|
||||
|
||||
select_residence_country.addEventListener("change", () => {
|
||||
let country_value = select_residence_country.value
|
||||
Array.from(document.getElementById('residence-state').options).forEach(element => {
|
||||
if (element.getAttribute('country_id') == country_value) {
|
||||
element.style="";
|
||||
} else {
|
||||
element.style="display:none";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var select_doc_type = document.getElementById("doc_type")
|
||||
var document_type_value = document.getElementById("docTypeId").textContent
|
||||
for (let i=0;i<select_doc_type.length;i++){
|
||||
if (select_doc_type[i].value == document_type_value){
|
||||
select_doc_type[i].setAttribute("selected","True")
|
||||
}
|
||||
}
|
||||
|
||||
var folio_id = document.getElementById("folio").value
|
||||
var checkin_partner_id = document.getElementById("checkin").value
|
||||
var access_token = document.getElementById("input_access_token").value
|
||||
@@ -999,6 +1165,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
name="birthdate_div"
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
Birthdate:
|
||||
@@ -1024,6 +1191,19 @@
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
Phone:
|
||||
<br />
|
||||
<span
|
||||
class="font-weight-normal ml-3"
|
||||
>
|
||||
<t
|
||||
t-esc="checkin_partner.phone"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
@@ -1053,13 +1233,112 @@
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
Country State:
|
||||
Partner Relationship:
|
||||
<br />
|
||||
<span
|
||||
class="font-weight-normal ml-3"
|
||||
>
|
||||
<t
|
||||
t-esc="checkin_partner.state_id.name"
|
||||
t-esc="checkin_partner.partner_relationship"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 mt-3 font-italic font-weight-bold"
|
||||
>
|
||||
Residence Address
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
Street:
|
||||
<br />
|
||||
<span
|
||||
class="font-weight-normal ml-3"
|
||||
>
|
||||
<t
|
||||
t-esc="checkin_partner.residence_street"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
Second Street:
|
||||
<br />
|
||||
<span
|
||||
class="font-weight-normal ml-3"
|
||||
>
|
||||
<t
|
||||
t-esc="checkin_partner.residence_street2"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
City:
|
||||
<br />
|
||||
<span
|
||||
class="font-weight-normal ml-3"
|
||||
>
|
||||
<t
|
||||
t-esc="checkin_partner.residence_city"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
ZIP:
|
||||
<br />
|
||||
<span
|
||||
class="font-weight-normal ml-3"
|
||||
>
|
||||
<t
|
||||
t-esc="checkin_partner.residence_zip"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
State:
|
||||
<br />
|
||||
<span
|
||||
class="font-weight-normal ml-3"
|
||||
>
|
||||
<t
|
||||
t-set="residence_state_id"
|
||||
t-value="int(checkin_partner.residence_state_id.id)"
|
||||
/>
|
||||
<t
|
||||
t-set="residence_state"
|
||||
t-value="folio.env['res.country.state'].browse(residence_state_id)"
|
||||
/>
|
||||
<t
|
||||
t-esc="residence_state.name"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
t-attf-class="form-group col-12 col-md-6 font-weight-bold"
|
||||
>
|
||||
Country:
|
||||
<br />
|
||||
<span
|
||||
class="font-weight-normal ml-3"
|
||||
>
|
||||
<t
|
||||
t-set="residence_country_id"
|
||||
t-value="int(checkin_partner.residence_country_id.id)"
|
||||
/>
|
||||
<t
|
||||
t-set="residence_country"
|
||||
t-value="folio.env['res.country'].browse(residence_country_id)"
|
||||
/>
|
||||
<t
|
||||
t-esc="residence_country.name"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='website']" position="after">
|
||||
<field name="url_advert" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
||||
15
pms/views/res_partner_id_category.xml
Normal file
15
pms/views/res_partner_id_category.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="view_partner_id_category_form" model="ir.ui.view">
|
||||
<field name="model">res.partner.id_category</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="partner_identification.view_partner_id_category_form"
|
||||
/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='code']" position="after">
|
||||
<field name="is_vat_equivalent" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -64,10 +64,17 @@
|
||||
<field name="is_agency" />
|
||||
</group>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='company_id']" position="after">
|
||||
<group>
|
||||
<field name="pms_property_ids" />
|
||||
</group>
|
||||
<xpath expr="//field[@name='vat']" position="after">
|
||||
<field
|
||||
name="vat_document_type"
|
||||
attrs="{'invisible':[('vat','=',False)]}"
|
||||
/>
|
||||
</xpath>
|
||||
<xpath
|
||||
expr="//notebook/page[@name='sales_purchases']/group/group/field[@name='company_id']"
|
||||
position="after"
|
||||
>
|
||||
<field name="pms_property_ids" widget="many2many_tags" />
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='internal_notes']" position="after">
|
||||
<page
|
||||
@@ -91,8 +98,70 @@
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='personal_information_page']">
|
||||
<group>
|
||||
<label for="residence_street" string="Residence Address" />
|
||||
<div class="o_address_format">
|
||||
<field
|
||||
name="residence_street"
|
||||
placeholder="Street..."
|
||||
class="o_address_street"
|
||||
/>
|
||||
<field
|
||||
name="residence_street2"
|
||||
placeholder="Street 2..."
|
||||
class="o_address_street"
|
||||
/>
|
||||
<field
|
||||
name="residence_city"
|
||||
placeholder="City"
|
||||
class="o_address_city"
|
||||
/>
|
||||
<field
|
||||
name="residence_state_id"
|
||||
class="o_address_state"
|
||||
placeholder="State"
|
||||
options="{'no_open': True, 'no_quick_create': True}"
|
||||
/>
|
||||
<field
|
||||
name="residence_zip"
|
||||
placeholder="ZIP"
|
||||
class="o_address_zip"
|
||||
/>
|
||||
<field
|
||||
name="residence_country_id"
|
||||
placeholder="Country"
|
||||
class="o_address_country"
|
||||
options='{"no_open": True, "no_create": True}'
|
||||
/>
|
||||
</div>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_partner_property_form" model="ir.ui.view">
|
||||
<field name="name">view.partner.property.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="account.view_partner_property_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//group[@name="accounting_entries"]' position='after'>
|
||||
<group string="PMS Invoice policy" name="pms_invoice_policy">
|
||||
<field name="invoicing_policy" />
|
||||
<field
|
||||
name="invoicing_month_day"
|
||||
attrs="{'invisible': [('invoicing_policy', '!=', 'month_day')]}"
|
||||
/>
|
||||
<field
|
||||
name="margin_days_autoinvoice"
|
||||
attrs="{'invisible': [('invoicing_policy', '!=', 'checkout')]}"
|
||||
/>
|
||||
<field name="default_invoice_lines" />
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_partner_data_form" model="ir.ui.view">
|
||||
<field name="name">res.partner.form.data</field>
|
||||
<field name="model">res.partner</field>
|
||||
|
||||
@@ -6,3 +6,4 @@ from . import folio_make_invoice_advance
|
||||
from . import wizard_payment_folio
|
||||
from . import wizard_folio_changes
|
||||
from . import wizard_several_partners
|
||||
from . import pms_booking_duplicate
|
||||
|
||||
@@ -153,7 +153,7 @@ class FolioAdvancePaymentInv(models.TransientModel):
|
||||
|
||||
def _prepare_invoice_values(self, order, name, amount, line):
|
||||
invoice_vals = {
|
||||
"ref": order.client_order_ref,
|
||||
"ref": order.name,
|
||||
"move_type": "out_invoice",
|
||||
"invoice_origin": order.name,
|
||||
"invoice_user_id": order.user_id.id,
|
||||
@@ -243,7 +243,7 @@ class FolioAdvancePaymentInv(models.TransientModel):
|
||||
folios._create_invoices(
|
||||
final=self.deduct_down_payments,
|
||||
lines_to_invoice=lines_to_invoice,
|
||||
partner_invoice_id=self.partner_invoice_id
|
||||
partner_invoice_id=self.partner_invoice_id.id
|
||||
if self.partner_invoice_id
|
||||
else False,
|
||||
)
|
||||
|
||||
563
pms/wizards/pms_booking_duplicate.py
Normal file
563
pms/wizards/pms_booking_duplicate.py
Normal file
@@ -0,0 +1,563 @@
|
||||
import datetime
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class BookingDuplicate(models.TransientModel):
|
||||
_name = "pms.booking.duplicate"
|
||||
_description = "Duplicate Booking"
|
||||
_check_pms_properties_auto = True
|
||||
|
||||
reference_folio_id = fields.Many2one(
|
||||
string="Folio Reference",
|
||||
help="Folio to copy data",
|
||||
comodel_name="pms.folio",
|
||||
check_pms_properties=True,
|
||||
)
|
||||
start_date = fields.Date(
|
||||
string="From:",
|
||||
help="Date from first copy Checkin (reference min checkin folio reservation)",
|
||||
required=True,
|
||||
)
|
||||
used_room_ids = fields.Many2many(
|
||||
string="Used Rooms",
|
||||
comodel_name="pms.room",
|
||||
compute="_compute_used_room_ids",
|
||||
)
|
||||
pricelist_id = fields.Many2one(
|
||||
string="Pricelist",
|
||||
help="Pricelist applied in folio",
|
||||
readonly=False,
|
||||
store=True,
|
||||
comodel_name="product.pricelist",
|
||||
compute="_compute_pricelist_id",
|
||||
check_pms_properties=True,
|
||||
)
|
||||
pms_property_id = fields.Many2one(
|
||||
related="reference_folio_id.pms_property_id",
|
||||
string="Property",
|
||||
help="Property to which the folio belongs",
|
||||
comodel_name="pms.property",
|
||||
check_pms_properties=True,
|
||||
)
|
||||
segmentation_ids = fields.Many2many(
|
||||
string="Segmentation",
|
||||
help="Partner Tags",
|
||||
ondelete="restrict",
|
||||
comodel_name="res.partner.category",
|
||||
compute="_compute_segmentation_ids",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
partner_name = fields.Char(
|
||||
string="Partner name",
|
||||
help="In whose name is the reservation",
|
||||
compute="_compute_partner_name",
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
string="Partner",
|
||||
help="Partner who made the reservation",
|
||||
comodel_name="res.partner",
|
||||
compute="_compute_partner_id",
|
||||
readonly=False,
|
||||
store=True,
|
||||
check_pms_properties=True,
|
||||
)
|
||||
|
||||
reservation_type = fields.Selection(
|
||||
string="Type",
|
||||
help="The type of the reservation. "
|
||||
"Can be 'Normal', 'Staff' or 'Out of Service'",
|
||||
selection=[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")],
|
||||
compute="_compute_reservation_type",
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
agency_id = fields.Many2one(
|
||||
string="Agency",
|
||||
help="Agency that made the reservation",
|
||||
comodel_name="res.partner",
|
||||
domain=[("is_agency", "=", True)],
|
||||
ondelete="restrict",
|
||||
)
|
||||
channel_type_id = fields.Many2one(
|
||||
string="Direct Sale Channel",
|
||||
help="Sales Channel through which the reservation was managed",
|
||||
readonly=False,
|
||||
store=True,
|
||||
comodel_name="pms.sale.channel",
|
||||
domain=[("channel_type", "=", "direct")],
|
||||
ondelete="restrict",
|
||||
compute="_compute_channel_type_id",
|
||||
)
|
||||
total_price_folio = fields.Float(
|
||||
string="Total Price",
|
||||
help="Total price of folio with taxes",
|
||||
compute="_compute_total_price_folio",
|
||||
)
|
||||
discount = fields.Float(
|
||||
string="Discount",
|
||||
help="Discount that be applied in total price",
|
||||
default=0,
|
||||
)
|
||||
internal_comment = fields.Text(
|
||||
string="Internal Folio Notes",
|
||||
help="Internal Folio notes for Staff",
|
||||
)
|
||||
created_folio_ids = fields.Many2many(
|
||||
string="Folios",
|
||||
help="Folios already created",
|
||||
comodel_name="pms.folio",
|
||||
)
|
||||
line_ids = fields.One2many(
|
||||
string="Rooms",
|
||||
help="Rooms to create",
|
||||
readonly=False,
|
||||
store=True,
|
||||
comodel_name="pms.reservation.duplicate",
|
||||
inverse_name="booking_duplicate_id",
|
||||
compute="_compute_line_ids",
|
||||
check_pms_properties=True,
|
||||
)
|
||||
recompute_prices = fields.Boolean(
|
||||
string="Recompute Price",
|
||||
help="""Leave unchecked if you want to respect
|
||||
the price of the original reservation regardless
|
||||
of what is marked in the rate""",
|
||||
default=False,
|
||||
)
|
||||
|
||||
@api.depends("line_ids", "line_ids.preferred_room_id")
|
||||
def _compute_used_room_ids(self):
|
||||
for record in self:
|
||||
record.used_room_ids = record.line_ids.mapped("preferred_room_id.id")
|
||||
|
||||
@api.depends("reference_folio_id")
|
||||
def _compute_pricelist_id(self):
|
||||
for record in self.filtered("reference_folio_id"):
|
||||
if not record.pricelist_id:
|
||||
record.pricelist_id = record.reference_folio_id.pricelist_id.id
|
||||
|
||||
@api.depends("reference_folio_id", "agency_id")
|
||||
def _compute_channel_type_id(self):
|
||||
for record in self.filtered("reference_folio_id"):
|
||||
if record.reference_folio_id.agency_id == record.agency_id:
|
||||
record.channel_type_id = record.reference_folio_id.channel_type_id
|
||||
elif record.agency_id:
|
||||
record.channel_type_id = record.agency_id.sale_channel_id.id
|
||||
|
||||
@api.depends("reference_folio_id")
|
||||
def _compute_segmentation_ids(self):
|
||||
for record in self:
|
||||
record.segmentation_ids = record.reference_folio_id.segmentation_ids
|
||||
|
||||
@api.depends("agency_id", "reference_folio_id")
|
||||
def _compute_partner_id(self):
|
||||
for record in self:
|
||||
if record.reference_folio_id.agency_id == record.agency_id:
|
||||
record.partner_id = record.reference_folio_id.partner_id
|
||||
elif record.agency_id and record.agency_id.invoice_to_agency:
|
||||
record.partner_id = record.agency_id.id
|
||||
elif not record.partner_id:
|
||||
record.partner_id = False
|
||||
|
||||
@api.depends("reference_folio_id")
|
||||
def _compute_reservation_type(self):
|
||||
self.reservation_type = "normal"
|
||||
for record in self:
|
||||
record.reservation_type = record.reference_folio_id.reservation_type
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_partner_name(self):
|
||||
for record in self:
|
||||
if record.reference_folio_id.partner_id == record.partner_id:
|
||||
record.partner_name = record.reference_folio_id.partner_name
|
||||
elif record.partner_id:
|
||||
record.partner_name = record.partner_id.name
|
||||
if (
|
||||
record.agency_id
|
||||
and not record.agency_id.invoice_to_agency
|
||||
and not record.partner_name
|
||||
):
|
||||
record.partner_name = _("Reservation from ") + record.agency_id.name
|
||||
elif not record.partner_name:
|
||||
record.partner_name = False
|
||||
|
||||
@api.depends("line_ids.price_total")
|
||||
def _compute_total_price_folio(self):
|
||||
for record in self:
|
||||
record.total_price_folio = 0
|
||||
for line in record.line_ids:
|
||||
record.total_price_folio += line.price_total
|
||||
record.total_price_folio = record.total_price_folio
|
||||
|
||||
@api.depends(
|
||||
"reference_folio_id",
|
||||
)
|
||||
def _compute_line_ids(self):
|
||||
self.ensure_one()
|
||||
reference_folio = self.reference_folio_id
|
||||
|
||||
if not reference_folio:
|
||||
self.line_ids = False
|
||||
return
|
||||
|
||||
cmds = [(5, 0)]
|
||||
|
||||
for reservation in reference_folio.reservation_ids.filtered(
|
||||
lambda r: r.state != "cancel"
|
||||
):
|
||||
cmds.append(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"reference_reservation_id": reservation.id,
|
||||
"booking_duplicate_id": self.id,
|
||||
"checkin": False,
|
||||
"checkout": False,
|
||||
"preferred_room_id": reservation.preferred_room_id.id,
|
||||
"room_type_id": reservation.room_type_id.id,
|
||||
"pricelist_id": reservation.pricelist_id.id,
|
||||
# "arrival_hour": reservation.arrival_hour,
|
||||
# "departure_hour": reservation.departure_hour,
|
||||
# "partner_internal_comment": reservation.partner_internal_comment,
|
||||
"board_service_room_id": reservation.board_service_room_id.id,
|
||||
"adults": reservation.adults,
|
||||
},
|
||||
)
|
||||
)
|
||||
self.line_ids = cmds
|
||||
|
||||
def create_and_new(self):
|
||||
self.create_folio()
|
||||
return {
|
||||
"name": _("Duplicate Folios"),
|
||||
"res_model": "pms.booking.duplicate",
|
||||
"type": "ir.actions.act_window",
|
||||
"view_id": self.env.ref("pms.booking_duplicate").id,
|
||||
"target": "new",
|
||||
"view_mode": "form",
|
||||
"context": {
|
||||
"default_reference_folio_id": self.reference_folio_id.id,
|
||||
"default_created_folio_ids": [(6, 0, self.created_folio_ids.ids)],
|
||||
"default_start_date": self.start_date,
|
||||
},
|
||||
}
|
||||
|
||||
def create_and_close(self):
|
||||
self.create_folio()
|
||||
folio_ids = self.mapped("created_folio_ids.id")
|
||||
action = self.env.ref("pms.open_pms_folio1_form_tree_all").read()[0]
|
||||
if len(folio_ids) > 1:
|
||||
action["domain"] = [("id", "in", folio_ids)]
|
||||
elif len(folio_ids) == 1:
|
||||
form_view = [(self.env.ref("pms.pms_folio_view_form").id, "form")]
|
||||
if "views" in action:
|
||||
action["views"] = form_view + [
|
||||
(state, view) for state, view in action["views"] if view != "form"
|
||||
]
|
||||
else:
|
||||
action["views"] = form_view
|
||||
action["res_id"] = folio_ids[0]
|
||||
else:
|
||||
action = {"type": "ir.actions.act_window_close"}
|
||||
return action
|
||||
|
||||
def view_folios(self):
|
||||
folio_ids = self.mapped("created_folio_ids.id")
|
||||
action = self.env.ref("pms.open_pms_folio1_form_tree_all").read()[0]
|
||||
if len(folio_ids) > 1:
|
||||
action["domain"] = [("id", "in", folio_ids)]
|
||||
elif len(folio_ids) == 1:
|
||||
form_view = [(self.env.ref("pms.pms_folio_view_form").id, "form")]
|
||||
if "views" in action:
|
||||
action["views"] = form_view + [
|
||||
(state, view) for state, view in action["views"] if view != "form"
|
||||
]
|
||||
else:
|
||||
action["views"] = form_view
|
||||
action["res_id"] = folio_ids[0]
|
||||
else:
|
||||
action = {"type": "ir.actions.act_window_close"}
|
||||
return action
|
||||
|
||||
def create_folio(self):
|
||||
if any(room.occupied_room for room in self.line_ids):
|
||||
raise UserError(
|
||||
_(
|
||||
"""You can not create a new folio because there are rooms already occupied.
|
||||
Please, check the rooms marked in red and try again."""
|
||||
)
|
||||
)
|
||||
folio = self.env["pms.folio"].create(
|
||||
{
|
||||
"reservation_type": self.reservation_type,
|
||||
"pricelist_id": self.pricelist_id.id,
|
||||
"partner_id": self.partner_id.id if self.partner_id else False,
|
||||
"partner_name": self.partner_name,
|
||||
"pms_property_id": self.pms_property_id.id,
|
||||
"agency_id": self.agency_id.id,
|
||||
"channel_type_id": self.channel_type_id.id,
|
||||
"segmentation_ids": [(6, 0, self.segmentation_ids.ids)],
|
||||
"internal_comment": self.internal_comment,
|
||||
}
|
||||
)
|
||||
for res in self.line_ids:
|
||||
displacement_days = (
|
||||
res.checkin - res.reference_reservation_id.checkin
|
||||
).days
|
||||
res_vals = {
|
||||
"folio_id": folio.id,
|
||||
"checkin": res.checkin,
|
||||
"checkout": res.checkout,
|
||||
"room_type_id": res.room_type_id.id,
|
||||
"partner_id": self.partner_id.id if self.partner_id else False,
|
||||
"partner_name": self.partner_name,
|
||||
"pricelist_id": res.pricelist_id.id,
|
||||
"pms_property_id": folio.pms_property_id.id,
|
||||
"board_service_room_id": res.board_service_room_id.id,
|
||||
"adults": res.adults,
|
||||
"preferred_room_id": res.preferred_room_id.id,
|
||||
}
|
||||
ser_vals = [(5, 0)]
|
||||
for service in res.reference_reservation_id.service_ids.filtered(
|
||||
lambda s: not s.is_board_service
|
||||
):
|
||||
ser_line_vals = [(5, 0)]
|
||||
if service.product_id.id in res.service_ids.ids:
|
||||
for ser_line in service.service_line_ids:
|
||||
ser_line_vals.append(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"day_qty": ser_line.day_qty,
|
||||
"price_unit": ser_line.price_unit,
|
||||
"discount": ser_line.discount,
|
||||
"date": ser_line.date
|
||||
+ datetime.timedelta(days=displacement_days)
|
||||
if service.per_day
|
||||
else fields.Date.today(),
|
||||
},
|
||||
)
|
||||
)
|
||||
ser_vals.append(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"product_id": service.product_id.id,
|
||||
"is_board_service": service.is_board_service,
|
||||
"service_line_ids": ser_line_vals,
|
||||
},
|
||||
)
|
||||
)
|
||||
res_vals["service_ids"] = ser_vals
|
||||
|
||||
if not self.recompute_prices:
|
||||
line_vals = [(5, 0)]
|
||||
for line in res.reference_reservation_id.reservation_line_ids:
|
||||
line_vals.append(
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"price": line.price,
|
||||
"discount": line.discount,
|
||||
"room_id": res.preferred_room_id.id,
|
||||
"date": line.date
|
||||
+ datetime.timedelta(days=displacement_days),
|
||||
},
|
||||
)
|
||||
)
|
||||
res_vals["reservation_line_ids"] = line_vals
|
||||
new_reservation = self.env["pms.reservation"].create(res_vals)
|
||||
# REVIEW: Board service overwrite prices
|
||||
for service in new_reservation.service_ids.filtered("is_board_service"):
|
||||
origin_services_board = (
|
||||
res.reference_reservation_id.service_ids.filtered(
|
||||
"is_board_service"
|
||||
)
|
||||
)
|
||||
if origin_services_board:
|
||||
service.service_line_ids.price_unit = (
|
||||
origin_services_board.service_line_ids[0].price_unit
|
||||
)
|
||||
self.created_folio_ids = [(4, folio.id)]
|
||||
|
||||
|
||||
class PmsReservationDuplicate(models.TransientModel):
|
||||
_name = "pms.reservation.duplicate"
|
||||
_description = "Rooms in Duplicate Folio"
|
||||
_check_pms_properties_auto = True
|
||||
|
||||
reference_reservation_id = fields.Many2one(
|
||||
string="Reservation Reference",
|
||||
help="Reservation to copy data",
|
||||
comodel_name="pms.reservation",
|
||||
check_pms_properties=True,
|
||||
)
|
||||
adults = fields.Integer(string="Adults")
|
||||
booking_duplicate_id = fields.Many2one(
|
||||
string="Folio Wizard ID",
|
||||
comodel_name="pms.booking.duplicate",
|
||||
)
|
||||
checkin = fields.Date(
|
||||
string="From:", help="Date Reservation starts ", compute="_compute_checkin"
|
||||
)
|
||||
checkout = fields.Date(
|
||||
string="To:",
|
||||
help="Date Reservation ends",
|
||||
compute="_compute_checkout",
|
||||
)
|
||||
room_type_id = fields.Many2one(
|
||||
string="Room Type",
|
||||
help="Room Type reserved",
|
||||
comodel_name="pms.room.type",
|
||||
check_pms_properties=True,
|
||||
)
|
||||
preferred_room_id = fields.Many2one(
|
||||
string="Room",
|
||||
help="Room reserved",
|
||||
comodel_name="pms.room",
|
||||
check_pms_properties=True,
|
||||
domain="["
|
||||
"('id', 'in', allowed_room_ids),"
|
||||
"('id', 'not in', used_room_ids),"
|
||||
"('pms_property_id', '=', pms_property_id),"
|
||||
"]",
|
||||
)
|
||||
used_room_ids = fields.Many2many(
|
||||
string="Used Rooms",
|
||||
comodel_name="pms.room",
|
||||
compute="_compute_used_room_ids",
|
||||
)
|
||||
allowed_room_ids = fields.Many2many(
|
||||
string="Allowed Rooms",
|
||||
help="It contains all available rooms for this reservation",
|
||||
comodel_name="pms.room",
|
||||
compute="_compute_allowed_room_ids",
|
||||
)
|
||||
occupied_room = fields.Boolean(
|
||||
string="Occupied Room",
|
||||
help="Check if the room is occupied",
|
||||
compute="_compute_occupied_room",
|
||||
)
|
||||
pricelist_id = fields.Many2one(
|
||||
string="Pricelist",
|
||||
help="Pricelist used for this reservation",
|
||||
comodel_name="product.pricelist",
|
||||
check_pms_properties=True,
|
||||
)
|
||||
price_total = fields.Float(
|
||||
string="Total price",
|
||||
help="The total price in the folio",
|
||||
compute="_compute_price_total",
|
||||
)
|
||||
pms_property_id = fields.Many2one(
|
||||
string="Property",
|
||||
help="Propertiy with access to the element;",
|
||||
related="booking_duplicate_id.pms_property_id",
|
||||
)
|
||||
board_service_room_id = fields.Many2one(
|
||||
string="Board Service",
|
||||
help="Board Service included in the room",
|
||||
comodel_name="pms.board.service.room.type",
|
||||
domain="[('pms_room_type_id','=',room_type_id)]",
|
||||
check_pms_properties=True,
|
||||
)
|
||||
service_ids = fields.Many2many(
|
||||
string="Services",
|
||||
comodel_name="product.product",
|
||||
relation="reservation_duplicate_product_rel",
|
||||
column1="reservation_duplicate_id",
|
||||
column2="product_id",
|
||||
compute="_compute_service_ids",
|
||||
readonly=False,
|
||||
store=True,
|
||||
)
|
||||
|
||||
@api.depends("booking_duplicate_id.start_date")
|
||||
def _compute_checkin(self):
|
||||
self.checkin = False
|
||||
start_date = self.booking_duplicate_id.start_date
|
||||
if start_date:
|
||||
checkin_ref = min(
|
||||
self.booking_duplicate_id.mapped(
|
||||
"reference_folio_id.reservation_ids.checkin"
|
||||
)
|
||||
)
|
||||
for record in self:
|
||||
if record.reference_reservation_id.checkin == checkin_ref:
|
||||
record.checkin = start_date
|
||||
else:
|
||||
dif_days = (
|
||||
record.reference_reservation_id.checkin - checkin_ref
|
||||
).days
|
||||
record.checkin = start_date + datetime.timedelta(days=dif_days)
|
||||
|
||||
@api.depends("checkin")
|
||||
def _compute_checkout(self):
|
||||
self.checkout = False
|
||||
for record in self.filtered("checkin"):
|
||||
res_days = record.reference_reservation_id.nights
|
||||
record.checkout = record.checkin + datetime.timedelta(days=res_days)
|
||||
|
||||
@api.depends(
|
||||
"checkin",
|
||||
"checkout",
|
||||
"preferred_room_id",
|
||||
"pms_property_id",
|
||||
)
|
||||
def _compute_allowed_room_ids(self):
|
||||
self.allowed_room_ids = False
|
||||
for reservation in self.filtered(lambda r: r.checkin and r.checkout):
|
||||
pms_property = reservation.pms_property_id
|
||||
pms_property = pms_property.with_context(
|
||||
checkin=reservation.checkin,
|
||||
checkout=reservation.checkout,
|
||||
room_type_id=False, # Allows to choose any available room
|
||||
pricelist_id=reservation.pricelist_id.id,
|
||||
class_id=reservation.room_type_id.class_id.id
|
||||
if reservation.room_type_id
|
||||
else False,
|
||||
real_avail=True,
|
||||
)
|
||||
allowed_room_ids = pms_property.free_room_ids.ids
|
||||
reservation.allowed_room_ids = self.env["pms.room"].browse(allowed_room_ids)
|
||||
|
||||
@api.depends("allowed_room_ids", "preferred_room_id")
|
||||
def _compute_occupied_room(self):
|
||||
self.occupied_room = False
|
||||
for record in self.filtered("preferred_room_id"):
|
||||
if (
|
||||
record.preferred_room_id.id not in record.allowed_room_ids.ids
|
||||
or record.preferred_room_id.id in record.used_room_ids.ids
|
||||
):
|
||||
record.occupied_room = True
|
||||
|
||||
@api.depends("preferred_room_id", "booking_duplicate_id.used_room_ids")
|
||||
def _compute_used_room_ids(self):
|
||||
self.used_room_ids = False
|
||||
for record in self:
|
||||
record.used_room_ids = list(
|
||||
set(record.booking_duplicate_id.used_room_ids.ids)
|
||||
- {record.preferred_room_id.id}
|
||||
)
|
||||
|
||||
@api.depends("room_type_id", "board_service_room_id", "checkin", "checkout")
|
||||
def _compute_price_total(self):
|
||||
self.price_total = 0
|
||||
for record in self.filtered("checkout"):
|
||||
record.price_total = record.reference_reservation_id.price_room_services_set
|
||||
|
||||
@api.depends("reference_reservation_id")
|
||||
def _compute_service_ids(self):
|
||||
for record in self:
|
||||
record.service_ids = list(
|
||||
set(record.reference_reservation_id.service_ids.mapped("product_id.id"))
|
||||
)
|
||||
194
pms/wizards/pms_booking_duplicate_views.xml
Normal file
194
pms/wizards/pms_booking_duplicate_views.xml
Normal file
@@ -0,0 +1,194 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
<record id="booking_duplicate" model="ir.ui.view">
|
||||
<field name="name">Duplicate Folio</field>
|
||||
<field name="model">pms.booking.duplicate</field>
|
||||
<field name="arch" type="xml">
|
||||
<form class="pt-1">
|
||||
<h2>
|
||||
<field name="reference_folio_id" required="1" readonly="1" />
|
||||
</h2>
|
||||
<div class="row">
|
||||
<div class="col-6 ">
|
||||
<group>
|
||||
<field name="pms_property_id" invisible="0" />
|
||||
<field name="used_room_ids" invisible="1" />
|
||||
<field name="reservation_type" />
|
||||
<field
|
||||
name="agency_id"
|
||||
attrs="{'invisible': [('reservation_type','!=','normal')]}"
|
||||
/>
|
||||
<field
|
||||
name="segmentation_ids"
|
||||
widget="many2many_tags"
|
||||
attrs="{'invisible': [('reservation_type','!=','normal')]}"
|
||||
/>
|
||||
</group>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<group>
|
||||
<field
|
||||
name="partner_id"
|
||||
string="Partner"
|
||||
options="{'no_create': True,'no_open': True}"
|
||||
attrs="{'invisible': [('reservation_type','=','out')]}"
|
||||
/>
|
||||
<field
|
||||
name="partner_name"
|
||||
string="Partner"
|
||||
required="1"
|
||||
attrs="{'invisible': [('reservation_type','=','out')]}"
|
||||
/>
|
||||
<field
|
||||
name="partner_name"
|
||||
string="Reason"
|
||||
required="1"
|
||||
attrs="{'invisible': [('reservation_type','!=','out')]}"
|
||||
/>
|
||||
<field
|
||||
default_focus="1"
|
||||
name="pricelist_id"
|
||||
string="Pricelist"
|
||||
options="{'no_create': True,'no_open': True}"
|
||||
attrs="{'required': [('reservation_type','=','normal')], 'invisible': [('reservation_type','!=','normal')]}"
|
||||
/>
|
||||
<field
|
||||
name="channel_type_id"
|
||||
attrs="{'required': [('reservation_type','=','normal')], 'invisible': [('reservation_type','!=','normal')]}"
|
||||
/>
|
||||
</group>
|
||||
<div>
|
||||
<group>
|
||||
<field
|
||||
name="internal_comment"
|
||||
placeholder="Internal comment Folio"
|
||||
nolabel="1"
|
||||
/>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<group>
|
||||
<field name="start_date" />
|
||||
<field name="recompute_prices" />
|
||||
</group>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<group>
|
||||
<field name="line_ids" nolabel="1">
|
||||
<tree
|
||||
editable="bottom"
|
||||
create="false"
|
||||
delete="false"
|
||||
decoration-muted="checkin == False"
|
||||
decoration-danger="checkin != False and occupied_room == True"
|
||||
decoration-success="checkin != False and occupied_room == False"
|
||||
>
|
||||
<field
|
||||
name="reference_reservation_id"
|
||||
invisible="1"
|
||||
/>
|
||||
<field name="booking_duplicate_id" invisible="1" />
|
||||
<field name="pms_property_id" invisible="1" />
|
||||
<field name="adults" />
|
||||
<field name="allowed_room_ids" invisible="1" />
|
||||
<field
|
||||
name="room_type_id"
|
||||
readonly="1"
|
||||
options="{'no_open': True}"
|
||||
force_save="1"
|
||||
/>
|
||||
<field
|
||||
name="pricelist_id"
|
||||
optional="hidden"
|
||||
options="{'no_create': True, 'no_edit': True, 'no_open': True}"
|
||||
/>
|
||||
<field
|
||||
name="preferred_room_id"
|
||||
options="{'no_create': True, 'no_edit': True, 'no_open': True}"
|
||||
/>
|
||||
<field name="occupied_room" invisible="1" />
|
||||
<field name="used_room_ids" invisible="1" />
|
||||
<field name="checkin" force_save="1" />
|
||||
<field name="checkout" force_save="1" />
|
||||
<field
|
||||
name="board_service_room_id"
|
||||
attrs="{'column_invisible': [('parent.reservation_type','!=','normal')]}"
|
||||
options="{'no_create': True, 'no_edit': True, 'no_open': True}"
|
||||
/>
|
||||
<field
|
||||
name="service_ids"
|
||||
widget="many2many_tags"
|
||||
attrs="{'column_invisible': [('parent.reservation_type','!=','normal')]}"
|
||||
readonly="1"
|
||||
/>
|
||||
<field
|
||||
name="price_total"
|
||||
readonly="1"
|
||||
force_save="1"
|
||||
attrs="{'column_invisible': [('parent.reservation_type','!=','normal')]}"
|
||||
/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row float-right border mr-2 mb-5">
|
||||
<div class="col-3 ">
|
||||
<div class="col-3 px-0">
|
||||
<group>
|
||||
<field
|
||||
name="total_price_folio"
|
||||
widget="monetary"
|
||||
attrs="{'invisible': [('reservation_type','!=','normal')]}"
|
||||
/>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<field name="created_folio_ids" readonly="1" />
|
||||
</div>
|
||||
<footer>
|
||||
<button
|
||||
name="create_and_new"
|
||||
string="Create and Continue"
|
||||
type="object"
|
||||
class="btn-secondary"
|
||||
/>
|
||||
<button
|
||||
name="create_and_close"
|
||||
string="Create and Close"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
/>
|
||||
<span>
|
||||
or
|
||||
</span>
|
||||
<button
|
||||
string="Cancel"
|
||||
class="btn-default border"
|
||||
special="cancel"
|
||||
/>
|
||||
<button
|
||||
name="view_folios"
|
||||
string="View Folios"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
attrs="{'invisible': [('created_folio_ids','=', False)]}"
|
||||
/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_booking_duplicate" model="ir.actions.act_window">
|
||||
<field name="name">Folio creation</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">pms.booking.duplicate</field>
|
||||
<field name="view_id" ref="booking_duplicate" />
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -1,4 +1,6 @@
|
||||
from odoo import _, api, fields, models
|
||||
from datetime import timedelta
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class WizardFolioChanges(models.TransientModel):
|
||||
@@ -11,6 +13,40 @@ class WizardFolioChanges(models.TransientModel):
|
||||
default=lambda self: self._default_folio_id(),
|
||||
comodel_name="pms.folio",
|
||||
)
|
||||
modification_type = fields.Selection(
|
||||
string="Modification Type",
|
||||
selection=[
|
||||
("reservations", "Reservations"),
|
||||
("dates", "Dates"),
|
||||
("services", "Services Prices"),
|
||||
],
|
||||
default="reservations",
|
||||
)
|
||||
room_type_filter_ids = fields.Many2many(
|
||||
string="Room types",
|
||||
default=lambda self: self._default_room_type_filter_ids(),
|
||||
comodel_name="pms.room.type",
|
||||
relation="folio_changes_room_type_rel",
|
||||
column1="folio_changes_id",
|
||||
column2="room_type_ids",
|
||||
domain="[('id', 'in', allowed_room_type_ids)]",
|
||||
)
|
||||
allowed_room_type_ids = fields.Many2many(
|
||||
string="Allowed Room Types",
|
||||
comodel_name="pms.room.type",
|
||||
relation="folio_changes_allowed_room_type_rel",
|
||||
column1="folio_changes_id",
|
||||
column2="allowed_room_type_ids",
|
||||
compute="_compute_allowed_room_type_ids",
|
||||
)
|
||||
change_from_date = fields.Date(
|
||||
string="Apply From",
|
||||
default=lambda self: self.default_change_from_date(),
|
||||
)
|
||||
change_to_date = fields.Date(
|
||||
string="Apply To",
|
||||
default=lambda self: self.default_change_to_date(),
|
||||
)
|
||||
reservation_ids = fields.Many2many(
|
||||
string="Reservations",
|
||||
default=lambda self: self._default_reservation_ids(),
|
||||
@@ -26,18 +62,114 @@ class WizardFolioChanges(models.TransientModel):
|
||||
relation="folio_changes_allowed_reservation_rel",
|
||||
column1="folio_changes_id",
|
||||
column2="allowed_reservation_ids",
|
||||
compute="_compute_allowed_reservations",
|
||||
compute="_compute_allowed_reservation_ids",
|
||||
)
|
||||
service_ids = fields.Many2many(
|
||||
string="Services",
|
||||
default=lambda self: self._default_service_ids(),
|
||||
comodel_name="pms.service",
|
||||
relation="folio_changes_service_rel",
|
||||
column1="folio_changes_id",
|
||||
column2="service_ids",
|
||||
domain="[('id', 'in', allowed_service_ids)]",
|
||||
)
|
||||
allowed_service_ids = fields.Many2many(
|
||||
string="Allowed Services",
|
||||
comodel_name="pms.service",
|
||||
relation="folio_changes_allowed_service_rel",
|
||||
column1="folio_changes_id",
|
||||
column2="allowed_service_ids",
|
||||
compute="_compute_allowed_service_ids",
|
||||
)
|
||||
apply_new_checkin = fields.Boolean(
|
||||
string="Apply Checkin Update",
|
||||
default=False,
|
||||
)
|
||||
new_checkin = fields.Date(
|
||||
string="New Checkin",
|
||||
default=lambda self: self.default_change_new_checkin(),
|
||||
)
|
||||
|
||||
apply_new_checkout = fields.Boolean(
|
||||
string="Apply Checkout Update",
|
||||
default=False,
|
||||
)
|
||||
new_checkout = fields.Date(
|
||||
string="New Checkout",
|
||||
default=lambda self: self.default_change_new_checkout(),
|
||||
)
|
||||
nights = fields.Integer(
|
||||
string="Nights",
|
||||
compute="_compute_nights",
|
||||
)
|
||||
dates_incongruence = fields.Boolean(
|
||||
string="Dates incrongruence",
|
||||
help="Indicates that there are reservations with different checkin and/or checkout",
|
||||
compute="_compute_dates_incongruence",
|
||||
store=True,
|
||||
)
|
||||
apply_price = fields.Boolean(
|
||||
string="Apply Price update",
|
||||
default=False,
|
||||
)
|
||||
new_price = fields.Float(
|
||||
string="New Price",
|
||||
)
|
||||
|
||||
apply_discount = fields.Boolean(
|
||||
string="Apply Discount update",
|
||||
default=False,
|
||||
)
|
||||
new_discount = fields.Float(
|
||||
string="New Discount %",
|
||||
)
|
||||
|
||||
apply_partner_id = fields.Boolean(
|
||||
string="Apply Customer",
|
||||
default=False,
|
||||
)
|
||||
new_partner_id = fields.Many2one(
|
||||
string="Customer",
|
||||
comodel_name="res.partner",
|
||||
)
|
||||
|
||||
apply_pricelist_id = fields.Boolean(
|
||||
string="Apply Pricelist",
|
||||
default=False,
|
||||
)
|
||||
new_pricelist_id = fields.Many2one(
|
||||
string="Pricelist",
|
||||
comodel_name="product.pricelist",
|
||||
)
|
||||
|
||||
apply_board_service = fields.Boolean(
|
||||
string="Add Board Service to reservations",
|
||||
default=False,
|
||||
)
|
||||
new_board_service_id = fields.Many2one(
|
||||
string="New Board Service",
|
||||
comodel_name="pms.board.service",
|
||||
)
|
||||
|
||||
apply_service = fields.Boolean(
|
||||
string="Add Service to reservations",
|
||||
default=False,
|
||||
)
|
||||
new_service_id = fields.Many2one(
|
||||
string="New Service",
|
||||
comodel_name="product.product",
|
||||
domain="[('sale_ok','=',True)]",
|
||||
)
|
||||
|
||||
apply_day_qty = fields.Boolean(
|
||||
string="Change cuantity service per day",
|
||||
help="If not set, it will use the default product day qty",
|
||||
default=False,
|
||||
)
|
||||
day_qty = fields.Integer(
|
||||
string="Quantity per day",
|
||||
)
|
||||
|
||||
apply_on_monday = fields.Boolean(
|
||||
string="Apply Availability Rule on mondays",
|
||||
default=False,
|
||||
@@ -81,13 +213,86 @@ class WizardFolioChanges(models.TransientModel):
|
||||
folio = self.env["pms.folio"].browse(folio_id)
|
||||
return folio.reservation_ids
|
||||
|
||||
def _default_room_type_filter_ids(self):
|
||||
folio_id = self._context.get("active_id")
|
||||
folio = self.env["pms.folio"].browse(folio_id)
|
||||
return self.env["pms.room.type"].browse(
|
||||
folio.mapped("reservation_ids.room_type_id.id")
|
||||
)
|
||||
|
||||
def default_change_new_checkin(self):
|
||||
folio_id = self._context.get("active_id")
|
||||
folio = self.env["pms.folio"].browse(folio_id)
|
||||
return min(folio.reservation_ids.mapped("checkin"), default=False)
|
||||
|
||||
def default_change_new_checkout(self):
|
||||
folio_id = self._context.get("active_id")
|
||||
folio = self.env["pms.folio"].browse(folio_id)
|
||||
return max(folio.reservation_ids.mapped("checkout"), default=False)
|
||||
|
||||
def _default_service_ids(self):
|
||||
folio_id = self._context.get("active_id")
|
||||
folio = self.env["pms.folio"].browse(folio_id)
|
||||
return folio.service_ids
|
||||
|
||||
def default_change_from_date(self):
|
||||
folio_id = self._context.get("active_id")
|
||||
folio = self.env["pms.folio"].browse(folio_id)
|
||||
return min(folio.reservation_ids.mapped("checkin"), default=False)
|
||||
|
||||
def default_change_to_date(self):
|
||||
folio_id = self._context.get("active_id")
|
||||
folio = self.env["pms.folio"].browse(folio_id)
|
||||
return max(folio.reservation_ids.mapped("checkout"), default=False)
|
||||
|
||||
@api.depends("new_checkin", "new_checkout")
|
||||
def _compute_nights(self):
|
||||
for record in self:
|
||||
record.nights = (record.new_checkout - record.new_checkin).days
|
||||
|
||||
@api.depends("reservation_ids")
|
||||
def _compute_dates_incongruence(self):
|
||||
self.dates_incongruence = False
|
||||
for record in self:
|
||||
if (
|
||||
len(set(record.reservation_ids.mapped("checkin"))) > 1
|
||||
or len(set(record.reservation_ids.mapped("checkout"))) > 1
|
||||
):
|
||||
record.dates_incongruence = True
|
||||
|
||||
@api.depends("folio_id")
|
||||
def _compute_allowed_reservations(self):
|
||||
def _compute_allowed_reservation_ids(self):
|
||||
self.ensure_one()
|
||||
self.allowed_reservation_ids = self.folio_id.reservation_ids
|
||||
|
||||
@api.depends("folio_id")
|
||||
def _compute_allowed_service_ids(self):
|
||||
self.ensure_one()
|
||||
self.allowed_service_ids = self.folio_id.service_ids
|
||||
|
||||
@api.depends("folio_id")
|
||||
def _compute_allowed_room_type_ids(self):
|
||||
self.ensure_one()
|
||||
self.allowed_room_type_ids = self.env["pms.room.type"].browse(
|
||||
self.folio_id.mapped("reservation_ids.room_type_id.id")
|
||||
)
|
||||
|
||||
@api.onchange("room_type_filter_ids")
|
||||
def _onchange_room_type_filter_ids(self):
|
||||
self.service_ids = self.folio_id.service_ids.filtered(
|
||||
lambda s: s.reservation_id
|
||||
and s.reservation_id.room_type_id.id in self.room_type_filter_ids.ids
|
||||
)
|
||||
self.reservation_ids = self.folio_id.reservation_ids.filtered(
|
||||
lambda r: r.room_type_id.id in self.room_type_filter_ids.ids
|
||||
)
|
||||
|
||||
@api.onchange("reservation_ids")
|
||||
def _onchange_reservations_ids(self):
|
||||
self.new_checkin = min(self.reservation_ids.mapped("checkin"), default=False)
|
||||
self.new_checkout = max(self.reservation_ids.mapped("checkout"), default=False)
|
||||
|
||||
def button_change(self):
|
||||
vals = {}
|
||||
week_days_to_apply = (
|
||||
self.apply_on_monday,
|
||||
self.apply_on_tuesday,
|
||||
@@ -97,52 +302,124 @@ class WizardFolioChanges(models.TransientModel):
|
||||
self.apply_on_saturday,
|
||||
self.apply_on_sunday,
|
||||
)
|
||||
reservation_lines = self.reservation_ids.reservation_line_ids
|
||||
if not self.apply_on_all_week:
|
||||
reservation_lines = reservation_lines.filtered(
|
||||
lambda x: week_days_to_apply[x.date.timetuple()[6]]
|
||||
if self.modification_type == "dates":
|
||||
self._update_dates(
|
||||
reservations=self.reservation_ids,
|
||||
new_checkin=self.new_checkin,
|
||||
new_checkout=self.new_checkout,
|
||||
)
|
||||
if self.new_price or self.new_discount:
|
||||
if self.new_price:
|
||||
vals["price"] = self.new_price
|
||||
if self.new_discount:
|
||||
vals["discount"] = self.new_discount
|
||||
|
||||
reservation_lines.write(vals)
|
||||
|
||||
self.folio_id.message_post(
|
||||
body=_(
|
||||
"Prices/Discounts have been changed from folio",
|
||||
)
|
||||
)
|
||||
reservations = self.env["pms.reservation"].browse(
|
||||
reservation_lines.mapped("reservation_id.id")
|
||||
)
|
||||
for reservation in reservations:
|
||||
reservation.message_post(
|
||||
body=_(
|
||||
"Prices/Discounts have been changed from folio",
|
||||
else:
|
||||
dates = [
|
||||
self.change_from_date + timedelta(days=d)
|
||||
for d in range((self.change_to_date - self.change_from_date).days + 1)
|
||||
]
|
||||
if self.modification_type == "reservations":
|
||||
reservation_lines = self.reservation_ids.reservation_line_ids
|
||||
if not self.apply_on_all_week:
|
||||
reservation_lines = reservation_lines.filtered(
|
||||
lambda x: week_days_to_apply[x.date.timetuple()[6]]
|
||||
and x.date in dates
|
||||
)
|
||||
if self.apply_discount or self.apply_price:
|
||||
self._update_reservations(
|
||||
reservation_lines=reservation_lines,
|
||||
new_price=self.apply_price and self.new_price,
|
||||
new_discount=self.apply_discount and self.new_discount,
|
||||
)
|
||||
if self.apply_board_service and self.new_board_service_id:
|
||||
self._add_board_service(
|
||||
reservations=self.reservation_ids,
|
||||
new_board_service_id=self.new_board_service_id.id,
|
||||
)
|
||||
if self.apply_service and self.new_service_id:
|
||||
self._add_service(
|
||||
reservations=self.reservation_ids,
|
||||
new_service_id=self.new_service_id.id,
|
||||
day_qty=self.day_qty if self.apply_day_qty else -1,
|
||||
)
|
||||
if self.apply_pricelist_id and self.new_pricelist_id:
|
||||
self.reservation_ids.pricelist_id = self.new_pricelist_id
|
||||
self.folio_id.pricelist_id = self.new_pricelist_id
|
||||
if self.apply_partner_id and self.new_partner_id:
|
||||
self.reservation_ids.partner_id = self.new_partner_id
|
||||
if not self.folio_id.partner_id:
|
||||
self.folio_id.partner_id = self.new_partner_id
|
||||
elif self.modification_type == "services":
|
||||
service_lines = self.service_ids.service_line_ids
|
||||
if not self.apply_on_all_week:
|
||||
reservation_lines = service_lines.filtered(
|
||||
lambda x: week_days_to_apply[x.date.timetuple()[6]]
|
||||
and x.date in dates
|
||||
)
|
||||
self._update_services(
|
||||
service_lines=service_lines,
|
||||
new_price=self.apply_price and self.new_price,
|
||||
new_discount=self.apply_discount and self.new_discount,
|
||||
)
|
||||
if self.new_board_service_id:
|
||||
for reservation in self.reservation_ids:
|
||||
if (
|
||||
self.new_board_service_id.id
|
||||
in reservation.room_type_id.board_service_room_type_ids.ids
|
||||
):
|
||||
reservation.board_service_room_id = (
|
||||
reservation.room_type_id.board_service_room_type_ids.filtered(
|
||||
lambda x: x.pms_board_service_id.id
|
||||
== self.new_board_service_id.id
|
||||
and (
|
||||
self.folio_id.pms_property_id.id
|
||||
in x.pms_property_ids.ids
|
||||
or not x.pms_property_ids
|
||||
)
|
||||
|
||||
def _update_dates(self, reservations, new_checkin, new_checkout):
|
||||
for res in reservations:
|
||||
if new_checkin:
|
||||
res.checkin = new_checkin
|
||||
if new_checkout:
|
||||
res.checkout = new_checkout
|
||||
|
||||
def _update_reservations(
|
||||
self, reservation_lines, new_price=False, new_discount=False
|
||||
):
|
||||
line_vals = {}
|
||||
if new_price:
|
||||
line_vals["price"] = new_price
|
||||
if new_discount:
|
||||
line_vals["discount"] = new_discount
|
||||
if line_vals:
|
||||
reservation_lines.write(line_vals)
|
||||
|
||||
def _add_board_service(self, reservations, new_board_service_id):
|
||||
for reservation in reservations:
|
||||
if new_board_service_id in reservation.room_type_id.mapped(
|
||||
"board_service_room_type_ids.pms_board_service_id.id"
|
||||
):
|
||||
reservation.board_service_room_id = (
|
||||
reservation.room_type_id.board_service_room_type_ids.filtered(
|
||||
lambda x: x.pms_board_service_id.id == new_board_service_id
|
||||
and (
|
||||
reservation.folio_id.pms_property_id.id
|
||||
in x.pms_property_ids.ids
|
||||
or not x.pms_property_ids
|
||||
)
|
||||
)
|
||||
reservation.message_post(
|
||||
body=_(
|
||||
"Board service has been changed from folio",
|
||||
)
|
||||
)
|
||||
|
||||
def _add_service(self, reservations, new_service_id, day_qty):
|
||||
old_services = reservations.service_ids
|
||||
reservations.write(
|
||||
{
|
||||
"service_ids": [
|
||||
(
|
||||
0,
|
||||
0,
|
||||
{
|
||||
"product_id": new_service_id,
|
||||
},
|
||||
)
|
||||
]
|
||||
}
|
||||
)
|
||||
new_services = reservations.service_ids - old_services
|
||||
# Use -1 to set default qty qty per day
|
||||
if day_qty > -1:
|
||||
new_services.day_qty = day_qty
|
||||
|
||||
def _update_services(
|
||||
self, service_lines, new_price=False, new_discount=False, new_day_qty=False
|
||||
):
|
||||
line_vals = {}
|
||||
if new_price:
|
||||
line_vals["price_unit"] = new_price
|
||||
if new_discount:
|
||||
line_vals["discount"] = new_discount
|
||||
if new_day_qty:
|
||||
line_vals["day_qty"] = new_day_qty
|
||||
if line_vals:
|
||||
service_lines.write(line_vals)
|
||||
|
||||
@@ -4,105 +4,355 @@
|
||||
<field name="name">wizard.folio.changes.view.form</field>
|
||||
<field name="model">wizard.folio.changes</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Folio Changes">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<table class="table table-bordered text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>All days</th>
|
||||
<th>Sunday</th>
|
||||
<th>Monday</th>
|
||||
<th>Tuesday</th>
|
||||
<th>Wednesday</th>
|
||||
<th>Thursday</th>
|
||||
<th>Friday</th>
|
||||
<th>Saturday</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_all_week"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_sunday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_monday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_tuesday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_wednesday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_thursday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_friday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_saturday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<form string="Folio Changes" class="pt-1">
|
||||
<group>
|
||||
<field name="modification_type" />
|
||||
</group>
|
||||
<group attrs="{'invisible':[('modification_type', '=', 'dates')]}">
|
||||
<field name="change_from_date" />
|
||||
<field name="change_to_date" />
|
||||
</group>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div
|
||||
class="col-12"
|
||||
attrs="{'invisible':[('modification_type', '=', 'dates')]}"
|
||||
>
|
||||
<table class="table table-bordered text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>All days</th>
|
||||
<th>Sunday</th>
|
||||
<th>Monday</th>
|
||||
<th>Tuesday</th>
|
||||
<th>Wednesday</th>
|
||||
<th>Thursday</th>
|
||||
<th>Friday</th>
|
||||
<th>Saturday</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_all_week"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_sunday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_monday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_tuesday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_wednesday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_thursday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_friday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<field
|
||||
name="apply_on_saturday"
|
||||
widget="boolean_toggle"
|
||||
attrs="{'invisible':[('apply_on_all_week','=',True)]}"
|
||||
/>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<field name="folio_id" invisible="1" />
|
||||
<field name="allowed_reservation_ids" invisible="1" />
|
||||
<field name="allowed_service_ids" invisible="1" />
|
||||
<field name="allowed_room_type_ids" invisible="1" />
|
||||
<field name="room_type_filter_ids" widget="many2many_tags" />
|
||||
<field
|
||||
name="reservation_ids"
|
||||
attrs="{'invisible':[('modification_type','=','services')]}"
|
||||
widget="many2many_tags"
|
||||
/>
|
||||
<field
|
||||
name="service_ids"
|
||||
attrs="{'invisible':[('modification_type','!=','services')]}"
|
||||
widget="many2many_tags"
|
||||
/>
|
||||
</group>
|
||||
<field name="dates_incongruence" invisible="1" force_save="1" />
|
||||
<div
|
||||
class="alert alert-warning"
|
||||
role="alert"
|
||||
attrs="{'invisible': [('dates_incongruence','=',False)]}"
|
||||
>
|
||||
Selected reservations with different dates
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
attrs="{'invisible':[('modification_type', '!=', 'dates')]}"
|
||||
>
|
||||
|
||||
<div class="col-4 pr-0">
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field
|
||||
name="apply_new_checkin"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="new_checkin" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="new_checkin"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_new_checkin','=',False)], 'required':[('apply_new_checkin','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 pr-0">
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field
|
||||
name="apply_new_checkout"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="new_checkout" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="new_checkout"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_new_checkout','=',False)], 'required':[('apply_new_checkout','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 pr-0">
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<label for="nights" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field name="nights" nolabel="1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-5 ">
|
||||
<group>
|
||||
<field name="folio_id" invisible="1" />
|
||||
<field name="allowed_reservation_ids" invisible="1" />
|
||||
<field
|
||||
name="reservation_ids"
|
||||
widget="many2many_tags"
|
||||
nolabel="1"
|
||||
/>
|
||||
</group>
|
||||
|
||||
<div
|
||||
class="row"
|
||||
attrs="{'invisible':[('modification_type','=','dates')]}"
|
||||
>
|
||||
<div class="col-4 pr-0">
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field name="apply_price" widget="boolean_toggle" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="new_price" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="new_price"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_price','=',False)], 'required':[('apply_price','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<group>
|
||||
<field name="new_price" />
|
||||
<field name="new_discount" />
|
||||
<field name="new_board_service_id" />
|
||||
</group>
|
||||
<div class="col-4 pr-0">
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field
|
||||
name="apply_discount"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="new_discount" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="new_discount"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_discount','=',False)], 'required':[('apply_discount','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-4 pr-0"
|
||||
attrs="{'invisible':[('modification_type','!=','reservations')]}"
|
||||
>
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field
|
||||
name="apply_board_service"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="new_board_service_id" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="new_board_service_id"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_board_service','=',False)], 'required':[('apply_board_service','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-4 pr-0"
|
||||
attrs="{'invisible':[('modification_type','!=','reservations')]}"
|
||||
>
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field
|
||||
name="apply_partner_id"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="new_partner_id" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="new_partner_id"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_partner_id','=',False)], 'required':[('apply_partner_id','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-4 pr-0"
|
||||
attrs="{'invisible':[('modification_type','!=','reservations')]}"
|
||||
>
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field
|
||||
name="apply_pricelist_id"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="new_pricelist_id" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="new_pricelist_id"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_pricelist_id','=',False)], 'required':[('apply_pricelist_id','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-4 pr-0">
|
||||
<div
|
||||
class="border h-100 pt-2 px-2"
|
||||
attrs="{'invisible':[('modification_type','!=','reservations')]}"
|
||||
>
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field
|
||||
name="apply_service"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="new_service_id" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="new_service_id"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_service','=',False)], 'required':[('apply_service','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="col-4 pr-0"
|
||||
attrs="{'invisible':[('modification_type','!=','services')]}"
|
||||
>
|
||||
<div class="border h-100 pt-2 px-2">
|
||||
<div class="row">
|
||||
<div class="col-2">
|
||||
<field
|
||||
name="apply_day_qty"
|
||||
widget="boolean_toggle"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label for="day_qty" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<field
|
||||
name="day_qty"
|
||||
nolabel="1"
|
||||
attrs="{'invisible':[('apply_day_qty','=',False)], 'required':[('apply_day_qty','=',True)]}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<button
|
||||
type="object"
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
"views/pms_room_views.xml",
|
||||
"views/pms_log_institution_traveller_report_views.xml",
|
||||
"views/pms_ine_tourism_type_category.xml",
|
||||
"views/res_partner_id_number_view.xml",
|
||||
"views/pms_checkin_partner_views.xml",
|
||||
"views/precheckin_portal_templates.xml",
|
||||
"wizards/traveller_report.xml",
|
||||
"wizards/wizard_ine.xml",
|
||||
],
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<record id="document_type_dni" model="res.partner.id_category">
|
||||
<field name="name">DNI</field>
|
||||
<field name="code">D</field>
|
||||
<field name="is_vat_equivalent">True</field>
|
||||
<field name="validation_code">
|
||||
letters = {
|
||||
0: "T",
|
||||
|
||||
@@ -7,3 +7,4 @@ from . import pms_ine_tourism_type_category
|
||||
from . import pms_room
|
||||
from . import res_partner
|
||||
from . import pms_checkin_partner
|
||||
from . import res_partner_id_number
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
|
||||
from odoo import api, models
|
||||
from odoo import api, fields, models
|
||||
|
||||
CODE_SPAIN = "ES"
|
||||
|
||||
@@ -10,6 +10,29 @@ _logger = logging.getLogger(__name__)
|
||||
class PmsCheckinParnert(models.Model):
|
||||
_inherit = "pms.checkin.partner"
|
||||
|
||||
support_number = fields.Char(
|
||||
string="Support number",
|
||||
help="ID support number",
|
||||
readonly=False,
|
||||
store=True,
|
||||
compute="_compute_support_number",
|
||||
)
|
||||
|
||||
@api.depends("partner_id")
|
||||
def _compute_support_number(self):
|
||||
for record in self:
|
||||
if not record.support_number:
|
||||
if record.partner_id.id_numbers:
|
||||
dni_numbers = record.partner_id.id_numbers.filtered(
|
||||
lambda x: x.category_id.name == "DNI"
|
||||
)
|
||||
if len(dni_numbers) == 1 and dni_numbers.support_number:
|
||||
record.support_number = dni_numbers.support_number
|
||||
else:
|
||||
record.support_number = False
|
||||
else:
|
||||
record.support_number = False
|
||||
|
||||
@api.model
|
||||
def _checkin_mandatory_fields(self, country=False, depends=False):
|
||||
mandatory_fields = super(PmsCheckinParnert, self)._checkin_mandatory_fields(
|
||||
@@ -26,5 +49,5 @@ class PmsCheckinParnert(models.Model):
|
||||
]
|
||||
)
|
||||
if depends or (country and country.code == CODE_SPAIN):
|
||||
mandatory_fields.append("state_id")
|
||||
mandatory_fields.append("residence_state_id")
|
||||
return mandatory_fields
|
||||
|
||||
@@ -16,7 +16,7 @@ class ResPartner(models.Model):
|
||||
store=True,
|
||||
)
|
||||
|
||||
@api.depends("nationality_id", "state_id")
|
||||
@api.depends("nationality_id", "residence_state_id")
|
||||
def _compute_ine_code(self):
|
||||
for record in self:
|
||||
if not record.nationality_id:
|
||||
@@ -24,6 +24,16 @@ class ResPartner(models.Model):
|
||||
elif record.nationality_id.code != CODE_SPAIN:
|
||||
record.ine_code = record.nationality_id.code_alpha3
|
||||
else:
|
||||
if not record.state_id:
|
||||
if not record.residence_state_id:
|
||||
record.ine_code = False
|
||||
record.ine_code = record.state_id.ine_code
|
||||
record.ine_code = record.residence_state_id.ine_code
|
||||
|
||||
def _check_enought_invoice_data(self):
|
||||
self.ensure_one()
|
||||
res = super(ResPartner, self)._check_enought_invoice_data()
|
||||
if not res:
|
||||
return res
|
||||
if self.country_id.code == "ES":
|
||||
if not self.state_id and not self.zip:
|
||||
return False
|
||||
return True
|
||||
|
||||
8
pms_l10n_es/models/res_partner_id_number.py
Normal file
8
pms_l10n_es/models/res_partner_id_number.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ResPartnerIdNumber(models.Model):
|
||||
_inherit = "res.partner.id_number"
|
||||
_description = "Partner ID Number"
|
||||
|
||||
support_number = fields.Char(string="Support number", help="DNI support number")
|
||||
@@ -46,9 +46,9 @@ class TestResPartner(TestPms):
|
||||
self.partner_1 = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "partner1",
|
||||
"country_id": country_spain.id,
|
||||
"residence_country_id": country_spain.id,
|
||||
"nationality_id": country_spain.id,
|
||||
"state_id": state_madrid.id,
|
||||
"residence_state_id": state_madrid.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class TestResPartner(TestPms):
|
||||
# ASSERT
|
||||
self.assertEqual(
|
||||
self.partner_1.ine_code,
|
||||
self.partner_1.state_id.ine_code,
|
||||
self.partner_1.residence_state_id.ine_code,
|
||||
"The ine code for Spanish partners must match the ine"
|
||||
" code of the state to which they belong",
|
||||
)
|
||||
|
||||
@@ -97,6 +97,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner1",
|
||||
"country_id": self.country_italy.id,
|
||||
"nationality_id": self.country_italy.id,
|
||||
"residence_country_id": self.country_italy.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -116,6 +117,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner2",
|
||||
"country_id": self.country_russia.id,
|
||||
"nationality_id": self.country_russia.id,
|
||||
"residence_country_id": self.country_russia.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -134,6 +136,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner3",
|
||||
"country_id": self.country_italy.id,
|
||||
"nationality_id": self.country_italy.id,
|
||||
"residence_country_id": self.country_italy.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -152,6 +155,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner4",
|
||||
"country_id": self.country_italy.id,
|
||||
"nationality_id": self.country_italy.id,
|
||||
"residence_country_id": self.country_italy.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -170,6 +174,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner5",
|
||||
"country_id": self.country_afghanistan.id,
|
||||
"nationality_id": self.country_afghanistan.id,
|
||||
"residence_country_id": self.country_afghanistan.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -188,6 +193,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner6",
|
||||
"country_id": self.country_afghanistan.id,
|
||||
"nationality_id": self.country_afghanistan.id,
|
||||
"residence_country_id": self.country_afghanistan.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -206,6 +212,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner7",
|
||||
"country_id": self.country_afghanistan.id,
|
||||
"nationality_id": self.country_afghanistan.id,
|
||||
"residence_country_id": self.country_afghanistan.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -364,6 +371,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner1",
|
||||
"country_id": self.country_russia.id,
|
||||
"nationality_id": self.country_russia.id,
|
||||
"residence_country_id": self.country_russia.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -373,6 +381,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner2",
|
||||
"country_id": self.country_russia.id,
|
||||
"nationality_id": self.country_russia.id,
|
||||
"residence_country_id": self.country_russia.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -382,6 +391,7 @@ class TestWizardINE(TestPms):
|
||||
"name": "partner3",
|
||||
"country_id": self.country_russia.id,
|
||||
"nationality_id": self.country_russia.id,
|
||||
"residence_country_id": self.country_russia.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -646,38 +656,38 @@ class TestWizardINE(TestPms):
|
||||
|
||||
self.checkin1.nationality_id = country_spain
|
||||
self.partner_1.nationality_id = country_spain
|
||||
self.checkin1.state_id = state_ourense
|
||||
self.partner_1.state_id = state_ourense
|
||||
self.checkin1.residence_state_id = state_ourense
|
||||
self.partner_1.residence_state_id = state_ourense
|
||||
|
||||
self.checkin2.nationality_id = country_spain
|
||||
self.partner_2.nationality_id = country_spain
|
||||
self.checkin2.state_id = state_pontevedra
|
||||
self.partner_2.state_id = state_pontevedra
|
||||
self.checkin2.residence_state_id = state_pontevedra
|
||||
self.partner_2.residence_state_id = state_pontevedra
|
||||
|
||||
self.checkin3.nationality_id = country_spain
|
||||
self.partner_3.nationality_id = country_spain
|
||||
self.checkin3.state_id = state_ourense
|
||||
self.partner_3.state_id = state_ourense
|
||||
self.checkin3.residence_state_id = state_ourense
|
||||
self.partner_3.residence_state_id = state_ourense
|
||||
|
||||
self.checkin4.nationality_id = country_spain
|
||||
self.partner_4.nationality_id = country_spain
|
||||
self.checkin4.state_id = state_ourense
|
||||
self.partner_4.state_id = state_ourense
|
||||
self.checkin4.residence_state_id = state_ourense
|
||||
self.partner_4.residence_state_id = state_ourense
|
||||
|
||||
self.checkin5.nationality_id = country_spain
|
||||
self.partner_5.nationality_id = country_spain
|
||||
self.checkin5.state_id = state_madrid
|
||||
self.partner_5.state_id = state_madrid
|
||||
self.checkin5.residence_state_id = state_madrid
|
||||
self.partner_5.residence_state_id = state_madrid
|
||||
|
||||
self.checkin6.nationality_id = country_spain
|
||||
self.partner_6.nationality_id = country_spain
|
||||
self.checkin6.state_id = state_madrid
|
||||
self.partner_6.state_id = state_madrid
|
||||
self.checkin6.residence_state_id = state_madrid
|
||||
self.partner_6.residence_state_id = state_madrid
|
||||
|
||||
self.checkin7.nationality_id = country_spain
|
||||
self.partner_7.nationality_id = country_spain
|
||||
self.checkin7.state_id = state_madrid
|
||||
self.partner_7.state_id = state_madrid
|
||||
self.checkin7.residence_state_id = state_madrid
|
||||
self.partner_7.residence_state_id = state_madrid
|
||||
|
||||
expected_result = {
|
||||
country_spain.code: {
|
||||
@@ -945,38 +955,38 @@ class TestWizardINE(TestPms):
|
||||
|
||||
self.checkin1.nationality_id = country_spain
|
||||
self.partner_1.nationality_id = country_spain
|
||||
self.checkin1.state_id = state_ourense
|
||||
self.partner_1.state_id = state_ourense
|
||||
self.checkin1.residence_state_id = state_ourense
|
||||
self.partner_1.residence_state_id = state_ourense
|
||||
|
||||
self.checkin2.nationality_id = country_spain
|
||||
self.partner_2.nationality_id = country_spain
|
||||
self.checkin2.state_id = False
|
||||
self.partner_2.state_id = False
|
||||
self.checkin2.residence_state_id = False
|
||||
self.partner_2.residence_state_id = False
|
||||
|
||||
self.checkin3.nationality_id = country_spain
|
||||
self.partner_3.nationality_id = country_spain
|
||||
self.checkin3.state_id = state_ourense
|
||||
self.partner_3.state_id = state_ourense
|
||||
self.checkin3.residence_state_id = state_ourense
|
||||
self.partner_3.residence_state_id = state_ourense
|
||||
|
||||
self.checkin4.nationality_id = country_spain
|
||||
self.partner_4.nationality_id = country_spain
|
||||
self.checkin4.state_id = state_ourense
|
||||
self.partner_4.state_id = state_ourense
|
||||
self.checkin4.residence_state_id = state_ourense
|
||||
self.partner_4.residence_state_id = state_ourense
|
||||
|
||||
self.checkin5.nationality_id = country_spain
|
||||
self.partner_5.nationality_id = country_spain
|
||||
self.checkin5.state_id = state_madrid
|
||||
self.partner_5.state_id = state_madrid
|
||||
self.checkin5.residence_state_id = state_madrid
|
||||
self.partner_5.residence_state_id = state_madrid
|
||||
|
||||
self.checkin6.nationality_id = country_spain
|
||||
self.partner_6.nationality_id = country_spain
|
||||
self.checkin6.state_id = state_madrid
|
||||
self.partner_6.state_id = state_madrid
|
||||
self.checkin6.residence_state_id = state_madrid
|
||||
self.partner_6.residence_state_id = state_madrid
|
||||
|
||||
self.checkin7.nationality_id = country_spain
|
||||
self.partner_7.nationality_id = country_spain
|
||||
self.checkin7.state_id = state_madrid
|
||||
self.partner_7.state_id = state_madrid
|
||||
self.checkin7.residence_state_id = state_madrid
|
||||
self.partner_7.residence_state_id = state_madrid
|
||||
|
||||
# ACT & ASSERT
|
||||
with self.assertRaises(
|
||||
|
||||
@@ -56,6 +56,7 @@ class TestWizardTravellerReport(TestPms):
|
||||
"name": "partner1",
|
||||
"country_id": self.country_italy.id,
|
||||
"nationality_id": self.country_italy.id,
|
||||
"residence_country_id": self.country_italy.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -75,6 +76,7 @@ class TestWizardTravellerReport(TestPms):
|
||||
"name": "partner2",
|
||||
"country_id": self.country_italy.id,
|
||||
"nationality_id": self.country_italy.id,
|
||||
"residence_country_id": self.country_italy.id,
|
||||
"birthdate_date": "2000-06-25",
|
||||
"gender": "male",
|
||||
}
|
||||
@@ -175,6 +177,7 @@ class TestWizardTravellerReport(TestPms):
|
||||
"reservation_id": self.reservation_1.id,
|
||||
"firstname": "John",
|
||||
"lastname": "Doe",
|
||||
"nationality_id": self.country_italy.id,
|
||||
}
|
||||
)
|
||||
# Create reservation 2
|
||||
|
||||
13
pms_l10n_es/views/pms_checkin_partner_views.xml
Normal file
13
pms_l10n_es/views/pms_checkin_partner_views.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="inherit_pms_checkin_partner_form" model="ir.ui.view">
|
||||
<field name="name">inherit.pms.checkin_partner.form</field>
|
||||
<field name="model">pms.checkin.partner</field>
|
||||
<field name="inherit_id" ref="pms.pms_checkin_partner_view_form" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='document_expedition_date']" position="after">
|
||||
<field name="support_number" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
79
pms_l10n_es/views/precheckin_portal_templates.xml
Normal file
79
pms_l10n_es/views/precheckin_portal_templates.xml
Normal file
@@ -0,0 +1,79 @@
|
||||
<odoo>
|
||||
<template
|
||||
id="inherit_pms_l10n_es_portal_my_precheckin_detail"
|
||||
inherit_id="pms.portal_my_precheckin_detail"
|
||||
>
|
||||
<xpath expr="//div[@name='document_type_div']" position="replace">
|
||||
<div t-attf-class="form-group col-md-4">
|
||||
<label class="col-form-label" for="document_type">Doc. Type</label>
|
||||
<div class="d-none"><p id="docTypeId"><t
|
||||
t-esc="checkin_partner_id.document_type"
|
||||
/></p></div>
|
||||
<select
|
||||
class="form-control #{error.get('document_type') and 'is-invalid' or ''}"
|
||||
name='document_type'
|
||||
id="doc_type"
|
||||
>
|
||||
<option value="">Select an option</option>
|
||||
<t t-foreach="doc_type_ids" t-as='doc_type'>
|
||||
<option
|
||||
t-att-value="doc_type.name"
|
||||
t-att-selected="doc_type.id == checkin_partner_id.document_type.id"
|
||||
>
|
||||
<t t-esc='doc_type.name' />
|
||||
</option>
|
||||
</t>
|
||||
</select>
|
||||
<t t-if="error_message">
|
||||
<span
|
||||
class="text-danger"
|
||||
t-esc="error_message.get('document_type')"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='document_number_div']" position="replace">
|
||||
<div t-attf-class="col-12 col-md-4">
|
||||
<label class="col-form-label" for="document_number">Doc. Number</label>
|
||||
<input
|
||||
type="text"
|
||||
name="document_number"
|
||||
t-attf-class="form-control #{error.get('document_number') and 'is-invalid' or ''}"
|
||||
t-att-value="doc_number or checkin_partner_id.document_number"
|
||||
/>
|
||||
<t t-if="error_message">
|
||||
<span
|
||||
class="text-danger"
|
||||
t-esc="error_message.get('document_number')"
|
||||
/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-attf-class="form-group col-md-4">
|
||||
<label
|
||||
class="col-form-label"
|
||||
for="support_number"
|
||||
>Doc. Support Number</label>
|
||||
<input
|
||||
type="text"
|
||||
name="support_number"
|
||||
t-attf-class="form-control"
|
||||
t-att-value="support_number or checkin_partner_id.support_number"
|
||||
/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
<template
|
||||
id="inherit_pms_l10n_es_portal_my_precheckin_end"
|
||||
inherit_id="pms.portal_my_precheckin_end"
|
||||
>
|
||||
<xpath expr="//div[@name='birthdate_div']" position="before">
|
||||
<div t-attf-class="form-group col-12 col-md-6 font-weight-bold">
|
||||
Doc. Support Number:
|
||||
<br />
|
||||
<span class="font-weight-normal ml-3">
|
||||
<t t-esc="checkin_partner.support_number" />
|
||||
</span>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
29
pms_l10n_es/views/res_partner_id_number_view.xml
Normal file
29
pms_l10n_es/views/res_partner_id_number_view.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<record id="inherit_partner_id_numbers_form" model="ir.ui.view">
|
||||
<field name="name">inherit.res.partner.id_number.form</field>
|
||||
<field name="model">res.partner.id_number</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="partner_identification.view_partner_id_numbers_form"
|
||||
/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='valid_until']" position="after">
|
||||
<field name="support_number" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<record id="inherit_partner_id_numbers_tree" model="ir.ui.view">
|
||||
<field name="name">inherit.res.partner.id_number.tree</field>
|
||||
<field name="model">res.partner.id_number</field>
|
||||
<field
|
||||
name="inherit_id"
|
||||
ref="partner_identification.view_partner_id_numbers_tree"
|
||||
/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='valid_until']" position="after">
|
||||
<field name="support_number" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -208,7 +208,7 @@ class WizardIne(models.TransientModel):
|
||||
)
|
||||
raise ValidationError(
|
||||
_(
|
||||
"The following guests have no nationality set :%s.",
|
||||
"The following guests have no residence nationality set :%s.",
|
||||
guests_with_no_nationality,
|
||||
)
|
||||
)
|
||||
@@ -234,13 +234,13 @@ class WizardIne(models.TransientModel):
|
||||
# arrivals grouped by state_id (Spain "provincias")
|
||||
read_by_arrivals_spain = self.env["res.partner"].read_group(
|
||||
entry["__domain"],
|
||||
["state_id"],
|
||||
["state_id"],
|
||||
["residence_state_id"],
|
||||
["residence_state_id"],
|
||||
lazy=False,
|
||||
)
|
||||
# iterate read_group results from Spain
|
||||
for entry_from_spain in read_by_arrivals_spain:
|
||||
if not entry_from_spain["state_id"]:
|
||||
if not entry_from_spain["residence_state_id"]:
|
||||
spanish_guests_with_no_state = self.env[
|
||||
"res.partner"
|
||||
].search(entry_from_spain["__domain"])
|
||||
@@ -255,10 +255,10 @@ class WizardIne(models.TransientModel):
|
||||
spanish_guests_with_no_state,
|
||||
)
|
||||
)
|
||||
state_id = self.env["res.country.state"].browse(
|
||||
entry_from_spain["state_id"][0]
|
||||
residence_state_id = self.env["res.country.state"].browse(
|
||||
entry_from_spain["residence_state_id"][0]
|
||||
) # .ine_code
|
||||
ine_code = state_id.ine_code
|
||||
ine_code = residence_state_id.ine_code
|
||||
|
||||
# get count of each result
|
||||
num_spain = entry_from_spain["__count"]
|
||||
@@ -317,18 +317,18 @@ class WizardIne(models.TransientModel):
|
||||
# if there are some checkin partners in the same reservation
|
||||
if chk_part_same_reserv_with_checkin:
|
||||
# create partner with same country & state
|
||||
country_other = (
|
||||
chk_part_same_reserv_with_checkin.partner_id.country_id.id
|
||||
)
|
||||
state_other = (
|
||||
chk_part_same_reserv_with_checkin.partner_id.state_id.id
|
||||
)
|
||||
country_other = chk_part_same_reserv_with_checkin[
|
||||
0
|
||||
].partner_id.nationality_id.id
|
||||
state_other = chk_part_same_reserv_with_checkin[
|
||||
0
|
||||
].partner_id.residence_state_id.id
|
||||
dummy_partner = self.env["res.partner"].create(
|
||||
{
|
||||
"name": "partner1",
|
||||
"country_id": country_other,
|
||||
"nationality_id": country_other,
|
||||
"state_id": state_other,
|
||||
"residence_state_id": state_other,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -339,7 +339,7 @@ class WizardIne(models.TransientModel):
|
||||
"name": "partner1",
|
||||
"country_id": country_spain.id,
|
||||
"nationality_id": country_spain.id,
|
||||
"state_id": state_madrid.id,
|
||||
"residence_state_id": state_madrid.id,
|
||||
}
|
||||
)
|
||||
fake_partners_ids.append(dummy_partner.id)
|
||||
|
||||
Reference in New Issue
Block a user