mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
[DEV] Board Service, Wizard refact, hotes services configuration, services lines by day
This commit is contained in:
@@ -44,14 +44,16 @@
|
||||
'views/hotel_room_type.xml',
|
||||
'views/hotel_room.xml',
|
||||
'views/hotel_room_type_class.xml',
|
||||
'views/general.xml',
|
||||
# 'views/hotel_service.xml',
|
||||
'views/inherit_product_product.xml',
|
||||
'views/inherit_product_template.xml',
|
||||
'views/hotel_room_amenities_type.xml',
|
||||
'views/hotel_room_amenities.xml',
|
||||
'views/hotel_room_type_restriction_views.xml',
|
||||
'views/hotel_room_type_restriction_item_views.xml',
|
||||
'views/hotel_reservation.xml',
|
||||
'views/room_closure_reason.xml',
|
||||
'views/hotel_board_service.xml',
|
||||
# 'views/room_type_views.xml',
|
||||
'views/cardex.xml',
|
||||
'views/hotel_room_type_availability.xml',
|
||||
@@ -61,6 +63,7 @@
|
||||
'data/email_template_cancel.xml',
|
||||
'data/email_template_reserv.xml',
|
||||
'data/email_template_exit.xml',
|
||||
'wizard/wizard_reservation.xml',
|
||||
],
|
||||
'css': ['static/src/css/room_kanban.css'],
|
||||
'auto_install': False,
|
||||
|
||||
@@ -15,7 +15,7 @@ from . import hotel_room_type
|
||||
from . import hotel_service
|
||||
from . import inherit_account_invoice
|
||||
# from . import inherit_product_category
|
||||
from . import inherit_product_product
|
||||
from . import inherit_product_template
|
||||
from . import inherit_res_company
|
||||
# from . import room_type
|
||||
from . import inherit_account_payment
|
||||
@@ -30,4 +30,6 @@ from . import inherit_res_partner
|
||||
from . import inherited_mail_compose_message
|
||||
from . import hotel_room_type_class
|
||||
from . import room_closure_reason
|
||||
from . import hotel_service_line
|
||||
from . import hotel_board_service
|
||||
#~ from . import hotel_dashboard
|
||||
|
||||
15
hotel/models/hotel_board_service.py
Normal file
15
hotel/models/hotel_board_service.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from openerp import models, fields, api, _
|
||||
|
||||
|
||||
class HotelBoardService(models.Model):
|
||||
_name = "hotel.board.service"
|
||||
_description = "Board Services"
|
||||
|
||||
name = fields.Char('Board Name', size=64, required=True, index=True)
|
||||
service_ids = fields.Many2many(comodel_name='product.template',
|
||||
relation='board_services_room',
|
||||
column1='board_id',
|
||||
column2='service_id')
|
||||
sequence = fields.Integer('Sequence', size=64)
|
||||
@@ -353,13 +353,12 @@ class HotelFolio(models.Model):
|
||||
#~ })
|
||||
return
|
||||
addr = self.partner_id.address_get(['invoice'])
|
||||
values = {'user_id': self.partner_id.user_id.id or self.env.uid,
|
||||
'pricelist_id':self.partner_id.property_product_pricelist and \
|
||||
pricelist = self.partner_id.property_product_pricelist and \
|
||||
self.partner_id.property_product_pricelist.id or \
|
||||
self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id'),
|
||||
'reservation_type': self.env['hotel.folio'].calcule_reservation_type(
|
||||
self.partner_id.is_staff,
|
||||
self.reservation_type)}
|
||||
self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id')
|
||||
values = {'user_id': self.partner_id.user_id.id or self.env.uid,
|
||||
'pricelist_id': pricelist
|
||||
}
|
||||
if self.env['ir.config_parameter'].sudo().get_param('sale.use_sale_note') and \
|
||||
self.env.user.company_id.sale_note:
|
||||
values['note'] = self.with_context(
|
||||
@@ -369,6 +368,15 @@ class HotelFolio(models.Model):
|
||||
values['team_id'] = self.partner_id.team_id.id
|
||||
self.update(values)
|
||||
|
||||
@api.multi
|
||||
@api.onchange('pricelist_id')
|
||||
def onchange_pricelist_id(self):
|
||||
values = {'reservation_type': self.env['hotel.folio'].calcule_reservation_type(
|
||||
self.pricelist_id.is_staff,
|
||||
self.reservation_type)}
|
||||
self.update(values)
|
||||
|
||||
|
||||
@api.model
|
||||
def calcule_reservation_type(self, is_staff, current_type):
|
||||
if current_type == 'out':
|
||||
|
||||
@@ -196,6 +196,7 @@ class HotelReservation(models.Model):
|
||||
parent_reservation = fields.Many2one('hotel.reservation',
|
||||
'Parent Reservation')
|
||||
overbooking = fields.Boolean('Is Overbooking', default=False)
|
||||
reselling = fields.Boolean('Is Reselling', default=False)
|
||||
|
||||
nights = fields.Integer('Nights', compute='_computed_nights', store=True)
|
||||
channel_type = fields.Selection([
|
||||
@@ -408,6 +409,7 @@ class HotelReservation(models.Model):
|
||||
'parent_reservation': self.parent_reservation.id,
|
||||
'state': self.state,
|
||||
'overbooking': self.overbooking,
|
||||
'reselling': self.reselling,
|
||||
'price_unit': self.price_unit,
|
||||
'splitted': self.splitted,
|
||||
# 'room_type_id': self.room_type_id.id,
|
||||
@@ -444,16 +446,22 @@ class HotelReservation(models.Model):
|
||||
@api.onchange('partner_id')
|
||||
def onchange_partner_id(self):
|
||||
#TODO: Change parity pricelist by default pricelist
|
||||
values = {
|
||||
'pricelist_id': self.partner_id.property_product_pricelist and \
|
||||
pricelist = self.partner_id.property_product_pricelist and \
|
||||
self.partner_id.property_product_pricelist.id or \
|
||||
self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id'),
|
||||
'reservation_type': self.env['hotel.folio'].calcule_reservation_type(
|
||||
self.partner_id.is_staff,
|
||||
self.reservation_type)
|
||||
self.env['ir.default'].sudo().get('res.config.settings', 'parity_pricelist_id')
|
||||
values = {
|
||||
'pricelist_id': pricelist,
|
||||
}
|
||||
self.update(values)
|
||||
|
||||
@api.multi
|
||||
@api.onchange('pricelist_id')
|
||||
def onchange_pricelist_id(self):
|
||||
values = {'reservation_type': self.env['hotel.folio'].calcule_reservation_type(
|
||||
self.pricelist_id.is_staff,
|
||||
self.reservation_type)}
|
||||
self.update(values)
|
||||
|
||||
@api.onchange('reservation_type')
|
||||
def assign_partner_company_on_out_service(self):
|
||||
if self.reservation_type == 'out':
|
||||
@@ -507,7 +515,7 @@ class HotelReservation(models.Model):
|
||||
def onchange_room_availabiltiy_domain(self):
|
||||
self.ensure_one()
|
||||
if self.checkin and self.checkout:
|
||||
if self.overbooking:
|
||||
if self.overbooking or self.reselling:
|
||||
return
|
||||
occupied = self.env['hotel.reservation'].get_reservations(
|
||||
self.checkin,
|
||||
@@ -842,7 +850,8 @@ class HotelReservation(models.Model):
|
||||
domain = [('reservation_line_ids.date', '>=', dfrom),
|
||||
('reservation_line_ids.date', '<', dto),
|
||||
('state', '!=', 'cancelled'),
|
||||
('overbooking', '=', False)]
|
||||
('overbooking', '=', False),
|
||||
('reselling', '=', False),]
|
||||
return domain
|
||||
|
||||
@api.model
|
||||
@@ -872,7 +881,7 @@ class HotelReservation(models.Model):
|
||||
return reservations_dates
|
||||
|
||||
# TODO: Use default values on checkin /checkout is empty
|
||||
@api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking')
|
||||
@api.constrains('checkin', 'checkout', 'state', 'room_id', 'overbooking', 'reselling')
|
||||
def check_dates(self):
|
||||
"""
|
||||
1.-When date_order is less then checkin date or
|
||||
|
||||
@@ -10,18 +10,6 @@ _logger = logging.getLogger(__name__)
|
||||
class HotelService(models.Model):
|
||||
_name = 'hotel.service'
|
||||
_description = 'Hotel Services and its charges'
|
||||
|
||||
@api.model
|
||||
def _service_checkin(self):
|
||||
if 'checkin' in self._context:
|
||||
return self._context['checkin']
|
||||
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
@api.model
|
||||
def _service_checkout(self):
|
||||
if 'checkout' in self._context:
|
||||
return self._context['checkout']
|
||||
return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
@api.model
|
||||
def _default_ser_room_line(self):
|
||||
@@ -33,42 +21,116 @@ class HotelService(models.Model):
|
||||
return False
|
||||
|
||||
name = fields.Char('Service description')
|
||||
# services in the hotel are products
|
||||
product_id = fields.Many2one('product.product', 'Service', required=True)
|
||||
|
||||
folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade')
|
||||
|
||||
product_id = fields.Many2one('product.product', 'Service',
|
||||
required=True)
|
||||
folio_id = fields.Many2one('hotel.folio', 'Folio',
|
||||
ondelete='cascade')
|
||||
ser_room_line = fields.Many2one('hotel.reservation', 'Room',
|
||||
default=_default_ser_room_line)
|
||||
|
||||
list_price = fields.Float(
|
||||
related='product_id.list_price')
|
||||
|
||||
service_line_ids = fields.One2many('hotel.service.line',
|
||||
'service_id')
|
||||
product_qty = fields.Integer('Quantity')
|
||||
pricelist_id = fields.Many2one(
|
||||
related='folio_id.pricelist_id')
|
||||
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_subtotal = fields.Monetary(string='Subtotal',
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_reservation')
|
||||
price_total = fields.Monetary(string='Total',
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_reservation')
|
||||
price_tax = fields.Float(string='Taxes',
|
||||
readonly=True,
|
||||
store=True,
|
||||
compute='_compute_amount_reservation')
|
||||
|
||||
ser_checkin = fields.Datetime('From Date', required=True,
|
||||
default=_service_checkin)
|
||||
ser_checkout = fields.Datetime('To Date', required=True,
|
||||
default=_service_checkout)
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_calc_qty(self):
|
||||
"""
|
||||
Compute the default quantity according to the
|
||||
configuration of the selected product
|
||||
"""
|
||||
for record in self:
|
||||
product = record.product_id
|
||||
reservation = record.ser_room_line
|
||||
if product and reservation:
|
||||
qty = 1
|
||||
if product.per_day:
|
||||
qty = qty * reservation.nights
|
||||
if product.per_person:
|
||||
qty = qty * (reservation.adults + reservation.children)
|
||||
record.product_qty = qty
|
||||
|
||||
@api.onchange('product_qty')
|
||||
def onchange_product_qty_days_selection(self):
|
||||
"""
|
||||
Try to calculate the days on which the product
|
||||
should be served as long as the product is per day
|
||||
"""
|
||||
for record in self:
|
||||
reservation = record.ser_room_line
|
||||
if record.product_id.per_day:
|
||||
days_diff = (
|
||||
fields.Date.from_string(reservation.checkout) - fields.Date.from_string(reservation.checkin)
|
||||
).days
|
||||
record.update(record.prepare_service_lines(
|
||||
reservation.checkin,
|
||||
days_diff))
|
||||
else:
|
||||
record.update(rec.prepare_service_lines(
|
||||
reservation.checkin, 1))
|
||||
|
||||
|
||||
# TODO Hierarchical relationship for parent-child tree
|
||||
# parent_id = fields.Many2one ...
|
||||
@api.multi
|
||||
def prepare_service_lines(self, dfrom, days, vals=False):
|
||||
self.ensure_one()
|
||||
old_qty = 0
|
||||
cmds = [(5, 0, 0)]
|
||||
if not vals:
|
||||
vals
|
||||
product = vals.get('product_id') or self.product_id
|
||||
old_lines_days = self.mapped('service_line_ids.date')
|
||||
for day in service_line_ids:
|
||||
old_qty = old_qty + day.day_qty
|
||||
qty_day = (self.product_qty - old_qty) // (days - count(old_line_days))
|
||||
rest_day = (self.product_qty - old_qty) % (days - count(old_line_days))
|
||||
reservation = rec.ser_room_line
|
||||
for i in range(0, days):
|
||||
idate = (fields.Date.from_string(dfrom) + timedelta(days=i)).strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
old_line = self.service_line_ids.filtered(lambda r: r.date == idate)
|
||||
if idate not in old_lines_days:
|
||||
cmds.append((0, False, {
|
||||
'date': idate,
|
||||
'day_qty': qty
|
||||
}))
|
||||
else:
|
||||
cmds.append((4, old_line.id))
|
||||
return {'service_line_ids': cmds}
|
||||
|
||||
# service_id = fields.Many2one('product.product', 'Service_id',
|
||||
# required=True, ondelete='cascade',
|
||||
# delegate=True)
|
||||
# service_type_id = fields.Many2one('hotel.service.type',
|
||||
# 'Service Catagory')
|
||||
# service_line_id = fields.Many2one('hotel.service.line',
|
||||
# 'Service Line')
|
||||
# @api.multi
|
||||
# def unlink(self):
|
||||
# # for record in self:
|
||||
# # record.service_id.unlink()
|
||||
# return super(HotelServices, self).unlink()
|
||||
@api.depends('qty_product', 'tax_id')
|
||||
def _compute_amount_service(self):
|
||||
"""
|
||||
Compute the amounts of the service line.
|
||||
"""
|
||||
for record in self:
|
||||
product = rec.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)
|
||||
record.update({
|
||||
'price_tax': sum(t.get('amount', 0.0) for t in taxes.get('taxes', [])),
|
||||
'price_total': taxes['total_included'],
|
||||
'price_subtotal': taxes['total_excluded'],
|
||||
})
|
||||
|
||||
|
||||
|
||||
32
hotel/models/hotel_service_line.py
Normal file
32
hotel/models/hotel_service_line.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# Copyright 2017-2018 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from odoo import models, fields
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
class HotelServiceLine(models.Model):
|
||||
_name = "hotel.service.line"
|
||||
_order = "date"
|
||||
|
||||
service_id = fields.Many2one('hotel.service', string='Service',
|
||||
ondelete='cascade', required=True,
|
||||
copy=False)
|
||||
date = fields.Date('Date')
|
||||
day_qty = fields.Integer('Units')
|
||||
product_id = fields.Many2one(related='service_id.product_id')
|
||||
|
||||
@api.constrains('day_qty')
|
||||
def no_free_resources(self):
|
||||
for record in self:
|
||||
limit = record.product_id.daily_limit
|
||||
if limit > 0:
|
||||
out_qty = sum(self.env['hotel.service.line'].search([(
|
||||
'product_id','=',record.product_id,
|
||||
'date','=',record.date)]).mapped('day_qty'))
|
||||
if limit < out_qty + record.day_qty:
|
||||
raise ValidationError(
|
||||
_("Limit exceeded for %s")% record.date)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from openerp import models, api
|
||||
from openerp import models, fields, api, _
|
||||
|
||||
|
||||
class ProductPricelist(models.Model):
|
||||
@@ -21,3 +21,6 @@ class ProductPricelist(models.Model):
|
||||
else:
|
||||
names.append((name[0], name[1]))
|
||||
return names
|
||||
|
||||
|
||||
is_staff = fields.Boolean('Is Staff')
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from openerp import models, fields, api, _
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = "product.product"
|
||||
|
||||
is_room_type = fields.Boolean('Is a Room Type', default=False)
|
||||
# iscategid = fields.Boolean('Is categ id')
|
||||
# isservice = fields.Boolean('Is Service id')
|
||||
13
hotel/models/inherit_product_template.py
Normal file
13
hotel/models/inherit_product_template.py
Normal file
@@ -0,0 +1,13 @@
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from openerp import models, fields, api, _
|
||||
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = "product.template"
|
||||
|
||||
is_hotel_service = fields.Boolean('Is a Hotel Service', default=False)
|
||||
per_day = fields.Boolean('Unit increment per day')
|
||||
per_person = fields.Boolean('Unit increment per person')
|
||||
daily_limit = fields.Integer('Daily limit')
|
||||
@@ -24,7 +24,6 @@ class ResPartner(models.Model):
|
||||
reservations_count = fields.Integer('Reservations',
|
||||
compute='_compute_reservations_count')
|
||||
folios_count = fields.Integer('Folios', compute='_compute_folios_count')
|
||||
is_staff = fields.Boolean('Is Staff')
|
||||
|
||||
""" TODO
|
||||
@api.onchange('is_staff')
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
// License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
odoo.define('hotel_calendar.listview_button_open_reservation_wizard', function(require) {
|
||||
'use strict';
|
||||
|
||||
var ListView = require('web.ListView'),
|
||||
Core = require('web.core'),
|
||||
|
||||
_t = Core._t;
|
||||
|
||||
|
||||
ListView.include({
|
||||
render_buttons: function () {
|
||||
var self = this;
|
||||
this._super.apply(this, arguments); // Sets this.$buttons
|
||||
|
||||
if (this.dataset.model == 'hotel.reservation') {
|
||||
this.$buttons.append("<button class='oe_button oe_open_reservation_wizard oe_highlight' type='button'>"+_t('Open Wizard')+"</button>");
|
||||
this.$buttons.find('.oe_open_reservation_wizard').on('click', function(){
|
||||
self.do_action('hotel_calendar.open_wizard_reservations');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ListView;
|
||||
|
||||
});
|
||||
11
hotel/views/general.xml
Normal file
11
hotel/views/general.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Backend stuff -->
|
||||
<template id="assets_backend" inherit_id="web.assets_backend">
|
||||
<xpath expr="." position="inside">
|
||||
<script type="text/javascript" src="/hotel/static/src/js/open_reservation_wizard_listview_button.js"></script>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</odoo>
|
||||
48
hotel/views/hotel_board_service.xml
Normal file
48
hotel/views/hotel_board_service.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!--==================================================== Board Services ==================================================== -->
|
||||
<!-- Form view of hotel board service -->
|
||||
<record model="ir.ui.view" id="view_hotel_board_service_form">
|
||||
<field name="name">hotel.board.service.form</field>
|
||||
<field name="model">hotel.board.service</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Hotel Board Service">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name" select="1" />
|
||||
<field name="sequence" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="service_ids" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Tree view of hotel floor -->
|
||||
<record model="ir.ui.view" id="view_hotel_board_service_tree">
|
||||
<field name="name">hotel.board.service.tree</field>
|
||||
<field name="model">hotel.board.service</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Hotel Board Services">
|
||||
<field name="name" />
|
||||
<field name="sequence" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action of hotel floor -->
|
||||
<record model="ir.actions.act_window" id="open_hotel_board_service_form_tree">
|
||||
<field name="name">Board Services</field>
|
||||
<field name="res_model">hotel.board.service</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Board Services" id="menu_open_hotel_board_service_form_tree"
|
||||
action="open_hotel_board_service_form_tree" sequence="25"
|
||||
parent="hotel.configuration_others" />
|
||||
|
||||
</odoo>
|
||||
@@ -414,10 +414,10 @@
|
||||
<field name="folio_id" invisible="1"/>
|
||||
<!-- <field name="layout_category_id" groups="sale.group_sale_layout"/> -->
|
||||
<field name="product_id"
|
||||
domain="[('sale_ok', '=', True),('is_room_type', '=', False)]"
|
||||
domain="[('sale_ok', '=', True)]"
|
||||
options="{'create': False, 'create_edit': False}" />
|
||||
<field name="name"/>
|
||||
<field name="list_price"/>
|
||||
<field name="pricelist_id"/>
|
||||
<field name="ser_room_line" options="{'create': False, 'create_edit': False}"/>
|
||||
<!-- <field name="product_uom_qty"
|
||||
string="Ordered Qty"
|
||||
|
||||
@@ -167,8 +167,7 @@
|
||||
<field name="closure_reason_id" default_focus="1"
|
||||
options="{'no_create': True,'no_open': True}"
|
||||
placeholder="Closure Reason"
|
||||
attrs="{'invisible':[('reservation_type','not in',('out'))]}"
|
||||
required="1"/>
|
||||
attrs="{'invisible':[('reservation_type','not in',('out'))]}"/>
|
||||
<span class="fa fa-user" style="margin-left:20px;"
|
||||
attrs="{'invisible': [('reservation_type','not in',('normal'))]}"/>
|
||||
<span class="fa fa-black-tie" style="margin-left:20px; color: #C67;"
|
||||
@@ -251,11 +250,11 @@
|
||||
<!-- <field name="sequence" widget="handle"/> -->
|
||||
<field name="folio_id" invisible="1"/>
|
||||
<field name="product_id"
|
||||
domain="[('sale_ok', '=', True),('is_room_type', '=', False)]"
|
||||
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="list_price"/>
|
||||
<field name="pricelist_id"/>
|
||||
<!-- <field name="ser_room_line" invisible="1" /> -->
|
||||
<!-- <field name="qty_delivered" invisible="1"
|
||||
attrs="{'readonly': [('qty_delivered_updateable', '=', False)]}"/> -->
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_product_product_form_inherited" model="ir.ui.view">
|
||||
<field name="name">view.product.product.form.inherited</field>
|
||||
<field name="model">product.product</field>
|
||||
<field name="inherit_id" ref="product.product_normal_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='active']" position="after">
|
||||
<field name="is_room_type" />
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
26
hotel/views/inherit_product_template.xml
Normal file
26
hotel/views/inherit_product_template.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_product_template_form_inherited" model="ir.ui.view">
|
||||
<field name="name">view.product.template.form.inherited</field>
|
||||
<field name="model">product.template</field>
|
||||
<field name="inherit_id" ref="product.product_template_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//page[@name='sales']" position="before">
|
||||
<page string="Hotel Service">
|
||||
<group colspan="4">
|
||||
<group>
|
||||
<field name="is_hotel_service" />
|
||||
<field name="daily_limit" />
|
||||
</group>
|
||||
<group>
|
||||
<field name="per_day" />
|
||||
<field name="per_person" />
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
@@ -28,3 +28,4 @@ from . import massive_changes
|
||||
from . import split_reservation
|
||||
from . import duplicate_reservation
|
||||
from . import massive_price_reservation_days
|
||||
from . import wizard_reservation
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</record>
|
||||
|
||||
<!--Action for folio report wizard -->
|
||||
<record model="ir.actions.act_window" id="hotel_folio_wizard">
|
||||
<record model="ir.actions.act_window" id="hotel_report_folio_wizard">
|
||||
<field name="name">Hotel Folio Report</field>
|
||||
<field name="res_model">folio.report.wizard</field>
|
||||
<field name="view_type">form</field>
|
||||
@@ -29,7 +29,7 @@
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- <menuitem name="Hotel Folio Report" action="hotel_folio_wizard"
|
||||
<!-- <menuitem name="Hotel Folio Report" action="hotel_report_folio_wizard"
|
||||
id="wizard_hotel_menu" parent="hotel_report_menu" sequence="31" />-->
|
||||
|
||||
</odoo>
|
||||
|
||||
496
hotel/wizard/wizard_reservation.py
Normal file
496
hotel/wizard/wizard_reservation.py
Normal file
@@ -0,0 +1,496 @@
|
||||
# Copyright 2018 Dario Lodeiros
|
||||
# Copyright 2018 Alexandre Díaz <dev@redneboa.es>
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
import time
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from openerp.exceptions import ValidationError
|
||||
from openerp.tools import (
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from openerp import models, fields, api, _
|
||||
import odoo.addons.decimal_precision as dp
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FolioWizard(models.TransientModel):
|
||||
_name = 'hotel.folio.wizard'
|
||||
|
||||
@api.model
|
||||
def _get_default_center_user(self):
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
return user.has_group('hotel.group_hotel_call')
|
||||
|
||||
@api.model
|
||||
def _get_default_checkin(self):
|
||||
folio = False
|
||||
if 'folio_id' in self._context:
|
||||
folio = self.env['hotel.folio'].search([
|
||||
('id', '=', self._context['folio_id'])
|
||||
])
|
||||
if folio and folio.room_lines:
|
||||
return folio.room_lines[0].checkin
|
||||
return fields.Date.today()
|
||||
|
||||
@api.model
|
||||
def _get_default_checkout(self):
|
||||
folio = False
|
||||
if 'folio_id' in self._context:
|
||||
folio = self.env['hotel.folio'].search([
|
||||
('id', '=', self._context['folio_id'])
|
||||
])
|
||||
if folio and folio.room_lines:
|
||||
return folio.room_lines[0].checkout
|
||||
return fields.Date.today()
|
||||
|
||||
@api.model
|
||||
def _get_default_channel_type(self):
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
if user.has_group('hotel.group_hotel_call'):
|
||||
return 'phone'
|
||||
|
||||
partner_id = fields.Many2one('res.partner',string="Customer")
|
||||
checkin = fields.Datetime('Check In', required=True,
|
||||
default=_get_default_checkin)
|
||||
checkout = fields.Datetime('Check Out', required=True,
|
||||
default=_get_default_checkout)
|
||||
reservation_wizard_ids = fields.One2many('hotel.reservation.wizard',
|
||||
'folio_wizard_id',
|
||||
string="Resevations")
|
||||
service_wizard_ids = fields.One2many('hotel.service.wizard',
|
||||
'folio_wizard_id',
|
||||
string='Services')
|
||||
total = fields.Float('Total', compute='_computed_total')
|
||||
confirm = fields.Boolean('Confirm Reservations', default="1")
|
||||
autoassign = fields.Boolean('Autoassign', default="1")
|
||||
channel_type = fields.Selection([
|
||||
('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone')
|
||||
], string='Sales Channel', default=_get_default_channel_type)
|
||||
room_type_wizard_ids = fields.Many2many('hotel.room.type.wizard',
|
||||
string="Virtual Rooms")
|
||||
call_center = fields.Boolean(default=_get_default_center_user)
|
||||
|
||||
def assign_rooms(self):
|
||||
self.assign = True
|
||||
|
||||
@api.onchange('autoassign')
|
||||
def create_reservations(self):
|
||||
self.ensure_one()
|
||||
total = 0
|
||||
cmds = []
|
||||
for line in self.room_type_wizard_ids:
|
||||
if line.rooms_num == 0:
|
||||
continue
|
||||
if line.rooms_num > line.max_rooms:
|
||||
raise ValidationError(_("Too many rooms!"))
|
||||
elif line.room_type_id:
|
||||
occupied = self.env['hotel.reservation'].occupied(
|
||||
line.checkin,
|
||||
line.checkout)
|
||||
rooms_occupied = occupied.mapped('product_id.id')
|
||||
free_rooms = self.env['hotel.room'].search([
|
||||
('product_id.id', 'not in', rooms_occupied),
|
||||
('price_room_type.id', '=', line.room_type_id.id)
|
||||
], order='sequence', limit=line.rooms_num)
|
||||
room_ids = free_rooms.mapped('product_id.id')
|
||||
product_list = self.env['product.product'].search([
|
||||
('id', 'in', room_ids)
|
||||
])
|
||||
checkin_dt = fields.Date.from_string(line.checkin)
|
||||
checkout_dt = fields.Date.from_string(line.checkout)
|
||||
nights = abs((checkout_dt - checkin_dt).days)
|
||||
for room in product_list:
|
||||
pricelist_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_pricelist_id')
|
||||
if pricelist_id:
|
||||
pricelist_id = int(pricelist_id)
|
||||
res_price = 0
|
||||
for i in range(0, nights):
|
||||
ndate = checkin_dt + timedelta(days=i)
|
||||
ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
prod = line.room_type_id.product_id.with_context(
|
||||
lang=self.partner_id.lang,
|
||||
partner=self.partner_id.id,
|
||||
quantity=1,
|
||||
date=ndate_str,
|
||||
pricelist=pricelist_id,
|
||||
uom=room.uom_id.id)
|
||||
res_price += prod.price
|
||||
adults = self.env['hotel.room'].search([
|
||||
('product_id.id', '=', room.id)
|
||||
]).capacity
|
||||
res_price = res_price - (res_price * line.discount)/100
|
||||
total += res_price
|
||||
cmds.append((0, False, {
|
||||
'checkin': line.checkin,
|
||||
'checkout': line.checkout,
|
||||
'discount': line.discount,
|
||||
'product_id': room.id,
|
||||
'nights': nights,
|
||||
'adults': adults,
|
||||
'children': 0,
|
||||
'room_type_id': line.room_type_id,
|
||||
'price': res_price,
|
||||
'amount_reservation': res_price
|
||||
}))
|
||||
self.reservation_wizard_ids = cmds
|
||||
self.total = total
|
||||
|
||||
@api.multi
|
||||
@api.onchange('checkin', 'checkout')
|
||||
def onchange_checks(self):
|
||||
'''
|
||||
When you change checkin or checkout it will checked it
|
||||
and update the qty of hotel folio line
|
||||
-----------------------------------------------------------------
|
||||
@param self: object pointer
|
||||
'''
|
||||
self.ensure_one()
|
||||
checkin_dt = datetime.now() if not self.checkin else fields.Date.from_string(self.checkin)
|
||||
checkout_dt = datetime.now() if not self.checkout else fields.Date.from_string(self.checkout)
|
||||
if checkin_dt >= checkout_dt:
|
||||
checkout_dt = checkin_dt + timedelta(days=1)
|
||||
|
||||
chekin_str = checkin_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
chekout_str = checkout_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
room_type_ids = self.env['hotel.room.type'].search([])
|
||||
cmds = room_type_ids.mapped(lambda x: (0, False, {
|
||||
'room_type_id': x.id,
|
||||
'checkin': chekin_str,
|
||||
'checkout': chekout_str,
|
||||
'folio_wizard_id': self.id,
|
||||
}))
|
||||
self.write({
|
||||
'checkin': chekin_str,
|
||||
'checkout': chekout_str,
|
||||
'room_type_wizard_ids': cmds,
|
||||
})
|
||||
for room_type in self.room_type_wizard_ids:
|
||||
room_type.update_price()
|
||||
|
||||
@api.depends('room_type_wizard_ids', 'reservation_wizard_ids', 'service_wizard_ids')
|
||||
def _computed_total(self):
|
||||
total = 0
|
||||
for line in self.service_wizard_ids:
|
||||
total += line.price_total
|
||||
if not self.reservation_wizard_ids:
|
||||
for line in self.room_type_wizard_ids:
|
||||
total += line.total_price
|
||||
self.total = total
|
||||
else:
|
||||
for line in self.reservation_wizard_ids:
|
||||
total += line.price
|
||||
self.total = total
|
||||
|
||||
@api.multi
|
||||
def create_folio(self):
|
||||
self.ensure_one()
|
||||
if not self.partner_id:
|
||||
raise ValidationError(_("We need know the customer!"))
|
||||
reservations = [(5, False, False)]
|
||||
services = [(5, False, False)]
|
||||
if self.autoassign == True:
|
||||
self.create_reservations()
|
||||
for line in self.reservation_wizard_ids:
|
||||
reservations.append((0, False, {
|
||||
'product_id': line.product_id.id,
|
||||
'adults': line.adults,
|
||||
'children': line.children,
|
||||
'checkin': line.checkin,
|
||||
'checkout': line.checkout,
|
||||
'discount': line.discount,
|
||||
'room_type_id': line.room_type_id.id,
|
||||
'to_read': line.to_read, #REFACT: wubook module
|
||||
'to_assign': line.to_assign,
|
||||
}))
|
||||
for line in self.service_wizard_ids:
|
||||
services.append((0, False, {
|
||||
'product_id': line.product_id.id,
|
||||
'discount': line.discount,
|
||||
'price_unit': line.price_unit,
|
||||
'product_uom_qty': line.product_uom_qty,
|
||||
}))
|
||||
vals = {
|
||||
'partner_id': self.partner_id.id,
|
||||
'channel_type': self.channel_type,
|
||||
'room_lines': reservations,
|
||||
'service_lines': services,
|
||||
}
|
||||
newfol = self.env['hotel.folio'].create(vals)
|
||||
for room in newfol.room_lines:
|
||||
room.on_change_checkin_checkout_product_id()
|
||||
newfol.compute_invoices_amount()
|
||||
if self.confirm:
|
||||
newfol.room_lines.confirm()
|
||||
action = self.env.ref('hotel.open_hotel_folio1_form_tree_all').read()[0]
|
||||
if newfol:
|
||||
action['views'] = [(self.env.ref('hotel.view_hotel_folio1_form').id, 'form')]
|
||||
action['res_id'] = newfol.id
|
||||
else:
|
||||
action = {'type': 'ir.actions.act_window_close'}
|
||||
return action
|
||||
|
||||
|
||||
class HotelRoomTypeWizards(models.TransientModel):
|
||||
_name = 'hotel.room.type.wizard'
|
||||
|
||||
@api.multi
|
||||
def _get_default_checkin(self):
|
||||
return self.folio_wizard_id.checkin
|
||||
|
||||
@api.model
|
||||
def _get_default_checkout(self):
|
||||
return self.folio_wizard_id.checkout
|
||||
|
||||
room_type_id = fields.Many2one('hotel.room.type',
|
||||
string="Virtual Rooms")
|
||||
rooms_num = fields.Integer('Number of Rooms')
|
||||
max_rooms = fields.Integer('Max', compute="_compute_max")
|
||||
price = fields.Float(string='Price by Room')
|
||||
total_price = fields.Float(string='Total Price')
|
||||
folio_wizard_id = fields.Many2one('hotel.folio.wizard')
|
||||
amount_reservation = fields.Float(string='Total', readonly=True)
|
||||
discount = fields.Float('discount')
|
||||
min_stay = fields.Integer('Min. Days', compute="_compute_max")
|
||||
checkin = fields.Datetime('Check In', required=True,
|
||||
default=_get_default_checkin)
|
||||
checkout = fields.Datetime('Check Out', required=True,
|
||||
default=_get_default_checkout)
|
||||
can_confirm = fields.Boolean(compute="_can_confirm")
|
||||
|
||||
@api.multi
|
||||
def _can_confirm(self):
|
||||
for record in self:
|
||||
date_start = fields.Date.from_string(record.checkin)
|
||||
date_end = fields.Date.from_string(record.checkout)
|
||||
date_diff = abs((date_end - date_start).days)
|
||||
record.can_confirm = record.max_rooms > 0 and record.min_stay <= date_diff
|
||||
|
||||
def _compute_max(self):
|
||||
for res in self:
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
date_start = fields.Date.from_string(res.checkin)
|
||||
date_end = fields.Date.from_string(res.checkout)
|
||||
date_diff = abs((date_end - date_start).days)
|
||||
minstay_restrictions = self.env['hotel.room.type.restriction.item'].search([
|
||||
('room_type_id', '=', res.room_type_id.id),
|
||||
])
|
||||
avail_restrictions = self.env['hotel.room.type.availability'].search([
|
||||
('room_type_id', '=', res.room_type_id.id)
|
||||
])
|
||||
real_max = len(res.room_type_id.check_availability_room(
|
||||
res.checkin,
|
||||
res.checkout,
|
||||
res.room_type_id.id))
|
||||
avail = 100000
|
||||
min_stay = 0
|
||||
dates = []
|
||||
for i in range(0, date_diff):
|
||||
ndate_dt = date_start + timedelta(days=i)
|
||||
ndate_str = ndate_dt.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
dates.append(ndate_str)
|
||||
if minstay_restrictions:
|
||||
date_min_days = minstay_restrictions.filtered(
|
||||
lambda r: r.date_start <= ndate_str and \
|
||||
r.date_end >= ndate_str).min_stay
|
||||
if date_min_days > min_stay:
|
||||
min_stay = date_min_days
|
||||
if user.has_group('hotel.group_hotel_call'):
|
||||
if avail_restrictions:
|
||||
max_avail = avail_restrictions.filtered(
|
||||
lambda r: r.date == ndate_str).wmax_avail
|
||||
if max_avail < avail:
|
||||
avail = min(max_avail, real_max)
|
||||
else:
|
||||
avail = real_max
|
||||
|
||||
|
||||
if 100000 < avail > 0:
|
||||
res.max_rooms = avail
|
||||
else:
|
||||
res.max_rooms = 0
|
||||
if min_stay > 0:
|
||||
res.min_stay = min_stay
|
||||
|
||||
@api.onchange('rooms_num', 'discount', 'price', 'room_type_id', 'checkin', 'checkout')
|
||||
def update_price(self):
|
||||
for record in self:
|
||||
if record.rooms_num > record.max_rooms:
|
||||
raise ValidationError(_("There are not enough rooms!"))
|
||||
|
||||
checkin = record.checkin or record.folio_wizard_id.checkin
|
||||
checkout = record.checkout or record.folio_wizard_id.checkout
|
||||
chkin_utc_dt = fields.Date.from_string(checkin)
|
||||
chkout_utc_dt = fields.Date.from_string(checkout)
|
||||
if chkin_utc_dt >= chkout_utc_dt:
|
||||
chkout_utc_dt = chkin_utc_dt + timedelta(days=1)
|
||||
chkout_utc_dt -= timedelta(days=1)
|
||||
nights = abs((chkout_utc_dt - chkin_utc_dt).days)
|
||||
|
||||
pricelist_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_pricelist_id')
|
||||
if pricelist_id:
|
||||
pricelist_id = int(pricelist_id)
|
||||
|
||||
res_price = 0
|
||||
for i in range(0, nights):
|
||||
ndate = chkin_utc_dt + timedelta(days=i)
|
||||
ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
prod = record.room_type_id.product_id.with_context(
|
||||
lang=record.folio_wizard_id.partner_id.lang,
|
||||
partner=record.folio_wizard_id.partner_id.id,
|
||||
quantity=1,
|
||||
date=ndate_str,
|
||||
pricelist=pricelist_id,
|
||||
uom=record.room_type_id.product_id.uom_id.id)
|
||||
res_price += prod.price
|
||||
|
||||
price = (res_price * record.discount) * 0.01
|
||||
total_price = record.rooms_num * price
|
||||
record.write({
|
||||
'checkin': checkin,
|
||||
'checkout': checkout,
|
||||
'price': price,
|
||||
'total_price': total_price,
|
||||
'amount_reservation': total_price,
|
||||
})
|
||||
|
||||
|
||||
class ReservationWizard(models.TransientModel):
|
||||
_name = 'hotel.reservation.wizard'
|
||||
|
||||
product_id = fields.Many2one('product.product',
|
||||
string="Room Types")
|
||||
|
||||
folio_wizard_id = fields.Many2one('hotel.folio.wizard')
|
||||
|
||||
adults = fields.Integer('Adults',
|
||||
help='List of adults there in guest list. ')
|
||||
children = fields.Integer('Children',
|
||||
help='Number of children there in guest list.')
|
||||
checkin = fields.Datetime('Check In', required=True)
|
||||
checkout = fields.Datetime('Check Out', required=True)
|
||||
room_type_id = fields.Many2one('hotel.room.type',
|
||||
string='Virtual Room Type',
|
||||
required=True)
|
||||
nights = fields.Integer('Nights', readonly=True)
|
||||
price = fields.Float(string='Total')
|
||||
amount_reservation = fields.Float(string='Total', readonly=True)
|
||||
partner_id = fields.Many2one(related='folio_wizard_id.partner_id')
|
||||
discount = fields.Float('discount')
|
||||
to_read = fields.Boolean(compute="_compute_to_read_assign")
|
||||
to_assign = fields.Boolean(compute="_compute_to_read_assign")
|
||||
|
||||
@api.multi
|
||||
def _compute_to_read_assign(self):
|
||||
for rec in self:
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
if user.has_group('hotel.group_hotel_call'):
|
||||
rec.to_read = rec.to_assign = True
|
||||
|
||||
@api.multi
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_id(self):
|
||||
for line in self:
|
||||
if line.checkin and line.checkout:
|
||||
room = self.env['hotel.room'].search([
|
||||
('product_id.id', '=', line.product_id.id)
|
||||
])
|
||||
if line.adults == 0:
|
||||
line.adults = room.capacity
|
||||
line.room_type_id = room.price_room_type.id
|
||||
checkout_dt = fields.Date.from_string(line.checkout)
|
||||
checkout_dt -= timedelta(days=1)
|
||||
occupied = self.env['hotel.reservation'].occupied(
|
||||
line.checkin,
|
||||
checkout_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
|
||||
rooms_occupied = occupied.mapped('product_id.id')
|
||||
if line.product_id.id in rooms_occupied:
|
||||
raise ValidationError(_("This room is occupied!, please, \
|
||||
choice other room or change the reservation date"))
|
||||
|
||||
@api.multi
|
||||
@api.onchange('checkin', 'checkout', 'room_type_id', 'discount')
|
||||
def onchange_dates(self):
|
||||
for line in self:
|
||||
if not self.checkin:
|
||||
self.checkin = self.folio_wizard_id.checkin
|
||||
if not self.checkout:
|
||||
self.checkout = self.folio_wizard_id.checkout
|
||||
|
||||
start_date_utc_dt = fields.Date.from_string(line.checkin)
|
||||
end_date_utc_dt = fields.Date.from_string(line.checkout)
|
||||
|
||||
if line.room_type_id:
|
||||
pricelist_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_pricelist_id')
|
||||
if pricelist_id:
|
||||
pricelist_id = int(pricelist_id)
|
||||
nights = abs((end_date_utc_dt - start_date_utc_dt).days)
|
||||
res_price = 0
|
||||
for i in range(0, nights):
|
||||
ndate = start_date_utc_dt + timedelta(days=i)
|
||||
ndate_str = ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)
|
||||
prod = line.room_type_id.product_id.with_context(
|
||||
lang=self.partner_id.lang,
|
||||
partner=self.partner_id.id,
|
||||
quantity=1,
|
||||
date=ndate_str,
|
||||
pricelist=pricelist_id,
|
||||
uom=line.product_id.uom_id.id)
|
||||
res_price += prod.price
|
||||
res_price = res_price - (res_price * self.discount) * 0.01
|
||||
line.amount_reservation = res_price
|
||||
line.price = res_price
|
||||
end_date_utc_dt -= timedelta(days=1)
|
||||
occupied = self.env['hotel.reservation'].occupied(
|
||||
line.checkin,
|
||||
end_date_utc_dt.strftime(DEFAULT_SERVER_DATE_FORMAT))
|
||||
rooms_occupied = occupied.mapped('product_id.id')
|
||||
domain_rooms = [
|
||||
('isroom', '=', True),
|
||||
('id', 'not in', rooms_occupied)
|
||||
]
|
||||
return {'domain': {'product_id': domain_rooms}}
|
||||
|
||||
|
||||
class ServiceWizard(models.TransientModel):
|
||||
_name = 'hotel.service.wizard'
|
||||
|
||||
product_id = fields.Many2one('product.product',
|
||||
string="Service")
|
||||
folio_wizard_id = fields.Many2one('hotel.folio.wizard')
|
||||
discount = fields.Float('discount')
|
||||
price_unit = fields.Float('Unit Price', required=True,
|
||||
digits=dp.get_precision('Product Price'),
|
||||
default=0.0)
|
||||
price_total = fields.Float(compute='_compute_amount', string='Subtotal',
|
||||
readonly=True, store=True)
|
||||
product_uom_qty = fields.Float(string='Quantity',
|
||||
digits=dp.get_precision('Product Unit of Measure'),
|
||||
required=True,
|
||||
default=1.0)
|
||||
|
||||
@api.onchange('product_id')
|
||||
def onchange_product_id(self):
|
||||
if self.product_id:
|
||||
#TODO change pricelist for partner
|
||||
pricelist_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_pricelist_id')
|
||||
prod = self.product_id.with_context(
|
||||
lang=self.folio_wizard_id.partner_id.lang,
|
||||
partner=self.folio_wizard_id.partner_id.id,
|
||||
quantity=1,
|
||||
date=fields.Datetime.now(),
|
||||
pricelist=pricelist_id,
|
||||
uom=self.product_id.uom_id.id)
|
||||
self.price_unit = prod.price
|
||||
|
||||
@api.depends('price_unit', 'product_uom_qty', 'discount')
|
||||
def _compute_amount(self):
|
||||
for ser in self:
|
||||
total = (ser.price_unit * ser.product_uom_qty)
|
||||
ser.price_total = total - (total * ser.discount) * 0.01
|
||||
101
hotel/wizard/wizard_reservation.xml
Normal file
101
hotel/wizard/wizard_reservation.xml
Normal file
@@ -0,0 +1,101 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="hotel_folio_wizard">
|
||||
<field name="name">hotel.folio.wizard</field>
|
||||
<field name="model">hotel.folio.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Reservation Wizard" >
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="checkin" required="1" colspan="1"/>
|
||||
<field name="checkout" required="1" colspan="1"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="call_center" invisible="1" />
|
||||
<field name="channel_type" required="1" attrs="{'readonly':[('call_center','=',True)]}"/>
|
||||
<field name="partner_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="room_type_wizard_ids" nolabel="1"
|
||||
attrs="{'invisible': [('autoassign','=',False)]}">
|
||||
<tree editable="bottom" create="false" delete="false"
|
||||
decoration-danger="max_rooms < rooms_num"
|
||||
decoration-muted="can_confirm == False"
|
||||
decoration-success="max_rooms >= rooms_num and rooms_num > 0">
|
||||
<field name="min_stay" />
|
||||
<field name="max_rooms" />
|
||||
<field name="room_type_id" string="Room Type" readonly="1"/>
|
||||
<field name="rooms_num" attrs="{'readonly': [('can_confirm','=',False)]}" />
|
||||
<field name="checkin" widget="date" />
|
||||
<field name="checkout" widget="date" />
|
||||
<field name="discount" attrs="{'readonly': [('can_confirm','=',False)]}"/>
|
||||
<field name="price" attrs="{'readonly': [('can_confirm','=',False)]}"/>
|
||||
<field name="amount_reservation" readonly="1" />
|
||||
<field name="total_price" invisible="1" />
|
||||
<field name="can_confirm" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<label for="autoassign" attrs="{'invisible': [('autoassign','=', False)]}"/>
|
||||
<field name="autoassign" attrs="{'invisible': [('autoassign','=', False)]}"/>
|
||||
<field name="confirm" invisible="1"/>
|
||||
<group colspan="2" class="oe_subtotal_footer">
|
||||
<field name="total"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="reservation_wizard_ids" colspan="4" string="Room Lines"
|
||||
nolabel="1" attrs="{'invisible': [('autoassign','=',True)]}">
|
||||
<tree string="Room Line" delete="false" editable="buttom">
|
||||
<field name="product_id" string="Room No" options="{'no_create': True}"/>
|
||||
<field name="room_type_id" options="{'no_create': True}" />
|
||||
<field name="checkin" />
|
||||
<field name="checkout" />
|
||||
<field name="nights" />
|
||||
<field name="adults" />
|
||||
<field name="children" />
|
||||
<field name="discount" />
|
||||
<field name="amount_reservation" />
|
||||
<field name="price" invisible = "1"/>
|
||||
<field name="partner_id" invisible = "1" />
|
||||
<field name="folio_wizard_id" invisible = "1" />
|
||||
<field name="to_read" invisible="1" />
|
||||
<field name="to_assign" invisible="1" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<group>
|
||||
<field name="service_wizard_ids" colspan="4" string="Services"
|
||||
nolabel="1" >
|
||||
<tree string="Services" editable="buttom">
|
||||
<field name="product_id" string="Service" options="{'no_create': True}"
|
||||
domain="[('isservice','=',True)]"/>
|
||||
<field name="product_uom_qty" />
|
||||
<field name="price_unit" />
|
||||
<field name="discount" />
|
||||
<field name="price_total" />
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="create_folio" string="Create Reservations" type="object"
|
||||
class="oe_highlight" />
|
||||
</footer>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="open_wizard_reservations" model="ir.actions.act_window">
|
||||
<field name="name">Hotel Reservation Wizard</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hotel.folio.wizard</field>
|
||||
<field name="view_id" ref="hotel_folio_wizard"/>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Reference in New Issue
Block a user