mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
First Commit
This commit is contained in:
30
hotel/wizard/__init__.py
Normal file
30
hotel/wizard/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Dario Lodeiros <>
|
||||
# Alexandre Díaz <dev@redneboa.es>
|
||||
#
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from . import hotel_wizard
|
||||
from . import folio_make_invoice_advance
|
||||
from . import checkinwizard
|
||||
from . import massive_changes
|
||||
from . import split_reservation
|
||||
from . import duplicate_reservation
|
||||
from . import massive_price_reservation_days
|
||||
225
hotel/wizard/checkinwizard.py
Normal file
225
hotel/wizard/checkinwizard.py
Normal file
@@ -0,0 +1,225 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import logging
|
||||
from openerp import models, fields, api
|
||||
from openerp.exceptions import UserError
|
||||
from openerp.tools.translate import _
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class Wizard(models.TransientModel):
|
||||
_name = 'checkin.wizard'
|
||||
|
||||
def default_enter_date(self):
|
||||
if ('reservation_ids' and 'folio') in self.env.context:
|
||||
ids = [item[1] for item in self.env.context.get('reservation_ids')]
|
||||
reservations = self.env['hotel.reservation'].browse(ids)
|
||||
for res in reservations:
|
||||
return res.checkin
|
||||
if 'enter_date' in self.env.context:
|
||||
return self.env.context['enter_date']
|
||||
return False
|
||||
|
||||
def default_exit_date(self):
|
||||
if ('reservation_ids' and 'folio') in self.env.context:
|
||||
ids = [item[1] for item in self.env.context.get('reservation_ids')]
|
||||
reservations = self.env['hotel.reservation'].browse(ids)
|
||||
for res in reservations:
|
||||
return res.checkout
|
||||
if 'exit_date' in self.env.context:
|
||||
return self.env.context['exit_date']
|
||||
return False
|
||||
|
||||
def default_reservation_id(self):
|
||||
if ('reservation_ids' and 'folio') in self.env.context:
|
||||
ids = [item[1] for item in self.env.context.get('reservation_ids')]
|
||||
reservations = self.env['hotel.reservation'].browse(ids)
|
||||
if len(reservations) == 1:
|
||||
# return current room line (onlyone in this case)
|
||||
return reservations
|
||||
for res in reservations:
|
||||
# return the first room line with free space for a cardex
|
||||
# TODO: add 'done' to res.state condition... Maybe too restrictive right now
|
||||
if res.cardex_count < (res.adults + res.children) and res.state not in ["cancelled"]:
|
||||
return res
|
||||
elif 'reservation_id' in self.env.context:
|
||||
return self.env['hotel.reservation'].browse(
|
||||
self.env.context['reservation_id'])
|
||||
|
||||
_logger.info('default_reservation_id is FALSE')
|
||||
return False
|
||||
|
||||
def default_partner_id(self):
|
||||
# no partner by default. User must search and choose one
|
||||
return False
|
||||
|
||||
def default_cardex_ids(self):
|
||||
if ('reservation_ids' and 'folio') in self.env.context:
|
||||
ids = [item[1] for item in self.env.context.get('reservation_ids')]
|
||||
reservations = self.env['hotel.reservation'].browse(ids)
|
||||
for res in reservations:
|
||||
return res.cardex_ids
|
||||
|
||||
def default_cardex_ids(self):
|
||||
if ('reservation_ids' and 'folio') in self.env.context:
|
||||
ids = [item[1] for item in self.env.context.get('reservation_ids')]
|
||||
reservations = self.env['hotel.reservation'].browse(ids)
|
||||
for res in reservations:
|
||||
return res.segmentation_id
|
||||
|
||||
''' TODO: clean-up
|
||||
def default_count_cardex(self):
|
||||
if 'reservation_ids' and 'folio' in self.env.context:
|
||||
ids = [item[1] for item in self.env.context['reservation_ids']]
|
||||
reservations = self.env['hotel.reservation'].browse(ids)
|
||||
for res in reservations:
|
||||
return res.cardex_count
|
||||
'''
|
||||
''' TODO: clean-up
|
||||
def default_pending_cardex(self):
|
||||
if 'reservation_ids' and 'folio' in self.env.context:
|
||||
ids = [item[1] for item in self.env.context['reservation_ids']]
|
||||
reservations = self.env['hotel.reservation'].browse(ids)
|
||||
for res in reservations:
|
||||
return res.adults + res.children - res.cardex_count
|
||||
'''
|
||||
''' TODO: clean-up - list of checkins on smart button clean is not used anymore
|
||||
def comp_checkin_list_visible(self):
|
||||
if 'partner_id' in self.env.context:
|
||||
self.list_checkin_cardex = False
|
||||
return
|
||||
'''
|
||||
def comp_checkin_edit(self):
|
||||
if 'edit_cardex' in self.env.context:
|
||||
return True
|
||||
return False
|
||||
|
||||
cardex_ids = fields.Many2many('cardex', 'reservation_id',
|
||||
default=default_cardex_ids)
|
||||
# count_cardex = fields.Integer('Cardex counter',
|
||||
# default=default_count_cardex)
|
||||
# pending_cardex = fields.Integer('Cardex pending',
|
||||
# default=default_pending_cardex)
|
||||
partner_id = fields.Many2one('res.partner',
|
||||
default=default_partner_id)
|
||||
reservation_id = fields.Many2one('hotel.reservation',
|
||||
default=default_reservation_id)
|
||||
enter_date = fields.Date(default=default_enter_date,
|
||||
required=True)
|
||||
exit_date = fields.Date(default=default_exit_date,
|
||||
required=True)
|
||||
|
||||
firstname_cardex = fields.Char('Firstname',
|
||||
required=True)
|
||||
lastname_cardex = fields.Char('Lastname',
|
||||
required=True)
|
||||
|
||||
email_cardex = fields.Char('E-mail')
|
||||
|
||||
mobile_cardex = fields.Char('Mobile')
|
||||
|
||||
segmentation_id = fields.Many2many(
|
||||
related='reservation_id.folio_id.segmentation_ids')
|
||||
|
||||
|
||||
''' TODO: clean-up - list of checkins on smart button clean is not used anymore
|
||||
list_checkin_cardex = fields.Boolean(compute=comp_checkin_list_visible,
|
||||
default=True, store=True)
|
||||
'''
|
||||
# edit_checkin_cardex = fields.Boolean(default=comp_checkin_edit,
|
||||
# store=True)
|
||||
|
||||
op_select_partner = fields.Selection([
|
||||
('S', 'Select a partner for checkin'),
|
||||
('C', 'Create a new partner for checkin')],
|
||||
default='S',
|
||||
string='Partner for checkin')
|
||||
# checkin mode:
|
||||
# 0 - no selection made by the user, so hide the client fields
|
||||
# 1 - select a client for update his values and do the checkin
|
||||
# 2 - create a new client with the values and do the checkin
|
||||
checkin_mode = fields.Integer(default=0)
|
||||
|
||||
@api.multi
|
||||
def action_save_check(self):
|
||||
# prepare partner values
|
||||
if self.op_select_partner == 'S':
|
||||
partner_vals = {
|
||||
'id': self.partner_id.id,
|
||||
'firstname': self.firstname_cardex,
|
||||
'lastname': self.lastname_cardex,
|
||||
'email': self.email_cardex,
|
||||
'mobile': self.mobile_cardex,
|
||||
}
|
||||
self.partner_id.sudo().write(partner_vals);
|
||||
elif self.op_select_partner == 'C':
|
||||
partner_vals = {
|
||||
'firstname': self.firstname_cardex,
|
||||
'lastname': self.lastname_cardex,
|
||||
'email': self.email_cardex,
|
||||
'mobile': self.mobile_cardex,
|
||||
}
|
||||
new_partner = self.env['res.partner'].create(partner_vals)
|
||||
self.partner_id = self.env['res.partner'].browse(new_partner.id)
|
||||
|
||||
# prepare checkin values
|
||||
cardex_val = {
|
||||
'partner_id': self.partner_id.id,
|
||||
'enter_date': self.enter_date,
|
||||
'exit_date': self.exit_date
|
||||
}
|
||||
record_id = self.env['hotel.reservation'].browse(
|
||||
self.reservation_id.id)
|
||||
# save the cardex for this reservation
|
||||
record_id.write({
|
||||
'cardex_ids': [(0, False, cardex_val)],
|
||||
'segmentation_id': self.segmentation_id,
|
||||
})
|
||||
|
||||
# update the state of the current reservation
|
||||
if record_id.cardex_count > 0:
|
||||
record_id.state = 'booking'
|
||||
record_id.is_checkin = False
|
||||
folio = self.env['hotel.folio'].browse(self.reservation_id.folio_id.id)
|
||||
folio.checkins_reservations -= 1
|
||||
|
||||
@api.onchange('reservation_id')
|
||||
def change_enter_exit_date(self):
|
||||
record_id = self.env['hotel.reservation'].browse(
|
||||
self.reservation_id.id)
|
||||
|
||||
self.enter_date = record_id.checkin
|
||||
self.exit_date = record_id.checkout
|
||||
|
||||
''' trying to filter the reservations only to pending checkins ...
|
||||
if 'reservation_ids' and 'folio' in self.env.context:
|
||||
ids = [item[1] for item in self.env.context['reservation_ids']]
|
||||
reservations = self.env['hotel.reservation'].browse(ids)
|
||||
for res in reservations:
|
||||
_logger.info('reservation cardex_count %d', res.cardex_count)
|
||||
|
||||
# return {
|
||||
# 'domain': {'reservation_id': [('folio_id','=', self.env.context['folio']), 'count_cardex','=','2']},
|
||||
# 'warning': {'title': "Warning", 'message': self.env.context['cardex_count']},
|
||||
# }
|
||||
'''
|
||||
|
||||
@api.onchange('partner_id')
|
||||
def onchange_partner_id(self):
|
||||
# update partner fields
|
||||
self.firstname_cardex = self.partner_id.firstname;
|
||||
self.lastname_cardex = self.partner_id.lastname;
|
||||
self.email_cardex = self.partner_id.email;
|
||||
self.mobile_cardex = self.partner_id.mobile;
|
||||
# show the checkin fields if a partner is selected
|
||||
if self.op_select_partner == 'S' and self.partner_id.id != False:
|
||||
self.checkin_mode = 1;
|
||||
|
||||
@api.onchange('op_select_partner')
|
||||
def onchange_op_select_partner(self):
|
||||
# field one2many return false is record does not exist
|
||||
if self.op_select_partner == 'S' and self.partner_id.id != False:
|
||||
self.checkin_mode = 1;
|
||||
# field one2many return 0 on empty record (nothing typed)
|
||||
elif self.op_select_partner == 'C' and self.partner_id.id == 0:
|
||||
self.checkin_mode = 2;
|
||||
104
hotel/wizard/checkinwizard.xml
Normal file
104
hotel/wizard/checkinwizard.xml
Normal file
@@ -0,0 +1,104 @@
|
||||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record id="checkin_wizard_form_2" model="ir.ui.view">
|
||||
<field name="name">wizard.form2</field>
|
||||
<field name="model">checkin.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Add Checkin">
|
||||
<sheet>
|
||||
<group col="4">
|
||||
<field options="{'no_quick_create': True, 'no_create_edit' : True, 'no_open': True}"
|
||||
name="reservation_id" nolabel="1"
|
||||
domain="[('folio_id','=',context.get('folio')), ('state', '!=', 'cancelled'), ('cardex_pending', '=', True)]"
|
||||
style="max-width: 95%; width: 32.2em"/>
|
||||
</group>
|
||||
<group col="4">
|
||||
<field name="enter_date" colspan="2"/>
|
||||
<field name="exit_date" colspan="2"/>
|
||||
</group>
|
||||
<group attrs="{'invisible':[('checkin_mode', '>', 0)]}">
|
||||
<field name="op_select_partner" widget="radio" options="{'horizontal': true}"/>
|
||||
<field options="{'no_quick_create': True, 'no_create_edit' : True, 'no_open': True}"
|
||||
name="partner_id" string="Client name"
|
||||
style="max-width: 95%; width: 20em"/>
|
||||
</group>
|
||||
|
||||
<group col="4" attrs="{'invisible':[('checkin_mode', '=', 0)]}" >
|
||||
<field name="firstname_cardex" colspan="2"/>
|
||||
<field name="lastname_cardex" colspan="2"/>
|
||||
</group>
|
||||
|
||||
<group col="4" attrs="{'invisible':[('checkin_mode', '=', 0)]}" >
|
||||
<field name="email_cardex" colspan="2"/>
|
||||
<field name="mobile_cardex" colspan="2"/>
|
||||
</group>
|
||||
|
||||
<footer>
|
||||
<button name="action_save_check" string="Save Checkin and Print" type="object"
|
||||
attrs="{'invisible':[('checkin_mode', '=', 0)]}" />
|
||||
<button name="cancel" string="Cancel" special="cancel"/>
|
||||
|
||||
<field name="checkin_mode" invisible="True"/>
|
||||
<!-- <field name="checkin_show" invisible="True"/> -->
|
||||
<!-- <field name="edit_checkin_cardex" invisible="True"/> -->
|
||||
</footer>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="launch_checkin_wizard_add" model="ir.actions.act_window">
|
||||
<field name="name">Add Check</field>
|
||||
<field name="res_model">checkin.wizard</field>
|
||||
<field name="view_id" ref="checkin_wizard_form_2"/>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- TODO: clean-up
|
||||
<record id="checkin_wizard_form_view" model="ir.ui.view">
|
||||
<field name="name">wizard.form</field>
|
||||
<field name="model">checkin.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="List Checkin">
|
||||
<sheet>
|
||||
<group col="4">
|
||||
<label for="reservation_id" string="Resevation"/>
|
||||
<field name="reservation_id" nolabel="1" domain="[('folio_id','=',context.get('folio'))]"/>
|
||||
<button type="action" class="oe_stat_button"
|
||||
id="cardex_smart_button"
|
||||
icon="fa-user-plus"
|
||||
name="%(launch_checkin_wizard_add)d"
|
||||
context="{'reservation_id': reservation_id, 'hidden_cardex': True}">
|
||||
<div>
|
||||
<field name="pending_cardex"
|
||||
string="Pending" widget="statinfo"/>
|
||||
</div>
|
||||
</button>
|
||||
</group>
|
||||
<field name="cardex_ids" readonly="1"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
-->
|
||||
<!-- TODO: clean-up
|
||||
<act_window id="launch_checkin_wizard_list"
|
||||
name="List Checks"
|
||||
res_model="checkin.wizard"
|
||||
view_mode="form"
|
||||
view_id="checkin_wizard_form_view"
|
||||
target="new"
|
||||
key2="client_action_multi"/>
|
||||
-->
|
||||
|
||||
<!-- TODO: clean-up
|
||||
<act_window id="launch_checkin_wizard"
|
||||
name="List Checkin"
|
||||
res_model="checkin.wizard"
|
||||
view_mode="form"
|
||||
target="new"
|
||||
key2="client_action_multi"/>
|
||||
-->
|
||||
|
||||
</odoo>
|
||||
103
hotel/wizard/duplicate_reservation.py
Normal file
103
hotel/wizard/duplicate_reservation.py
Normal file
@@ -0,0 +1,103 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Alexandre Díaz <dev@redneboa.es>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from datetime import datetime, timedelta
|
||||
from openerp.exceptions import ValidationError
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.tools import (
|
||||
DEFAULT_SERVER_DATETIME_FORMAT,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
||||
|
||||
class DuplicateReservationWizard(models.TransientModel):
|
||||
_name = 'hotel.wizard.duplicate.reservation'
|
||||
|
||||
num = fields.Integer('Num. New Reservations', default=1, min=1)
|
||||
|
||||
@api.multi
|
||||
def duplicate_reservation(self):
|
||||
self.ensure_one()
|
||||
hotel_reservation_obj = self.env['hotel.reservation']
|
||||
reservation_id = hotel_reservation_obj.browse(
|
||||
self.env.context.get('active_id'))
|
||||
if not reservation_id:
|
||||
return False
|
||||
|
||||
if reservation_id.splitted:
|
||||
raise ValidationError(_("Can't duplicate splitted reservations"))
|
||||
|
||||
hotel_room_obj = self.env['hotel.room']
|
||||
hotel_vroom_obj = self.env['hotel.virtual.room']
|
||||
|
||||
room_id = hotel_room_obj.search([
|
||||
('product_id', '=', reservation_id.product_id.id)
|
||||
], limit=1)
|
||||
vroom_ids = hotel_vroom_obj.search([
|
||||
'|', ('room_ids', 'in', [room_id.id]),
|
||||
('room_type_ids', 'in', [room_id.categ_id.id])
|
||||
])
|
||||
|
||||
cmds_reservation_lines = []
|
||||
for rline in reservation_id.reservation_lines:
|
||||
cmds_reservation_lines.append((0, False, {
|
||||
'date': rline.date,
|
||||
'price': rline.price,
|
||||
}))
|
||||
|
||||
# Check Input
|
||||
total_free_rooms = 0
|
||||
for vroom in vroom_ids:
|
||||
avails = otel_vroom_obj.check_availability_virtual_room(
|
||||
reservation_id.checkin,
|
||||
reservation_id.checkout,
|
||||
virtual_room_id=vroom.id)
|
||||
total_free_rooms += len(avails)
|
||||
|
||||
if total_free_rooms < self.num:
|
||||
raise ValidationError(_("Too much duplicated reservations! \
|
||||
There are no '%d' free rooms") % self.num)
|
||||
|
||||
for i in range(0, self.num):
|
||||
for vroom in vroom_ids:
|
||||
free_rooms = hotel_vroom_obj.check_availability_virtual_room(
|
||||
reservation_id.checkin,
|
||||
reservation_id.checkout,
|
||||
virtual_room_id=vroom.id)
|
||||
if any(free_rooms):
|
||||
new_reservation_id = hotel_reservation_obj.create({
|
||||
'product_id': free_rooms[0].product_id.id,
|
||||
'folio_id': reservation_id.folio_id.id,
|
||||
'checkin': reservation_id.checkin,
|
||||
'checkout': reservation_id.checkout,
|
||||
'adults': reservation_id.adults,
|
||||
'children': reservation_id.children,
|
||||
'name': reservation_id.name,
|
||||
'reservation_lines': cmds_reservation_lines,
|
||||
'price_unit': reservation_id.price_unit,
|
||||
})
|
||||
if new_reservation_id:
|
||||
rpartner_id = reservation_id.order_id.partner_id
|
||||
new_reservation_id.order_id.partner_id = rpartner_id
|
||||
break
|
||||
else:
|
||||
raise ValidationError(_("Unexpected Error: Can't found a \
|
||||
free room"))
|
||||
return True
|
||||
35
hotel/wizard/duplicate_reservation.xml
Normal file
35
hotel/wizard/duplicate_reservation.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_hotel_duplicate_reservation_wizard">
|
||||
<field name="name">hotel.wizard.duplicate.reservation</field>
|
||||
<field name="model">hotel.wizard.duplicate.reservation</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Duplicate Rerservation" >
|
||||
<!-- Common Fields -->
|
||||
<group>
|
||||
<field name="num" required="1" />
|
||||
</group>
|
||||
<footer>
|
||||
<button name="duplicate_reservation" string="Duplicate" type="object"
|
||||
class="oe_highlight" />
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hotel_duplicate_reservation" model="ir.actions.act_window">
|
||||
<field name="name">Duplicate Reservation</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hotel.wizard.duplicate.reservation</field>
|
||||
<field name="view_id" ref="view_hotel_duplicate_reservation_wizard"/>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="binding_model_id" ref="model_hotel_reservation" />
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
183
hotel/wizard/folio_make_invoice_advance.py
Normal file
183
hotel/wizard/folio_make_invoice_advance.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import time
|
||||
from odoo import api, fields, models, _
|
||||
import odoo.addons.decimal_precision as dp
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class FolioAdvancePaymentInv(models.TransientModel):
|
||||
_name = "folio.advance.payment.inv"
|
||||
_description = "Folios Advance Payment Invoice"
|
||||
|
||||
@api.model
|
||||
def _count(self):
|
||||
return len(self._context.get('active_ids', []))
|
||||
|
||||
@api.model
|
||||
def _get_advance_payment_method(self):
|
||||
if self._count() == 1:
|
||||
sale_obj = self.env['sale.order']
|
||||
folio_obj = self.env['hotel.folio']
|
||||
folio = folio_obj.browse(self._context.get('active_ids'))[0]
|
||||
order = sale_obj.browse(folio_obj.mapped('order_id.id'))
|
||||
if all([line.product_id.invoice_policy == 'order' for line in order.order_line]) or order.invoice_count:
|
||||
return 'all'
|
||||
return 'delivered'
|
||||
|
||||
@api.model
|
||||
def _default_product_id(self):
|
||||
product_id = self.env['ir.default'].sudo().get('sale.config.settings', 'deposit_product_id_setting')
|
||||
return self.env['product.product'].browse(product_id)
|
||||
|
||||
@api.model
|
||||
def _default_deposit_account_id(self):
|
||||
return self._default_product_id().property_account_income_id
|
||||
|
||||
@api.model
|
||||
def _default_deposit_taxes_id(self):
|
||||
return self._default_product_id().taxes_id
|
||||
|
||||
advance_payment_method = fields.Selection([
|
||||
('delivered', 'Invoiceable lines'),
|
||||
('all', 'Invoiceable lines (deduct down payments)'),
|
||||
('percentage', 'Down payment (percentage)'),
|
||||
('fixed', 'Down payment (fixed amount)')
|
||||
], string='What do you want to invoice?', default=_get_advance_payment_method, required=True)
|
||||
product_id = fields.Many2one('product.product', string='Down Payment Product', domain=[('type', '=', 'service')],
|
||||
default=_default_product_id)
|
||||
count = fields.Integer(default=_count, string='# of Orders')
|
||||
amount = fields.Float('Down Payment Amount', digits=dp.get_precision('Account'), help="The amount to be invoiced in advance, taxes excluded.")
|
||||
deposit_account_id = fields.Many2one("account.account", string="Income Account", domain=[('deprecated', '=', False)],
|
||||
help="Account used for deposits", default=_default_deposit_account_id)
|
||||
deposit_taxes_id = fields.Many2many("account.tax", string="Customer Taxes", help="Taxes used for deposits", default=_default_deposit_taxes_id)
|
||||
|
||||
@api.onchange('advance_payment_method')
|
||||
def onchange_advance_payment_method(self):
|
||||
if self.advance_payment_method == 'percentage':
|
||||
return {'value': {'amount': 0}}
|
||||
return {}
|
||||
|
||||
@api.multi
|
||||
def _create_invoice(self, order, so_line, amount):
|
||||
inv_obj = self.env['account.invoice']
|
||||
ir_property_obj = self.env['ir.property']
|
||||
|
||||
account_id = False
|
||||
if self.product_id.id:
|
||||
account_id = self.product_id.property_account_income_id.id or self.product_id.categ_id.property_account_income_categ_id.id
|
||||
if not account_id:
|
||||
inc_acc = ir_property_obj.get('property_account_income_categ_id', 'product.category')
|
||||
account_id = order.fiscal_position_id.map_account(inc_acc).id if inc_acc else False
|
||||
if not account_id:
|
||||
raise UserError(
|
||||
_('There is no income account defined for this product: "%s". You may have to install a chart of account from Accounting app, settings menu.') %
|
||||
(self.product_id.name,))
|
||||
|
||||
if self.amount <= 0.00:
|
||||
raise UserError(_('The value of the down payment amount must be positive.'))
|
||||
context = {'lang': order.partner_id.lang}
|
||||
if self.advance_payment_method == 'percentage':
|
||||
amount = order.amount_untaxed * self.amount / 100
|
||||
name = _("Down payment of %s%%") % (self.amount,)
|
||||
else:
|
||||
amount = self.amount
|
||||
name = _('Down Payment')
|
||||
del context
|
||||
taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id)
|
||||
if order.fiscal_position_id and taxes:
|
||||
tax_ids = order.fiscal_position_id.map_tax(taxes).ids
|
||||
else:
|
||||
tax_ids = taxes.ids
|
||||
|
||||
invoice = inv_obj.create({
|
||||
'name': order.client_order_ref or order.name,
|
||||
'origin': order.name,
|
||||
'type': 'out_invoice',
|
||||
'reference': False,
|
||||
'account_id': order.partner_id.property_account_receivable_id.id,
|
||||
'partner_id': order.partner_invoice_id.id,
|
||||
'partner_shipping_id': order.partner_shipping_id.id,
|
||||
'invoice_line_ids': [(0, 0, {
|
||||
'name': name,
|
||||
'origin': order.name,
|
||||
'account_id': account_id,
|
||||
'price_unit': amount,
|
||||
'quantity': 1.0,
|
||||
'discount': 0.0,
|
||||
'uom_id': self.product_id.uom_id.id,
|
||||
'product_id': self.product_id.id,
|
||||
'sale_line_ids': [(6, 0, [so_line.id])],
|
||||
'invoice_line_tax_ids': [(6, 0, tax_ids)],
|
||||
'account_analytic_id': order.project_id.id or False,
|
||||
})],
|
||||
'currency_id': order.pricelist_id.currency_id.id,
|
||||
'payment_term_id': order.payment_term_id.id,
|
||||
'fiscal_position_id': order.fiscal_position_id.id or order.partner_id.property_account_position_id.id,
|
||||
'team_id': order.team_id.id,
|
||||
'user_id': order.user_id.id,
|
||||
'comment': order.note,
|
||||
})
|
||||
invoice.compute_taxes()
|
||||
invoice.message_post_with_view('mail.message_origin_link',
|
||||
values={'self': invoice, 'origin': order},
|
||||
subtype_id=self.env.ref('mail.mt_note').id)
|
||||
return invoice
|
||||
|
||||
@api.multi
|
||||
def create_invoices(self):
|
||||
folios = self.env['hotel.folio'].browse(self._context.get('active_ids', []))
|
||||
sale_orders = self.env['sale.order'].browse(folios.mapped('order_id.id'))
|
||||
|
||||
if self.advance_payment_method == 'delivered':
|
||||
sale_orders.action_invoice_create()
|
||||
elif self.advance_payment_method == 'all':
|
||||
sale_orders.action_invoice_create(final=True)
|
||||
else:
|
||||
# Create deposit product if necessary
|
||||
if not self.product_id:
|
||||
vals = self._prepare_deposit_product()
|
||||
self.product_id = self.env['product.product'].create(vals)
|
||||
self.env['ir.default'].sudo().set('sale.config.settings', 'deposit_product_id_setting', self.product_id.id)
|
||||
|
||||
sale_line_obj = self.env['sale.order.line']
|
||||
for order in sale_orders:
|
||||
if self.advance_payment_method == 'percentage':
|
||||
amount = order.amount_untaxed * self.amount / 100
|
||||
else:
|
||||
amount = self.amount
|
||||
if self.product_id.invoice_policy != 'order':
|
||||
raise UserError(_('The product used to invoice a down payment should have an invoice policy set to "Ordered quantities". Please update your deposit product to be able to create a deposit invoice.'))
|
||||
if self.product_id.type != 'service':
|
||||
raise UserError(_("The product used to invoice a down payment should be of type 'Service'. Please use another product or update this product."))
|
||||
taxes = self.product_id.taxes_id.filtered(lambda r: not order.company_id or r.company_id == order.company_id)
|
||||
if order.fiscal_position_id and taxes:
|
||||
tax_ids = order.fiscal_position_id.map_tax(taxes).ids
|
||||
else:
|
||||
tax_ids = taxes.ids
|
||||
context = {'lang': order.partner_id.lang}
|
||||
so_line = sale_line_obj.create({
|
||||
'name': _('Advance: %s') % (time.strftime('%m %Y'),),
|
||||
'price_unit': amount,
|
||||
'product_uom_qty': 0.0,
|
||||
'order_id': order.id,
|
||||
'discount': 0.0,
|
||||
'product_uom': self.product_id.uom_id.id,
|
||||
'product_id': self.product_id.id,
|
||||
'tax_id': [(6, 0, tax_ids)],
|
||||
})
|
||||
del context
|
||||
self._create_invoice(order, so_line, amount)
|
||||
if self._context.get('open_invoices', False):
|
||||
return sale_orders.action_view_invoice()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def _prepare_deposit_product(self):
|
||||
return {
|
||||
'name': 'Down payment',
|
||||
'type': 'service',
|
||||
'invoice_policy': 'order',
|
||||
'property_account_income_id': self.deposit_account_id.id,
|
||||
'taxes_id': [(6, 0, self.deposit_taxes_id.ids)],
|
||||
}
|
||||
53
hotel/wizard/folio_make_invoice_advance_views.xml
Normal file
53
hotel/wizard/folio_make_invoice_advance_views.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_folio_advance_payment_inv" model="ir.ui.view">
|
||||
<field name="name">Invoice Orders</field>
|
||||
<field name="model">folio.advance.payment.inv</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Invoice Sales Order">
|
||||
<p class="oe_grey">
|
||||
Invoices will be created in draft so that you can review
|
||||
them before validation.
|
||||
</p>
|
||||
<group>
|
||||
<field name="count" invisible="[('count','=',1)]" readonly="True"/>
|
||||
<field name="advance_payment_method" class="oe_inline" widget="radio"
|
||||
attrs="{'invisible': [('count','>',1)]}"/>
|
||||
<field name="product_id"
|
||||
context="{'search_default_services': 1, 'default_type': 'service', 'default_invoice_policy': 'order'}" class="oe_inline"
|
||||
attrs="{'invisible': 1}"/>
|
||||
<label for="amount" attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}"/>
|
||||
<div attrs="{'invisible': [('advance_payment_method', 'not in', ('fixed','percentage'))]}">
|
||||
<field name="amount"
|
||||
attrs="{'required': [('advance_payment_method', 'in', ('fixed','percentage'))]}" class="oe_inline" widget="monetary"/>
|
||||
<label string="%%"
|
||||
attrs="{'invisible': [('advance_payment_method', '!=', 'percentage')]}" class="oe_inline"/>
|
||||
</div>
|
||||
<field name="deposit_account_id" class="oe_inline"
|
||||
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}" groups="account.group_account_manager"/>
|
||||
<field name="deposit_taxes_id" class="oe_inline" widget="many2many_tags"
|
||||
domain="[('type_tax_use','=','sale')]"
|
||||
attrs="{'invisible': ['|', ('advance_payment_method', 'not in', ('fixed', 'percentage')), ('product_id', '!=', False)]}"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="create_invoices" string="Create and View Invoices" type="object"
|
||||
context="{'open_invoices': True}" class="btn-primary"/>
|
||||
<button name="create_invoices" string="Create Invoices" type="object"
|
||||
class="btn-primary"/>
|
||||
<button string="Cancel" class="btn-default" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_folio_advance_payment_inv" model="ir.actions.act_window">
|
||||
<field name="name">Invoice Order</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">folio.advance.payment.inv</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
<field name="groups_id" eval="[(4,ref('sales_team.group_sale_salesman'))]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
22
hotel/wizard/hotel_wizard.py
Normal file
22
hotel/wizard/hotel_wizard.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# 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 FolioReportWizard(models.TransientModel):
|
||||
_name = 'folio.report.wizard'
|
||||
_rec_name = 'date_start'
|
||||
|
||||
date_start = fields.Datetime('Start Date')
|
||||
date_end = fields.Datetime('End Date')
|
||||
|
||||
@api.multi
|
||||
def print_report(self):
|
||||
data = {
|
||||
'ids': self.ids,
|
||||
'model': 'hotel.folio',
|
||||
'form': self.read(['date_start', 'date_end'])[0]
|
||||
}
|
||||
return self.env.ref('hotel.report_hotel_folio').report_action(self, data=data)
|
||||
35
hotel/wizard/hotel_wizard.xml
Normal file
35
hotel/wizard/hotel_wizard.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
<!--Form view for folio report wizard -->
|
||||
<record model="ir.ui.view" id="view_hotel_folio_wizard">
|
||||
<field name="name">folio.report.wizard</field>
|
||||
<field name="model">folio.report.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Folio Report" >
|
||||
<group col="4">
|
||||
<field name="date_start" required="1" />
|
||||
<field name="date_end" required="1" />
|
||||
</group>
|
||||
<footer>
|
||||
<button name="print_report" string="Print Folio" type="object"
|
||||
class="oe_highlight" />
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!--Action for folio report wizard -->
|
||||
<record model="ir.actions.act_window" id="hotel_folio_wizard">
|
||||
<field name="name">Hotel Folio Report</field>
|
||||
<field name="res_model">folio.report.wizard</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
<!-- <menuitem name="Hotel Folio Report" action="hotel_folio_wizard"
|
||||
id="wizard_hotel_menu" parent="hotel_report_menu" sequence="31" />-->
|
||||
|
||||
</odoo>
|
||||
302
hotel/wizard/massive_changes.py
Normal file
302
hotel/wizard/massive_changes.py
Normal file
@@ -0,0 +1,302 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Alexandre Díaz <dev@redneboa.es>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from datetime import datetime, timedelta
|
||||
from openerp.exceptions import ValidationError
|
||||
from openerp import models, fields, api
|
||||
from openerp.tools import (
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from odoo.addons.hotel import date_utils
|
||||
|
||||
|
||||
class MassiveChangesWizard(models.TransientModel):
|
||||
_name = 'hotel.wizard.massive.changes'
|
||||
|
||||
# Common fields
|
||||
section = fields.Selection([
|
||||
('0', 'Availability'),
|
||||
('1', 'Restrictions'),
|
||||
('2', 'Pricelist'),
|
||||
], string='Section', default='0')
|
||||
date_start = fields.Datetime('Start Date', required=True)
|
||||
date_end = fields.Datetime('End Date', required=True)
|
||||
dmo = fields.Boolean('Monday', default=True)
|
||||
dtu = fields.Boolean('Tuesday', default=True)
|
||||
dwe = fields.Boolean('Wednesday', default=True)
|
||||
dth = fields.Boolean('Thursday', default=True)
|
||||
dfr = fields.Boolean('Friday', default=True)
|
||||
dsa = fields.Boolean('Saturday', default=True)
|
||||
dsu = fields.Boolean('Sunday', default=True)
|
||||
applied_on = fields.Selection([
|
||||
('0', 'Global'),
|
||||
('1', 'Virtual Room'),
|
||||
], string='Applied On', default='0')
|
||||
# virtual_room_ids = fields.Many2many('hotel.virtual.room',
|
||||
# string="Virtual Rooms")
|
||||
room_type_ids = fields.Many2many('hotel.room.type',
|
||||
string="Room Types")
|
||||
|
||||
# Availability fields
|
||||
change_avail = fields.Boolean(default=False)
|
||||
avail = fields.Integer('Avail', default=0)
|
||||
change_no_ota = fields.Boolean(default=False)
|
||||
no_ota = fields.Boolean('No OTA', default=False)
|
||||
|
||||
# Restriction fields
|
||||
restriction_id = fields.Many2one('hotel.virtual.room.restriction',
|
||||
'Restriction Plan')
|
||||
change_min_stay = fields.Boolean(default=False)
|
||||
min_stay = fields.Integer("Min. Stay")
|
||||
change_min_stay_arrival = fields.Boolean(default=False)
|
||||
min_stay_arrival = fields.Integer("Min. Stay Arrival")
|
||||
change_max_stay = fields.Boolean(default=False)
|
||||
max_stay = fields.Integer("Max. Stay")
|
||||
change_max_stay_arrival = fields.Boolean(default=False)
|
||||
max_stay_arrival = fields.Integer("Max. Stay Arrival")
|
||||
change_closed = fields.Boolean(default=False)
|
||||
closed = fields.Boolean('Closed')
|
||||
change_closed_departure = fields.Boolean(default=False)
|
||||
closed_departure = fields.Boolean('Closed Departure')
|
||||
change_closed_arrival = fields.Boolean(default=False)
|
||||
closed_arrival = fields.Boolean('Closed Arrival')
|
||||
|
||||
# Pricelist fields
|
||||
pricelist_id = fields.Many2one('product.pricelist', 'Pricelist')
|
||||
price = fields.Char('Price', help="Can use '+','-' \
|
||||
or '%'...\nExamples:\n a) +12.3 \
|
||||
\t> Increase the price in 12.3\n \
|
||||
b) -1.45% \t> Substract 1.45%\n c) 45 \
|
||||
\t\t> Sets the price to 45")
|
||||
|
||||
@api.onchange('date_start')
|
||||
def onchange_date_start(self):
|
||||
self.ensure_one()
|
||||
self.date_end = self.date_start
|
||||
|
||||
@api.multi
|
||||
def is_valid_date(self, chkdate):
|
||||
self.ensure_one()
|
||||
date_start_dt = fields.Datetime.from_string(self.date_start)
|
||||
date_end_dt = fields.Datetime.from_string(self.date_end)
|
||||
wday = chkdate.timetuple()[6]
|
||||
wedays = (self.dmo, self.dtu, self.dwe, self.dth, self.dfr, self.dsa,
|
||||
self.dsu)
|
||||
return (chkdate >= self.date_start and chkdate <= self.date_end
|
||||
and wedays[wday])
|
||||
|
||||
@api.model
|
||||
def _save_prices(self, ndate, vrooms, record):
|
||||
product_pricelist_item_obj = self.env['product.pricelist.item']
|
||||
price = 0.0
|
||||
operation = 'a'
|
||||
if record.price[0] == '+' or record.price[0] == '-':
|
||||
if record.price[-1] == '%':
|
||||
price = float(record.price[1:-1])
|
||||
operation = (record.price[0] == '+') and 'ap' or 'sp'
|
||||
else:
|
||||
price = float(record.price[1:])
|
||||
operation = (record.price[0] == '+') and 'a' or 's'
|
||||
else:
|
||||
if record.price[-1] == '%':
|
||||
price = float(record.price[:-1])
|
||||
operation = 'np'
|
||||
else:
|
||||
price = float(record.price)
|
||||
operation = 'n'
|
||||
|
||||
domain = [
|
||||
('pricelist_id', '=', record.pricelist_id.id),
|
||||
('date_start', '>=', ndate.strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT)),
|
||||
('date_end', '<=', ndate.strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT)),
|
||||
('compute_price', '=', 'fixed'),
|
||||
('applied_on', '=', '1_product'),
|
||||
]
|
||||
|
||||
product_tmpl_ids = vrooms.mapped(
|
||||
'product_id.product_tmpl_id')
|
||||
for vroom in vrooms:
|
||||
prod_tmpl_id = vroom.product_id.product_tmpl_id
|
||||
pricelist_item_ids = product_pricelist_item_obj.search(
|
||||
domain+[('product_tmpl_id', '=', prod_tmpl_id.id)])
|
||||
if any(pricelist_item_ids):
|
||||
if operation != 'n':
|
||||
for pli in pricelist_item_ids:
|
||||
pli_price = pli.fixed_price
|
||||
if operation == 'a':
|
||||
pli.write({
|
||||
'fixed_price': pli_price + price})
|
||||
elif operation == 'ap':
|
||||
pli.write({'fixed_price': pli_price + price * pli_price * 0.01})
|
||||
elif operation == 's':
|
||||
pli.write({
|
||||
'fixed_price': pli_price - price})
|
||||
elif operation == 'sp':
|
||||
pli.write({'fixed_price': pli_price - price * pli_price * 0.01})
|
||||
elif operation == 'np':
|
||||
pli.write({'fixed_price': price * pli_price * 0.01})
|
||||
else:
|
||||
pricelist_item_ids.write({'fixed_price': price})
|
||||
else:
|
||||
product_pricelist_item_obj.create({
|
||||
'pricelist_id': record.pricelist_id.id,
|
||||
'date_start': ndate.strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT),
|
||||
'date_end': ndate.strftime(
|
||||
DEFAULT_SERVER_DATE_FORMAT),
|
||||
'compute_price': 'fixed',
|
||||
'applied_on': '1_product',
|
||||
'product_tmpl_id': prod_tmpl_id.id,
|
||||
'fixed_price': price,
|
||||
})
|
||||
|
||||
@api.model
|
||||
def _get_restrictions_values(self, ndate, vroom, record):
|
||||
vals = {}
|
||||
if record.change_min_stay:
|
||||
vals.update({'min_stay': record.min_stay})
|
||||
if record.change_min_stay_arrival:
|
||||
vals.update({'min_stay_arrival': record.min_stay_arrival})
|
||||
if record.change_max_stay:
|
||||
vals.update({'max_stay': record.max_stay})
|
||||
if record.change_max_stay_arrival:
|
||||
vals.update({'max_stay_arrival': record.max_stay_arrival})
|
||||
if record.change_closed:
|
||||
vals.update({'closed': record.closed})
|
||||
if record.change_closed_departure:
|
||||
vals.update({'closed_departure': record.closed_departure})
|
||||
if record.change_closed_arrival:
|
||||
vals.update({'closed_arrival': record.closed_arrival})
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
def _save_restrictions(self, ndate, vrooms, record):
|
||||
hotel_vroom_re_it_obj = self.env['hotel.virtual.room.restriction.item']
|
||||
domain = [
|
||||
('date_start', '>=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)),
|
||||
('date_end', '<=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT)),
|
||||
('restriction_id', '=', record.restriction_id.id),
|
||||
('applied_on', '=', '0_virtual_room'),
|
||||
]
|
||||
|
||||
for vroom in vrooms:
|
||||
vals = self._get_restrictions_values(ndate, vroom, record)
|
||||
if not any(vals):
|
||||
continue
|
||||
|
||||
rrest_item_ids = hotel_vroom_re_it_obj.search(
|
||||
domain+[('virtual_room_id', '=', vroom.id)])
|
||||
if any(rrest_item_ids):
|
||||
rrest_item_ids.write(vals)
|
||||
else:
|
||||
vals.update({
|
||||
'date_start': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||||
'date_end': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||||
'restriction_id': record.restriction_id.id,
|
||||
'virtual_room_id': vroom.id,
|
||||
'applied_on': '0_virtual_room',
|
||||
})
|
||||
hotel_vroom_re_it_obj.create(vals)
|
||||
|
||||
@api.model
|
||||
def _get_availability_values(self, ndate, vroom, record):
|
||||
hotel_vroom_obj = self.env['hotel.virtual.room']
|
||||
vals = {}
|
||||
if record.change_no_ota:
|
||||
vals.update({'no_ota': record.no_ota})
|
||||
if record.change_avail:
|
||||
cavail = len(hotel_vroom_obj.check_availability_virtual_room(
|
||||
ndate.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
ndate.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
virtual_room_id=vroom.id))
|
||||
vals.update({
|
||||
'avail': min(cavail, vroom.total_rooms_count, record.avail),
|
||||
})
|
||||
return vals
|
||||
|
||||
@api.model
|
||||
def _save_availability(self, ndate, vrooms, record):
|
||||
hotel_vroom_obj = self.env['hotel.virtual.room']
|
||||
hotel_vroom_avail_obj = self.env['hotel.virtual.room.availability']
|
||||
domain = [('date', '=', ndate.strftime(DEFAULT_SERVER_DATE_FORMAT))]
|
||||
|
||||
for vroom in vrooms:
|
||||
vals = self._get_availability_values(ndate, vroom, record)
|
||||
if not any(vals):
|
||||
continue
|
||||
|
||||
vrooms_avail = hotel_vroom_avail_obj.search(
|
||||
domain+[('virtual_room_id', '=', vroom.id)]
|
||||
)
|
||||
if any(vrooms_avail):
|
||||
# Mail module want a singleton
|
||||
for vr_avail in vrooms_avail:
|
||||
vr_avail.write(vals)
|
||||
else:
|
||||
vals.update({
|
||||
'date': ndate.strftime(DEFAULT_SERVER_DATE_FORMAT),
|
||||
'virtual_room_id': vroom.id
|
||||
})
|
||||
hotel_vroom_avail_obj.with_context({
|
||||
'mail_create_nosubscribe': True,
|
||||
}).create(vals)
|
||||
|
||||
@api.multi
|
||||
def massive_change_close(self):
|
||||
self._do_massive_change()
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def massive_change(self):
|
||||
self._do_massive_change()
|
||||
return {
|
||||
"type": "ir.actions.do_nothing",
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def _do_massive_change(self):
|
||||
hotel_vroom_obj = self.env['hotel.virtual.room']
|
||||
for record in self:
|
||||
date_start_dt = date_utils.get_datetime(record.date_start,
|
||||
hours=False)
|
||||
# Use min '1' for same date
|
||||
diff_days = date_utils.date_diff(record.date_start,
|
||||
record.date_end,
|
||||
hours=False) + 1
|
||||
wedays = (record.dmo, record.dtu, record.dwe, record.dth,
|
||||
record.dfr, record.dsa, record.dsu)
|
||||
vrooms = record.applied_on == '1' and record.room_type_id \
|
||||
or hotel_vroom_obj.search([])
|
||||
|
||||
for i in range(0, diff_days):
|
||||
ndate = date_start_dt + timedelta(days=i)
|
||||
if not wedays[ndate.timetuple()[6]]:
|
||||
continue
|
||||
|
||||
if record.section == '0':
|
||||
self._save_availability(ndate, vrooms, record)
|
||||
elif record.section == '1':
|
||||
self._save_restrictions(ndate, vrooms, record)
|
||||
elif record.section == '2':
|
||||
self._save_prices(ndate, vrooms, record)
|
||||
return True
|
||||
113
hotel/wizard/massive_changes.xml
Normal file
113
hotel/wizard/massive_changes.xml
Normal file
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_hotel_massive_changes_wizard">
|
||||
<field name="name">hotel.wizard.massive.changes</field>
|
||||
<field name="model">hotel.wizard.massive.changes</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Massive Changes" >
|
||||
<!-- Common Fields -->
|
||||
<group>
|
||||
<field name="section" required="1" />
|
||||
<field name="applied_on" required="1" />
|
||||
<!-- <field name="virtual_room_ids" widget="many2many_tags" attrs="{'invisible':[('applied_on', '!=', '1')], 'required':[('applied_on', '=', '1')]}" /> -->
|
||||
<field name="room_type_ids" widget="many2many_tags" attrs="{'invisible':[('applied_on', '!=', '1')], 'required':[('applied_on', '=', '1')]}" />
|
||||
</group>
|
||||
<group colspan="8" col="8">
|
||||
<field name="date_start" required="1" colspan="4"/>
|
||||
<field name="date_end" required="1" colspan="4" />
|
||||
<field name="dmo" colspan="1" />
|
||||
<field name="dtu" colspan="1" />
|
||||
<field name="dwe" colspan="1" />
|
||||
<field name="dth" colspan="1" />
|
||||
<field name="dfr" colspan="1" />
|
||||
<field name="dsa" colspan="1" />
|
||||
<field name="dsu" colspan="1" />
|
||||
</group>
|
||||
<!-- Availability Fields -->
|
||||
<group col="3" colspan="3" attrs="{'invisible':[('section', '!=', '0')]}">
|
||||
<table class="oe_form_group">
|
||||
<thead>
|
||||
<th width="12%"></th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_avail" /> <strong> Avail</strong></td>
|
||||
<td class="oe_form_group_cell" colspan="3"><field name="avail" attrs="{'readonly':[('change_avail', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_no_ota" /> <strong> No OTA</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="no_ota" attrs="{'readonly':[('change_no_ota', '=', False)]}" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</group>
|
||||
<!-- Restricion Fields -->
|
||||
<group col="4" colspan="4" attrs="{'invisible':[('section', '!=', '1')]}">
|
||||
<field name="restriction_id" colspan="4" attrs="{'required':[('section', '=', '1')]}" />
|
||||
<table class="oe_form_group" colspan="4">
|
||||
<thead>
|
||||
<th width="20%"></th>
|
||||
<th></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_min_stay" /> <strong> Stay Min.</strong></td>
|
||||
<td class="oe_form_group_cell" colspan="3"><field name="min_stay" attrs="{'readonly':[('change_min_stay', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_max_stay" /> <strong> Stay Max.</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="max_stay" attrs="{'readonly':[('change_max_stay', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_min_stay_arrival" /> <strong> Stay Min. Arrival</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="min_stay_arrival" attrs="{'readonly':[('change_min_stay_arrival', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_max_stay_arrival" /> <strong> Stay Max. Arrival</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="max_stay_arrival" attrs="{'readonly':[('change_max_stay_arrival', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_closed" /> <strong> Closed</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="closed" attrs="{'readonly':[('change_closed', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_closed_departure" /> <strong> Closed Departure</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="closed_departure" attrs="{'readonly':[('change_closed_departure', '=', False)]}" /></td>
|
||||
</tr>
|
||||
<tr class="oe_form_group_row">
|
||||
<td class="oe_form_group_cell oe_form_group_cell_label"><field name="change_closed_arrival" /> <strong> Closed Arrival</strong></td>
|
||||
<td class="oe_form_group_cell"><field name="closed_arrival" attrs="{'readonly':[('change_closed_arrival', '=', False)]}" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</group>
|
||||
<!-- Priclist Fields -->
|
||||
<group attrs="{'invisible':[('section', '!=', '2')]}">
|
||||
<field name="pricelist_id" attrs="{'required':[('section', '=', '2')]}"/>
|
||||
<field name="price" attrs="{'required':[('section', '=', '2')]}"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="massive_change" string="Massive Change" type="object"
|
||||
class="oe_highlight" />
|
||||
<button name="massive_change_close" string="Massive Change & Close" type="object"
|
||||
class="oe_highlight" />
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hotel_massive_change" model="ir.actions.act_window">
|
||||
<field name="name">Hotel Massive Change</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hotel.wizard.massive.changes</field>
|
||||
<field name="view_id" ref="view_hotel_massive_changes_wizard"/>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
65
hotel/wizard/massive_price_reservation_days.py
Normal file
65
hotel/wizard/massive_price_reservation_days.py
Normal file
@@ -0,0 +1,65 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Alexandre Díaz <dev@redneboa.es>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
from openerp import models, fields, api
|
||||
|
||||
|
||||
class MassivePriceChangeWizard(models.TransientModel):
|
||||
_name = 'hotel.wizard.massive.price.reservation.days'
|
||||
|
||||
new_price = fields.Float('New Price', default=1, min=1)
|
||||
|
||||
@api.multi
|
||||
def massive_price_change_days(self):
|
||||
self.ensure_one()
|
||||
hotel_reservation_obj = self.env['hotel.reservation']
|
||||
reservation_id = hotel_reservation_obj.browse(
|
||||
self.env.context.get('active_id'))
|
||||
if not reservation_id:
|
||||
return False
|
||||
|
||||
cmds = []
|
||||
for rline in reservation_id.reservation_lines:
|
||||
cmds.append((
|
||||
1,
|
||||
rline.id,
|
||||
{
|
||||
'price': self.new_price
|
||||
}
|
||||
))
|
||||
reservation_id.write({
|
||||
'reservation_lines': cmds
|
||||
})
|
||||
# FIXME: For some reason need force reservation price calcs
|
||||
reservation_id._computed_amount_reservation()
|
||||
# FIXME: Workaround for dispatch updated price
|
||||
reservation_id.folio_id.write({
|
||||
'room_lines': [
|
||||
(
|
||||
1,
|
||||
reservation_id.id, {
|
||||
'reservation_lines': cmds
|
||||
}
|
||||
)
|
||||
]
|
||||
})
|
||||
|
||||
return True
|
||||
32
hotel/wizard/massive_price_reservation_days.xml
Normal file
32
hotel/wizard/massive_price_reservation_days.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_hotel_massive_price_change_wizard">
|
||||
<field name="name">hotel.wizard.massive.price.reservation.days</field>
|
||||
<field name="model">hotel.wizard.massive.price.reservation.days</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Massive Price Change" >
|
||||
<group>
|
||||
<field name="new_price" required="1" />
|
||||
</group>
|
||||
<footer>
|
||||
<button name="massive_price_change_days" string="Massive Change" type="object"
|
||||
class="oe_highlight" />
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hotel_massive_price_change_reservation_days" model="ir.actions.act_window">
|
||||
<field name="name">Massive Price Change</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hotel.wizard.massive.price.reservation.days</field>
|
||||
<field name="view_id" ref="view_hotel_massive_price_change_wizard"/>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
102
hotel/wizard/split_reservation.py
Normal file
102
hotel/wizard/split_reservation.py
Normal file
@@ -0,0 +1,102 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Alexandre Díaz <dev@redneboa.es>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from openerp.exceptions import ValidationError
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.tools import (
|
||||
DEFAULT_SERVER_DATETIME_FORMAT,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
from odoo.addons.hotel import date_utils
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SplitReservationWizard(models.TransientModel):
|
||||
_name = 'hotel.wizard.split.reservation'
|
||||
|
||||
nights = fields.Integer('Nights', default=1, min=1)
|
||||
|
||||
@api.multi
|
||||
def split_reservation(self):
|
||||
reservation_id = self.env['hotel.reservation'].browse(
|
||||
self.env.context.get('active_id'))
|
||||
if reservation_id:
|
||||
date_start_dt = date_utils.get_datetime(reservation_id.checkin)
|
||||
date_end_dt = date_utils.get_datetime(reservation_id.checkout)
|
||||
date_diff = date_utils.date_diff(date_start_dt, date_end_dt,
|
||||
hours=False)
|
||||
for record in self:
|
||||
new_start_date_dt = date_start_dt + \
|
||||
timedelta(days=date_diff-record.nights)
|
||||
if record.nights >= date_diff or record.nights < 1:
|
||||
raise ValidationError(_("Invalid Nights! Max is \
|
||||
'%d'") % (date_diff-1))
|
||||
|
||||
vals = reservation_id.generate_copy_values(
|
||||
new_start_date_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
date_end_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
)
|
||||
# Days Price
|
||||
reservation_lines = [[], []]
|
||||
tprice = [0.0, 0.0]
|
||||
div_dt = date_utils.dt_no_hours(new_start_date_dt)
|
||||
for rline in reservation_id.reservation_lines:
|
||||
rline_dt = date_utils.get_datetime(rline.date, hours=False)
|
||||
if rline_dt >= div_dt:
|
||||
reservation_lines[1].append((0, False, {
|
||||
'date': rline.date,
|
||||
'price': rline.price
|
||||
}))
|
||||
tprice[1] += rline.price
|
||||
reservation_lines[0].append((2, rline.id, False))
|
||||
else:
|
||||
tprice[0] += rline.price
|
||||
|
||||
reservation_id.write({
|
||||
'checkout': new_start_date_dt.strftime(
|
||||
DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'price_unit': tprice[0],
|
||||
'splitted': True,
|
||||
})
|
||||
reservation_id.reservation_lines = reservation_lines[0]
|
||||
parent_res = reservation_id.parent_reservation or \
|
||||
reservation_id
|
||||
vals.update({
|
||||
'splitted': True,
|
||||
'price_unit': tprice[1],
|
||||
'parent_reservation': parent_res.id,
|
||||
'virtual_room_id': parent_res.virtual_room_id.id,
|
||||
'discount': parent_res.discount,
|
||||
})
|
||||
reservation_copy = self.env['hotel.reservation'].create(vals)
|
||||
if not reservation_copy:
|
||||
raise ValidationError(_("Unexpected error copying record. \
|
||||
Can't split reservation!"))
|
||||
reservation_copy.reservation_lines = reservation_lines[1]
|
||||
# return {
|
||||
# 'type': 'ir.actions.act_window',
|
||||
# 'res_model': 'hotel.folio',
|
||||
# 'views': [[False, "form"]],
|
||||
# 'target': 'new',
|
||||
# 'res_id': reservation_id.folio_id.id,
|
||||
# }
|
||||
return True
|
||||
33
hotel/wizard/split_reservation.xml
Normal file
33
hotel/wizard/split_reservation.xml
Normal file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" ?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="view_hotel_split_reservation_wizard">
|
||||
<field name="name">hotel.wizard.split.reservation</field>
|
||||
<field name="model">hotel.wizard.split.reservation</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Split Reservation" >
|
||||
<!-- Common Fields -->
|
||||
<group>
|
||||
<field name="nights" required="1" />
|
||||
</group>
|
||||
<footer>
|
||||
<button name="split_reservation" string="Split" type="object"
|
||||
class="oe_highlight" />
|
||||
or
|
||||
<button string="Cancel" class="oe_link" special="cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hotel_split_reservation" model="ir.actions.act_window">
|
||||
<field name="name">Split Reservation</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="res_model">hotel.wizard.split.reservation</field>
|
||||
<field name="view_id" ref="view_hotel_split_reservation_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