[DEV] Board Service, Wizard refact, hotes services configuration, services lines by day

This commit is contained in:
Dario Lodeiros
2018-11-06 10:18:23 +01:00
parent 15a7e3ae7e
commit d485968d78
27 changed files with 326 additions and 99 deletions

View File

@@ -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,

View File

@@ -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

View 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)

View File

@@ -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':

View File

@@ -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

View File

@@ -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'],
})

View 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)

View File

@@ -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')

View File

@@ -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')

View 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')

View File

@@ -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')

View File

@@ -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
View 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>

View 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>

View File

@@ -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"

View File

@@ -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)]}"/> -->

View File

@@ -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>

View 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>

View File

@@ -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

View File

@@ -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>

View File

@@ -2,4 +2,3 @@
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from . import models
from . import controllers
from . import wizard

View File

@@ -33,7 +33,6 @@
'data/menus.xml',
'data/records.xml',
'security/ir.model.access.csv',
'wizard/wizard_reservation.xml'
],
'qweb': [
'static/src/xml/*.xml',

View File

@@ -17,7 +17,7 @@
BOOKS<br/>
</button>
<input type="edit" id="bookings_search" placeholder="Search..." style="width:100%; border:1px solid lightgray;" />
<button class="btn btn-default col-xs-12 col-md-12" id="btn_action_control" data-action="hotel_calendar.open_wizard_reservations">
<button class="btn btn-default col-xs-12 col-md-12" id="btn_action_control" data-action="hotel.open_wizard_reservations">
WIZARD
</button>
<button class="btn btn-default col-xs-12 col-md-12" id="btn_swap">

View File

@@ -24,8 +24,6 @@
<script type="text/javascript" src="/hotel_calendar/static/src/js/views/calendar_management/hotel_calendar_management_model.js"></script>
<script type="text/javascript" src="/hotel_calendar/static/src/js/views/calendar_management/hotel_calendar_management_renderer.js"></script>
<script type="text/javascript" src="/hotel_calendar/static/src/js/views/calendar_management/hotel_calendar_management_view.js"></script>
<script type="text/javascript" src="/hotel_calendar/static/src/js/open_reservation_wizard_listview_button.js"></script>
</xpath>
</template>

View File

@@ -1 +0,0 @@
from . import wizard_reservation