From f85a11e0ae477a09c318d984a37c47bf45ea6b1a Mon Sep 17 00:00:00 2001 From: Sara <49147098+saralb9@users.noreply.github.com> Date: Sat, 22 May 2021 08:45:24 +0200 Subject: [PATCH] [RFC] Code review (#91) * [REF] Refactor wizard models * [REF] Refactor in pms models --- pms/models/account_bank_statement.py | 9 +- pms/models/account_bank_statement_line.py | 25 +- pms/models/account_journal.py | 2 + pms/models/account_move.py | 54 +- pms/models/account_move_line.py | 23 +- pms/models/account_payment.py | 6 +- pms/models/folio_sale_line.py | 1077 +++++------ pms/models/payment_return.py | 15 +- pms/models/pms_cancelation_rule.py | 72 +- pms/models/pms_checkin_partner.py | 145 +- pms/models/pms_folio.py | 1612 +++++++++-------- pms/models/product_pricelist.py | 88 +- pms/models/product_pricelist_item.py | 35 +- pms/models/product_product.py | 4 +- pms/wizards/folio_make_invoice_advance.py | 151 +- pms/wizards/wizard_advanced_filters.py | 8 +- pms/wizards/wizard_folio.py | 48 +- pms/wizards/wizard_folio_availability.py | 32 +- pms/wizards/wizard_folio_changes.py | 36 +- pms/wizards/wizard_massive_changes.py | 59 +- pms/wizards/wizard_payment_folio.py | 24 +- .../wizard_split_join_swap_reservation.py | 28 +- 22 files changed, 1935 insertions(+), 1618 deletions(-) diff --git a/pms/models/account_bank_statement.py b/pms/models/account_bank_statement.py index 3eb3d9b35..26bb1222b 100644 --- a/pms/models/account_bank_statement.py +++ b/pms/models/account_bank_statement.py @@ -4,7 +4,14 @@ from odoo import fields, models class AccountBankStatement(models.Model): _inherit = "account.bank.statement" - property_id = fields.Many2one("pms.property", string="Property", copy=False) + property_id = fields.Many2one( + string="Property", + help="Properties with access to the element", + copy=False, + comodel_name="pms.property", + ) company_id = fields.Many2one( + string="Company", + help="The company for Account Bank Statement", check_pms_properties=True, ) diff --git a/pms/models/account_bank_statement_line.py b/pms/models/account_bank_statement_line.py index 06ebb848f..e0ae8d426 100644 --- a/pms/models/account_bank_statement_line.py +++ b/pms/models/account_bank_statement_line.py @@ -5,12 +5,31 @@ class AccountBankStatementLine(models.Model): _inherit = "account.bank.statement.line" statement_folio_ids = fields.Many2many( - "pms.folio", string="Folios", ondelete="cascade" + string="Folios", + comodel_name="pms.folio", + ondelete="cascade", + relation="account_bank_statement_statement_folio_ids_rel", + column1="account_journal_id", + column2="statement_folio_ids_id", ) reservation_ids = fields.Many2many( - "pms.reservation", string="Reservations", ondelete="cascade" + string="Reservations", + help="Reservations in which the Account Bank Statement Lines are included", + comodel_name="pms.reservation", + ondelete="cascade", + relation="account_bank_statement_reservation_ids_rel", + column1="account_bank_statement_id", + column2="reservation_ids_id", + ) + service_ids = fields.Many2many( + string="Services", + help="Services in which the Account Bank Statement Lines are included", + comodel_name="pms.service", + ondelete="cascade", + relation="account_bank_statement_service_ids_rel", + column1="account_bank_statement_id", + column2="service_ids_id", ) - service_ids = fields.Many2many("pms.service", string="Services", ondelete="cascade") @api.model def _prepare_move_line_default_vals(self, counterpart_account_id=None): diff --git a/pms/models/account_journal.py b/pms/models/account_journal.py index 07121008f..841b18ed4 100644 --- a/pms/models/account_journal.py +++ b/pms/models/account_journal.py @@ -16,5 +16,7 @@ class AccountJournal(models.Model): check_pms_properties=True, ) company_id = fields.Many2one( + string="Company", + help="The company for Account Jouarnal", check_pms_properties=True, ) diff --git a/pms/models/account_move.py b/pms/models/account_move.py index d4bd0eae4..d9ac60d18 100644 --- a/pms/models/account_move.py +++ b/pms/models/account_move.py @@ -11,9 +11,19 @@ class AccountMove(models.Model): # Field Declarations folio_ids = fields.Many2many( - comodel_name="pms.folio", compute="_compute_folio_origin" + string="Folios", + help="Folios where the account move are included", + comodel_name="pms.folio", + compute="_compute_folio_origin", + relation="account_move_folio_ids_rel", + column1="account_move_id", + column2="folio_ids_id", + ) + pms_property_id = fields.Many2one( + string="Property", + help="Property with access to the element", + comodel_name="pms.property", ) - pms_property_id = fields.Many2one("pms.property") outstanding_folios_debits_widget = fields.Text( compute="_compute_get_outstanding_folios_JSON" ) @@ -21,8 +31,6 @@ class AccountMove(models.Model): compute="_compute_get_outstanding_folios_JSON" ) - # Compute and Search methods - def _compute_folio_origin(self): for inv in self: inv.folio_ids = False @@ -30,27 +38,6 @@ class AccountMove(models.Model): if folios: inv.folio_ids = [(6, 0, folios.ids)] - # Action methods - - def action_folio_payments(self): - self.ensure_one() - sales = self.mapped("invoice_line_ids.sale_line_ids.order_id") - folios = self.env["pms.folio"].search([("order_id.id", "in", sales.ids)]) - payments_obj = self.env["account.payment"] - payments = payments_obj.search([("folio_id", "in", folios.ids)]) - payment_ids = payments.mapped("id") - return { - "name": _("Payments"), - "view_type": "form", - "view_mode": "tree,form", - "res_model": "account.payment", - "target": "new", - "type": "ir.actions.act_window", - "domain": [("id", "in", payment_ids)], - } - - # Business methods - def _compute_get_outstanding_folios_JSON(self): self.ensure_one() self.outstanding_folios_debits_widget = json.dumps(False) @@ -120,3 +107,20 @@ class AccountMove(models.Model): info["title"] = type_payment self.outstanding_folios_debits_widget = json.dumps(info) self.has_folio_outstanding = True + + def action_folio_payments(self): + self.ensure_one() + sales = self.mapped("invoice_line_ids.sale_line_ids.order_id") + folios = self.env["pms.folio"].search([("order_id.id", "in", sales.ids)]) + payments_obj = self.env["account.payment"] + payments = payments_obj.search([("folio_id", "in", folios.ids)]) + payment_ids = payments.mapped("id") + return { + "name": _("Payments"), + "view_type": "form", + "view_mode": "tree,form", + "res_model": "account.payment", + "target": "new", + "type": "ir.actions.act_window", + "domain": [("id", "in", payment_ids)], + } diff --git a/pms/models/account_move_line.py b/pms/models/account_move_line.py index 0cc5d19a6..515edf5d6 100644 --- a/pms/models/account_move_line.py +++ b/pms/models/account_move_line.py @@ -10,25 +10,26 @@ class AccountMoveLine(models.Model): # Fields declaration # TODO: REVIEW why not a Many2one? folio_line_ids = fields.Many2many( - "folio.sale.line", - "folio_sale_line_invoice_rel", - "invoice_line_id", - "sale_line_id", string="Folio Lines", + help="The folio lines in the account move lines", copy=False, + comodel_name="folio.sale.line", + relation="folio_sale_line_invoice_rel", + column1="invoice_line_id", + column2="sale_line_id", ) folio_ids = fields.Many2many( - "pms.folio", - "payment_folio_rel", - "move_id", - "folio_id", string="Folios", + comodel_name="pms.folio", + relation="payment_folio_rel", + column1="move_id", + column2="folio_id", ) name_changed_by_user = fields.Boolean( - default=False, - readonly=False, - store=True, string="Custom label", + readonly=False, + default=False, + store=True, compute="_compute_name_changed_by_user", ) diff --git a/pms/models/account_payment.py b/pms/models/account_payment.py index 389e1b9e3..271dacaf2 100644 --- a/pms/models/account_payment.py +++ b/pms/models/account_payment.py @@ -7,7 +7,11 @@ class AccountPayment(models.Model): _inherit = "account.payment" # Fields declaration - folio_id = fields.Many2one("pms.folio", string="Folio Reference") + folio_id = fields.Many2one( + string="Folio Reference", + help="Folio in account payment", + comodel_name="pms.folio", + ) # Business methods diff --git a/pms/models/folio_sale_line.py b/pms/models/folio_sale_line.py index ba2a78c61..f907c4d87 100644 --- a/pms/models/folio_sale_line.py +++ b/pms/models/folio_sale_line.py @@ -15,6 +15,487 @@ class FolioSaleLine(models.Model): _check_company_auto = True + folio_id = fields.Many2one( + string="Folio Reference", + help="Folio to which folio sale line belongs", + required=True, + index=True, + copy=False, + comodel_name="pms.folio", + ondelete="cascade", + ) + reservation_id = fields.Many2one( + string="Reservation Reference", + help="Reservation to which folio sale line belongs", + index=True, + copy=False, + comodel_name="pms.reservation", + ondelete="cascade", + ) + service_id = fields.Many2one( + string="Service Reference", + help="Sevice included in folio sale line", + index=True, + copy=False, + comodel_name="pms.service", + ondelete="cascade", + ) + is_board_service = fields.Boolean( + string="Board Service", + help="Indicates if the service included in " + "folio sale line is part of a board service", + store=True, + related="service_id.is_board_service", + ) + + name = fields.Text( + string="Description", + help="Description of folio sale line", + readonly=False, + store=True, + compute="_compute_name", + ) + reservation_line_ids = fields.Many2many( + string="Nights", + help="Reservation lines associated with folio sale line," + " they corresponds with nights", + comodel_name="pms.reservation.line", + ) + service_line_ids = fields.Many2many( + string="Service Lines", + help="Subservices included in folio sale line service", + comodel_name="pms.service.line", + ) + sequence = fields.Integer(string="Sequence", help="", default=10) + + invoice_lines = fields.Many2many( + string="Invoice Lines", + copy=False, + help="Folio sale line invoice lines", + comodel_name="account.move.line", + relation="folio_sale_line_invoice_rel", + column1="sale_line_id", + column2="invoice_line_id", + ) + invoice_status = fields.Selection( + string="Invoice Status", + help="Invoice Status; it can be: upselling, invoiced, to invoice, no", + readonly=True, + default="no", + store=True, + selection=[ + ("upselling", "Upselling Opportunity"), + ("invoiced", "Fully Invoiced"), + ("to invoice", "To Invoice"), + ("no", "Nothing to Invoice"), + ], + compute="_compute_invoice_status", + ) + price_unit = fields.Float( + string="Unit Price", + help="Unit Price of folio sale line", + digits="Product Price", + ) + + price_subtotal = fields.Monetary( + string="Subtotal", + help="Subtotal price without taxes", + readonly=True, + store=True, + compute="_compute_amount", + ) + price_tax = fields.Float( + string="Total Tax", + help="Total of taxes in a reservation", + readonly=True, + store=True, + compute="_compute_amount", + ) + price_total = fields.Monetary( + string="Total", + help="Total price with taxes", + readonly=True, + store=True, + compute="_compute_amount", + ) + price_reduce = fields.Float( + string="Price Reduce", + help="Reduced price amount, that is, total price with discounts applied", + readonly=True, + store=True, + digits="Product Price", + compute="_compute_get_price_reduce", + ) + tax_ids = fields.Many2many( + string="Taxes", + help="Taxes applied in the folio sale line", + store=True, + comodel_name="account.tax", + compute="_compute_tax_ids", + domain=["|", ("active", "=", False), ("active", "=", True)], + ) + price_reduce_taxinc = fields.Monetary( + string="Price Reduce Tax inc", + help="Price with discounts applied and taxes included", + readonly=True, + store=True, + compute="_compute_get_price_reduce_tax", + ) + price_reduce_taxexcl = fields.Monetary( + string="Price Reduce Tax excl", + help="Price with discounts applied without taxes", + readonly=True, + store=True, + compute="_compute_get_price_reduce_notax", + ) + + discount = fields.Float( + string="Discount (%)", + help="Discount of total price in folio sale line", + readonly=False, + store=True, + digits="Discount", + compute="_compute_discount", + ) + + product_id = fields.Many2one( + string="Product", + help="Product associated with folio sale line, " + "can be product associated with service " + "or product associated with" + "reservation's room type, in other case it's false", + store=True, + comodel_name="product.product", + domain="[('sale_ok', '=', True),\ + '|', ('company_id', '=', False), \ + ('company_id', '=', company_id)]", + ondelete="restrict", + compute="_compute_product_id", + check_company=True, + change_default=True, + ) + product_uom_qty = fields.Float( + string="Quantity", + help="", + readonly=False, + store=True, + digits="Product Unit of Measure", + compute="_compute_product_uom_qty", + ) + product_uom = fields.Many2one( + string="Unit of Measure", + help="", + comodel_name="uom.uom", + domain="[('category_id', '=', product_uom_category_id)]", + ) + product_uom_category_id = fields.Many2one( + string="Unit of Measure Category", + help="", + readonly=True, + related="product_id.uom_id.category_id", + ) + product_uom_readonly = fields.Boolean( + string="", help="", compute="_compute_product_uom_readonly" + ) + + product_custom_attribute_value_ids = fields.One2many( + string="Custom Values", + copy=True, + comodel_name="product.attribute.custom.value", + inverse_name="sale_order_line_id", + ) + + qty_to_invoice = fields.Float( + string="To Invoice Quantity", + help="The quantity to invoice. If the invoice policy is order, " + "the quantity to invoice is calculated from the ordered quantity. " + "Otherwise, the quantity delivered is used.", + readonly=True, + store=True, + digits="Product Unit of Measure", + compute="_compute_get_to_invoice_qty", + ) + qty_invoiced = fields.Float( + string="Invoiced Quantity", + help="It is the amount invoiced when an invoice is issued", + readonly=True, + store=True, + digits="Product Unit of Measure", + compute="_compute_get_invoice_qty", + compute_sudo=True, + ) + + untaxed_amount_invoiced = fields.Monetary( + string="Untaxed Invoiced Amount", + help="The amount to invoice without taxes in the line of folio", + store=True, + compute="_compute_untaxed_amount_invoiced", + compute_sudo=True, + ) + untaxed_amount_to_invoice = fields.Monetary( + string="Untaxed Amount To Invoice", + help="The invoiced amount without taxes in the line of the folio", + store=True, + compute="_compute_untaxed_amount_to_invoice", + compute_sudo=True, + ) + + currency_id = fields.Many2one( + string="Currency", + help="The currency for the folio", + readonly=True, + store=True, + depends=["folio_id.currency_id"], + related="folio_id.currency_id", + ) + company_id = fields.Many2one( + string="Company", + help="The company in the folio sale line", + readonly=True, + store=True, + index=True, + related="folio_id.company_id", + check_pms_properties=True, + ) + folio_partner_id = fields.Many2one( + string="Customer", + help="Related customer with Folio Sale Line", + readonly=False, + store=True, + related="folio_id.partner_id", + ) + analytic_tag_ids = fields.Many2many( + string="Analytic Tags", + comodel_name="account.analytic.tag", + domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", + ) + analytic_line_ids = fields.One2many( + string="Analytic lines", + comodel_name="account.analytic.line", + inverse_name="so_line", + ) + is_downpayment = fields.Boolean( + string="Is a down payment", + help="Down payments are made when creating invoices from a folio." + " They are not copied when duplicating a folio.", + ) + + state = fields.Selection( + string="Folio Status", + help="The status of the folio related with folio sale line", + readonly=True, + copy=False, + store=True, + related="folio_id.state", + ) + + display_type = fields.Selection( + string="Display Type", + help="Technical field for UX purpose.", + selection=[("line_section", "Section"), ("line_note", "Note")], + default=False, + ) + + service_order = fields.Integer( + string="Service Id", + help="Field to order by service id", + readonly=True, + store=True, + compute="_compute_service_order", + ) + + reservation_order = fields.Integer( + string="Reservation Id", + help="Field to order by reservation id", + readonly=True, + store=True, + compute="_compute_reservation_order", + ) + + date_order = fields.Date( + string="Date", + help="Field to order by service", + readonly=True, + store=True, + compute="_compute_date_order", + ) + + @api.depends("qty_to_invoice") + def _compute_service_order(self): + for record in self: + record.service_order = ( + record.service_id + if record.service_id + else -1 + if record.display_type + else 0 + ) + + @api.depends("service_order") + def _compute_date_order(self): + for record in self: + if record.display_type: + record.date_order = 0 + elif record.reservation_id and not record.service_id: + record.date_order = ( + min(record.reservation_line_ids.mapped("date")) + if record.reservation_line_ids + else 0 + ) + elif record.reservation_id and record.service_id: + record.date_order = ( + min(record.service_line_ids.mapped("date")) + if record.service_line_ids + else 0 + ) + else: + record.date_order = 0 + + @api.depends("date_order") + def _compute_reservation_order(self): + for record in self: + record.reservation_order = ( + record.reservation_id if record.reservation_id else 0 + ) + + @api.depends("reservation_line_ids", "service_line_ids", "service_line_ids.day_qty") + def _compute_product_uom_qty(self): + for line in self: + if line.reservation_line_ids: + line.product_uom_qty = len(line.reservation_line_ids) + elif line.service_line_ids: + line.product_uom_qty = sum(line.service_line_ids.mapped("day_qty")) + elif not line.product_uom_qty: + line.product_uom_qty = False + + @api.depends("state") + def _compute_product_uom_readonly(self): + for line in self: + line.product_uom_readonly = line.state in ["sale", "done", "cancel"] + + @api.depends( + "invoice_lines", + "invoice_lines.price_total", + "invoice_lines.move_id.state", + "invoice_lines.move_id.move_type", + ) + def _compute_untaxed_amount_invoiced(self): + """Compute the untaxed amount already invoiced from + the sale order line, taking the refund attached + the so line into account. This amount is computed as + SUM(inv_line.price_subtotal) - SUM(ref_line.price_subtotal) + where + `inv_line` is a customer invoice line linked to the SO line + `ref_line` is a customer credit note (refund) line linked to the SO line + """ + for line in self: + amount_invoiced = 0.0 + for invoice_line in line.invoice_lines: + if invoice_line.move_id.state == "posted": + invoice_date = ( + invoice_line.move_id.invoice_date or fields.Date.today() + ) + if invoice_line.move_id.move_type == "out_invoice": + amount_invoiced += invoice_line.currency_id._convert( + invoice_line.price_subtotal, + line.currency_id, + line.company_id, + invoice_date, + ) + elif invoice_line.move_id.move_type == "out_refund": + amount_invoiced -= invoice_line.currency_id._convert( + invoice_line.price_subtotal, + line.currency_id, + line.company_id, + invoice_date, + ) + line.untaxed_amount_invoiced = amount_invoiced + + @api.depends( + "state", + "price_reduce", + "product_id", + "untaxed_amount_invoiced", + "product_uom_qty", + ) + def _compute_untaxed_amount_to_invoice(self): + """Total of remaining amount to invoice on the sale order line (taxes excl.) as + total_sol - amount already invoiced + where Total_sol depends on the invoice policy of the product. + + Note: Draft invoice are ignored on purpose, the 'to invoice' amount should + come only from the SO lines. + """ + for line in self: + amount_to_invoice = 0.0 + if line.state != "draft": + # Note: do not use price_subtotal field as it returns + # zero when the ordered quantity is zero. + # It causes problem for expense line (e.i.: ordered qty = 0, + # deli qty = 4, price_unit = 20 ; subtotal is zero), + # but when you can invoice the line, + # you see an amount and not zero. + # Since we compute untaxed amount, we can use directly the price + # reduce (to include discount) without using `compute_all()` + # method on taxes. + price_subtotal = 0.0 + price_subtotal = line.price_reduce * line.product_uom_qty + if len(line.tax_ids.filtered(lambda tax: tax.price_include)) > 0: + # As included taxes are not excluded from the computed subtotal, + # `compute_all()` method has to be called to retrieve + # the subtotal without them. + # `price_reduce_taxexcl` cannot be used as it is computed from + # `price_subtotal` field. (see upper Note) + price_subtotal = line.tax_ids.compute_all( + price_subtotal, + currency=line.folio_id.currency_id, + quantity=line.product_uom_qty, + product=line.product_id, + partner=line.folio_id.partner_shipping_id, + )["total_excluded"] + + if any( + line.invoice_lines.mapped(lambda l: l.discount != line.discount) + ): + # In case of re-invoicing with different + # discount we try to calculate manually the + # remaining amount to invoice + amount = 0 + for inv_line in line.invoice_lines: + if ( + len( + inv_line.tax_ids.filtered(lambda tax: tax.price_include) + ) + > 0 + ): + amount += inv_line.tax_ids.compute_all( + inv_line.currency_id._convert( + inv_line.price_unit, + line.currency_id, + line.company_id, + inv_line.date or fields.Date.today(), + round=False, + ) + * inv_line.quantity + )["total_excluded"] + else: + amount += ( + inv_line.currency_id._convert( + inv_line.price_unit, + line.currency_id, + line.company_id, + inv_line.date or fields.Date.today(), + round=False, + ) + * inv_line.quantity + ) + + amount_to_invoice = max(price_subtotal - amount, 0) + else: + amount_to_invoice = price_subtotal - line.untaxed_amount_invoiced + + line.untaxed_amount_to_invoice = amount_to_invoice + @api.depends("state", "product_uom_qty", "qty_to_invoice", "qty_invoiced") def _compute_invoice_status(self): """ @@ -55,53 +536,6 @@ class FolioSaleLine(models.Model): record.service_line_ids, ) - @api.model - def generate_folio_sale_name( - self, - reservation_id, - product_id, - service_id, - reservation_line_ids, - service_line_ids, - qty=False, - ): - if reservation_line_ids: - month = False - name = False - lines = reservation_line_ids.sorted(key="date") - for index, date in enumerate(lines.mapped("date")): - if qty and index > (qty - 1): - break - if date.month != month: - name = name + "\n" if name else "" - name += date.strftime("%B-%Y") + ": " - name += date.strftime("%d") - month = date.month - else: - name += ", " + date.strftime("%d") - - return "{} ({}).".format(product_id.name, name) - elif service_line_ids: - month = False - name = False - lines = service_line_ids.filtered( - lambda x: x.service_id == service_id - ).sorted(key="date") - - for index, date in enumerate(lines.mapped("date")): - if qty and index > (ceil(qty / reservation_id.adults) - 1): - break - if date.month != month: - name = name + "\n" if name else "" - name += date.strftime("%B-%Y") + ": " - name += date.strftime("%d") - month = date.month - else: - name += ", " + date.strftime("%d") - return "{} ({}).".format(service_id.name, name) - else: - return service_id.name - @api.depends("product_uom_qty", "discount", "price_unit", "tax_ids") def _compute_amount(self): """ @@ -301,6 +735,89 @@ class FolioSaleLine(models.Model): # product_uom IS NULL AND customer_lead = 0))", # "Forbidden values on non-accountable sale order line"), # ] + @api.model + def _name_search( + self, name, args=None, operator="ilike", limit=100, name_get_uid=None + ): + if operator in ("ilike", "like", "=", "=like", "=ilike"): + args = expression.AND( + [ + args or [], + ["|", ("folio_id.name", operator, name), ("name", operator, name)], + ] + ) + return super(FolioSaleLine, self)._name_search( + name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid + ) + + def name_get(self): + result = [] + for so_line in self.sudo(): + name = "{} - {}".format( + so_line.folio_id.name, + so_line.name and so_line.name.split("\n")[0] or so_line.product_id.name, + ) + result.append((so_line.id, name)) + return result + + @api.model + def generate_folio_sale_name( + self, + reservation_id, + product_id, + service_id, + reservation_line_ids, + service_line_ids, + qty=False, + ): + if reservation_line_ids: + month = False + name = False + lines = reservation_line_ids.sorted(key="date") + for index, date in enumerate(lines.mapped("date")): + if qty and index > (qty - 1): + break + if date.month != month: + name = name + "\n" if name else "" + name += date.strftime("%B-%Y") + ": " + name += date.strftime("%d") + month = date.month + else: + name += ", " + date.strftime("%d") + + return "{} ({}).".format(product_id.name, name) + elif service_line_ids: + month = False + name = False + lines = service_line_ids.filtered( + lambda x: x.service_id == service_id + ).sorted(key="date") + + for index, date in enumerate(lines.mapped("date")): + if qty and index > (ceil(qty / reservation_id.adults) - 1): + break + if date.month != month: + name = name + "\n" if name else "" + name += date.strftime("%B-%Y") + ": " + name += date.strftime("%d") + month = date.month + else: + name += ", " + date.strftime("%d") + return "{} ({}).".format(service_id.name, name) + else: + return service_id.name + + def _get_invoice_line_sequence(self, new=0, old=0): + """ + Method intended to be overridden in third-party + module if we want to prevent the resequencing of invoice lines. + + :param int new: the new line sequence + :param int old: the old line sequence + + :return: the sequence of the SO line, by default the new one. + """ + return new or old def _update_line_quantity(self, values): folios = self.mapped("folio_id") @@ -362,446 +879,7 @@ class FolioSaleLine(models.Model): # ) # result = super(SaleOrderLine, self).write(values) - # return result - - folio_id = fields.Many2one( - "pms.folio", - string="Folio Reference", - required=True, - ondelete="cascade", - index=True, - copy=False, - ) - reservation_id = fields.Many2one( - "pms.reservation", - string="Reservation Reference", - ondelete="cascade", - index=True, - copy=False, - ) - service_id = fields.Many2one( - "pms.service", - string="Service Reference", - ondelete="cascade", - index=True, - copy=False, - ) - is_board_service = fields.Boolean( - string="Board Service", - related="service_id.is_board_service", - store=True, - ) - - name = fields.Text( - string="Description", compute="_compute_name", store=True, readonly=False - ) - reservation_line_ids = fields.Many2many( - "pms.reservation.line", - string="Nights", - ) - service_line_ids = fields.Many2many( - "pms.service.line", - string="Service Lines", - ) - sequence = fields.Integer(string="Sequence", default=10) - - invoice_lines = fields.Many2many( - "account.move.line", - "folio_sale_line_invoice_rel", - "sale_line_id", - "invoice_line_id", - string="Invoice Lines", - copy=False, - ) - invoice_status = fields.Selection( - [ - ("upselling", "Upselling Opportunity"), - ("invoiced", "Fully Invoiced"), - ("to invoice", "To Invoice"), - ("no", "Nothing to Invoice"), - ], - string="Invoice Status", - compute="_compute_invoice_status", - store=True, - readonly=True, - default="no", - ) - price_unit = fields.Float( - "Unit Price", - digits="Product Price", - ) - - price_subtotal = fields.Monetary( - compute="_compute_amount", string="Subtotal", readonly=True, store=True - ) - price_tax = fields.Float( - compute="_compute_amount", string="Total Tax", readonly=True, store=True - ) - price_total = fields.Monetary( - compute="_compute_amount", string="Total", readonly=True, store=True - ) - price_reduce = fields.Float( - compute="_compute_get_price_reduce", - string="Price Reduce", - digits="Product Price", - readonly=True, - store=True, - ) - tax_ids = fields.Many2many( - "account.tax", - compute="_compute_tax_ids", - store=True, - string="Taxes", - domain=["|", ("active", "=", False), ("active", "=", True)], - ) - price_reduce_taxinc = fields.Monetary( - compute="_compute_get_price_reduce_tax", - string="Price Reduce Tax inc", - readonly=True, - store=True, - ) - price_reduce_taxexcl = fields.Monetary( - compute="_compute_get_price_reduce_notax", - string="Price Reduce Tax excl", - readonly=True, - store=True, - ) - - discount = fields.Float( - string="Discount (%)", - digits="Discount", - compute="_compute_discount", - readonly=False, - store=True, - ) - - product_id = fields.Many2one( - "product.product", - string="Product", - domain="[('sale_ok', '=', True),\ - '|', ('company_id', '=', False), \ - ('company_id', '=', company_id)]", - change_default=True, - ondelete="restrict", - check_company=True, - compute="_compute_product_id", - store=True, - ) - # product_updatable = fields.Boolean( - # compute='_compute_product_updatable', - # string='Can Edit Product', - # readonly=True, - # default=True) - product_uom_qty = fields.Float( - string="Quantity", - digits="Product Unit of Measure", - compute="_compute_product_uom_qty", - store=True, - readonly=False, - ) - product_uom = fields.Many2one( - "uom.uom", - string="Unit of Measure", - domain="[('category_id', '=', product_uom_category_id)]", - ) - product_uom_category_id = fields.Many2one( - related="product_id.uom_id.category_id", readonly=True - ) - product_uom_readonly = fields.Boolean(compute="_compute_product_uom_readonly") - product_custom_attribute_value_ids = fields.One2many( - "product.attribute.custom.value", - "sale_order_line_id", - string="Custom Values", - copy=True, - ) - - qty_to_invoice = fields.Float( - compute="_compute_get_to_invoice_qty", - string="To Invoice Quantity", - store=True, - readonly=True, - digits="Product Unit of Measure", - ) - qty_invoiced = fields.Float( - compute="_compute_get_invoice_qty", - string="Invoiced Quantity", - store=True, - readonly=True, - compute_sudo=True, - digits="Product Unit of Measure", - ) - - untaxed_amount_invoiced = fields.Monetary( - "Untaxed Invoiced Amount", - compute="_compute_untaxed_amount_invoiced", - compute_sudo=True, - store=True, - ) - untaxed_amount_to_invoice = fields.Monetary( - "Untaxed Amount To Invoice", - compute="_compute_untaxed_amount_to_invoice", - compute_sudo=True, - store=True, - ) - - currency_id = fields.Many2one( - related="folio_id.currency_id", - depends=["folio_id.currency_id"], - store=True, - string="Currency", - readonly=True, - ) - company_id = fields.Many2one( - related="folio_id.company_id", - string="Company", - store=True, - readonly=True, - index=True, - check_pms_properties=True, - ) - folio_partner_id = fields.Many2one( - related="folio_id.partner_id", store=True, string="Customer", readonly=False - ) - analytic_tag_ids = fields.Many2many( - "account.analytic.tag", - string="Analytic Tags", - domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", - ) - analytic_line_ids = fields.One2many( - "account.analytic.line", "so_line", string="Analytic lines" - ) - is_downpayment = fields.Boolean( - string="Is a down payment", - help="Down payments are made when creating invoices from a folio." - " They are not copied when duplicating a folio.", - ) - - state = fields.Selection( - related="folio_id.state", - string="Folio Status", - readonly=True, - copy=False, - store=True, - ) - - display_type = fields.Selection( - [("line_section", "Section"), ("line_note", "Note")], - default=False, - help="Technical field for UX purpose.", - ) - - service_order = fields.Integer( - string="Service id", - compute="_compute_service_order", - help="Field to order by service id", - store=True, - readonly=True, - ) - - reservation_order = fields.Integer( - string="Reservation id", - compute="_compute_reservation_order", - help="Field to order by reservation id", - store=True, - readonly=True, - ) - - date_order = fields.Date( - string="Date", - compute="_compute_date_order", - help="Field to order by service date", - store=True, - readonly=True, - ) - - @api.depends("qty_to_invoice") - def _compute_service_order(self): - for record in self: - record.service_order = ( - record.service_id - if record.service_id - else -1 - if record.display_type - else 0 - ) - - @api.depends("service_order") - def _compute_date_order(self): - for record in self: - if record.display_type: - record.date_order = 0 - elif record.reservation_id and not record.service_id: - record.date_order = ( - min(record.reservation_line_ids.mapped("date")) - if record.reservation_line_ids - else 0 - ) - elif record.reservation_id and record.service_id: - record.date_order = ( - min(record.service_line_ids.mapped("date")) - if record.service_line_ids - else 0 - ) - else: - record.date_order = 0 - - @api.depends("date_order") - def _compute_reservation_order(self): - for record in self: - record.reservation_order = ( - record.reservation_id if record.reservation_id else 0 - ) - - @api.depends("reservation_line_ids", "service_line_ids", "service_line_ids.day_qty") - def _compute_product_uom_qty(self): - for line in self: - if line.reservation_line_ids: - line.product_uom_qty = len(line.reservation_line_ids) - elif line.service_line_ids: - line.product_uom_qty = sum(line.service_line_ids.mapped("day_qty")) - elif not line.product_uom_qty: - line.product_uom_qty = False - - @api.depends("state") - def _compute_product_uom_readonly(self): - for line in self: - line.product_uom_readonly = line.state in ["sale", "done", "cancel"] - - @api.depends( - "invoice_lines", - "invoice_lines.price_total", - "invoice_lines.move_id.state", - "invoice_lines.move_id.move_type", - ) - def _compute_untaxed_amount_invoiced(self): - """Compute the untaxed amount already invoiced from - the sale order line, taking the refund attached - the so line into account. This amount is computed as - SUM(inv_line.price_subtotal) - SUM(ref_line.price_subtotal) - where - `inv_line` is a customer invoice line linked to the SO line - `ref_line` is a customer credit note (refund) line linked to the SO line - """ - for line in self: - amount_invoiced = 0.0 - for invoice_line in line.invoice_lines: - if invoice_line.move_id.state == "posted": - invoice_date = ( - invoice_line.move_id.invoice_date or fields.Date.today() - ) - if invoice_line.move_id.move_type == "out_invoice": - amount_invoiced += invoice_line.currency_id._convert( - invoice_line.price_subtotal, - line.currency_id, - line.company_id, - invoice_date, - ) - elif invoice_line.move_id.move_type == "out_refund": - amount_invoiced -= invoice_line.currency_id._convert( - invoice_line.price_subtotal, - line.currency_id, - line.company_id, - invoice_date, - ) - line.untaxed_amount_invoiced = amount_invoiced - - @api.depends( - "state", - "price_reduce", - "product_id", - "untaxed_amount_invoiced", - "product_uom_qty", - ) - def _compute_untaxed_amount_to_invoice(self): - """Total of remaining amount to invoice on the sale order line (taxes excl.) as - total_sol - amount already invoiced - where Total_sol depends on the invoice policy of the product. - - Note: Draft invoice are ignored on purpose, the 'to invoice' amount should - come only from the SO lines. - """ - for line in self: - amount_to_invoice = 0.0 - if line.state != "draft": - # Note: do not use price_subtotal field as it returns - # zero when the ordered quantity is zero. - # It causes problem for expense line (e.i.: ordered qty = 0, - # deli qty = 4, price_unit = 20 ; subtotal is zero), - # but when you can invoice the line, - # you see an amount and not zero. - # Since we compute untaxed amount, we can use directly the price - # reduce (to include discount) without using `compute_all()` - # method on taxes. - price_subtotal = 0.0 - price_subtotal = line.price_reduce * line.product_uom_qty - if len(line.tax_ids.filtered(lambda tax: tax.price_include)) > 0: - # As included taxes are not excluded from the computed subtotal, - # `compute_all()` method has to be called to retrieve - # the subtotal without them. - # `price_reduce_taxexcl` cannot be used as it is computed from - # `price_subtotal` field. (see upper Note) - price_subtotal = line.tax_ids.compute_all( - price_subtotal, - currency=line.folio_id.currency_id, - quantity=line.product_uom_qty, - product=line.product_id, - partner=line.folio_id.partner_shipping_id, - )["total_excluded"] - - if any( - line.invoice_lines.mapped(lambda l: l.discount != line.discount) - ): - # In case of re-invoicing with different - # discount we try to calculate manually the - # remaining amount to invoice - amount = 0 - for inv_line in line.invoice_lines: - if ( - len( - inv_line.tax_ids.filtered(lambda tax: tax.price_include) - ) - > 0 - ): - amount += inv_line.tax_ids.compute_all( - inv_line.currency_id._convert( - inv_line.price_unit, - line.currency_id, - line.company_id, - inv_line.date or fields.Date.today(), - round=False, - ) - * inv_line.quantity - )["total_excluded"] - else: - amount += ( - inv_line.currency_id._convert( - inv_line.price_unit, - line.currency_id, - line.company_id, - inv_line.date or fields.Date.today(), - round=False, - ) - * inv_line.quantity - ) - - amount_to_invoice = max(price_subtotal - amount, 0) - else: - amount_to_invoice = price_subtotal - line.untaxed_amount_invoiced - - line.untaxed_amount_to_invoice = amount_to_invoice - - def _get_invoice_line_sequence(self, new=0, old=0): - """ - Method intended to be overridden in third-party - module if we want to prevent the resequencing of invoice lines. - - :param int new: the new line sequence - :param int old: the old line sequence - - :return: the sequence of the SO line, by default the new one. - """ - return new or old - + # return resul def _prepare_invoice_line(self, qty=False, **optional_values): def _prepare_invoice_line(self, qty=False, **optional_values): """ Prepare the dict of values to create the new invoice line for a folio sale line. @@ -838,31 +916,6 @@ class FolioSaleLine(models.Model): res["account_id"] = False return res - def name_get(self): - result = [] - for so_line in self.sudo(): - name = "{} - {}".format( - so_line.folio_id.name, - so_line.name and so_line.name.split("\n")[0] or so_line.product_id.name, - ) - result.append((so_line.id, name)) - return result - - @api.model - def _name_search( - self, name, args=None, operator="ilike", limit=100, name_get_uid=None - ): - if operator in ("ilike", "like", "=", "=like", "=ilike"): - args = expression.AND( - [ - args or [], - ["|", ("folio_id.name", operator, name), ("name", operator, name)], - ] - ) - return super(FolioSaleLine, self)._name_search( - name, args=args, operator=operator, limit=limit, name_get_uid=name_get_uid - ) - def _check_line_unlink(self): """ Check wether a line can be deleted or not. diff --git a/pms/models/payment_return.py b/pms/models/payment_return.py index 17f7d4d45..f3fd43634 100644 --- a/pms/models/payment_return.py +++ b/pms/models/payment_return.py @@ -7,16 +7,23 @@ class PaymentReturn(models.Model): _inherit = "payment.return" # Fields declaration - folio_id = fields.Many2one("pms.folio", string="Folio") + folio_id = fields.Many2one( + string="Folio", help="Folio in payment return", comodel_name="pms.folio" + ) pms_property_id = fields.Many2one( - "pms.property", store=True, readonly=True, related="folio_id.pms_property_id" + string="Property", + help="Property with access to the element", + store=True, + readonly=True, + comodel_name="pms.property", + related="folio_id.pms_property_id", ) company_id = fields.Many2one( + string="Company", + help="The company for Payment Return", check_pms_properties=True, ) - # Business methods - def action_confirm(self): pay = super(PaymentReturn, self).action_confirm() if pay: diff --git a/pms/models/pms_cancelation_rule.py b/pms/models/pms_cancelation_rule.py index fc1c559e8..6e5bfd776 100644 --- a/pms/models/pms_cancelation_rule.py +++ b/pms/models/pms_cancelation_rule.py @@ -4,18 +4,21 @@ from odoo import fields, models -# TODO: refactoring to cancellation.rule class PmsCancelationRule(models.Model): _name = "pms.cancelation.rule" _description = "Cancelation Rules" _check_pms_properties_auto = True - # Fields declaration - name = fields.Char(string="Cancelation Rule", translate=True, required=True) + name = fields.Char( + string="Cancelation Rule", + required=True, + translate=True, + ) pricelist_ids = fields.One2many( - "product.pricelist", - "cancelation_rule_id", - "Pricelist that use this rule", + string="Pricelist", + help="Pricelist that use this rule", + comodel_name="product.pricelist", + inverse_name="cancelation_rule_id", check_pms_properties=True, ) pms_property_ids = fields.Many2many( @@ -23,28 +26,63 @@ class PmsCancelationRule(models.Model): help="Properties with access to the element;" " if not set, all properties can access", required=False, - ondelete="restrict", comodel_name="pms.property", relation="pms_cancelation_rule_pms_property_rel", column1="pms_cancelation_rule_id", column2="pms_property_id", + ondelete="restrict", check_pms_properties=True, ) - active = fields.Boolean("Active", default=True) + active = fields.Boolean( + string="Active", help="Determines if cancelation rule is active", default=True + ) days_intime = fields.Integer( - "Days Late", help="Maximum number of days for free cancellation before Checkin" + string="Days Late", + help="Maximum number of days for free cancellation before Checkin", + ) + penalty_late = fields.Integer( + string="% Penalty Late", + help="Percentage of the total price that partner has " + "to pay in case of late arrival", + default="100", ) - penalty_late = fields.Integer("% Penalty Late", default="100") apply_on_late = fields.Selection( - [("first", "First Day"), ("all", "All Days"), ("days", "Specify days")], - "Late apply on", + string="Late apply on", + help="Days on which the cancelation rule applies when " + "the reason is late arrival. " + "Can be first, all days or specify the days.", default="first", + selection=[ + ("first", "First Day"), + ("all", "All Days"), + ("days", "Specify days"), + ], + ) + days_late = fields.Integer( + string="Late first days", + help="Is number of days late in the cancelation rule " + "if the value of the apply_on_late field is specify days.", + default="2", + ) + penalty_noshow = fields.Integer( + string="% Penalty No Show", + help="Percentage of the total price that partner has to pay in case of no show", + default="100", ) - days_late = fields.Integer("Late first days", default="2") - penalty_noshow = fields.Integer("% Penalty No Show", default="100") apply_on_noshow = fields.Selection( - [("first", "First Day"), ("all", "All Days"), ("days", "Specify days")], - "No Show apply on", + string="No Show apply on", + help="Days on which the cancelation rule applies when" + " the reason is no show. Can be first, all days or specify the days.", + selection=[ + ("first", "First Day"), + ("all", "All Days"), + ("days", "Specify days"), + ], default="all", ) - days_noshow = fields.Integer("NoShow first days", default="2") + days_noshow = fields.Integer( + string="NoShow first days", + help="Is number of days no show in the cancelation rule " + "if the value of the apply_on_show field is specify days.", + default="2", + ) diff --git a/pms/models/pms_checkin_partner.py b/pms/models/pms_checkin_partner.py index 09baf4009..66e5f2139 100644 --- a/pms/models/pms_checkin_partner.py +++ b/pms/models/pms_checkin_partner.py @@ -15,63 +15,96 @@ class PmsCheckinPartner(models.Model): _rec_name = "identifier" _check_pms_properties_auto = True - # Fields declaration identifier = fields.Char( - "Identifier", readonly=True, index=True, default=lambda self: _("New") + string="Identifier", + help="Checkin Partner Id", + readonly=True, + index=True, + default=lambda self: _("New"), ) partner_id = fields.Many2one( - "res.partner", + string="Partner", + help="Partner associated with checkin partner", + comodel_name="res.partner", domain="[('is_company', '=', False)]", ) reservation_id = fields.Many2one( - "pms.reservation", + string="Reservation", + help="Reservation to which checkin partners belong", + comodel_name="pms.reservation", check_pms_properties=True, ) folio_id = fields.Many2one( - "pms.folio", - compute="_compute_folio_id", + string="Folio", + help="Folio to which reservation of checkin partner belongs", store=True, + comodel_name="pms.folio", + compute="_compute_folio_id", check_pms_properties=True, ) pms_property_id = fields.Many2one( - "pms.property", store=True, readonly=True, related="folio_id.pms_property_id" + string="Property", + help="Property to which the folio associated belongs", + readonly=True, + store=True, + comodel_name="pms.property", + related="folio_id.pms_property_id", ) name = fields.Char( - "Name", - compute="_compute_name", - store=True, + string="Name", + help="Checkin partner name", readonly=False, + store=True, + compute="_compute_name", ) email = fields.Char( - "E-mail", - compute="_compute_email", - store=True, + string="E-mail", + help="Checkin Partner Email", readonly=False, + store=True, + compute="_compute_email", ) mobile = fields.Char( - "Mobile", + string="Mobile", + help="Checkin Partner Mobile", compute="_compute_mobile", store=True, readonly=False, ) image_128 = fields.Image( + string="Image", + help="Checkin Partner Image, it corresponds with Partner Image associated", related="partner_id.image_128", ) segmentation_ids = fields.Many2many( + string="Segmentation", + help="Segmentation tags to classify checkin partners", related="reservation_id.segmentation_ids", readonly=True, ) checkin = fields.Date( - related="reservation_id.checkin", store=True, depends=["reservation_id.checkin"] + string="Checkin", + help="Checkin date", + store=True, + related="reservation_id.checkin", + depends=["reservation_id.checkin"], ) checkout = fields.Date( - related="reservation_id.checkout", + string="Checkout", + help="Checkout date", store=True, + related="reservation_id.checkout", depends=["reservation_id.checkout"], ) - arrival = fields.Datetime("Enter") - departure = fields.Datetime("Exit") + arrival = fields.Datetime("Enter", help="Checkin partner arrival date and time") + departure = fields.Datetime( + string="Exit", help="Checkin partner departure date and time" + ) state = fields.Selection( + string="State", + help="Status of the checkin partner regarding the reservation", + readonly=True, + store=True, selection=[ ("draft", "Unkown Guest"), ("precheckin", "Pending arrival"), @@ -79,10 +112,7 @@ class PmsCheckinPartner(models.Model): ("done", "Out"), ("cancelled", "Cancelled"), ], - string="State", compute="_compute_state", - store=True, - readonly=True, ) # Compute @@ -147,22 +177,6 @@ class PmsCheckinPartner(models.Model): if not record.mobile: record.mobile = record.partner_id.mobile - @api.model - def _checkin_mandatory_fields(self, depends=False): - # api.depends need "reservation_id.state" in the lambda function - if depends: - return ["reservation_id.state", "name"] - return ["name"] - - @api.model - def _checkin_partner_fields(self): - # api.depends need "reservation_id.state" in the lambda function - checkin_fields = self._checkin_mandatory_fields() - checkin_fields.extend(["mobile", "email"]) - return checkin_fields - - # Constraints and onchanges - @api.constrains("departure", "arrival") def _check_departure(self): for record in self: @@ -209,7 +223,6 @@ class PmsCheckinPartner(models.Model): ): raise ValidationError(_("'%s' is not a valid phone", record.mobile)) - # CRUD @api.model def create(self, vals): # The checkin records are created automatically from adult depends @@ -284,6 +297,41 @@ class PmsCheckinPartner(models.Model): reservations._compute_checkin_partner_ids() return res + @api.model + def _checkin_mandatory_fields(self, depends=False): + # api.depends need "reservation_id.state" in the lambda function + if depends: + return ["reservation_id.state", "name"] + return ["name"] + + @api.model + def _checkin_partner_fields(self): + # api.depends need "reservation_id.state" in the lambda function + checkin_fields = self._checkin_mandatory_fields() + checkin_fields.extend(["mobile", "email"]) + return checkin_fields + + @api.model + def import_room_list_json(self, roomlist_json): + roomlist_json = json.loads(roomlist_json) + for checkin_dict in roomlist_json: + identifier = checkin_dict["identifier"] + reservation_id = checkin_dict["reservation_id"] + checkin = self.env["pms.checkin.partner"].search( + [("identifier", "=", identifier)] + ) + reservation = self.env["pms.reservation"].browse(reservation_id) + if not checkin: + raise ValidationError( + _("%s not found in checkins (%s)"), identifier, reservation.name + ) + checkin_vals = {} + for key, value in checkin_dict.items(): + if key in ("reservation_id", "folio_id", "identifier"): + continue + checkin_vals[key] = value + checkin.write(checkin_vals) + def action_on_board(self): for record in self: if record.reservation_id.checkin > fields.Date.today(): @@ -310,24 +358,3 @@ class PmsCheckinPartner(models.Model): } record.update(vals) return True - - @api.model - def import_room_list_json(self, roomlist_json): - roomlist_json = json.loads(roomlist_json) - for checkin_dict in roomlist_json: - identifier = checkin_dict["identifier"] - reservation_id = checkin_dict["reservation_id"] - checkin = self.env["pms.checkin.partner"].search( - [("identifier", "=", identifier)] - ) - reservation = self.env["pms.reservation"].browse(reservation_id) - if not checkin: - raise ValidationError( - _("%s not found in checkins (%s)"), identifier, reservation.name - ) - checkin_vals = {} - for key, value in checkin_dict.items(): - if key in ("reservation_id", "folio_id", "identifier"): - continue - checkin_vals[key] = value - checkin.write(checkin_vals) diff --git a/pms/models/pms_folio.py b/pms/models/pms_folio.py index d2696f839..8c25b74ae 100644 --- a/pms/models/pms_folio.py +++ b/pms/models/pms_folio.py @@ -20,7 +20,417 @@ class PmsFolio(models.Model): _check_company_auto = True _check_pms_properties_auto = True - # Default Methods ang Gets + name = fields.Char( + string="Folio Number", + help="Folio name. When creating a folio the " + "name is automatically formed with a sequence", + readonly=True, + index=True, + default=lambda self: _("New"), + ) + pms_property_id = fields.Many2one( + string="Property", + help="The property for folios", + comodel_name="pms.property", + required=True, + default=lambda self: self.env.user.get_active_property_ids()[0], + ) + partner_id = fields.Many2one( + string="Partner", + help="The folio customer", + readonly=False, + store=True, + tracking=True, + compute="_compute_partner_id", + comodel_name="res.partner", + ondelete="restrict", + check_pms_properties=True, + ) + reservation_ids = fields.One2many( + string="Reservations", + help="Room reservation detail", + readonly=False, + states={"done": [("readonly", True)]}, + comodel_name="pms.reservation", + inverse_name="folio_id", + check_company=True, + check_pms_properties=True, + ) + number_of_rooms = fields.Integer( + string="Number of Rooms", + help="Number of rooms in folio. Canceled rooms do not count.", + store="True", + compute="_compute_number_of_rooms", + ) + number_of_services = fields.Integer( + string="Number of Services", + help="Number of services in the folio", + store="True", + compute="_compute_number_of_services", + ) + service_ids = fields.One2many( + string="Service", + help="Services detail provide to customer and it will " + "include in main Invoice.", + readonly=False, + states={"done": [("readonly", True)]}, + comodel_name="pms.service", + inverse_name="folio_id", + check_company=True, + check_pms_properties=True, + ) + sale_line_ids = fields.One2many( + string="Sale lines", + help="Sale lines in folio. It correspond with reservation nights", + store="True", + compute="_compute_sale_line_ids", + compute_sudo=True, + comodel_name="folio.sale.line", + inverse_name="folio_id", + ) + invoice_count = fields.Integer( + string="Invoice Count", + help="The amount of invoices in out invoice and out refund status", + readonly=True, + compute="_compute_get_invoiced", + ) + company_id = fields.Many2one( + string="Company", + help="The company for folio", + store=True, + comodel_name="res.company", + compute="_compute_company_id", + ) + move_line_ids = fields.Many2many( + string="Payments", + help="Folio payments", + readonly=True, + comodel_name="account.move.line", + relation="payment_folio_rel", + column1="folio_id", + column2="move_id", + ) + analytic_account_id = fields.Many2one( + string="Analytic Account", + help="The analytic account related to a folio.", + readonly=True, + states={"draft": [("readonly", False)], "sent": [("readonly", False)]}, + copy=False, + comodel_name="account.analytic.account", + ) + currency_id = fields.Many2one( + string="Currency", + help="The currency of the property location", + readonly=True, + required=True, + related="pricelist_id.currency_id", + ondelete="restrict", + ) + pricelist_id = fields.Many2one( + string="Pricelist", + help="Pricelist for current folio.", + readonly=False, + store=True, + comodel_name="product.pricelist", + ondelete="restrict", + check_pms_properties=True, + compute="_compute_pricelist_id", + ) + commission = fields.Float( + string="Commission", + readonly=True, + default=0, + store=True, + compute="_compute_commission", + ) + user_id = fields.Many2one( + string="Salesperson", + help="The user who created the folio", + readonly=False, + index=True, + store=True, + comodel_name="res.users", + ondelete="restrict", + compute="_compute_user_id", + tracking=True, + ) + agency_id = fields.Many2one( + string="Agency", + help="Only allowed if the field of partner is_agency is True", + comodel_name="res.partner", + domain=[("is_agency", "=", True)], + ondelete="restrict", + check_pms_properties=True, + ) + channel_type_id = fields.Many2one( + string="Direct Sale Channel", + help="Only allowed if the field of sale channel channel_type is 'direct'", + readonly=False, + store=True, + comodel_name="pms.sale.channel", + domain=[("channel_type", "=", "direct")], + ondelete="restrict", + compute="_compute_channel_type_id", + check_pms_properties=True, + ) + transaction_ids = fields.Many2many( + string="Transactions", + readonly=True, + copy=False, + comodel_name="payment.transaction", + relation="folio_transaction_rel", + column1="folio_id", + column2="transaction_id", + ) + payment_term_id = fields.Many2one( + string="Payment Terms", + help="Pricelist for current folio.", + readonly=False, + store=True, + comodel_name="account.payment.term", + ondelete="restrict", + compute="_compute_payment_term_id", + ) + checkin_partner_ids = fields.One2many( + string="Checkin Partners", + help="The checkin partners on a folio", + comodel_name="pms.checkin.partner", + inverse_name="folio_id", + ) + count_rooms_pending_arrival = fields.Integer( + string="Pending Arrival", + help="The number of rooms left to occupy.", + store=True, + compute="_compute_count_rooms_pending_arrival", + ) + checkins_ratio = fields.Integer( + string="Pending Arrival Ratio", + compute="_compute_checkins_ratio", + ) + pending_checkin_data = fields.Integer( + string="Checkin Data", + compute="_compute_pending_checkin_data", + store=True, + ) + ratio_checkin_data = fields.Integer( + string="Pending Checkin Data", + help="Field that stores the number of checkin partners pending " + "to checkin (with the state = draft)", + compute="_compute_ratio_checkin_data", + ) + move_ids = fields.Many2many( + string="Invoices", + help="Folio invoices related to account move.", + readonly=True, + copy=False, + comodel_name="account.move", + compute="_compute_get_invoiced", + search="_search_invoice_ids", + ) + payment_state = fields.Selection( + string="Payment Status", + help="The state of the payment", + copy=False, + readonly=True, + store=True, + selection=[ + ("not_paid", "Not Paid"), + ("paid", "Paid"), + ("partial", "Partially Paid"), + ], + compute="_compute_amount", + tracking=True, + ) + partner_invoice_ids = fields.Many2many( + string="Billing addresses", + help="Invoice address for current group.", + readonly=False, + store=True, + comodel_name="res.partner", + relation="pms_folio_partner_rel", + column1="folio", + column2="partner", + compute="_compute_partner_invoice_ids", + check_pms_properties=True, + ) + # REVIEW THIS + # partner_invoice_state_id = fields.Many2one(related="partner_invoice_id.state_id") + # partner_invoice_country_id = fields.Many2one( + # related="partner_invoice_id.country_id" + # ) + fiscal_position_id = fields.Many2one( + string="Fiscal Position", + help="The fiscal position depends on the location of the client", + comodel_name="account.fiscal.position", + ) + closure_reason_id = fields.Many2one( + string="Closure Reason", + help="The closure reason for a closure room", + comodel_name="room.closure.reason", + check_pms_properties=True, + ) + segmentation_ids = fields.Many2many( + string="Segmentation", + help="Segmentation tags to classify folios", + 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. " + "Can be 'Normal', 'Staff' or 'Out of Service'", + default=lambda *a: "normal", + selection=[("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")], + ) + date_order = fields.Datetime( + string="Order Date", + help="Date on which folio is sold", + readonly=True, + required=True, + index=True, + default=fields.Datetime.now, + states={"draft": [("readonly", False)], "sent": [("readonly", False)]}, + copy=False, + ) + confirmation_date = fields.Datetime( + string="Confirmation Date", + help="Date on which the folio is confirmed.", + readonly=True, + index=True, + copy=False, + ) + state = fields.Selection( + string="Status", + help="Folio status; it can be Quotation, " + "Quotation Sent, Confirmed, Locked or Cancelled", + readonly=True, + index=True, + default="draft", + copy=False, + selection=[ + ("draft", "Quotation"), + ("sent", "Quotation Sent"), + ("confirm", "Confirmed"), + ("done", "Locked"), + ("cancel", "Cancelled"), + ], + tracking=True, + ) + email = fields.Char( + string="E-mail", help="Partner Email", related="partner_id.email" + ) + mobile = fields.Char( + string="Mobile", help="Partner Mobile", related="partner_id.mobile" + ) + phone = fields.Char( + string="Phone", help="Partner Phone", related="partner_id.phone" + ) + partner_internal_comment = fields.Text( + string="Internal Partner Notes", + help="Internal notes of the partner", + related="partner_id.comment", + ) + credit_card_details = fields.Text( + string="Credit Card Details", + help="Details of partner credit card", + ) + + pending_amount = fields.Monetary( + string="Pending Amount", + help="The amount that remains to be paid", + store=True, + compute="_compute_amount", + ) + # refund_amount = fields.Monetary( + # compute="_compute_amount", store=True, string="Payment Returns" + # ) + invoices_paid = fields.Monetary( + string="Paid Out", + help="Amount of invoices paid", + store=True, + compute="_compute_amount", + tracking=True, + ) + amount_untaxed = fields.Monetary( + string="Untaxed Amount", + help="The price without taxes on a folio", + readonly=True, + store=True, + compute="_compute_amount_all", + tracking=True, + ) + amount_tax = fields.Monetary( + string="Taxes", + help="Price with taxes on a folio", + readonly=True, + store=True, + compute="_compute_amount_all", + ) + amount_total = fields.Monetary( + string="Total", + help="Total amount to be paid", + readonly=True, + store=True, + compute="_compute_amount_all", + tracking=True, + ) + reservation_pending_arrival_ids = fields.One2many( + string="Pending Arrival Rooms", + comodel_name="pms.checkin.partner", + compute="_compute_reservations_pending_arrival", + ) + reservations_pending_count = fields.Integer( + compute="_compute_reservations_pending_arrival" + ) + max_reservation_prior = fields.Integer( + string="Max reservation priority on the entire folio", + help="Max reservation priority on the entire folio", + compute="_compute_max_reservation_prior", + ) + invoice_status = fields.Selection( + string="Invoice Status", + help="Invoice Status; it can be: upselling, invoiced, to invoice, no", + readonly=True, + default="no", + store=True, + selection=[ + ("invoiced", "Fully Invoiced"), + ("to invoice", "To Invoice"), + ("no", "Nothing to Invoice"), + ], + compute="_compute_get_invoice_status", + compute_sudo=True, + ) + internal_comment = fields.Text( + string="Internal Folio Notes", + help="Internal Folio notes for Staff", + ) + cancelled_reason = fields.Text( + string="Cause of cancelled", + help="Indicates cause of cancelled", + ) + prepaid_warning_days = fields.Integer( + string="Prepaid Warning Days", + help="Margin in days to create a notice if a payment \ + advance has not been recorded", + ) + sequence = fields.Integer( + string="Sequence", + help="Sequence used to form the name of the folio", + default=10, + ) + note = fields.Text( + string="Terms and conditions", + help="Folio billing terms and conditions", + default=lambda self: self._default_note(), + ) + reference = fields.Char( + string="Payment Ref.", + help="The payment communication of this sale order.", + copy=False, + ) + def name_get(self): result = [] for folio in self: @@ -39,342 +449,109 @@ class PmsFolio(models.Model): or "" ) - # Fields declaration - name = fields.Char( - string="Folio Number", readonly=True, index=True, default=lambda self: _("New") - ) - pms_property_id = fields.Many2one( - "pms.property", - default=lambda self: self.env.user.get_active_property_ids()[0], - required=True, - ) - partner_id = fields.Many2one( - "res.partner", - compute="_compute_partner_id", - tracking=True, - ondelete="restrict", - store=True, - readonly=False, - check_pms_properties=True, - ) - reservation_ids = fields.One2many( - "pms.reservation", - "folio_id", - readonly=False, - states={"done": [("readonly", True)]}, - help="Room reservation detail.", - check_company=True, - check_pms_properties=True, - ) - number_of_rooms = fields.Integer( - "Number of Rooms", - compute="_compute_number_of_rooms", - store="True", - ) - number_of_services = fields.Integer( - "Number of Services", - compute="_compute_number_of_services", - store="True", - ) - service_ids = fields.One2many( - "pms.service", - "folio_id", - readonly=False, - states={"done": [("readonly", True)]}, - help="Services detail provide to customer and it will " - "include in main Invoice.", - check_company=True, - check_pms_properties=True, - ) - sale_line_ids = fields.One2many( - "folio.sale.line", - "folio_id", - compute="_compute_sale_line_ids", - compute_sudo=True, - store="True", - ) - invoice_count = fields.Integer( - string="Invoice Count", compute="_compute_get_invoiced", readonly=True - ) - company_id = fields.Many2one( - "res.company", - "Company", - compute="_compute_company_id", - store=True, - ) - move_line_ids = fields.Many2many( - "account.move.line", - "payment_folio_rel", - "folio_id", - "move_id", - string="Payments", - readonly=True, - ) - analytic_account_id = fields.Many2one( - "account.analytic.account", - "Analytic Account", - readonly=True, - states={"draft": [("readonly", False)], "sent": [("readonly", False)]}, - help="The analytic account related to a folio.", - copy=False, - ) - currency_id = fields.Many2one( - related="pricelist_id.currency_id", - string="Currency", - readonly=True, - required=True, - ondelete="restrict", - ) - pricelist_id = fields.Many2one( - "product.pricelist", - string="Pricelist", - ondelete="restrict", - compute="_compute_pricelist_id", - store=True, - readonly=False, - help="Pricelist for current folio.", - check_pms_properties=True, - ) - commission = fields.Float( - string="Commission", - compute="_compute_commission", - store=True, - readonly=True, - default=0, - ) - user_id = fields.Many2one( - "res.users", - string="Salesperson", - index=True, - ondelete="restrict", - tracking=True, - compute="_compute_user_id", - store=True, - readonly=False, - ) - agency_id = fields.Many2one( - "res.partner", - string="Agency", - ondelete="restrict", - domain=[("is_agency", "=", True)], - check_pms_properties=True, - ) - channel_type_id = fields.Many2one( - "pms.sale.channel", - compute="_compute_channel_type_id", - readonly=False, - store=True, - string="Direct Sale Channel", - ondelete="restrict", - domain=[("channel_type", "=", "direct")], - check_pms_properties=True, - ) - transaction_ids = fields.Many2many( - "payment.transaction", - "folio_transaction_rel", - "folio_id", - "transaction_id", - string="Transactions", - copy=False, - readonly=True, - ) - payment_term_id = fields.Many2one( - "account.payment.term", - string="Payment Terms", - ondelete="restrict", - compute="_compute_payment_term_id", - store=True, - readonly=False, - help="Pricelist for current folio.", - ) - checkin_partner_ids = fields.One2many("pms.checkin.partner", "folio_id") - count_rooms_pending_arrival = fields.Integer( - "Pending Arrival", - compute="_compute_count_rooms_pending_arrival", - store=True, - ) - checkins_ratio = fields.Integer( - string="Pending Arrival Ratio", - compute="_compute_checkins_ratio", - ) - pending_checkin_data = fields.Integer( - "Checkin Data", - compute="_compute_pending_checkin_data", - store=True, - ) - ratio_checkin_data = fields.Integer( - string="Pending Checkin Data", - compute="_compute_ratio_checkin_data", - ) - move_ids = fields.Many2many( - "account.move", - string="Invoices", - compute="_compute_get_invoiced", - search="_search_invoice_ids", - readonly=True, - copy=False, - ) - payment_state = fields.Selection( - selection=[ - ("not_paid", "Not Paid"), - ("paid", "Paid"), - ("partial", "Partially Paid"), - ], - string="Payment Status", - store=True, - readonly=True, - copy=False, - tracking=True, - compute="_compute_amount", - ) - partner_invoice_ids = fields.Many2many( - string="Billing addresses", - comodel_name="res.partner", - relation="pms_folio_partner_rel", - column1="folio", - column2="partner", - compute="_compute_partner_invoice_ids", - store=True, - readonly=False, - help="Invoice address for current group.", - check_pms_properties=True, - ) - # REVIEW THIS - # partner_invoice_state_id = fields.Many2one(related="partner_invoice_id.state_id") - # partner_invoice_country_id = fields.Many2one( - # related="partner_invoice_id.country_id" - # ) - fiscal_position_id = fields.Many2one( - "account.fiscal.position", string="Fiscal Position" - ) - closure_reason_id = fields.Many2one( - "room.closure.reason", - check_pms_properties=True, - ) - segmentation_ids = fields.Many2many( - "res.partner.category", string="Segmentation", ondelete="restrict" - ) - client_order_ref = fields.Char(string="Customer Reference", copy=False) - reservation_type = fields.Selection( - [("normal", "Normal"), ("staff", "Staff"), ("out", "Out of Service")], - string="Type", - default=lambda *a: "normal", - ) - date_order = fields.Datetime( - string="Order Date", - required=True, - readonly=True, - index=True, - states={"draft": [("readonly", False)], "sent": [("readonly", False)]}, - copy=False, - default=fields.Datetime.now, - ) - confirmation_date = fields.Datetime( - string="Confirmation Date", - readonly=True, - index=True, - help="Date on which the folio is confirmed.", - copy=False, - ) - state = fields.Selection( - [ - ("draft", "Quotation"), - ("sent", "Quotation Sent"), - ("confirm", "Confirmed"), - ("done", "Locked"), - ("cancel", "Cancelled"), - ], - string="Status", - readonly=True, - copy=False, - index=True, - tracking=True, - default="draft", - ) - # Partner fields for being used directly in the Folio views--------- - email = fields.Char("E-mail", related="partner_id.email") - mobile = fields.Char("Mobile", related="partner_id.mobile") - phone = fields.Char("Phone", related="partner_id.phone") - partner_internal_comment = fields.Text( - string="Internal Partner Notes", related="partner_id.comment" - ) - # Payment Fields----------------------------------------------------- - credit_card_details = fields.Text("Credit Card Details") + def _get_report_base_filename(self): + self.ensure_one() + return "Folio %s" % self.name - # Amount Fields------------------------------------------------------ - pending_amount = fields.Monetary( - compute="_compute_amount", store=True, string="Pending in Folio" - ) - # refund_amount = fields.Monetary( - # compute="_compute_amount", store=True, string="Payment Returns" - # ) - invoices_paid = fields.Monetary( - compute="_compute_amount", - store=True, - tracking=True, - string="Paid Out", - ) - amount_untaxed = fields.Monetary( - string="Untaxed Amount", - store=True, - readonly=True, - compute="_compute_amount_all", - tracking=True, - ) - amount_tax = fields.Monetary( - string="Taxes", store=True, readonly=True, compute="_compute_amount_all" - ) - amount_total = fields.Monetary( - string="Total", - store=True, - readonly=True, - compute="_compute_amount_all", - tracking=True, - ) - # Checkin Fields----------------------------------------------------- - reservation_pending_arrival_ids = fields.One2many( - comodel_name="pms.checkin.partner", - string="Pending Arrival Rooms", - compute="_compute_reservations_pending_arrival", - ) - reservations_pending_count = fields.Integer( - compute="_compute_reservations_pending_arrival" - ) - max_reservation_prior = fields.Integer( - string="Max reservation priority on the entire folio", - compute="_compute_max_reservation_prior", - ) - # Invoice Fields----------------------------------------------------- - invoice_status = fields.Selection( - [ - ("invoiced", "Fully Invoiced"), - ("to invoice", "To Invoice"), - ("no", "Nothing to Invoice"), - ], - string="Invoice Status", - compute="_compute_get_invoice_status", - store=True, - readonly=True, - default="no", - compute_sudo=True, - ) - # Generic Fields----------------------------------------------------- - internal_comment = fields.Text(string="Internal Folio Notes") - cancelled_reason = fields.Text("Cause of cancelled") - prepaid_warning_days = fields.Integer( - "Prepaid Warning Days", - help="Margin in days to create a notice if a payment \ - advance has not been recorded", - ) - sequence = fields.Integer(string="Sequence", default=10) - note = fields.Text("Terms and conditions", default=_default_note) - reference = fields.Char( - string="Payment Ref.", - copy=False, - help="The payment communication of this sale order.", - ) + def _get_invoice_grouping_keys(self): + return ["company_id", "partner_id", "currency_id"] + + def get_invoice_vals_list( + self, final=False, lines_to_invoice=False, partner_invoice_id=False + ): + precision = self.env["decimal.precision"].precision_get( + "Product Unit of Measure" + ) + 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( + 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) + + # 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 + ) + 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() + + invoice_vals["invoice_line_ids"] = [ + (0, 0, invoice_line_id) for invoice_line_id in invoice_lines_vals + ] + + invoice_vals_list.append(invoice_vals) + return invoice_vals_list + + def _get_tax_amount_by_group(self): + self.ensure_one() + res = {} + for line in self.sale_line_ids: + price_reduce = line.price_total + product = line.product_id + taxes = line.tax_ids.compute_all(price_reduce, quantity=1, product=product)[ + "taxes" + ] + for tax in line.tax_ids: + group = tax.tax_group_id + res.setdefault(group, {"amount": 0.0, "base": 0.0}) + for t in taxes: + if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids: + res[group]["amount"] += t["amount"] + res[group]["base"] += t["base"] + res = sorted(res.items(), key=lambda line: line[0].sequence) + res = [ + (line[0].name, line[1]["amount"], line[1]["base"], len(res)) for line in res + ] + return res - # Compute and Search methods @api.depends("reservation_ids", "reservation_ids.state") def _compute_number_of_rooms(self): for folio in self: @@ -415,191 +592,11 @@ class PmsFolio(models.Model): # FOLIO SERVICES self.generate_folio_services_sale_lines(folio) - @api.model - def generate_reservation_lines_sale_lines(self, folio, reservation): - if not reservation.sale_line_ids.filtered(lambda x: x.name == reservation.name): - reservation.sale_line_ids = [ - ( - 0, - 0, - { - "name": reservation.name, - "display_type": "line_section", - "folio_id": folio.id, - }, - ) - ] - expected_reservation_lines = self.env["pms.reservation.line"].read_group( - [ - ("reservation_id", "=", reservation.id), - ("cancel_discount", "<", 100), - ], - ["price", "discount", "cancel_discount"], - ["price", "discount", "cancel_discount"], - lazy=False, - ) - current_sale_line_ids = reservation.sale_line_ids.filtered( - lambda x: x.reservation_id.id == reservation.id - and not x.display_type - and not x.service_id - ) - - for index, item in enumerate(expected_reservation_lines): - lines_to = self.env["pms.reservation.line"].search(item["__domain"]) - final_discount = self.concat_discounts( - item["discount"], item["cancel_discount"] - ) - - if current_sale_line_ids and index <= (len(current_sale_line_ids) - 1): - current_sale_line_ids[index].price_unit = item["price"] - current_sale_line_ids[index].discount = final_discount - current_sale_line_ids[index].reservation_line_ids = lines_to.ids - else: - new = { - "reservation_id": reservation.id, - "price_unit": item["price"], - "discount": final_discount, - "folio_id": folio.id, - "reservation_line_ids": [(6, 0, lines_to.ids)], - } - reservation.sale_line_ids = [(0, 0, new)] - if len(expected_reservation_lines) < len(current_sale_line_ids): - folio_sale_lines_to_remove = [ - value.id - for index, value in enumerate(current_sale_line_ids) - if index > (len(expected_reservation_lines) - 1) - ] - for fsl in folio_sale_lines_to_remove: - self.env["folio.sale.line"].browse(fsl).unlink() - @api.depends("pms_property_id") def _compute_company_id(self): for record in self: record.company_id = record.pms_property_id.company_id - @api.model - def generate_reservation_services_sale_lines(self, folio, reservation): - for service in reservation.service_ids: - expected_reservation_services = self.env["pms.service.line"].read_group( - [ - ("reservation_id", "=", reservation.id), - ("service_id", "=", service.id), - ("cancel_discount", "<", 100), - ], - ["price_unit", "discount", "cancel_discount"], - ["price_unit", "discount", "cancel_discount"], - lazy=False, - ) - current_sale_service_ids = reservation.sale_line_ids.filtered( - lambda x: x.reservation_id.id == reservation.id - and not x.display_type - and x.service_id.id == service.id - ) - - for index, item in enumerate(expected_reservation_services): - lines_to = self.env["pms.service.line"].search(item["__domain"]) - final_discount = self.concat_discounts( - item["discount"], item["cancel_discount"] - ) - - if current_sale_service_ids and index <= ( - len(current_sale_service_ids) - 1 - ): - current_sale_service_ids[index].price_unit = item["price_unit"] - current_sale_service_ids[index].discount = final_discount - current_sale_service_ids[index].service_line_ids = lines_to.ids - else: - new = { - "service_id": service.id, - "price_unit": item["price_unit"], - "discount": final_discount, - "folio_id": folio.id, - "service_line_ids": [(6, 0, lines_to.ids)], - } - reservation.sale_line_ids = [(0, 0, new)] - if len(expected_reservation_services) < len(current_sale_service_ids): - folio_sale_lines_to_remove = [ - value.id - for index, value in enumerate(current_sale_service_ids) - if index > (len(expected_reservation_services) - 1) - ] - for fsl in folio_sale_lines_to_remove: - self.env["folio.sale.line"].browse(fsl).unlink() - - @api.model - def generate_folio_services_sale_lines(self, folio): - folio_services = folio.service_ids.filtered(lambda x: not x.reservation_id) - if folio_services: - if not folio.sale_line_ids.filtered(lambda x: x.name == _("Others")): - folio.sale_line_ids = [ - ( - 0, - False, - { - "display_type": "line_section", - "name": _("Others"), - }, - ) - ] - for folio_service in folio_services: - expected_folio_services = self.env["pms.service.line"].read_group( - [ - ("service_id.folio_id", "=", folio.id), - ("service_id", "=", folio_service.id), - ("reservation_id", "=", False), - ("cancel_discount", "<", 100), - ], - ["price_unit", "discount", "cancel_discount"], - ["price_unit", "discount", "cancel_discount"], - lazy=False, - ) - current_folio_service_ids = folio.sale_line_ids.filtered( - lambda x: x.service_id.folio_id.id == folio.id - and not x.display_type - and not x.reservation_id - and x.service_id.id == folio_service.id - ) - - for index, item in enumerate(expected_folio_services): - lines_to = self.env["pms.service.line"].search(item["__domain"]) - final_discount = self.concat_discounts( - item["discount"], item["cancel_discount"] - ) - if current_folio_service_ids and index <= ( - len(current_folio_service_ids) - 1 - ): - current_folio_service_ids[index].price_unit = item["price_unit"] - current_folio_service_ids[index].discount = final_discount - current_folio_service_ids[index].service_line_ids = lines_to.ids - else: - new = { - "service_id": folio_service.id, - "price_unit": item["price_unit"], - "discount": final_discount, - "folio_id": folio.id, - "service_line_ids": [(6, 0, lines_to.ids)], - } - folio.sale_line_ids = [(0, 0, new)] - if len(expected_folio_services) < len(current_folio_service_ids): - folio_sale_lines_to_remove = [ - value.id - for index, value in enumerate(current_folio_service_ids) - if index > (len(expected_folio_services) - 1) - ] - for fsl in folio_sale_lines_to_remove: - self.env["folio.sale.line"].browse(fsl).unlink() - else: - to_unlink = folio.sale_line_ids.filtered(lambda x: x.name == _("Others")) - to_unlink.unlink() - - @api.model - def concat_discounts(self, discount, cancel_discount): - discount_factor = 1.0 - for discount in [discount, cancel_discount]: - discount_factor = discount_factor * ((100.0 - discount) / 100.0) - final_discount = 100.0 - (discount_factor * 100.0) - return final_discount - @api.depends("partner_id", "agency_id") def _compute_pricelist_id(self): for folio in self: @@ -687,43 +684,6 @@ class PmsFolio(models.Model): for folio in self: folio.access_url = "/my/folios/%s" % (folio.id) - def preview_folio(self): - self.ensure_one() - return { - "type": "ir.actions.act_url", - "target": "self", - "url": self.get_portal_url(), - } - - def _search_invoice_ids(self, operator, value): - if operator == "in" and value: - self.env.cr.execute( - """ - SELECT array_agg(so.id) - FROM pms_folio so - JOIN folio_sale_line sol ON sol.folio_id = so.id - JOIN folio_sale_line_invoice_rel soli_rel ON \ - soli_rel.sale_line_ids = sol.id - 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.id = ANY(%s) - """, - (list(value),), - ) - so_ids = self.env.cr.fetchone()[0] or [] - return [("id", "in", so_ids)] - return [ - "&", - ( - "sale_line_ids.invoice_lines.move_id.move_type", - "in", - ("out_invoice", "out_refund"), - ), - ("sale_line_ids.invoice_lines.move_id", operator, value), - ] - @api.depends("state", "sale_line_ids.invoice_status") def _compute_get_invoice_status(self): """ @@ -892,10 +852,74 @@ class PmsFolio(models.Model): reservation_priors = record.reservation_ids.mapped("priority") record.max_reservation_prior = max(reservation_priors) - # Action methods - def _get_report_base_filename(self): - self.ensure_one() - return "Folio %s" % self.name + def _compute_checkin_partner_count(self): + for record in self: + if record.reservation_type == "normal" and record.reservation_ids: + filtered_reservs = record.reservation_ids.filtered( + lambda x: x.state != "cancelled" + ) + mapped_checkin_partner = filtered_reservs.mapped( + "checkin_partner_ids.id" + ) + record.checkin_partner_count = len(mapped_checkin_partner) + mapped_checkin_partner_count = filtered_reservs.mapped( + lambda x: (x.adults + x.children) - len(x.checkin_partner_ids) + ) + record.checkin_partner_pending_count = sum(mapped_checkin_partner_count) + + def _search_invoice_ids(self, operator, value): + if operator == "in" and value: + self.env.cr.execute( + """ + SELECT array_agg(so.id) + FROM pms_folio so + JOIN folio_sale_line sol ON sol.folio_id = so.id + JOIN folio_sale_line_invoice_rel soli_rel ON \ + soli_rel.sale_line_ids = sol.id + 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.id = ANY(%s) + """, + (list(value),), + ) + so_ids = self.env.cr.fetchone()[0] or [] + return [("id", "in", so_ids)] + return [ + "&", + ( + "sale_line_ids.invoice_lines.move_id.move_type", + "in", + ("out_invoice", "out_refund"), + ), + ("sale_line_ids.invoice_lines.move_id", operator, value), + ] + + @api.constrains("agency_id", "channel_type_id") + def _check_only_one_channel(self): + for record in self: + if ( + record.agency_id + and record.channel_type_id.channel_type + != record.agency_id.sale_channel_id.channel_type + ): + raise models.ValidationError( + _("The Sale Channel does not correspond to the agency's") + ) + + @api.model + def create(self, vals): + if vals.get("name", _("New")) == _("New") or "name" not in vals: + pms_property_id = ( + self.env.user.get_active_property_ids()[0] + if "pms_property_id" not in vals + else vals["pms_property_id"] + ) + pms_property = self.env["pms.property"].browse(pms_property_id) + vals["name"] = pms_property.folio_sequence_id._next_do() + result = super(PmsFolio, self).create(vals) + return result def action_pay(self): self.ensure_one() @@ -987,21 +1011,6 @@ class PmsFolio(models.Model): action["domain"] = [("id", "in", reservations.ids)] return action - # ORM Overrides - @api.model - def create(self, vals): - if vals.get("name", _("New")) == _("New") or "name" not in vals: - pms_property_id = ( - self.env.user.get_active_property_ids()[0] - if "pms_property_id" not in vals - else vals["pms_property_id"] - ) - pms_property = self.env["pms.property"].browse(pms_property_id) - vals["name"] = pms_property.folio_sequence_id._next_do() - result = super(PmsFolio, self).create(vals) - return result - - # Business methods def action_done(self): reservation_ids = self.mapped("reservation_ids") for line in reservation_ids: @@ -1039,63 +1048,6 @@ class PmsFolio(models.Model): # CHECKIN/OUT PROCESS - def _compute_checkin_partner_count(self): - for record in self: - if record.reservation_type == "normal" and record.reservation_ids: - filtered_reservs = record.reservation_ids.filtered( - lambda x: x.state != "cancelled" - ) - mapped_checkin_partner = filtered_reservs.mapped( - "checkin_partner_ids.id" - ) - record.checkin_partner_count = len(mapped_checkin_partner) - mapped_checkin_partner_count = filtered_reservs.mapped( - lambda x: (x.adults + x.children) - len(x.checkin_partner_ids) - ) - record.checkin_partner_pending_count = sum(mapped_checkin_partner_count) - - def _prepare_invoice(self, partner_invoice_id=False): - """ - Prepare the dict of values to create the new invoice for a folio. - This method may be overridden to implement custom invoice generation - (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") - ._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) - ) - - invoice_vals = { - "ref": self.client_order_ref or "", - "move_type": "out_invoice", - "narration": self.note, - "currency_id": self.pricelist_id.currency_id.id, - # 'campaign_id': self.campaign_id.id, - # 'medium_id': self.medium_id.id, - # 'source_id': self.source_id.id, - "invoice_user_id": self.user_id and self.user_id.id, - "partner_id": partner_invoice_id - if partner_invoice_id - else self.partner_invoice_ids[0], - "partner_bank_id": self.company_id.partner_id.bank_ids[:1].id, - "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, - } - return invoice_vals - def action_view_invoice(self): invoices = self.mapped("move_ids") action = self.env["ir.actions.actions"]._for_xml_id( @@ -1134,22 +1086,13 @@ class PmsFolio(models.Model): action["context"] = context return action - def _get_invoice_grouping_keys(self): - return ["company_id", "partner_id", "currency_id"] - - @api.model - def _nothing_to_invoice_error(self): - msg = _( - """There is nothing to invoice!\n - Reason(s) of this behavior could be: - - You should deliver your products before invoicing them: Click on the "truck" - icon (top-right of your screen) and follow instructions. - - You should modify the invoicing policy of your product: Open the product, - go to the "Sales tab" and modify invoicing policy from "delivered quantities" - to "ordered quantities". - """ - ) - return UserError(msg) + def preview_folio(self): + self.ensure_one() + return { + "type": "ir.actions.act_url", + "target": "self", + "url": self.get_portal_url(), + } def _create_invoices( self, @@ -1292,136 +1235,47 @@ class PmsFolio(models.Model): ) return moves - def get_invoice_vals_list( - self, final=False, lines_to_invoice=False, partner_invoice_id=False - ): - precision = self.env["decimal.precision"].precision_get( - "Product Unit of Measure" - ) - 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( - 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) - - # 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 - ) - 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() - - invoice_vals["invoice_line_ids"] = [ - (0, 0, invoice_line_id) for invoice_line_id in invoice_lines_vals - ] - - invoice_vals_list.append(invoice_vals) - return invoice_vals_list - - def _get_tax_amount_by_group(self): + def _prepare_invoice(self, partner_invoice_id=False): + """ + Prepare the dict of values to create the new invoice for a folio. + This method may be overridden to implement custom invoice generation + (making sure to call super() to establish a clean extension chain). + """ self.ensure_one() - res = {} - for line in self.sale_line_ids: - price_reduce = line.price_total - product = line.product_id - taxes = line.tax_ids.compute_all(price_reduce, quantity=1, product=product)[ - "taxes" - ] - for tax in line.tax_ids: - group = tax.tax_group_id - res.setdefault(group, {"amount": 0.0, "base": 0.0}) - for t in taxes: - if t["id"] == tax.id or t["id"] in tax.children_tax_ids.ids: - res[group]["amount"] += t["amount"] - res[group]["base"] += t["base"] - res = sorted(res.items(), key=lambda line: line[0].sequence) - res = [ - (line[0].name, line[1]["amount"], line[1]["base"], len(res)) for line in res - ] - return res + journal = ( + self.env["account.move"] + .with_context(default_move_type="out_invoice") + ._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) + ) - # Check that only one sale channel is selected - @api.constrains("agency_id", "channel_type_id") - def _check_only_one_channel(self): - for record in self: - if ( - record.agency_id - and record.channel_type_id.channel_type - != record.agency_id.sale_channel_id.channel_type - ): - raise models.ValidationError( - _("The Sale Channel does not correspond to the agency's") - ) - - @api.model - def _prepare_down_payment_section_line(self, **optional_values): - """ - Prepare the dict of values to create a new down - payment section for a sales order line. - :param optional_values: any parameter that should - be added to the returned down payment section - """ - down_payments_section_line = { - "display_type": "line_section", - "name": _("Down Payments"), - "product_id": False, - "product_uom_id": False, - "quantity": 0, - "discount": 0, - "price_unit": 0, - "account_id": False, + invoice_vals = { + "ref": self.client_order_ref or "", + "move_type": "out_invoice", + "narration": self.note, + "currency_id": self.pricelist_id.currency_id.id, + # 'campaign_id': self.campaign_id.id, + # 'medium_id': self.medium_id.id, + # 'source_id': self.source_id.id, + "invoice_user_id": self.user_id and self.user_id.id, + "partner_id": partner_invoice_id + if partner_invoice_id + else self.partner_invoice_ids[0], + "partner_bank_id": self.company_id.partner_id.bank_ids[:1].id, + "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, } - if optional_values: - down_payments_section_line.update(optional_values) - return down_payments_section_line + return invoice_vals def do_payment( self, @@ -1504,3 +1358,219 @@ class PmsFolio(models.Model): "journal_id": statement.journal_id.id, "counterpart_account_id": receivable_account.id, } + + @api.model + def generate_reservation_lines_sale_lines(self, folio, reservation): + if not reservation.sale_line_ids.filtered(lambda x: x.name == reservation.name): + reservation.sale_line_ids = [ + ( + 0, + 0, + { + "name": reservation.name, + "display_type": "line_section", + "folio_id": folio.id, + }, + ) + ] + expected_reservation_lines = self.env["pms.reservation.line"].read_group( + [ + ("reservation_id", "=", reservation.id), + ("cancel_discount", "<", 100), + ], + ["price", "discount", "cancel_discount"], + ["price", "discount", "cancel_discount"], + lazy=False, + ) + current_sale_line_ids = reservation.sale_line_ids.filtered( + lambda x: x.reservation_id.id == reservation.id + and not x.display_type + and not x.service_id + ) + + for index, item in enumerate(expected_reservation_lines): + lines_to = self.env["pms.reservation.line"].search(item["__domain"]) + final_discount = self.concat_discounts( + item["discount"], item["cancel_discount"] + ) + + if current_sale_line_ids and index <= (len(current_sale_line_ids) - 1): + current_sale_line_ids[index].price_unit = item["price"] + current_sale_line_ids[index].discount = final_discount + current_sale_line_ids[index].reservation_line_ids = lines_to.ids + else: + new = { + "reservation_id": reservation.id, + "price_unit": item["price"], + "discount": final_discount, + "folio_id": folio.id, + "reservation_line_ids": [(6, 0, lines_to.ids)], + } + reservation.sale_line_ids = [(0, 0, new)] + if len(expected_reservation_lines) < len(current_sale_line_ids): + folio_sale_lines_to_remove = [ + value.id + for index, value in enumerate(current_sale_line_ids) + if index > (len(expected_reservation_lines) - 1) + ] + for fsl in folio_sale_lines_to_remove: + self.env["folio.sale.line"].browse(fsl).unlink() + + @api.model + def _prepare_down_payment_section_line(self, **optional_values): + """ + Prepare the dict of values to create a new down + payment section for a sales order line. + :param optional_values: any parameter that should + be added to the returned down payment section + """ + down_payments_section_line = { + "display_type": "line_section", + "name": _("Down Payments"), + "product_id": False, + "product_uom_id": False, + "quantity": 0, + "discount": 0, + "price_unit": 0, + "account_id": False, + } + if optional_values: + down_payments_section_line.update(optional_values) + return down_payments_section_line + + @api.model + def _nothing_to_invoice_error(self): + msg = _( + """There is nothing to invoice!\n + Reason(s) of this behavior could be: + - You should deliver your products before invoicing them: Click on the "truck" + icon (top-right of your screen) and follow instructions. + - You should modify the invoicing policy of your product: Open the product, + go to the "Sales tab" and modify invoicing policy from "delivered quantities" + to "ordered quantities". + """ + ) + return UserError(msg) + + @api.model + def generate_reservation_services_sale_lines(self, folio, reservation): + for service in reservation.service_ids: + expected_reservation_services = self.env["pms.service.line"].read_group( + [ + ("reservation_id", "=", reservation.id), + ("service_id", "=", service.id), + ("cancel_discount", "<", 100), + ], + ["price_unit", "discount", "cancel_discount"], + ["price_unit", "discount", "cancel_discount"], + lazy=False, + ) + current_sale_service_ids = reservation.sale_line_ids.filtered( + lambda x: x.reservation_id.id == reservation.id + and not x.display_type + and x.service_id.id == service.id + ) + + for index, item in enumerate(expected_reservation_services): + lines_to = self.env["pms.service.line"].search(item["__domain"]) + final_discount = self.concat_discounts( + item["discount"], item["cancel_discount"] + ) + + if current_sale_service_ids and index <= ( + len(current_sale_service_ids) - 1 + ): + current_sale_service_ids[index].price_unit = item["price_unit"] + current_sale_service_ids[index].discount = final_discount + current_sale_service_ids[index].service_line_ids = lines_to.ids + else: + new = { + "service_id": service.id, + "price_unit": item["price_unit"], + "discount": final_discount, + "folio_id": folio.id, + "service_line_ids": [(6, 0, lines_to.ids)], + } + reservation.sale_line_ids = [(0, 0, new)] + if len(expected_reservation_services) < len(current_sale_service_ids): + folio_sale_lines_to_remove = [ + value.id + for index, value in enumerate(current_sale_service_ids) + if index > (len(expected_reservation_services) - 1) + ] + for fsl in folio_sale_lines_to_remove: + self.env["folio.sale.line"].browse(fsl).unlink() + + @api.model + def generate_folio_services_sale_lines(self, folio): + folio_services = folio.service_ids.filtered(lambda x: not x.reservation_id) + if folio_services: + if not folio.sale_line_ids.filtered(lambda x: x.name == _("Others")): + folio.sale_line_ids = [ + ( + 0, + False, + { + "display_type": "line_section", + "name": _("Others"), + }, + ) + ] + for folio_service in folio_services: + expected_folio_services = self.env["pms.service.line"].read_group( + [ + ("service_id.folio_id", "=", folio.id), + ("service_id", "=", folio_service.id), + ("reservation_id", "=", False), + ("cancel_discount", "<", 100), + ], + ["price_unit", "discount", "cancel_discount"], + ["price_unit", "discount", "cancel_discount"], + lazy=False, + ) + current_folio_service_ids = folio.sale_line_ids.filtered( + lambda x: x.service_id.folio_id.id == folio.id + and not x.display_type + and not x.reservation_id + and x.service_id.id == folio_service.id + ) + + for index, item in enumerate(expected_folio_services): + lines_to = self.env["pms.service.line"].search(item["__domain"]) + final_discount = self.concat_discounts( + item["discount"], item["cancel_discount"] + ) + if current_folio_service_ids and index <= ( + len(current_folio_service_ids) - 1 + ): + current_folio_service_ids[index].price_unit = item["price_unit"] + current_folio_service_ids[index].discount = final_discount + current_folio_service_ids[index].service_line_ids = lines_to.ids + else: + new = { + "service_id": folio_service.id, + "price_unit": item["price_unit"], + "discount": final_discount, + "folio_id": folio.id, + "service_line_ids": [(6, 0, lines_to.ids)], + } + folio.sale_line_ids = [(0, 0, new)] + if len(expected_folio_services) < len(current_folio_service_ids): + folio_sale_lines_to_remove = [ + value.id + for index, value in enumerate(current_folio_service_ids) + if index > (len(expected_folio_services) - 1) + ] + for fsl in folio_sale_lines_to_remove: + self.env["folio.sale.line"].browse(fsl).unlink() + else: + to_unlink = folio.sale_line_ids.filtered(lambda x: x.name == _("Others")) + to_unlink.unlink() + + @api.model + def concat_discounts(self, discount, cancel_discount): + discount_factor = 1.0 + for discount in [discount, cancel_discount]: + discount_factor = discount_factor * ((100.0 - discount) / 100.0) + final_discount = 100.0 - (discount_factor * 100.0) + return final_discount diff --git a/pms/models/product_pricelist.py b/pms/models/product_pricelist.py index d5858efd2..82a91a3a1 100644 --- a/pms/models/product_pricelist.py +++ b/pms/models/product_pricelist.py @@ -30,58 +30,40 @@ class ProductPricelist(models.Model): check_pms_properties=True, ) company_id = fields.Many2one( + string="Company", + help="Company to which the pricelist belongs", check_pms_properties=True, ) cancelation_rule_id = fields.Many2one( - "pms.cancelation.rule", string="Cancelation Policy", + help="Cancelation Policy included in the room", + comodel_name="pms.cancelation.rule", check_pms_properties=True, ) pricelist_type = fields.Selection( - [("daily", "Daily Plan")], string="Pricelist Type", default="daily" + string="Pricelist Type", + help="Pricelist types, it can be Daily Plan", + default="daily", + selection=[("daily", "Daily Plan")], ) pms_sale_channel_ids = fields.Many2many( - "pms.sale.channel", string="Available Channels", + help="Sale channel for which the pricelist is included", + comodel_name="pms.sale.channel", check_pms_properties=True, ) availability_plan_id = fields.Many2one( - comodel_name="pms.availability.plan", string="Availability Plan", + help="Availability Plan for which the pricelist is included", + comodel_name="pms.availability.plan", ondelete="restrict", check_pms_properties=True, ) - item_ids = fields.One2many(check_pms_properties=True) - - # Constraints and onchanges - # @api.constrains("pricelist_type", "pms_property_ids") - # def _check_pricelist_type_property_ids(self): - # for record in self: - # if record.pricelist_type == "daily" and len(record.pms_property_ids) != 1: - # raise ValidationError( - # _( - # "A daily pricelist is used as a daily Rate Plan " - # "for room types and therefore must be related with " - # "one and only one property." - # ) - # ) - - # if record.pricelist_type == "daily" and len(record.pms_property_ids) == 1: - # pms_property_id = ( - # self.env["pms.property"].search( - # [("default_pricelist_id", "=", record.id)] - # ) - # or None - # ) - # if pms_property_id and pms_property_id != record.pms_property_ids: - # raise ValidationError( - # _("Relationship mismatch.") - # + " " - # + _( - # "This pricelist is used as default in a " - # "different property." - # ) - # ) + item_ids = fields.One2many( + string="Items", + help="Items for which the pricelist is made up", + check_pms_properties=True, + ) def _compute_price_rule_get_items( self, products_qty_partner, date, uom_id, prod_tmpl_ids, prod_ids, categ_ids @@ -91,11 +73,6 @@ class ProductPricelist(models.Model): and self._context["property"] and self._context.get("consumption_date") ): - # board_service_id = self._context.get("board_service") - # on_board_service_bool = True if board_service_id else False - # self.env["product.pricelist.item"].flush( - # ["price", "currency_id", "company_id"] - # ) self.env.cr.execute( """ SELECT item.id @@ -159,7 +136,36 @@ class ProductPricelist(models.Model): ) return items - # Action methods + # Constraints and onchanges + # @api.constrains("pricelist_type", "pms_property_ids") + # def _check_pricelist_type_property_ids(self): + # for record in self: + # if record.pricelist_type == "daily" and len(record.pms_property_ids) != 1: + # raise ValidationError( + # _( + # "A daily pricelist is used as a daily Rate Plan " + # "for room types and therefore must be related with " + # "one and only one property." + # ) + # ) + + # if record.pricelist_type == "daily" and len(record.pms_property_ids) == 1: + # pms_property_id = ( + # self.env["pms.property"].search( + # [("default_pricelist_id", "=", record.id)] + # ) + # or None + # ) + # if pms_property_id and pms_property_id != record.pms_property_ids: + # raise ValidationError( + # _("Relationship mismatch.") + # + " " + # + _( + # "This pricelist is used as default in a " + # "different property." + # ) + # ) + def open_massive_changes_wizard(self): if self.ensure_one(): diff --git a/pms/models/product_pricelist_item.py b/pms/models/product_pricelist_item.py index 79756e74c..267f145a1 100644 --- a/pms/models/product_pricelist_item.py +++ b/pms/models/product_pricelist_item.py @@ -26,17 +26,32 @@ class ProductPricelistItem(models.Model): string="End Date Overnight", help="End date to apply daily pricelist items", ) - on_board_service = fields.Boolean("Those included in Board Services") + on_board_service = fields.Boolean( + string="On Board Service", + help="Those included in Board Services", + ) board_service_room_type_ids = fields.Many2many( - "pms.board.service.room.type", - "board_service_pricelist_item_rel", - "pricelist_item_id", - "board_service_id", - string="Board Services on Room Types", - ondelete="cascade", # check_company=True, + string="Board Services", help="""Specify a Board services on Room Types.""", + comodel_name="pms.board.service.room.type", + relation="board_service_pricelist_item_rel", + column1="pricelist_item_id", + column2="board_service_id", + ondelete="cascade", + check_pms_properties=True, + ) + pricelist_id = fields.Many2one( + string="Pricelist", + help="Pricelist in which this item is included", + check_pms_properties=True, + ) + product_id = fields.Many2one( + string="Product", + help="Product associated with the item", + check_pms_properties=True, + ) + product_tmpl_id = fields.Many2one( + string="Product Template", + help="Product template associated with the item", check_pms_properties=True, ) - pricelist_id = fields.Many2one(check_pms_properties=True) - product_id = fields.Many2one(check_pms_properties=True) - product_tmpl_id = fields.Many2one(check_pms_properties=True) diff --git a/pms/models/product_product.py b/pms/models/product_product.py index ec2d88699..2990f6ecd 100644 --- a/pms/models/product_product.py +++ b/pms/models/product_product.py @@ -5,10 +5,10 @@ class ProductProduct(models.Model): _inherit = "product.product" board_price = fields.Float( - "Board Service Price", + string="Board Service Price", + help="Get price on board service", digits="Product Price", compute="_compute_board_price", - help="Get price price on board service", ) @api.depends_context("consumption_date") diff --git a/pms/wizards/folio_make_invoice_advance.py b/pms/wizards/folio_make_invoice_advance.py index 5743c2cd8..cbce18971 100644 --- a/pms/wizards/folio_make_invoice_advance.py +++ b/pms/wizards/folio_make_invoice_advance.py @@ -10,6 +10,78 @@ class FolioAdvancePaymentInv(models.TransientModel): _name = "folio.advance.payment.inv" _description = "Folio Advance Payment Invoice" + partner_invoice_id = fields.Many2one( + string="Billing contact", + help="Invoice address for current partner", + default=lambda self: self._default_partner_invoice_id, + comodel_name="res.partner", + ) + advance_payment_method = fields.Selection( + string="Create Invoice", + help="A standard invoice is issued with all the order \ + lines ready for invoicing, \ + according to their invoicing policy \ + (based on ordered or delivered quantity).", + required=True, + default="delivered", + selection=[ + ("delivered", "Regular invoice"), + ("percentage", "Down payment (percentage)"), + ("fixed", "Down payment (fixed amount)"), + ], + ) + bill_services = fields.Boolean( + string="Bill Services", help="Bill Services", default=True + ) + bill_rooms = fields.Boolean(string="Bill Rooms", help="Bill Rooms", default=True) + deduct_down_payments = fields.Boolean( + string="Deduct down payments", help="Deduct down payments", default=True + ) + has_down_payments = fields.Boolean( + string="Has down payments", + help="Has down payments", + readonly=True, + default=lambda self: self._default_has_down_payment, + ) + product_id = fields.Many2one( + string="Down Payment Product", + default=lambda self: self._default_product_id, + comodel_name="product.product", + domain=[("type", "=", "service")], + ) + count = fields.Integer( + string="Order Count", + default=lambda self: self._count, + ) + amount = fields.Float( + string="Down Payment Amount", + help="The percentage of amount to be invoiced in advance, taxes excluded.", + digits="Account", + ) + currency_id = fields.Many2one( + string="Currency", + help="Currency used in invoices", + comodel_name="res.currency", + default=lambda self: self._default_currency_id, + ) + fixed_amount = fields.Monetary( + string="Down Payment Amount (Fixed)", + help="The fixed amount to be invoiced in advance, taxes excluded.", + ) + deposit_account_id = fields.Many2one( + string="Income Account", + help="Account used for deposits", + default=lambda self: self._default_deposit_account_id, + comodel_name="account.account", + domain=[("deprecated", "=", False)], + ) + deposit_taxes_id = fields.Many2many( + string="Customer Taxes", + help="Taxes used for deposits", + default=lambda self: self._default_deposit_taxes_id, + comodel_name="account.tax", + ) + @api.model def _count(self): return len(self._context.get("active_ids", [])) @@ -57,64 +129,17 @@ class FolioAdvancePaymentInv(models.TransientModel): folio = self.env["pms.folio"].browse(self._context.get("active_id", [])) return folio.partner_invoice_ids[0] - partner_invoice_id = fields.Many2one( - comodel_name="res.partner", - string="Billing contact", - default=_default_partner_invoice_id, - ) + def _get_advance_details(self, order): + context = {"lang": order.partner_id.lang} + if self.advance_payment_method == "percentage": + amount = order.amount_untaxed * self.amount / 100 + name = _("Down payment of %s%%") % (self.amount) + else: + amount = self.fixed_amount + name = _("Down Payment") + del context - advance_payment_method = fields.Selection( - [ - ("delivered", "Regular invoice"), - ("percentage", "Down payment (percentage)"), - ("fixed", "Down payment (fixed amount)"), - ], - string="Create Invoice", - default="delivered", - required=True, - help="A standard invoice is issued with all the order \ - lines ready for invoicing, \ - according to their invoicing policy \ - (based on ordered or delivered quantity).", - ) - bill_services = fields.Boolean("Bill Services", default=True) - bill_rooms = fields.Boolean("Bill Rooms", default=True) - deduct_down_payments = fields.Boolean("Deduct down payments", default=True) - has_down_payments = fields.Boolean( - "Has down payments", default=_default_has_down_payment, readonly=True - ) - product_id = fields.Many2one( - "product.product", - string="Down Payment Product", - domain=[("type", "=", "service")], - default=_default_product_id, - ) - count = fields.Integer(default=_count, string="Order Count") - amount = fields.Float( - "Down Payment Amount", - digits="Account", - help="The percentage of amount to be invoiced in advance, taxes excluded.", - ) - currency_id = fields.Many2one( - "res.currency", string="Currency", default=_default_currency_id - ) - fixed_amount = fields.Monetary( - "Down Payment Amount (Fixed)", - help="The fixed amount to be invoiced in advance, taxes excluded.", - ) - deposit_account_id = fields.Many2one( - "account.account", - string="Income Account", - domain=[("deprecated", "=", False)], - help="Account used for deposits", - default=_default_deposit_account_id, - ) - deposit_taxes_id = fields.Many2many( - "account.tax", - string="Customer Taxes", - help="Taxes used for deposits", - default=_default_deposit_taxes_id, - ) + return amount, name @api.onchange("advance_payment_method") def onchange_advance_payment_method(self): @@ -161,18 +186,6 @@ class FolioAdvancePaymentInv(models.TransientModel): return invoice_vals - def _get_advance_details(self, order): - context = {"lang": order.partner_id.lang} - if self.advance_payment_method == "percentage": - amount = order.amount_untaxed * self.amount / 100 - name = _("Down payment of %s%%") % (self.amount) - else: - amount = self.fixed_amount - name = _("Down Payment") - del context - - return amount, name - def _create_invoice(self, order, line, amount): if (self.advance_payment_method == "percentage" and self.amount <= 0.00) or ( self.advance_payment_method == "fixed" and self.fixed_amount <= 0.00 diff --git a/pms/wizards/wizard_advanced_filters.py b/pms/wizards/wizard_advanced_filters.py index c10dda971..eed691c51 100644 --- a/pms/wizards/wizard_advanced_filters.py +++ b/pms/wizards/wizard_advanced_filters.py @@ -18,17 +18,17 @@ class AdvancedFiltersWizard(models.TransientModel): _description = "Wizard for advanced filters" pms_model_id = fields.Many2one( - "ir.model", string="Recipients Model", - ondelete="cascade", required=True, - domain=[("model", "in", PMS_BUSINESS_MODELS)], default=lambda self: self.env.ref("pms.model_pms_reservation").id, + comodel_name="ir.model", + domain=[("model", "in", PMS_BUSINESS_MODELS)], + ondelete="cascade", ) pms_model_name = fields.Char( string="Recipients Model Name", - related="pms_model_id.model", readonly=True, + related="pms_model_id.model", related_sudo=True, ) pms_domain = fields.Char(string="Domain") diff --git a/pms/wizards/wizard_folio.py b/pms/wizards/wizard_folio.py index 56f7e620f..a5948fe72 100644 --- a/pms/wizards/wizard_folio.py +++ b/pms/wizards/wizard_folio.py @@ -11,70 +11,89 @@ class FolioWizard(models.TransientModel): ) _check_pms_properties_auto = True - # Fields declaration start_date = fields.Date( string="From:", + help="Start date for creation of reservations and folios", required=True, ) end_date = fields.Date( string="To:", + help="End date for creation of reservations and folios", required=True, ) pricelist_id = fields.Many2one( - comodel_name="product.pricelist", string="Pricelist", - compute="_compute_pricelist_id", - store=True, + 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( - comodel_name="pms.property", string="Property", + help="Property to which the folio belongs", default=lambda self: self._default_pms_property_id(), + comodel_name="pms.property", ) segmentation_ids = fields.Many2many( - "res.partner.category", string="Segmentation", ondelete="restrict" + string="Segmentation", + help="Partner Tags", + ondelete="restrict", + comodel_name="res.partner.category", ) partner_id = fields.Many2one( - "res.partner", + string="Partner", + help="Partner who made the reservation", + comodel_name="res.partner", check_pms_properties=True, ) folio_id = fields.Many2one( - "pms.folio", + string="Folio", + help="Folio in which are included new reservations", + comodel_name="pms.folio", check_pms_properties=True, ) availability_results = fields.One2many( + strign="Availability Results", + help="Availability Results", + readonly=False, + store=True, comodel_name="pms.folio.availability.wizard", inverse_name="folio_wizard_id", compute="_compute_availability_results", - store=True, - readonly=False, check_pms_properties=True, ) agency_id = fields.Many2one( string="Agency", + help="Agency that made the reservation", comodel_name="res.partner", - ondelete="restrict", 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")], - compute="_compute_channel_type_id", ondelete="restrict", + compute="_compute_channel_type_id", ) total_price_folio = fields.Float( - string="Total Price", compute="_compute_total_price_folio" + 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, ) - can_create_folio = fields.Boolean(compute="_compute_can_create_folio") + can_create_folio = fields.Boolean( + string="Can create folio", compute="_compute_can_create_folio" + ) def _default_pms_property_id(self): if self._context.get("default_folio_id"): @@ -164,7 +183,6 @@ class FolioWizard(models.TransientModel): key=lambda s: s.num_rooms_available, reverse=True ) - # actions def create_folio(self): for record in self: if not record.folio_id: diff --git a/pms/wizards/wizard_folio_availability.py b/pms/wizards/wizard_folio_availability.py index 958f0d615..42ae946b3 100644 --- a/pms/wizards/wizard_folio_availability.py +++ b/pms/wizards/wizard_folio_availability.py @@ -18,51 +18,63 @@ class AvailabilityWizard(models.TransientModel): _name = "pms.folio.availability.wizard" _check_pms_properties_auto = True - # Fields declarations folio_wizard_id = fields.Many2one( + string="Folio Wizard ID", comodel_name="pms.folio.wizard", ) checkin = fields.Date( string="From:", + help="Date Reservation starts ", required=True, ) checkout = fields.Date( string="To:", + help="Date Reservation ends", required=True, ) room_type_id = fields.Many2one( + string="Room Type", + help="Room Type reserved", comodel_name="pms.room.type", check_pms_properties=True, ) num_rooms_available = fields.Integer( string="Available rooms", - compute="_compute_num_rooms_available", + help="Number of rooms that are available", store="true", + compute="_compute_num_rooms_available", ) num_rooms_selected = fields.Many2one( + string="Selected rooms", + readonly=False, + store=True, comodel_name="pms.num.rooms.selection", inverse_name="folio_wizard_id", - string="Selected rooms", - compute="_compute_num_rooms_selected", - store=True, - readonly=False, domain="[('value', '<=', num_rooms_available), " "('room_type_id', '=', room_type_id)]", + compute="_compute_num_rooms_selected", ) value_num_rooms_selected = fields.Integer( - compute="_compute_value_num_rooms_selected", - store=True, + string="Number of Rooms Selected", readonly=False, + store=True, + compute="_compute_value_num_rooms_selected", ) price_per_room = fields.Float( string="Price per room", + help="Price per room in folio", compute="_compute_price_per_room", ) - price_total = fields.Float(string="Total price", compute="_compute_price_total") + price_total = fields.Float( + string="Total price", + help="The total price in the folio", + compute="_compute_price_total", + ) pms_property_id = fields.Many2one( - related="folio_wizard_id.pms_property_id", string="Property", + help="Propertiy with access to the element;", + related="folio_wizard_id.pms_property_id", ) board_service_room_id = fields.Many2one( string="Board Service", diff --git a/pms/wizards/wizard_folio_changes.py b/pms/wizards/wizard_folio_changes.py index d1040a97f..73b8bd6a1 100644 --- a/pms/wizards/wizard_folio_changes.py +++ b/pms/wizards/wizard_folio_changes.py @@ -6,30 +6,26 @@ class WizardFolioChanges(models.TransientModel): _name = "wizard.folio.changes" _description = "Folio Changes" - def _default_folio_id(self): - folio_id = self._context.get("active_id") - folio = self.env["pms.folio"].browse(folio_id) - return folio - - def _default_reservation_ids(self): - folio_id = self._context.get("active_id") - folio = self.env["pms.folio"].browse(folio_id) - return folio.reservation_ids - folio_id = fields.Many2one( - "pms.folio", string="Folio", - default=_default_folio_id, + default=lambda self: self._default_folio_id(), + comodel_name="pms.folio", ) reservation_ids = fields.Many2many( - "pms.reservation", string="Reservations", - default=_default_reservation_ids, + default=lambda self: self._default_reservation_ids(), + comodel_name="pms.reservation", + relation="folio_changes_reservation_rel", + column1="folio_changes_id", + column2="reservation_ids", domain="[('id', 'in', allowed_reservation_ids)]", ) allowed_reservation_ids = fields.Many2many( - "pms.reservation", string="Allowed Reservations", + comodel_name="pms.reservation", + relation="folio_changes_allowed_reservation_rel", + column1="folio_changes_id", + column2="allowed_reservation_ids", compute="_compute_allowed_reservations", ) new_price = fields.Float( @@ -71,6 +67,16 @@ class WizardFolioChanges(models.TransientModel): default=True, ) + def _default_folio_id(self): + folio_id = self._context.get("active_id") + folio = self.env["pms.folio"].browse(folio_id) + return folio + + def _default_reservation_ids(self): + folio_id = self._context.get("active_id") + folio = self.env["pms.folio"].browse(folio_id) + return folio.reservation_ids + @api.depends("folio_id") def _compute_allowed_reservations(self): self.ensure_one() diff --git a/pms/wizards/wizard_massive_changes.py b/pms/wizards/wizard_massive_changes.py index b5f89d2ae..7ac68e466 100644 --- a/pms/wizards/wizard_massive_changes.py +++ b/pms/wizards/wizard_massive_changes.py @@ -10,39 +10,37 @@ class AvailabilityWizard(models.TransientModel): _description = "Wizard for massive changes on Availability Plans & Pricelists." _check_pms_properties_auto = True - def _default_avail_readonly(self): - return True if self._context.get("availability_plan_id") else False - - def _default_pricelist_readonly(self): - return True if self._context.get("pricelist_id") else False - - # Fields declaration pms_property_ids = fields.Many2many( - comodel_name="pms.property", string="Property", + comodel_name="pms.property", default=lambda self: self.env["pms.property"].browse( self.env.user.get_active_property_ids()[0] ), ) massive_changes_on = fields.Selection( - [("pricelist", "Pricelist"), ("availability_plan", "Availability Plan")], string="On", - default="availability_plan", + selection=[ + ("pricelist", "Pricelist"), + ("availability_plan", "Availability Plan"), + ], required=True, + default="availability_plan", ) availability_plan_id = fields.Many2one( - comodel_name="pms.availability.plan", string="Availability Plan to apply massive changes", + comodel_name="pms.availability.plan", check_pms_properties=True, # can be setted by context from availability plan detail ) pricelist_id = fields.Many2one( - comodel_name="product.pricelist", string="Pricelist to apply massive changes", + comodel_name="product.pricelist", check_pms_properties=True, ) allowed_pricelist_ids = fields.One2many( - comodel_name="product.pricelist", compute="_compute_allowed_pricelist_ids" + string="Allowed pricelists", + comodel_name="product.pricelist", + compute="_compute_allowed_pricelist_ids", ) start_date = fields.Date( string="From", @@ -53,8 +51,8 @@ class AvailabilityWizard(models.TransientModel): required=True, ) room_type_id = fields.Many2one( - comodel_name="pms.room.type", string="Room Type", + comodel_name="pms.room.type", check_pms_properties=True, ) price = fields.Float(string="Price") @@ -176,31 +174,45 @@ class AvailabilityWizard(models.TransientModel): ) rules_to_overwrite = fields.One2many( + string="Rule to Overwrite", + readonly=True, + store=False, comodel_name="pms.availability.plan.rule", compute="_compute_rules_to_overwrite", - store=False, - readonly=True, ) pricelist_items_to_overwrite = fields.One2many( + string="Pricelist Items to Override", + readonly=True, + store=False, comodel_name="product.pricelist.item", compute="_compute_pricelist_items_to_overwrite", - store=False, - readonly=True, ) num_rules_to_overwrite = fields.Integer( string="Rules to overwrite on massive changes", - compute="_compute_num_rules_to_overwrite", - store=False, readonly=True, + store=False, + compute="_compute_num_rules_to_overwrite", ) num_pricelist_items_to_overwrite = fields.Integer( string="Pricelist items to overwrite on massive changes", compute="_compute_num_pricelist_items_to_overwrite", - store=False, readonly=True, + store=False, ) - avail_readonly = fields.Boolean(default=_default_avail_readonly) - pricelist_readonly = fields.Boolean(default=_default_pricelist_readonly) + avail_readonly = fields.Boolean( + string="Avialability Readonly", + default=lambda self: self._default_avail_readonly(), + ) + pricelist_readonly = fields.Boolean( + string="Pricelist Readonly", + default=lambda self: self._default_pricelist_readonly(), + ) + + def _default_avail_readonly(self): + return True if self._context.get("availability_plan_id") else False + + def _default_pricelist_readonly(self): + return True if self._context.get("pricelist_id") else False @api.depends("massive_changes_on") def _compute_allowed_pricelist_ids(self): @@ -417,7 +429,6 @@ class AvailabilityWizard(models.TransientModel): ) return domain_overwrite - # actions def apply_massive_changes(self): for record in self: diff --git a/pms/wizards/wizard_payment_folio.py b/pms/wizards/wizard_payment_folio.py index 5279bbe80..15ad88fd4 100644 --- a/pms/wizards/wizard_payment_folio.py +++ b/pms/wizards/wizard_payment_folio.py @@ -11,35 +11,35 @@ class WizardPaymentFolio(models.TransientModel): _description = "Payments" folio_id = fields.Many2one( - "pms.folio", string="Folio", required=True, + comodel_name="pms.folio", ) reservation_ids = fields.Many2many( - "pms.reservation", string="Reservations", + comodel_name="pms.reservation", ) service_ids = fields.Many2many( - "pms.service", string="Services", + comodel_name="pms.service", ) payment_method_id = fields.Many2one( - "account.journal", string="Payment Method", required=True, + comodel_name="account.journal", domain="[('id', 'in', allowed_method_ids)]", ) allowed_method_ids = fields.Many2many( - "account.journal", - "allowed_payment_journal_rel", - "payment_id", - "journal_id", - compute="_compute_allowed_method_ids", store="True", + comodel_name="account.journal", + relation="allowed_payment_journal_rel", + column1="payment_id", + column2="journal_id", + compute="_compute_allowed_method_ids", ) - amount = fields.Float("Amount", digits=("Product Price")) - date = fields.Date("Date", default=fields.Date.context_today, required=True) - partner_id = fields.Many2one("res.partner") + amount = fields.Float(string="Amount", digits=("Product Price")) + date = fields.Date(String="Date", required=True, default=fields.Date.context_today) + partner_id = fields.Many2one(string="Partner", comodel_name="res.partner") @api.depends("folio_id") def _compute_allowed_method_ids(self): diff --git a/pms/wizards/wizard_split_join_swap_reservation.py b/pms/wizards/wizard_split_join_swap_reservation.py index a2bd62642..abb1cc76d 100644 --- a/pms/wizards/wizard_split_join_swap_reservation.py +++ b/pms/wizards/wizard_split_join_swap_reservation.py @@ -6,29 +6,30 @@ from odoo.exceptions import UserError class ReservationSplitJoinSwapWizard(models.TransientModel): _name = "pms.reservation.split.join.swap.wizard" + string = ("Operation",) + help = ("Operation to be applied on the reservation",) operation = fields.Selection( [ ("swap", "Swap rooms"), ("split", "Split reservation"), ("join", "Join reservation"), ], - string="Operation", - help="Operation to be applied on the reservation", default=lambda self: self._context.get("default_operation") if self._context.get("default_operation") else "swap", ) reservation_id = fields.Many2one( string="Reservation", - comodel_name="pms.reservation", default=lambda self: self.env["pms.reservation"] .browse(self._context.get("active_id")) .id if self._context.get("active_id") else False, + comodel_name="pms.reservation", ) checkin = fields.Date( string="Check In", + help="Checkin in reservation", default=lambda self: self.env["pms.reservation"] .browse(self._context.get("active_id")) .checkin @@ -37,6 +38,7 @@ class ReservationSplitJoinSwapWizard(models.TransientModel): ) checkout = fields.Date( string="Check Out", + help="checkout in reservation", default=lambda self: self.env["pms.reservation"] .browse(self._context.get("active_id")) .checkout @@ -45,15 +47,13 @@ class ReservationSplitJoinSwapWizard(models.TransientModel): ) reservations = fields.Many2many( string="Reservations", - comodel_name="pms.reservation", - compute="_compute_reservations", readonly=False, store=True, + comodel_name="pms.reservation", + compute="_compute_reservations", ) room_source = fields.Many2one( string="Room Source", - comodel_name="pms.room", - domain="[('id', 'in', allowed_rooms_sources)]", default=lambda self: self.env["pms.reservation"] .browse(self._context.get("active_id")) .preferred_room_id @@ -62,6 +62,8 @@ class ReservationSplitJoinSwapWizard(models.TransientModel): .browse(self._context.get("active_id")) .splitted else False, + comodel_name="pms.room", + domain="[('id', 'in', allowed_rooms_sources)]", ) room_target = fields.Many2one( string="Room Target", @@ -70,25 +72,26 @@ class ReservationSplitJoinSwapWizard(models.TransientModel): ) allowed_rooms_sources = fields.Many2many( string="Allowed rooms source", + store=True, + readonly=False, comodel_name="pms.room", relation="pms_wizard_split_join_swap_reservation_rooms_source", column1="wizard_id", column2="room_id", compute="_compute_allowed_rooms_source", - store=True, - readonly=False, ) allowed_rooms_target = fields.Many2many( string="Allowed rooms target", comodel_name="pms.room", + store=True, + readonly=False, relation="pms_wizard_split_join_swap_reservation_rooms_target", column1="wizard_id", column2="room_id", compute="_compute_allowed_rooms_target", - store=True, - readonly=False, ) reservation_lines_to_change = fields.One2many( + string="Reservations Lines To Change", comodel_name="pms.wizard.reservation.lines.split", inverse_name="reservation_wizard_id", compute="_compute_reservation_lines", @@ -306,6 +309,7 @@ class ReservationLinesToSplit(models.TransientModel): _name = "pms.wizard.reservation.lines.split" reservation_wizard_id = fields.Many2one( + string="Reservation Wizard", comodel_name="pms.reservation.split.join.swap.wizard", ) date = fields.Date( @@ -319,9 +323,9 @@ class ReservationLinesToSplit(models.TransientModel): allowed_room_ids = fields.Many2many( string="Allowed Rooms", help="It contains all available rooms for this line", + store=True, comodel_name="pms.room", compute="_compute_allowed_room_ids", - store=True, # readonly=False )