mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
@@ -46,6 +46,7 @@
|
||||
'views/hotel_room_type_restriction_item_views.xml',
|
||||
'views/hotel_reservation_views.xml',
|
||||
'views/hotel_room_closure_reason_views.xml',
|
||||
'views/hotel_service_views.xml',
|
||||
'views/hotel_board_service_views.xml',
|
||||
'views/hotel_checkin_partner_views.xml',
|
||||
'views/hotel_room_type_availability_views.xml',
|
||||
|
||||
@@ -44,30 +44,11 @@ class HotelFolio(models.Model):
|
||||
def _amount_all(self):
|
||||
pass
|
||||
|
||||
#Main Fields--------------------------------------------------------
|
||||
name = fields.Char('Folio Number', readonly=True, index=True,
|
||||
default=lambda self: _('New'))
|
||||
partner_id = fields.Many2one('res.partner',
|
||||
track_visibility='onchange')
|
||||
closure_reason_id = fields.Many2one('room.closure.reason')
|
||||
# partner_invoice_id = fields.Many2one('res.partner',
|
||||
# string='Invoice Address',
|
||||
# readonly=True, required=True,
|
||||
# states={'draft': [('readonly', False)],
|
||||
# 'sent': [('readonly', False)]},
|
||||
# help="Invoice address for current sales order.")
|
||||
|
||||
# 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')
|
||||
|
||||
#Review: How to use state in folio?
|
||||
state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'),
|
||||
('booking', 'On Board'), ('done', 'Out'),
|
||||
('cancelled', 'Cancelled')],
|
||||
'State', readonly=True,
|
||||
default=lambda *a: 'draft',
|
||||
track_visibility='onchange')
|
||||
|
||||
room_lines = fields.One2many('hotel.reservation', 'folio_id',
|
||||
readonly=False,
|
||||
@@ -80,8 +61,6 @@ class HotelFolio(models.Model):
|
||||
help="Hotel services detail provide to "
|
||||
"customer and it will include in "
|
||||
"main Invoice.")
|
||||
hotel_invoice_id = fields.Many2one('account.invoice', 'Invoice')
|
||||
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
|
||||
currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id',
|
||||
@@ -90,10 +69,50 @@ class HotelFolio(models.Model):
|
||||
pricelist_id = fields.Many2one('product.pricelist',
|
||||
string='Pricelist',
|
||||
required=True,
|
||||
readonly=True,
|
||||
states={'draft': [('readonly', False)],
|
||||
'sent': [('readonly', False)]},
|
||||
help="Pricelist for current folio.")
|
||||
reservation_type = fields.Selection([('normal', 'Normal'),
|
||||
('staff', 'Staff'),
|
||||
('out', 'Out of Service')],
|
||||
'Type', default=lambda *a: 'normal')
|
||||
channel_type = fields.Selection([('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone'),
|
||||
('web', 'Web')], 'Sales Channel', default='door')
|
||||
user_id = fields.Many2one('res.users', string='Salesperson', index=True,
|
||||
track_visibility='onchange', default=lambda self: self.env.user)
|
||||
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)
|
||||
state = fields.Selection([
|
||||
('draft', 'Quotation'),
|
||||
('sent', 'Quotation Sent'),
|
||||
('confirm', 'Confirmed'),
|
||||
('done', 'Locked'),
|
||||
('cancel', 'Cancelled'),
|
||||
], string='Status',
|
||||
readonly=True, copy=False,
|
||||
index=True, track_visibility='onchange',
|
||||
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-----------------------------------------------------
|
||||
payment_ids = fields.One2many('account.payment', 'folio_id',
|
||||
readonly=True)
|
||||
return_ids = fields.One2many('payment.return', 'folio_id',
|
||||
readonly=True)
|
||||
|
||||
#Amount Fields------------------------------------------------------
|
||||
pending_amount = fields.Monetary(compute='compute_amount',
|
||||
store=True,
|
||||
string="Pending in Folio")
|
||||
@@ -103,50 +122,25 @@ class HotelFolio(models.Model):
|
||||
invoices_paid = fields.Monetary(compute='compute_amount',
|
||||
store=True, track_visibility='onchange',
|
||||
string="Payments")
|
||||
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True,
|
||||
readonly=True, compute='_amount_all',
|
||||
track_visibility='onchange')
|
||||
amount_tax = fields.Monetary(string='Taxes', store=True,
|
||||
readonly=True, compute='_amount_all')
|
||||
amount_total = fields.Monetary(string='Total', store=True, readonly=True,
|
||||
compute='_amount_all', track_visibility='always')
|
||||
|
||||
#Checkin Fields-----------------------------------------------------
|
||||
booking_pending = fields.Integer('Booking pending',
|
||||
compute='_compute_checkin_partner_count')
|
||||
checkin_partner_count = fields.Integer('Checkin counter',
|
||||
compute='_compute_checkin_partner_count')
|
||||
checkin_partner_pending_count = fields.Integer('Checkin Pending',
|
||||
compute='_compute_checkin_partner_count')
|
||||
partner_internal_comment = fields.Text(string='Internal Partner Notes',
|
||||
related='partner_id.comment')
|
||||
internal_comment = fields.Text(string='Internal Folio Notes')
|
||||
cancelled_reason = fields.Text('Cause of cancelled')
|
||||
payment_ids = fields.One2many('account.payment', 'folio_id',
|
||||
readonly=True)
|
||||
return_ids = fields.One2many('payment.return', 'folio_id',
|
||||
readonly=True)
|
||||
prepaid_warning_days = fields.Integer(
|
||||
'Prepaid Warning Days',
|
||||
help='Margin in days to create a notice if a payment \
|
||||
advance has not been recorded')
|
||||
reservation_type = fields.Selection([('normal', 'Normal'),
|
||||
('staff', 'Staff'),
|
||||
('out', 'Out of Service')],
|
||||
'Type', default=lambda *a: 'normal')
|
||||
channel_type = fields.Selection([('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone'),
|
||||
('web', 'Web')], 'Sales Channel', default='door')
|
||||
num_invoices = fields.Integer(compute='_compute_num_invoices')
|
||||
rooms_char = fields.Char('Rooms', compute='_computed_rooms_char')
|
||||
segmentation_ids = fields.Many2many('res.partner.category',
|
||||
string='Segmentation')
|
||||
has_confirmed_reservations_to_send = fields.Boolean(
|
||||
compute='_compute_has_confirmed_reservations_to_send')
|
||||
has_cancelled_reservations_to_send = fields.Boolean(
|
||||
compute='_compute_has_cancelled_reservations_to_send')
|
||||
has_checkout_to_send = fields.Boolean(
|
||||
compute='_compute_has_checkout_to_send')
|
||||
# fix_price = fields.Boolean(compute='_compute_fix_price')
|
||||
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)
|
||||
|
||||
#Invoice Fields-----------------------------------------------------
|
||||
hotel_invoice_id = fields.Many2one('account.invoice', 'Invoice')
|
||||
num_invoices = fields.Integer(compute='_compute_num_invoices')
|
||||
invoice_ids = fields.Many2many('account.invoice', string='Invoices',
|
||||
compute='_get_invoiced', readonly=True, copy=False)
|
||||
invoice_status = fields.Selection([('upselling', 'Upselling Opportunity'),
|
||||
@@ -156,17 +150,52 @@ class HotelFolio(models.Model):
|
||||
string='Invoice Status',
|
||||
compute='_compute_invoice_status',
|
||||
store=True, readonly=True, default='no')
|
||||
#~ partner_invoice_id = fields.Many2one('res.partner',
|
||||
#~ string='Invoice Address',
|
||||
#~ readonly=True, required=True,
|
||||
#~ states={'draft': [('readonly', False)],
|
||||
#~ 'sent': [('readonly', False)]},
|
||||
#~ help="Invoice address for current sales order.")
|
||||
|
||||
#WorkFlow Mail Fields-----------------------------------------------
|
||||
has_confirmed_reservations_to_send = fields.Boolean(
|
||||
compute='_compute_has_confirmed_reservations_to_send')
|
||||
has_cancelled_reservations_to_send = fields.Boolean(
|
||||
compute='_compute_has_cancelled_reservations_to_send')
|
||||
has_checkout_to_send = fields.Boolean(
|
||||
compute='_compute_has_checkout_to_send')
|
||||
|
||||
#Generic Fields-----------------------------------------------------
|
||||
internal_comment = fields.Text(string='Internal Folio Notes')
|
||||
cancelled_reason = fields.Text('Cause of cancelled')
|
||||
closure_reason_id = fields.Many2one('room.closure.reason')
|
||||
prepaid_warning_days = fields.Integer(
|
||||
'Prepaid Warning Days',
|
||||
help='Margin in days to create a notice if a payment \
|
||||
advance has not been recorded')
|
||||
rooms_char = fields.Char('Rooms', compute='_computed_rooms_char')
|
||||
segmentation_ids = fields.Many2many('res.partner.category',
|
||||
string='Segmentation')
|
||||
client_order_ref = fields.Char(string='Customer Reference', copy=False)
|
||||
note = fields.Text('Terms and conditions')
|
||||
# layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
|
||||
user_id = fields.Many2one('res.users', string='Salesperson', index=True,
|
||||
track_visibility='onchange', default=lambda self: self.env.user)
|
||||
|
||||
sequence = fields.Integer(string='Sequence', default=10)
|
||||
# sale.order
|
||||
amount_total = fields.Float(string='Total', store=True, readonly=True,
|
||||
track_visibility='always')
|
||||
|
||||
@api.depends('room_lines.price_total','service_ids.price_total')
|
||||
def _amount_all(self):
|
||||
"""
|
||||
Compute the total amounts of the SO.
|
||||
"""
|
||||
for record in self:
|
||||
amount_untaxed = amount_tax = 0.0
|
||||
amount_untaxed = sum(record.room_lines.mapped('price_subtotal')) + \
|
||||
sum(record.service_ids.mapped('price_subtotal'))
|
||||
amount_tax = sum(record.room_lines.mapped('price_tax')) + \
|
||||
sum(record.service_ids.mapped('price_tax'))
|
||||
record.update({
|
||||
'amount_untaxed': record.pricelist_id.currency_id.round(amount_untaxed),
|
||||
'amount_tax': record.pricelist_id.currency_id.round(amount_tax),
|
||||
'amount_total': amount_untaxed + amount_tax,
|
||||
})
|
||||
|
||||
def _computed_rooms_char(self):
|
||||
for record in self:
|
||||
@@ -317,7 +346,7 @@ class HotelFolio(models.Model):
|
||||
if 'company_id' in vals:
|
||||
vals['name'] = self.env['ir.sequence'].with_context(
|
||||
force_company=vals['company_id']
|
||||
).next_by_code('sale.order') or _('New')
|
||||
).next_by_code('hotel.folio') or _('New')
|
||||
else:
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio') or _('New')
|
||||
|
||||
|
||||
@@ -173,8 +173,7 @@ class HotelReservation(models.Model):
|
||||
service_ids = fields.One2many('hotel.service', 'ser_room_line')
|
||||
|
||||
pricelist_id = fields.Many2one('product.pricelist',
|
||||
related='folio_id.pricelist_id',
|
||||
readonly="1")
|
||||
related='folio_id.pricelist_id') #TODO: Warning Mens to update pricelist
|
||||
checkin_partner_ids = fields.One2many('hotel.checkin.partner', 'reservation_id')
|
||||
# TODO: As checkin_partner_count is a computed field, it can't not be used in a domain filer
|
||||
# Non-stored field hotel.reservation.checkin_partner_count cannot be searched
|
||||
@@ -259,6 +258,14 @@ class HotelReservation(models.Model):
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_reservation')
|
||||
price_services = fields.Monetary(string='Services Total',
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_room_services')
|
||||
price_room_services_set = fields.Monetary(string='Room Services Total',
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_set')
|
||||
# FIXME discount per night
|
||||
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
|
||||
|
||||
@@ -290,6 +297,7 @@ class HotelReservation(models.Model):
|
||||
board_services.append((0, False, {
|
||||
'product_id': product.id,
|
||||
'is_board_service': True,
|
||||
'folio_id': vals.get('folio_id'),
|
||||
}))
|
||||
vals.update({'service_ids': board_services})
|
||||
if self.compute_price_out_vals(vals):
|
||||
@@ -327,6 +335,7 @@ class HotelReservation(models.Model):
|
||||
board_services.append((0, False, {
|
||||
'product_id': product.id,
|
||||
'is_board_service': True,
|
||||
'folio_id': record.folio_id.id or vals.get('folio_id')
|
||||
}))
|
||||
# NEED REVIEW: Why I need add manually the old IDs if board service is (0,0,(-)) ¿?¿?¿
|
||||
record.update({'service_ids': [(6, 0, record.service_ids.ids)] + board_services})
|
||||
@@ -610,6 +619,7 @@ class HotelReservation(models.Model):
|
||||
vals = {
|
||||
'product_id': product.id,
|
||||
'is_board_service': True,
|
||||
'folio_id': self.folio_id.id,
|
||||
}
|
||||
vals.update(self.env['hotel.service'].prepare_service_lines(
|
||||
dfrom=self.checkin,
|
||||
@@ -619,8 +629,10 @@ class HotelReservation(models.Model):
|
||||
old_line_days=False))
|
||||
board_services.append((0, False, vals))
|
||||
other_services = self.service_ids.filtered(lambda r: r.is_board_service == False)
|
||||
|
||||
self.update({'service_ids': [(6, 0, other_services.ids)] + board_services})
|
||||
for service in self.service_ids.filtered(lambda r: r.is_board_service == True):
|
||||
service._compute_tax_ids()
|
||||
service.price_unit = service._compute_price_unit()
|
||||
|
||||
"""
|
||||
STATE WORKFLOW -----------------------------------------------------
|
||||
@@ -706,6 +718,16 @@ class HotelReservation(models.Model):
|
||||
"""
|
||||
PRICE PROCESS ------------------------------------------------------
|
||||
"""
|
||||
@api.depends('service_ids.price_total')
|
||||
def _compute_amount_room_services(self):
|
||||
for record in self:
|
||||
record.price_services = sum(record.mapped('service_ids.price_total'))
|
||||
|
||||
@api.depends('price_services','price_total')
|
||||
def _compute_amount_set(self):
|
||||
for record in self:
|
||||
record.price_room_services_set = record.price_services + record.price_total
|
||||
|
||||
@api.multi
|
||||
def compute_price_out_vals(self, vals):
|
||||
"""
|
||||
|
||||
@@ -5,6 +5,10 @@ from odoo import models, fields, api, _
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from datetime import timedelta
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.addons import decimal_precision as dp
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HotelService(models.Model):
|
||||
_name = 'hotel.service'
|
||||
@@ -50,31 +54,34 @@ class HotelService(models.Model):
|
||||
product_qty = fields.Integer('Quantity')
|
||||
days_qty = fields.Integer(compute="_compute_days_qty", store=True)
|
||||
is_board_service = fields.Boolean()
|
||||
pricelist_id = fields.Many2one(related='folio_id.pricelist_id')
|
||||
# Non-stored related field to allow portal user to see the image of the product he has ordered
|
||||
product_image = fields.Binary('Product Image', related="product_id.image", store=False)
|
||||
channel_type = fields.Selection([
|
||||
('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone'),
|
||||
('call', 'Call Center'),
|
||||
('web', 'Web')], 'Sales Channel')
|
||||
currency_id = fields.Many2one('res.currency',
|
||||
related='pricelist_id.currency_id',
|
||||
string='Currency', readonly=True, required=True)
|
||||
price_unit = fields.Float('Unit Price', required=True, digits=dp.get_precision('Product Price'), default=0.0)
|
||||
tax_ids = fields.Many2many('account.tax', string='Taxes', domain=['|', ('active', '=', False), ('active', '=', True)])
|
||||
discount = fields.Float(string='Discount (%)', digits=dp.get_precision('Discount'), default=0.0)
|
||||
currency_id = fields.Many2one(related='folio_id.currency_id', store=True, string='Currency', readonly=True)
|
||||
price_subtotal = fields.Monetary(string='Subtotal',
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_reservation')
|
||||
compute='_compute_amount_service')
|
||||
price_total = fields.Monetary(string='Total',
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_reservation')
|
||||
compute='_compute_amount_service')
|
||||
price_tax = fields.Float(string='Taxes',
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_reservation')
|
||||
compute='_compute_amount_service')
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
vals.update(self._prepare_add_missing_fields(vals))
|
||||
if self.compute_lines_out_vals(vals):
|
||||
reservation = self.env['hotel.reservation'].browse(vals['ser_room_line'])
|
||||
product = self.env['product.product'].browse(vals['product_id'])
|
||||
@@ -114,6 +121,20 @@ class HotelService(models.Model):
|
||||
res = super(HotelService, self).write(vals)
|
||||
return res
|
||||
|
||||
@api.model
|
||||
def _prepare_add_missing_fields(self, values):
|
||||
""" Deduce missing required fields from the onchange """
|
||||
res = {}
|
||||
onchange_fields = ['price_unit','tax_ids']
|
||||
if values.get('product_id'):
|
||||
line = self.new(values)
|
||||
if any(f not in values for f in onchange_fields):
|
||||
line.onchange_product_calc_qty()
|
||||
for field in onchange_fields:
|
||||
if field not in values:
|
||||
res[field] = line._fields[field].convert_to_write(line[field], line)
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def compute_lines_out_vals(self, vals):
|
||||
"""
|
||||
@@ -128,6 +149,26 @@ class HotelService(models.Model):
|
||||
return True
|
||||
return False
|
||||
|
||||
@api.multi
|
||||
def _compute_tax_ids(self):
|
||||
for record in self:
|
||||
# If company_id is set, always filter taxes by the company
|
||||
folio = self.folio_id or self.env.context.get('default_folio_id')
|
||||
record.tax_id = record.product_id.taxes_id.filtered(lambda r: not record.company_id or r.company_id == folio.company_id)
|
||||
|
||||
@api.multi
|
||||
def _get_display_price(self, product):
|
||||
folio = self.folio_id or self.env.context.get('default_folio_id')
|
||||
if folio.pricelist_id.discount_policy == 'with_discount':
|
||||
return product.with_context(pricelist=folio.pricelist_id.id).price
|
||||
product_context = dict(self.env.context, partner_id=folio.partner_id.id, date=folio.date_order, uom=self.product_id.uom_id.id)
|
||||
final_price, rule_id = folio.pricelist_id.with_context(product_context).get_product_price_rule(self.product_id, self.product_qty or 1.0, folio.partner_id)
|
||||
base_price, currency_id = self.with_context(product_context)._get_real_price_currency(product, rule_id, self.product_qty, product_id.uom_id, folio.pricelist_id.id)
|
||||
if currency_id != folio.pricelist_id.currency_id.id:
|
||||
base_price = self.env['res.currency'].browse(currency_id).with_context(product_context).compute(base_price, folio.pricelist_id.currency_id)
|
||||
# negative discounts (= surcharge) are included in the display price
|
||||
return max(base_price, final_price)
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_calc_qty(self):
|
||||
"""
|
||||
@@ -135,11 +176,15 @@ class HotelService(models.Model):
|
||||
configuration of the selected product, in per_day product configuration,
|
||||
the qty is autocalculated and readonly based on service_lines qty
|
||||
"""
|
||||
if not self.product_id:
|
||||
return
|
||||
vals = {}
|
||||
vals['product_qty'] = 1.0
|
||||
for record in self:
|
||||
if record.per_day and record.ser_room_line:
|
||||
product = record.product_id
|
||||
reservation = record.ser_room_line
|
||||
record.update(self.prepare_service_lines(
|
||||
vals.update(self.prepare_service_lines(
|
||||
dfrom=reservation.checkin,
|
||||
days=reservation.nights,
|
||||
per_person=product.per_person,
|
||||
@@ -148,6 +193,30 @@ class HotelService(models.Model):
|
||||
if record.product_id.daily_limit > 0:
|
||||
for day in record.service_line_ids:
|
||||
day.no_free_resources()
|
||||
"""
|
||||
Compute tax and price unit
|
||||
"""
|
||||
self._compute_tax_ids()
|
||||
vals['price_unit'] = self._compute_price_unit()
|
||||
record.update(vals)
|
||||
|
||||
@api.multi
|
||||
def _compute_price_unit(self):
|
||||
"""
|
||||
Compute tax and price unit
|
||||
"""
|
||||
folio = self.folio_id or self.env.context.get('default_folio_id')
|
||||
product = self.product_id.with_context(
|
||||
lang=folio.partner_id.lang,
|
||||
partner=folio.partner_id.id,
|
||||
quantity=self.product_qty,
|
||||
date=folio.date_order,
|
||||
pricelist=folio.pricelist_id.id,
|
||||
uom=self.product_id.uom_id.id,
|
||||
fiscal_position=False
|
||||
)
|
||||
return self.env['account.tax']._fix_tax_included_price_company(self._get_display_price(product), product.taxes_id, self.tax_ids, folio.company_id)
|
||||
|
||||
|
||||
@api.model
|
||||
def prepare_service_lines(self, **kwargs):
|
||||
@@ -155,16 +224,15 @@ class HotelService(models.Model):
|
||||
Prepare line and respect the old manual changes on lines
|
||||
"""
|
||||
cmds = [(5, 0, 0)]
|
||||
old_lines_days = kwargs.get('old_lines_days')
|
||||
old_line_days = kwargs.get('old_line_days')
|
||||
total_qty = 0
|
||||
day_qty = 1
|
||||
if kwargs.get('per_person'): #WARNING: Change adults in reservation NOT update qty service!!
|
||||
day_qty = kwargs.get('persons')
|
||||
old_line_days = self.env['hotel.service.line'].browse(kwargs.get('old_line_days'))
|
||||
for i in range(0, kwargs.get('days')):
|
||||
idate = (fields.Date.from_string(kwargs.get('dfrom')) + timedelta(days=i)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
if not old_lines_days or idate not in old_lines_days.mapped('date'):
|
||||
if not old_line_days or idate not in old_line_days.mapped('date'):
|
||||
cmds.append((0, False, {
|
||||
'date': idate,
|
||||
'day_qty': day_qty
|
||||
@@ -176,15 +244,16 @@ class HotelService(models.Model):
|
||||
total_qty = total_qty + old_line.day_qty
|
||||
return {'service_line_ids': cmds, 'product_qty': total_qty}
|
||||
|
||||
@api.depends('qty_product', 'tax_id')
|
||||
@api.depends('product_qty', 'discount', 'price_unit', 'tax_ids')
|
||||
def _compute_amount_service(self):
|
||||
"""
|
||||
Compute the amounts of the service line.
|
||||
"""
|
||||
for record in self:
|
||||
folio = record.folio_id or self.env.context.get('default_folio_id')
|
||||
product = record.product_id
|
||||
price = amount_room * (1 - (record.discount or 0.0) * 0.01)
|
||||
taxes = record.tax_id.compute_all(price, record.currency_id, 1, product=product)
|
||||
price = record.price_unit * (1 - (record.discount or 0.0) * 0.01)
|
||||
taxes = record.tax_ids.compute_all(price, folio.currency_id, record.product_qty, product=product)
|
||||
record.update({
|
||||
'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])),
|
||||
'price_total': taxes['total_included'],
|
||||
@@ -203,12 +272,20 @@ class HotelService(models.Model):
|
||||
else:
|
||||
vals = {'days_qty': 0}
|
||||
record.update(vals)
|
||||
|
||||
@api.multi
|
||||
def open_service_lines(self):
|
||||
action = self.env.ref('hotel.action_hotel_services_form').read()[0]
|
||||
action['views'] = [(self.env.ref('hotel.hotel_service_view_form').id, 'form')]
|
||||
action['res_id'] = self.id
|
||||
action['target'] = 'new'
|
||||
return action
|
||||
|
||||
@api.constrains('qty_product')
|
||||
def constrains_qty_per_day(self):
|
||||
for record in self:
|
||||
if record.per_day:
|
||||
service_lines = self.env['hotel.service_line']
|
||||
total_day_qty = sum(service_lines.with_context({'service_id': record.id}).mapped('day_qty'))
|
||||
if record.qty_product != total_day_qty:
|
||||
raise ValidationError (_('The quantity per line and per day does not correspond'))
|
||||
#~ @api.constrains('product_qty')
|
||||
#~ def constrains_qty_per_day(self):
|
||||
#~ for record in self:
|
||||
#~ if record.per_day:
|
||||
#~ service_lines = self.env['hotel.service_line']
|
||||
#~ total_day_qty = sum(service_lines.with_context({'service_id': record.id}).mapped('day_qty'))
|
||||
#~ if record.product_qty != total_day_qty:
|
||||
#~ raise ValidationError (_('The quantity per line and per day does not correspond'))
|
||||
|
||||
@@ -135,6 +135,7 @@
|
||||
<field name="cancelled_reason" attrs="{'invisible':[('state','not in',('cancel'))]}"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="pricelist_id" domain="[('type','=','sale')]" />
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<field name="reservation_type" attrs="{'readonly':[('state','not in',('draft'))]}"/>
|
||||
<field name="channel_type" attrs="{'required':[('reservation_type','=','normal')]}"/>
|
||||
@@ -149,14 +150,12 @@
|
||||
</group>
|
||||
</group>
|
||||
<group invisible="1">
|
||||
<!-- <field name="pricelist_id" domain="[('type','=','sale')]" invisible="1"/> -->
|
||||
<!-- <field name="partner_shipping_id" invisible="1" domain="[('parent_id','=',partner_id)]" /> -->
|
||||
<!-- <field name="warehouse_id" string="Branch" invisible="1"/> -->
|
||||
<field name="invoice_ids" invisible="1"/>
|
||||
<field name="invoice_status" invisible="1" />
|
||||
<!-- <field name="hotel_invoice_id" states='progress,done,cancel'
|
||||
readonly="1" invisible="1" /> -->
|
||||
<!-- <field name="fix_price" invisible="1" /> -->
|
||||
</group>
|
||||
<notebook colspan="4" col="1">
|
||||
<page string="Lines">
|
||||
@@ -287,19 +286,24 @@
|
||||
</h3>
|
||||
|
||||
<group col="6">
|
||||
<group string="General Info" name="contact_details" invisible="1">
|
||||
<group string="General Info" name="contact_details" >
|
||||
<field name="email" placeholder="email" widget="email" />
|
||||
<field name="mobile" placeholder="mobile" widget="phone" />
|
||||
<field name="phone" placeholder="phone" widget="phone" />
|
||||
<field name="pricelist_id"/>
|
||||
<field name="partner_internal_comment" string="Partner Note"/>
|
||||
<field name="cancelled_reason" attrs="{'invisible':[('state','not in',('cancelled'))]}"/>
|
||||
</group>
|
||||
<group colspan="4" string="Reservation Details" name="reservation_details">
|
||||
<field name="cancelled_reason" attrs="{'invisible':[('state','not in',('cancelled'))]}"/>
|
||||
<field name="arrival_hour"/>
|
||||
<field name="departure_hour"/>
|
||||
<field name="nights" invisible="1"/>
|
||||
<field name="board_service_id" />
|
||||
<field name="name"/>
|
||||
<field name="adults"/>
|
||||
<field name="children"/>
|
||||
<field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
|
||||
attrs="{'readonly':[('state','not in',('draft'))]}"/>
|
||||
attrs="{'readonly':[('state','not in',('draft'))]}"/>
|
||||
<field name="channel_type" attrs="{'required':[('reservation_type','not in',('staff','out'))]}"/>
|
||||
</group>
|
||||
<group class="oe_subtotal_footer" style="margin-right: 20px; !important" colspan="2" name="reservation_total" string="Amounts">
|
||||
@@ -331,8 +335,6 @@
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<!-- <field name="check_rooms" invisible="1"/> -->
|
||||
<field name="checkin_partner_pending_count" invisible="1"/>
|
||||
<!-- <field name="pricelist_id" invisible="1"/> -->
|
||||
<field name="nights" invisible="1"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="days" string="Service and Days">
|
||||
@@ -417,7 +419,6 @@
|
||||
domain="[('sale_ok', '=', True)]"
|
||||
options="{'create': False, 'create_edit': False}" />
|
||||
<field name="name"/>
|
||||
<field name="pricelist_id"/>
|
||||
<field name="ser_room_line" options="{'create': False, 'create_edit': False}"/>
|
||||
<!-- <field name="product_uom_qty"
|
||||
string="Ordered Qty"
|
||||
@@ -447,6 +448,15 @@
|
||||
<field name="channel_type" sttrs="{'invisible':[('channel_type', '!=', 'call')]"/>
|
||||
</tree>
|
||||
</field>
|
||||
<group colspan="2" class="oe_subtotal_footer oe_right">
|
||||
<field name="amount_untaxed" sum="Untaxed amount" widget='monetary' />
|
||||
<field name="amount_tax" widget='monetary' />
|
||||
<div class="oe_subtotal_footer_separator oe_inline">
|
||||
<label for="amount_total" />
|
||||
</div>
|
||||
<field name="amount_total" nolabel="1" sum="Total amount"
|
||||
widget='monetary' />
|
||||
</group>
|
||||
<div class="oe_clear" />
|
||||
<group>
|
||||
<field name="note" />
|
||||
|
||||
@@ -183,18 +183,18 @@
|
||||
<field name="email" placeholder="email" widget="email" />
|
||||
<field name="mobile" placeholder="mobile" widget="phone" />
|
||||
<field name="phone" placeholder="phone" widget="phone" />
|
||||
<field name="pricelist_id"/>
|
||||
<field name="partner_internal_comment" string="Partner Note"/>
|
||||
<field name="cancelled_reason" attrs="{'invisible':[('state','not in',('cancelled'))]}"/>
|
||||
</group>
|
||||
<group colspan="4" string="Reservation Details" name="reservation_details">
|
||||
<field name="arrival_hour"/>
|
||||
<field name="departure_hour"/>
|
||||
<field name="nights" invisible="1"/>
|
||||
<field name="board_service_id" />
|
||||
<field name="name"/>
|
||||
<field name="adults"/>
|
||||
<field name="children"/>
|
||||
<!-- <field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
|
||||
attrs="{'readonly':[('state','not in',('draft'))]}"/> -->
|
||||
<field name="room_type_id" on_change="1" options="{'no_create': True,'no_open': True}"
|
||||
attrs="{'readonly':[('state','not in',('draft'))]}"/>
|
||||
<field name="channel_type" attrs="{'required':[('reservation_type','not in',('staff','out'))]}"/>
|
||||
@@ -224,6 +224,8 @@
|
||||
<!-- <field name="customer_lead" invisible="1"/> -->
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="price_subtotal" widget="monetary"/>
|
||||
<field name="price_services" widget="monetary"/>
|
||||
<field name="price_room_services_set" widget="monetary"/>
|
||||
</group>
|
||||
</group>
|
||||
<field name="folio_internal_comment" nolabel="1" placeholder="Reservation Notes"/>
|
||||
@@ -231,29 +233,36 @@
|
||||
<field name="company_id" options="{'no_create': True}" groups="base.group_multi_company"/>
|
||||
<!-- <field name="check_rooms" invisible="1"/> -->
|
||||
<field name="checkin_partner_pending_count" invisible="1"/>
|
||||
<!-- <field name="pricelist_id" invisible="1"/> -->
|
||||
<field name="nights" invisible="1"/>
|
||||
<!-- <field name="product_uom" string="Rent(UOM)" invisible="1" /> -->
|
||||
</group>
|
||||
<notebook>
|
||||
<page name="days" string="Service and Days">
|
||||
<group col="9">
|
||||
<group colspan="6" string="Reservation Services" name="reservation_services" attrs="{'invisible': [('folio_id','=',False)]}">
|
||||
<group string="Reservation Services" name="reservation_services" attrs="{'invisible': [('folio_id','=',False)]}">
|
||||
<field name="service_ids"
|
||||
nolabel="1" style="padding-right:10px !important;">
|
||||
<!-- <field name="service_ids"> -->
|
||||
<tree string="Services">
|
||||
context="{'default_ser_room_line': active_id, 'default_folio_id': folio_id}"
|
||||
nolabel="1">
|
||||
<tree string="Services" editable="bottom">
|
||||
<!-- <field name="sequence" widget="handle"/> -->
|
||||
<field name="per_day" />
|
||||
<field name="is_board_service" />
|
||||
<field name="per_day" invisible="1"/>
|
||||
<field name="is_board_service" invisible="1" />
|
||||
<field name="folio_id" invisible="1"/>
|
||||
<field name="product_id"
|
||||
domain="[('sale_ok', '=', True)]"
|
||||
options="{'create': False, 'create_edit': False}" />
|
||||
<!-- <field name="layout_category_id" groups="sale.group_sale_layout"/> -->
|
||||
<field name="name"/>
|
||||
<field name="product_qty" attrs="{'readonly': [('per_day','=',True)]}" force_save="1"/>
|
||||
<field name="days_qty" />
|
||||
<field name="product_qty" attrs="{'readonly': [('per_day','=',True)]}" force_save="1"/>
|
||||
<button type="object" class="oe_stat_button"
|
||||
id="go_service_lines" icon="fa fa-2x fa-bars"
|
||||
name="open_service_lines"
|
||||
attrs="{'invisible': [('per_day','=',False)]}"/>
|
||||
<field name="days_qty" invisible="1"/>
|
||||
<field name="price_unit" />
|
||||
<field name="discount" />
|
||||
<field name="tax_ids" widget="many2many_tags"/>
|
||||
<field name="price_subtotal" />
|
||||
<field name="price_tax" />
|
||||
<field name="price_total" />
|
||||
<field name="service_line_ids" invisible="1">
|
||||
<tree string="Days" >
|
||||
<field name="date" />
|
||||
@@ -261,26 +270,9 @@
|
||||
</tree>
|
||||
</field>
|
||||
</tree>
|
||||
<form string="Services">
|
||||
<!-- <field name="sequence" widget="handle"/> -->
|
||||
<field name="per_day" />
|
||||
<field name="folio_id" invisible="1"/>
|
||||
<field name="product_id"
|
||||
domain="[('sale_ok', '=', True)]"
|
||||
options="{'create': False, 'create_edit': False}" />
|
||||
<!-- <field name="layout_category_id" groups="sale.group_sale_layout"/> -->
|
||||
<field name="name"/>
|
||||
<field name="product_qty" attrs="{'readonly': [('per_day','=',True)]}" force_save="1"/>
|
||||
<field name="service_line_ids">
|
||||
<tree string="Days" >
|
||||
<field name="date" />
|
||||
<field name="day_qty" />
|
||||
</tree>
|
||||
</field>
|
||||
</form>
|
||||
</field>
|
||||
</group>
|
||||
<group colspan="4" string="Days" name="days">
|
||||
<group string="Days" name="days">
|
||||
<field name="reservation_line_ids" nolabel="1">
|
||||
<tree create="false" delete="false" editable="bottom">
|
||||
<field name="date" />
|
||||
@@ -289,7 +281,6 @@
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page name="others" string="Others">
|
||||
<group>
|
||||
|
||||
@@ -9,19 +9,29 @@
|
||||
<form string="Reservation Service">
|
||||
<sheet>
|
||||
<h1>
|
||||
<label string="Service" />
|
||||
<field name="name" select="1" />
|
||||
<field name="name" readonly="1"/>
|
||||
</h1>
|
||||
<group>
|
||||
<field name="product_id" />
|
||||
<field name="product_qty" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="price_subtotal" />
|
||||
<field name="price_total" />
|
||||
</group>
|
||||
<field name="service_line_ids">
|
||||
<tree string="Days" >
|
||||
<field name="per_day" invisible="1"/>
|
||||
<field name="is_board_service" invisible="1" />
|
||||
<field name="folio_id" invisible="1"/>
|
||||
<field name="product_id"
|
||||
domain="[('sale_ok', '=', True)]"
|
||||
options="{'create': False, 'create_edit': False}"
|
||||
invisible="1" />
|
||||
<!-- <field name="layout_category_id" groups="sale.group_sale_layout"/> -->
|
||||
<field name="product_qty" invisible="0"
|
||||
attrs="{'readonly': [('per_day','=',True)]}"
|
||||
force_save="1"/>
|
||||
<field name="days_qty" invisible="0"/>
|
||||
<field name="price_unit" invisible="1" />
|
||||
<field name="discount" invisible="1"/>
|
||||
<field name="tax_ids" widget="many2many_tags"
|
||||
invisible="1"/>
|
||||
<field name="price_subtotal" invisible="1"/>
|
||||
<field name="price_tax" invisible="1"/>
|
||||
<field name="price_total" invisible="1"/>
|
||||
<field name="service_line_ids" nolabel="1">
|
||||
<tree string="Days" editable="bottom" >
|
||||
<field name="date" />
|
||||
<field name="day_qty" />
|
||||
</tree>
|
||||
@@ -68,36 +78,10 @@
|
||||
<field name="name">Hotel Services</field>
|
||||
<field name="res_model">hotel.service</field>
|
||||
<field name="view_type">form</field>
|
||||
<!-- <field name="context">{'default_isservice':1}
|
||||
</field> -->
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Services" id="menu_open_hotel_services_form"
|
||||
action="action_hotel_services_form" sequence="8"
|
||||
parent="hotel.menu_hotel_service" />
|
||||
|
||||
<!-- Categories for Services -->
|
||||
|
||||
<record model="ir.actions.act_window" id="hotel_service_category_action">
|
||||
<field name="name">Services by Category</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">product.category</field>
|
||||
<!-- <field name="domain">[('parent_id','=',False),('isservicetype','=',True)]
|
||||
</field> -->
|
||||
<field name="view_type">tree</field>
|
||||
<!-- <field name="view_id" ref="product_category_tree_view" /> -->
|
||||
</record>
|
||||
|
||||
<!--record id="ir_service_category_open" model='ir.default'>
|
||||
<field eval="'tree_but_open'" name="key2"/>
|
||||
<field eval="'product.category'" name="model"/>
|
||||
<field name="name">Services</field>
|
||||
<field eval="'ir.actions.act_window,%d'%action_room_cate" name="value"/>
|
||||
</record-->
|
||||
|
||||
<menuitem name="Services by Type" id="menu_hotel_service_category_action"
|
||||
action="hotel_service_category_action" sequence="10"
|
||||
parent="hotel.menu_hotel_service" />
|
||||
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user