mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
Merge pull request #11 from commitsun/14.0-pms-sales_channel
Sales Channel Segmentation
This commit is contained in:
@@ -60,6 +60,7 @@
|
|||||||
"views/res_partner_views.xml",
|
"views/res_partner_views.xml",
|
||||||
"views/product_pricelist_views.xml",
|
"views/product_pricelist_views.xml",
|
||||||
"views/product_pricelist_item_views.xml",
|
"views/product_pricelist_item_views.xml",
|
||||||
|
"views/pms_sale_channel.xml",
|
||||||
"views/product_template_views.xml",
|
"views/product_template_views.xml",
|
||||||
"views/webclient_templates.xml",
|
"views/webclient_templates.xml",
|
||||||
"views/ir_sequence_views.xml",
|
"views/ir_sequence_views.xml",
|
||||||
|
|||||||
@@ -34,5 +34,22 @@
|
|||||||
<field name="pms_property_ids" eval="[(4, ref('main_pms_property'))]" />
|
<field name="pms_property_ids" eval="[(4, ref('main_pms_property'))]" />
|
||||||
<field name="groups_id" eval="[(4,ref('pms.group_pms_manager'))]" />
|
<field name="groups_id" eval="[(4,ref('pms.group_pms_manager'))]" />
|
||||||
</record>
|
</record>
|
||||||
|
<!-- pms.sale.channel-->
|
||||||
|
<record id="main_pms_sale_channel_0" model="pms.sale.channel">
|
||||||
|
<field name="name">Door</field>
|
||||||
|
<field name="channel_type">direct</field>
|
||||||
|
</record>
|
||||||
|
<record id="main_pms_sale_channel_1" model="pms.sale.channel">
|
||||||
|
<field name="name">Phone</field>
|
||||||
|
<field name="channel_type">direct</field>
|
||||||
|
</record>
|
||||||
|
<record id="main_pms_sale_channel_2" model="pms.sale.channel">
|
||||||
|
<field name="name">Mail</field>
|
||||||
|
<field name="channel_type">direct</field>
|
||||||
|
</record>
|
||||||
|
<record id="main_pms_sale_channel_3" model="pms.sale.channel">
|
||||||
|
<field name="name">Agency</field>
|
||||||
|
<field name="channel_type">indirect</field>
|
||||||
|
</record>
|
||||||
</data>
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from . import pms_checkin_partner
|
|||||||
from . import product_pricelist
|
from . import product_pricelist
|
||||||
from . import product_pricelist_item
|
from . import product_pricelist_item
|
||||||
from . import res_partner
|
from . import res_partner
|
||||||
|
from . import pms_sale_channel
|
||||||
|
|
||||||
# from . import mail_compose_message
|
# from . import mail_compose_message
|
||||||
from . import pms_room_type_class
|
from . import pms_room_type_class
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ class PmsFolio(models.Model):
|
|||||||
pms_property_id = fields.Many2one(
|
pms_property_id = fields.Many2one(
|
||||||
"pms.property", default=_get_default_pms_property, required=True
|
"pms.property", default=_get_default_pms_property, required=True
|
||||||
)
|
)
|
||||||
partner_id = fields.Many2one("res.partner", tracking=True, ondelete="restrict")
|
partner_id = fields.Many2one(
|
||||||
|
"res.partner", compute="_compute_partner_id", tracking=True, ondelete="restrict"
|
||||||
|
)
|
||||||
reservation_ids = fields.One2many(
|
reservation_ids = fields.One2many(
|
||||||
"pms.reservation",
|
"pms.reservation",
|
||||||
"folio_id",
|
"folio_id",
|
||||||
@@ -102,6 +104,13 @@ class PmsFolio(models.Model):
|
|||||||
readonly=False,
|
readonly=False,
|
||||||
help="Pricelist for current folio.",
|
help="Pricelist for current folio.",
|
||||||
)
|
)
|
||||||
|
commission = fields.Float(
|
||||||
|
string="Commission",
|
||||||
|
compute="_compute_commission",
|
||||||
|
store=True,
|
||||||
|
readonly=True,
|
||||||
|
default=0,
|
||||||
|
)
|
||||||
user_id = fields.Many2one(
|
user_id = fields.Many2one(
|
||||||
"res.users",
|
"res.users",
|
||||||
string="Salesperson",
|
string="Salesperson",
|
||||||
@@ -114,10 +123,16 @@ class PmsFolio(models.Model):
|
|||||||
)
|
)
|
||||||
agency_id = fields.Many2one(
|
agency_id = fields.Many2one(
|
||||||
"res.partner",
|
"res.partner",
|
||||||
"Agency",
|
string="Agency",
|
||||||
ondelete="restrict",
|
ondelete="restrict",
|
||||||
domain=[("is_agency", "=", True)],
|
domain=[("is_agency", "=", True)],
|
||||||
)
|
)
|
||||||
|
channel_type_id = fields.Many2one(
|
||||||
|
"pms.sale.channel",
|
||||||
|
string="Direct Sale Channel",
|
||||||
|
ondelete="restrict",
|
||||||
|
domain=[("channel_type", "=", "direct")],
|
||||||
|
)
|
||||||
payment_ids = fields.One2many("account.payment", "folio_id", readonly=True)
|
payment_ids = fields.One2many("account.payment", "folio_id", readonly=True)
|
||||||
# return_ids = fields.One2many("payment.return", "folio_id", readonly=True)
|
# return_ids = fields.One2many("payment.return", "folio_id", readonly=True)
|
||||||
payment_term_id = fields.Many2one(
|
payment_term_id = fields.Many2one(
|
||||||
@@ -163,15 +178,6 @@ class PmsFolio(models.Model):
|
|||||||
string="Type",
|
string="Type",
|
||||||
default=lambda *a: "normal",
|
default=lambda *a: "normal",
|
||||||
)
|
)
|
||||||
channel_type = fields.Selection(
|
|
||||||
[
|
|
||||||
("direct", "Direct"),
|
|
||||||
("agency", "Agency"),
|
|
||||||
],
|
|
||||||
string="Sales Channel",
|
|
||||||
compute="_compute_channel_type",
|
|
||||||
store=True,
|
|
||||||
)
|
|
||||||
date_order = fields.Datetime(
|
date_order = fields.Datetime(
|
||||||
string="Order Date",
|
string="Order Date",
|
||||||
required=True,
|
required=True,
|
||||||
@@ -284,17 +290,26 @@ class PmsFolio(models.Model):
|
|||||||
folio.reservation_ids.filtered(lambda a: a.state != "cancelled")
|
folio.reservation_ids.filtered(lambda a: a.state != "cancelled")
|
||||||
)
|
)
|
||||||
|
|
||||||
@api.depends("partner_id")
|
@api.depends("partner_id", "agency_id")
|
||||||
def _compute_pricelist_id(self):
|
def _compute_pricelist_id(self):
|
||||||
for folio in self:
|
for folio in self:
|
||||||
pricelist_id = (
|
if folio.partner_id and folio.partner_id.property_product_pricelist:
|
||||||
folio.partner_id.property_product_pricelist
|
pricelist_id = folio.partner_id.property_product_pricelist.id
|
||||||
and folio.partner_id.property_product_pricelist.id
|
else:
|
||||||
or self.env.user.pms_property_id.default_pricelist_id.id
|
pricelist_id = self.env.user.pms_property_id.default_pricelist_id.id
|
||||||
)
|
|
||||||
if folio.pricelist_id.id != pricelist_id:
|
if folio.pricelist_id.id != pricelist_id:
|
||||||
# TODO: Warning change de pricelist?
|
# TODO: Warning change de pricelist?
|
||||||
folio.pricelist_id = pricelist_id
|
folio.pricelist_id = pricelist_id
|
||||||
|
if folio.agency_id and folio.agency_id.apply_pricelist:
|
||||||
|
pricelist_id = folio.agency_id.property_product_pricelist.id
|
||||||
|
|
||||||
|
@api.depends("agency_id")
|
||||||
|
def _compute_partner_id(self):
|
||||||
|
for folio in self:
|
||||||
|
if folio.agency_id and folio.agency_id.invoice_agency:
|
||||||
|
folio.partner_id = folio.agency_id.id
|
||||||
|
elif not folio.partner_id:
|
||||||
|
folio.partner_id = False
|
||||||
|
|
||||||
@api.depends("partner_id")
|
@api.depends("partner_id")
|
||||||
def _compute_user_id(self):
|
def _compute_user_id(self):
|
||||||
@@ -308,24 +323,25 @@ class PmsFolio(models.Model):
|
|||||||
addr = folio.partner_id.address_get(["invoice"])
|
addr = folio.partner_id.address_get(["invoice"])
|
||||||
folio.partner_invoice_id = addr["invoice"]
|
folio.partner_invoice_id = addr["invoice"]
|
||||||
|
|
||||||
@api.depends("agency_id")
|
|
||||||
def _compute_channel_type(self):
|
|
||||||
for folio in self:
|
|
||||||
if folio.agency_id:
|
|
||||||
folio.channel_type = "agency"
|
|
||||||
else:
|
|
||||||
folio.channel_type = "direct"
|
|
||||||
|
|
||||||
@api.depends("partner_id")
|
@api.depends("partner_id")
|
||||||
def _compute_payment_term_id(self):
|
def _compute_payment_term_id(self):
|
||||||
self.payment_term_id = False
|
self.payment_term_id = False
|
||||||
for folio in self:
|
for folio in self:
|
||||||
folio.payment_term_id = (
|
folio.payment_term_id = (
|
||||||
self.partner_id.property_payment_term_id
|
folio.partner_id.property_payment_term_id
|
||||||
and self.partner_id.property_payment_term_id.id
|
and folio.partner_id.property_payment_term_id.id
|
||||||
or False
|
or False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@api.depends("reservation_ids")
|
||||||
|
def _compute_commission(self):
|
||||||
|
for folio in self:
|
||||||
|
for reservation in folio.reservation_ids:
|
||||||
|
if reservation.commission_amount != 0:
|
||||||
|
folio.commission += reservation.commission_amount
|
||||||
|
else:
|
||||||
|
folio.commission = 0
|
||||||
|
|
||||||
@api.depends(
|
@api.depends(
|
||||||
"state", "reservation_ids.invoice_status", "service_ids.invoice_status"
|
"state", "reservation_ids.invoice_status", "service_ids.invoice_status"
|
||||||
)
|
)
|
||||||
@@ -667,3 +683,10 @@ class PmsFolio(models.Model):
|
|||||||
(line[0].name, line[1]["amount"], line[1]["base"], len(res)) for line in res
|
(line[0].name, line[1]["amount"], line[1]["base"], len(res)) for line in res
|
||||||
]
|
]
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
raise models.ValidationError(_("There must be only one sale channel"))
|
||||||
|
|||||||
@@ -147,7 +147,14 @@ class PmsReservation(models.Model):
|
|||||||
store=True,
|
store=True,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
)
|
)
|
||||||
agency_id = fields.Many2one(related="folio_id.agency_id")
|
agency_id = fields.Many2one(
|
||||||
|
related="folio_id.agency_id",
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
channel_type_id = fields.Many2one(
|
||||||
|
related="folio_id.channel_type_id",
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
partner_invoice_id = fields.Many2one(
|
partner_invoice_id = fields.Many2one(
|
||||||
"res.partner",
|
"res.partner",
|
||||||
string="Invoice Address",
|
string="Invoice Address",
|
||||||
@@ -186,6 +193,17 @@ class PmsReservation(models.Model):
|
|||||||
store=True,
|
store=True,
|
||||||
readonly=False,
|
readonly=False,
|
||||||
)
|
)
|
||||||
|
commission_percent = fields.Float(
|
||||||
|
string="Commission percent (%)",
|
||||||
|
compute="_compute_commission_percent",
|
||||||
|
store=True,
|
||||||
|
readonly=False,
|
||||||
|
)
|
||||||
|
commission_amount = fields.Float(
|
||||||
|
string="Commission amount",
|
||||||
|
compute="_compute_commission_amount",
|
||||||
|
store=True,
|
||||||
|
)
|
||||||
# TODO: Warning Mens to update pricelist
|
# TODO: Warning Mens to update pricelist
|
||||||
checkin_partner_ids = fields.One2many(
|
checkin_partner_ids = fields.One2many(
|
||||||
"pms.checkin.partner",
|
"pms.checkin.partner",
|
||||||
@@ -342,22 +360,6 @@ class PmsReservation(models.Model):
|
|||||||
overbooking = fields.Boolean("Is Overbooking", default=False)
|
overbooking = fields.Boolean("Is Overbooking", default=False)
|
||||||
reselling = fields.Boolean("Is Reselling", default=False)
|
reselling = fields.Boolean("Is Reselling", default=False)
|
||||||
nights = fields.Integer("Nights", compute="_compute_nights", store=True)
|
nights = fields.Integer("Nights", compute="_compute_nights", store=True)
|
||||||
channel_type = fields.Selection(
|
|
||||||
selection=[
|
|
||||||
("direct", "Direct"),
|
|
||||||
("agency", "Agency"),
|
|
||||||
],
|
|
||||||
string="Sales Channel",
|
|
||||||
default="direct",
|
|
||||||
)
|
|
||||||
subchannel_direct = fields.Selection(
|
|
||||||
selection=[
|
|
||||||
("door", "Door"),
|
|
||||||
("mail", "Mail"),
|
|
||||||
("phone", "Phone"),
|
|
||||||
],
|
|
||||||
string="Direct Channel",
|
|
||||||
)
|
|
||||||
origin = fields.Char("Origin", compute="_compute_origin", store=True)
|
origin = fields.Char("Origin", compute="_compute_origin", store=True)
|
||||||
detail_origin = fields.Char(
|
detail_origin = fields.Char(
|
||||||
"Detail Origin", compute="_compute_detail_origin", store=True
|
"Detail Origin", compute="_compute_detail_origin", store=True
|
||||||
@@ -519,7 +521,7 @@ class PmsReservation(models.Model):
|
|||||||
)
|
)
|
||||||
reservation.allowed_room_ids = rooms_available
|
reservation.allowed_room_ids = rooms_available
|
||||||
|
|
||||||
@api.depends("reservation_type")
|
@api.depends("reservation_type", "agency_id")
|
||||||
def _compute_partner_id(self):
|
def _compute_partner_id(self):
|
||||||
for reservation in self:
|
for reservation in self:
|
||||||
if reservation.reservation_type == "out":
|
if reservation.reservation_type == "out":
|
||||||
@@ -528,6 +530,8 @@ class PmsReservation(models.Model):
|
|||||||
reservation.partner_id = reservation.folio_id.partner_id
|
reservation.partner_id = reservation.folio_id.partner_id
|
||||||
else:
|
else:
|
||||||
reservation.partner_id = False
|
reservation.partner_id = False
|
||||||
|
if not reservation.partner_id and reservation.agency_id:
|
||||||
|
reservation.partner_id = reservation.agency_id
|
||||||
|
|
||||||
@api.depends("partner_id")
|
@api.depends("partner_id")
|
||||||
def _compute_partner_invoice_id(self):
|
def _compute_partner_invoice_id(self):
|
||||||
@@ -760,6 +764,26 @@ class PmsReservation(models.Model):
|
|||||||
|
|
||||||
return [("checkout", searching_for_true, today)]
|
return [("checkout", searching_for_true, today)]
|
||||||
|
|
||||||
|
@api.depends("agency_id")
|
||||||
|
def _compute_commission_percent(self):
|
||||||
|
for reservation in self:
|
||||||
|
if reservation.agency_id:
|
||||||
|
reservation.commission_percent = (
|
||||||
|
reservation.agency_id.default_commission
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
reservation.commission_percent = 0
|
||||||
|
|
||||||
|
@api.depends("commission_percent", "price_total")
|
||||||
|
def _compute_commission_amount(self):
|
||||||
|
for reservation in self:
|
||||||
|
if reservation.commission_percent > 0:
|
||||||
|
reservation.commission_amount = (
|
||||||
|
reservation.price_total * reservation.commission_percent
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
reservation.commission_amount = 0
|
||||||
|
|
||||||
# REVIEW: Dont run with set room_type_id -> room_id(compute)-> No set adults¿?
|
# REVIEW: Dont run with set room_type_id -> room_id(compute)-> No set adults¿?
|
||||||
@api.depends("preferred_room_id")
|
@api.depends("preferred_room_id")
|
||||||
def _compute_adults(self):
|
def _compute_adults(self):
|
||||||
@@ -1105,19 +1129,11 @@ class PmsReservation(models.Model):
|
|||||||
|
|
||||||
@api.model
|
@api.model
|
||||||
def create(self, vals):
|
def create(self, vals):
|
||||||
if "folio_id" in vals and "channel_type" not in vals:
|
if "folio_id" in vals:
|
||||||
folio = self.env["pms.folio"].browse(vals["folio_id"])
|
folio = self.env["pms.folio"].browse(vals["folio_id"])
|
||||||
channel_type = (
|
|
||||||
vals["channel_type"] if "channel_type" in vals else folio.channel_type
|
|
||||||
)
|
|
||||||
partner_id = (
|
|
||||||
vals["partner_id"] if "partner_id" in vals else folio.partner_id.id
|
|
||||||
)
|
|
||||||
vals.update({"channel_type": channel_type, "partner_id": partner_id})
|
|
||||||
elif "partner_id" in vals:
|
elif "partner_id" in vals:
|
||||||
folio_vals = {
|
folio_vals = {
|
||||||
"partner_id": int(vals.get("partner_id")),
|
"partner_id": int(vals.get("partner_id")),
|
||||||
"channel_type": vals.get("channel_type"),
|
|
||||||
}
|
}
|
||||||
# Create the folio in case of need
|
# Create the folio in case of need
|
||||||
# (To allow to create reservations direct)
|
# (To allow to create reservations direct)
|
||||||
@@ -1126,7 +1142,6 @@ class PmsReservation(models.Model):
|
|||||||
{
|
{
|
||||||
"folio_id": folio.id,
|
"folio_id": folio.id,
|
||||||
"reservation_type": vals.get("reservation_type"),
|
"reservation_type": vals.get("reservation_type"),
|
||||||
"channel_type": vals.get("channel_type"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
record = super(PmsReservation, self).create(vals)
|
record = super(PmsReservation, self).create(vals)
|
||||||
@@ -1288,20 +1303,6 @@ class PmsReservation(models.Model):
|
|||||||
record.checkin_partner_count = 0
|
record.checkin_partner_count = 0
|
||||||
record.checkin_partner_pending_count = 0
|
record.checkin_partner_pending_count = 0
|
||||||
|
|
||||||
@api.depends("channel_type", "subchannel_direct")
|
|
||||||
def _compute_origin(self):
|
|
||||||
for reservation in self:
|
|
||||||
if reservation.channel_type == "direct":
|
|
||||||
reservation.origin = reservation.subchannel_direct
|
|
||||||
elif reservation.channel_type == "agency":
|
|
||||||
reservation.origin = reservation.agency_id.name
|
|
||||||
|
|
||||||
@api.depends("origin")
|
|
||||||
def _compute_detail_origin(self):
|
|
||||||
for reservation in self:
|
|
||||||
if reservation.channel_type in ["direct", "agency"]:
|
|
||||||
reservation.detail_origin = reservation.sudo().create_uid.name
|
|
||||||
|
|
||||||
def _search_checkin_partner_pending(self, operator, value):
|
def _search_checkin_partner_pending(self, operator, value):
|
||||||
self.ensure_one()
|
self.ensure_one()
|
||||||
recs = self.search([]).filtered(lambda x: x.checkin_partner_pending_count > 0)
|
recs = self.search([]).filtered(lambda x: x.checkin_partner_pending_count > 0)
|
||||||
|
|||||||
12
pms/models/pms_sale_channel.py
Normal file
12
pms/models/pms_sale_channel.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from odoo import fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class PmsSaleChannel(models.Model):
|
||||||
|
_name = "pms.sale.channel"
|
||||||
|
_description = "Sales Channel"
|
||||||
|
|
||||||
|
# Fields declaration
|
||||||
|
name = fields.Text(string="Sale Channel Name")
|
||||||
|
channel_type = fields.Selection(
|
||||||
|
[("direct", "Direct"), ("indirect", "Indirect")], string="Sale Channel Type"
|
||||||
|
)
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from odoo import api, fields, models
|
from odoo import _, api, fields, models
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -21,20 +21,41 @@ class ResPartner(models.Model):
|
|||||||
folios_count = fields.Integer("Folios", compute="_compute_folios_count")
|
folios_count = fields.Integer("Folios", compute="_compute_folios_count")
|
||||||
unconfirmed = fields.Boolean("Unconfirmed", default=True)
|
unconfirmed = fields.Boolean("Unconfirmed", default=True)
|
||||||
is_agency = fields.Boolean("Is Agency")
|
is_agency = fields.Boolean("Is Agency")
|
||||||
|
sale_channel_id = fields.Many2one(
|
||||||
|
"pms.sale.channel",
|
||||||
|
string="Sale Channel",
|
||||||
|
ondelete="restrict",
|
||||||
|
domain=[("channel_type", "=", "indirect")],
|
||||||
|
)
|
||||||
|
default_commission = fields.Integer("Commission")
|
||||||
|
apply_pricelist = fields.Boolean("Apply Pricelist")
|
||||||
|
invoice_agency = fields.Boolean("Invoice Agency")
|
||||||
|
|
||||||
# Compute and Search methods
|
# Compute and Search methods
|
||||||
def _compute_reservations_count(self):
|
def _compute_reservations_count(self):
|
||||||
pms_reservation_obj = self.env["pms.reservation"]
|
pms_reservation_obj = self.env["pms.reservation"]
|
||||||
for record in self:
|
for record in self:
|
||||||
record.reservations_count = pms_reservation_obj.search_count(
|
record.reservations_count = pms_reservation_obj.search_count(
|
||||||
[("partner_id.id", "=", record.id)]
|
[
|
||||||
|
(
|
||||||
|
"partner_id.id",
|
||||||
|
"=",
|
||||||
|
record.id if isinstance(record.id, int) else False,
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
def _compute_folios_count(self):
|
def _compute_folios_count(self):
|
||||||
pms_folio_obj = self.env["pms.folio"]
|
pms_folio_obj = self.env["pms.folio"]
|
||||||
for record in self:
|
for record in self:
|
||||||
record.folios_count = pms_folio_obj.search_count(
|
record.folios_count = pms_folio_obj.search_count(
|
||||||
[("partner_id.id", "=", record.id)]
|
[
|
||||||
|
(
|
||||||
|
"partner_id.id",
|
||||||
|
"=",
|
||||||
|
record.id if isinstance(record.id, int) else False,
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# ORM Overrides
|
# ORM Overrides
|
||||||
@@ -64,3 +85,11 @@ class ResPartner(models.Model):
|
|||||||
name, args=args, operator=operator, limit=limit_rest
|
name, args=args, operator=operator, limit=limit_rest
|
||||||
)
|
)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@api.constrains("is_agency", "sale_channel_id")
|
||||||
|
def _check_is_agency(self):
|
||||||
|
for record in self:
|
||||||
|
if record.is_agency and not record.sale_channel_id:
|
||||||
|
raise models.ValidationError(_("Sale Channel must be entered"))
|
||||||
|
if not record.is_agency and record.sale_channel_id:
|
||||||
|
record.sale_channel_id = None
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ user_access_pms_cancelation_rule,user_access_pms_cancelation_rule,model_pms_canc
|
|||||||
user_access_account_full_reconcile,user_access_account_full_reconcile,account.model_account_full_reconcile,pms.group_pms_user,1,1,1,1
|
user_access_account_full_reconcile,user_access_account_full_reconcile,account.model_account_full_reconcile,pms.group_pms_user,1,1,1,1
|
||||||
user_access_property,user_access_property,model_pms_property,pms.group_pms_user,1,0,0,0
|
user_access_property,user_access_property,model_pms_property,pms.group_pms_user,1,0,0,0
|
||||||
user_access_availability,user_access_availability,model_pms_room_type_availability,pms.group_pms_user,1,0,0,0
|
user_access_availability,user_access_availability,model_pms_room_type_availability,pms.group_pms_user,1,0,0,0
|
||||||
|
user_access_pms_sale_channel,user_access_pms_sale_channel,model_pms_sale_channel,pms.group_pms_user,1,0,0,0
|
||||||
manager_access_pms_floor,manager_access_pms_floor,model_pms_floor,pms.group_pms_manager,1,1,1,1
|
manager_access_pms_floor,manager_access_pms_floor,model_pms_floor,pms.group_pms_manager,1,1,1,1
|
||||||
manager_access_pms_amenity,manager_access_pms_amenity,model_pms_amenity,pms.group_pms_manager,1,1,1,1
|
manager_access_pms_amenity,manager_access_pms_amenity,model_pms_amenity,pms.group_pms_manager,1,1,1,1
|
||||||
manager_access_pms_amenity_type,manager_access_pms_amenity_type,model_pms_amenity_type,pms.group_pms_manager,1,1,1,1
|
manager_access_pms_amenity_type,manager_access_pms_amenity_type,model_pms_amenity_type,pms.group_pms_manager,1,1,1,1
|
||||||
@@ -47,4 +48,5 @@ manager_access_pms_board_service_line,manager_access_pms_board_service_line,mode
|
|||||||
manager_access_property,manager_access_property,model_pms_property,pms.group_pms_manager,1,1,1,1
|
manager_access_property,manager_access_property,model_pms_property,pms.group_pms_manager,1,1,1,1
|
||||||
manager_access_pms_cancelation_rule,manager_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_manager,1,1,1,1
|
manager_access_pms_cancelation_rule,manager_access_pms_cancelation_rule,model_pms_cancelation_rule,pms.group_pms_manager,1,1,1,1
|
||||||
manager_access_availability,manager_access_availability,model_pms_room_type_availability,pms.group_pms_manager,1,1,1,1
|
manager_access_availability,manager_access_availability,model_pms_room_type_availability,pms.group_pms_manager,1,1,1,1
|
||||||
|
manager_access_pms_sale_channel,manager_access_pms_sale_channel,model_pms_sale_channel,pms.group_pms_manager,1,1,1,1
|
||||||
user_access_pms_reservation_wizard,user_access_pms_reservation_wizard,model_pms_reservation_wizard,pms.group_pms_user,1,1,1,1
|
user_access_pms_reservation_wizard,user_access_pms_reservation_wizard,model_pms_reservation_wizard,pms.group_pms_user,1,1,1,1
|
||||||
|
|||||||
|
@@ -22,3 +22,5 @@
|
|||||||
from . import test_pms_reservation
|
from . import test_pms_reservation
|
||||||
from . import test_pms_pricelist
|
from . import test_pms_pricelist
|
||||||
from . import test_pms_checkin_partner
|
from . import test_pms_checkin_partner
|
||||||
|
from . import test_pms_sale_channel
|
||||||
|
from . import test_pms_folio
|
||||||
|
|||||||
58
pms/tests/test_pms_folio.py
Normal file
58
pms/tests/test_pms_folio.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
from .common import TestHotel
|
||||||
|
|
||||||
|
freeze_time("2000-02-02")
|
||||||
|
|
||||||
|
|
||||||
|
class TestPmsFolio(TestHotel):
|
||||||
|
def test_commission_and_partner_correct(self):
|
||||||
|
# ARRANGE
|
||||||
|
PmsFolio = self.env["pms.folio"]
|
||||||
|
PmsReservation = self.env["pms.reservation"]
|
||||||
|
PmsPartner = self.env["res.partner"]
|
||||||
|
PmsSaleChannel = self.env["pms.sale.channel"]
|
||||||
|
# ACT
|
||||||
|
saleChannel = PmsSaleChannel.create(
|
||||||
|
{"name": "saleChannel1", "channel_type": "indirect"}
|
||||||
|
)
|
||||||
|
agency = PmsPartner.create(
|
||||||
|
{
|
||||||
|
"name": "partner1",
|
||||||
|
"is_agency": True,
|
||||||
|
"invoice_agency": True,
|
||||||
|
"default_commission": 15,
|
||||||
|
"sale_channel_id": saleChannel.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
reservation = PmsReservation.create(
|
||||||
|
{
|
||||||
|
"checkin": datetime.datetime.now(),
|
||||||
|
"checkout": datetime.datetime.now() + datetime.timedelta(days=3),
|
||||||
|
"agency_id": agency.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
folio = PmsFolio.create(
|
||||||
|
{
|
||||||
|
"agency_id": agency.id,
|
||||||
|
"reservation_ids": [reservation.id],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
commission = 0
|
||||||
|
for reservation in folio:
|
||||||
|
commission += reservation.commission_amount
|
||||||
|
|
||||||
|
# ASSERT
|
||||||
|
self.assertEqual(
|
||||||
|
folio.commission,
|
||||||
|
commission,
|
||||||
|
"Folio commission don't math with his reservation commission",
|
||||||
|
)
|
||||||
|
if folio.agency_id:
|
||||||
|
self.assertEqual(
|
||||||
|
folio.agency_id, folio.partner_id, "Agency has to be the partner"
|
||||||
|
)
|
||||||
103
pms/tests/test_pms_sale_channel.py
Normal file
103
pms/tests/test_pms_sale_channel.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
|
from freezegun import freeze_time
|
||||||
|
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
from .common import TestHotel
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2010-01-01")
|
||||||
|
class TestPmsSaleChannel(TestHotel):
|
||||||
|
def test_not_agency_as_agency(self):
|
||||||
|
# ARRANGE
|
||||||
|
PmsReservation = self.env["pms.reservation"]
|
||||||
|
not_agency = self.env["res.partner"].create(
|
||||||
|
{"name": "partner1", "is_agency": False}
|
||||||
|
)
|
||||||
|
|
||||||
|
# ACT & ASSERT
|
||||||
|
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||||
|
PmsReservation.create(
|
||||||
|
{
|
||||||
|
"checkin": datetime.datetime.now(),
|
||||||
|
"checkout": datetime.datetime.now() + datetime.timedelta(days=3),
|
||||||
|
"agency_id": not_agency.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_partner_as_direct_channel(self):
|
||||||
|
# ARRANGE
|
||||||
|
PmsReservation = self.env["pms.reservation"]
|
||||||
|
partner = self.env.ref("base.res_partner_12")
|
||||||
|
# ACT & ASSERT
|
||||||
|
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||||
|
PmsReservation.create(
|
||||||
|
{
|
||||||
|
"checkin": datetime.datetime.now(),
|
||||||
|
"checkout": datetime.datetime.now() + datetime.timedelta(days=3),
|
||||||
|
"channel_type_id": partner.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_channel_type_id_only_directs(self):
|
||||||
|
# ARRANGE
|
||||||
|
PmsReservation = self.env["pms.reservation"]
|
||||||
|
PmsSaleChannel = self.env["pms.sale.channel"]
|
||||||
|
# ACT
|
||||||
|
saleChannel = PmsSaleChannel.create({"channel_type": "direct"})
|
||||||
|
reservation = PmsReservation.create(
|
||||||
|
{
|
||||||
|
"checkin": datetime.datetime.now(),
|
||||||
|
"checkout": datetime.datetime.now() + datetime.datetimedelta(days=3),
|
||||||
|
"channel_type_id": saleChannel.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# ASSERT
|
||||||
|
self.assertEqual(
|
||||||
|
self.browse_ref(reservation.channel_type_id).channel_type,
|
||||||
|
"direct",
|
||||||
|
"Sale channel is not direct",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_agency_id_is_agency(self):
|
||||||
|
# ARRANGE
|
||||||
|
PmsReservation = self.env["pms.reservation"]
|
||||||
|
|
||||||
|
# ACT
|
||||||
|
agency = self.env["res.partner"].create({"name": "partner1", "is_agency": True})
|
||||||
|
reservation = PmsReservation.create(
|
||||||
|
{
|
||||||
|
"checkin": datetime.datetime.now(),
|
||||||
|
"checkout": datetime.datetime.now() + datetime.datetimedelta(days=3),
|
||||||
|
"agency_id": agency.id,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
# ASSERT
|
||||||
|
self.assertEqual(
|
||||||
|
self.browse_ref(reservation.agency_id).is_agency,
|
||||||
|
True,
|
||||||
|
"Agency_id doesn't correspond to an agency",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_sale_channel_id_only_indirect(self):
|
||||||
|
# ARRANGE
|
||||||
|
PmsSaleChannel = self.env["pms.sale.channel"]
|
||||||
|
# ACT
|
||||||
|
saleChannel = PmsSaleChannel.create({"channel_type": "indirect"})
|
||||||
|
agency = self.env["res.partner"].create(
|
||||||
|
{"name": "example", "is_agency": True, "sale_channel_id": saleChannel.id}
|
||||||
|
)
|
||||||
|
# ASSERT
|
||||||
|
self.assertEqual(
|
||||||
|
self.browse_ref(agency.sale_channel_id).channel_type,
|
||||||
|
"indirect",
|
||||||
|
"An agency should be a indirect channel",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_agency_without_sale_channel_id(self):
|
||||||
|
# ARRANGE & ACT & ASSERT
|
||||||
|
with self.assertRaises(ValidationError), self.cr.savepoint():
|
||||||
|
self.env["res.partner"].create(
|
||||||
|
{"name": "example", "is_agency": True, "sale_channel_id": None}
|
||||||
|
)
|
||||||
@@ -98,14 +98,16 @@
|
|||||||
name="reservation_type"
|
name="reservation_type"
|
||||||
attrs="{'readonly':[('state','not in',('draft'))]}"
|
attrs="{'readonly':[('state','not in',('draft'))]}"
|
||||||
/>
|
/>
|
||||||
<field
|
<!--<field
|
||||||
name="channel_type"
|
name="channel_type"
|
||||||
attrs="{'required':[('reservation_type','=','normal')]}"
|
attrs="{'required':[('reservation_type','=','normal')]}"
|
||||||
/>
|
/>
|
||||||
<field
|
<field
|
||||||
name="agency_id"
|
name="agency_id"
|
||||||
options="{'no_create': True,'no_open': True}"
|
options="{'no_create': True,'no_open': True}"
|
||||||
/>
|
/>-->
|
||||||
|
<field name="agency_id" />
|
||||||
|
<field name="channel_type_id" />
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field name="partner_internal_comment" />
|
<field name="partner_internal_comment" />
|
||||||
|
|||||||
@@ -255,19 +255,6 @@
|
|||||||
'readonly': [('partner_id', '!=', False),
|
'readonly': [('partner_id', '!=', False),
|
||||||
('mobile','!=', False)]}"
|
('mobile','!=', False)]}"
|
||||||
/>
|
/>
|
||||||
<field
|
|
||||||
name="phone"
|
|
||||||
colspan="2"
|
|
||||||
nolabel="1"
|
|
||||||
placeholder="phone"
|
|
||||||
widget="phone"
|
|
||||||
force_save="1"
|
|
||||||
attrs="{'invisible': [('reservation_type','in',('out'))],
|
|
||||||
'required': [('channel_type','in',('door','mail','phone')),
|
|
||||||
('mobile','=','')],
|
|
||||||
'readonly': [('partner_id', '!=', False),
|
|
||||||
('phone','!=', False)]}"
|
|
||||||
/>
|
|
||||||
<field
|
<field
|
||||||
placeholder="Partner Notes"
|
placeholder="Partner Notes"
|
||||||
colspan="2"
|
colspan="2"
|
||||||
@@ -297,10 +284,10 @@
|
|||||||
name="pricelist_id"
|
name="pricelist_id"
|
||||||
attrs="{'invisible': [('reservation_type','in',('out'))]}"
|
attrs="{'invisible': [('reservation_type','in',('out'))]}"
|
||||||
/>
|
/>
|
||||||
<field
|
<!--<field
|
||||||
name="agency_id"
|
name="agency_id"
|
||||||
options="{'no_create': True,'no_open': True}"
|
options="{'no_create': True,'no_open': True}"
|
||||||
/>
|
/>-->
|
||||||
<field
|
<field
|
||||||
name="cancelled_reason"
|
name="cancelled_reason"
|
||||||
attrs="{'invisible': [('state', 'not in', ('cancelled'))]}"
|
attrs="{'invisible': [('state', 'not in', ('cancelled'))]}"
|
||||||
@@ -341,6 +328,8 @@
|
|||||||
nolabel="1"
|
nolabel="1"
|
||||||
placeholder="Reservation Notes"
|
placeholder="Reservation Notes"
|
||||||
/>
|
/>
|
||||||
|
<field name="agency_id" />
|
||||||
|
<field name="channel_type_id" />
|
||||||
</group>
|
</group>
|
||||||
<group
|
<group
|
||||||
colspan="2"
|
colspan="2"
|
||||||
@@ -380,6 +369,17 @@
|
|||||||
string="Only Room"
|
string="Only Room"
|
||||||
widget="monetary"
|
widget="monetary"
|
||||||
/>
|
/>
|
||||||
|
<field
|
||||||
|
name="commission_percent"
|
||||||
|
string="Commission percent (%)"
|
||||||
|
>
|
||||||
|
<field
|
||||||
|
name="commission_amount"
|
||||||
|
string="Commission Amount"
|
||||||
|
>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
</field>
|
||||||
<field name="invoice_status" invisible="1" />
|
<field name="invoice_status" invisible="1" />
|
||||||
<field name="currency_id" invisible="1" />
|
<field name="currency_id" invisible="1" />
|
||||||
<field
|
<field
|
||||||
@@ -527,10 +527,10 @@
|
|||||||
<field name="overbooking" />
|
<field name="overbooking" />
|
||||||
</group>
|
</group>
|
||||||
<group>
|
<group>
|
||||||
<field
|
<!--<field
|
||||||
name="channel_type"
|
name="channel_type"
|
||||||
attrs="{'required':[('reservation_type','not in',('staff','out'))]}"
|
attrs="{'required':[('reservation_type','not in',('staff','out'))]}"
|
||||||
/>
|
/>-->
|
||||||
<field name="reservation_type" />
|
<field name="reservation_type" />
|
||||||
</group>
|
</group>
|
||||||
</page>
|
</page>
|
||||||
@@ -736,7 +736,7 @@
|
|||||||
domain="[('to_assign','=',True)]"
|
domain="[('to_assign','=',True)]"
|
||||||
/>
|
/>
|
||||||
<separator />
|
<separator />
|
||||||
<filter
|
<!--<filter
|
||||||
string="Web"
|
string="Web"
|
||||||
name="web"
|
name="web"
|
||||||
domain="[('channel_type', '=', 'web')]"
|
domain="[('channel_type', '=', 'web')]"
|
||||||
@@ -755,7 +755,7 @@
|
|||||||
string="Phone"
|
string="Phone"
|
||||||
name="phone"
|
name="phone"
|
||||||
domain="[('channel_type', '=', 'phone')]"
|
domain="[('channel_type', '=', 'phone')]"
|
||||||
/>
|
/>-->
|
||||||
<separator />
|
<separator />
|
||||||
<filter
|
<filter
|
||||||
string="Still to be paid"
|
string="Still to be paid"
|
||||||
|
|||||||
39
pms/views/pms_sale_channel.xml
Normal file
39
pms/views/pms_sale_channel.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record model="ir.ui.view" id="pms_sale_channel_view_form">
|
||||||
|
<field name="name">pms.sale.channel.form</field>
|
||||||
|
<field name="model">pms.sale.channel</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Sale Channel">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name" colspan="1" />
|
||||||
|
<field name="channel_type" />
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.ui.view" id="pms_sale_channel_view_tree">
|
||||||
|
<field name="name">pms.sale.channel.tree</field>
|
||||||
|
<field name="model">pms.sale.channel</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<tree string=" Sale Channel">
|
||||||
|
<field name="name" />
|
||||||
|
<field name="channel_type" />
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.actions.act_window" id="open_pms_sale_channel_form_tree">
|
||||||
|
<field name="name">Sale Channel</field>
|
||||||
|
<field name="res_model">pms.sale.channel</field>
|
||||||
|
<field name="view_mode">tree,form</field>
|
||||||
|
</record>
|
||||||
|
<menuitem
|
||||||
|
name="Sale Channel"
|
||||||
|
id="menu_open_pms_sale_channel_form_tree"
|
||||||
|
action="open_pms_sale_channel_form_tree"
|
||||||
|
sequence="21"
|
||||||
|
parent="pms.configuration_others"
|
||||||
|
/>
|
||||||
|
</odoo>
|
||||||
@@ -39,12 +39,32 @@
|
|||||||
<field string="Folios" name="folios_count" widget="statinfo" />
|
<field string="Folios" name="folios_count" widget="statinfo" />
|
||||||
</button>
|
</button>
|
||||||
</xpath>
|
</xpath>
|
||||||
<xpath expr="//field[@name='user_id']" position="after">
|
<xpath expr="//field[@name='name']" position="after">
|
||||||
<field name="company_type" invisible="1" />
|
<group>
|
||||||
<field
|
<field name="is_agency" />
|
||||||
name="is_agency"
|
</group>
|
||||||
attrs="{'invisible': [('company_type','!=','company')]}"
|
</xpath>
|
||||||
/>
|
<xpath expr="//page[@name='internal_notes']" position="after">
|
||||||
|
<page
|
||||||
|
name="agency"
|
||||||
|
string="Agency"
|
||||||
|
attrs="{'invisible':[('is_agency','!=',True)]}"
|
||||||
|
>
|
||||||
|
<group>
|
||||||
|
<field
|
||||||
|
name="sale_channel_id"
|
||||||
|
options="{'no_create': True,'no_open': True}"
|
||||||
|
/>
|
||||||
|
<field name="default_commission" />
|
||||||
|
<!-- <label for="price_discount"/>
|
||||||
|
<div class="o_row">
|
||||||
|
<field name="price_discount"/>
|
||||||
|
<span>%%</span>
|
||||||
|
</div>-->
|
||||||
|
<field name="apply_pricelist" />
|
||||||
|
<field name="invoice_agency" />
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
Reference in New Issue
Block a user