mirror of
https://github.com/OCA/pms.git
synced 2025-01-29 00:17:45 +02:00
First Commit
This commit is contained in:
33
hotel/models/__init__.py
Normal file
33
hotel/models/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Alexandre Díaz
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
from . import currency_exchange
|
||||
#~ from . import folio_room_line
|
||||
from . import inherit_payment_return
|
||||
from . import hotel_floor
|
||||
from . import hotel_folio
|
||||
from . import hotel_reservation
|
||||
from . import hotel_room
|
||||
from . import hotel_room_amenities
|
||||
from . import hotel_room_amenities_type
|
||||
from . import hotel_room_type
|
||||
# from . import hotel_service_line
|
||||
from . import hotel_service
|
||||
# from . import hotel_service_type
|
||||
from . import inherit_account_invoice
|
||||
# from . import inherit_product_category
|
||||
from . import inherit_product_product
|
||||
from . import inherit_res_company
|
||||
# from . import virtual_room
|
||||
from . import inherit_account_payment
|
||||
from . import hotel_virtual_room_restriction
|
||||
from . import hotel_virtual_room_restriction_item
|
||||
from . import hotel_reservation_line
|
||||
from . import cardex
|
||||
from . import hotel_virtual_room_availability
|
||||
from . import inherit_product_pricelist
|
||||
from . import res_config
|
||||
from . import inherit_res_partner
|
||||
from . import inherited_mail_compose_message
|
||||
#~ from . import hotel_dashboard
|
||||
88
hotel/models/cardex.py
Normal file
88
hotel/models/cardex.py
Normal file
@@ -0,0 +1,88 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Dario Lodeiros <>
|
||||
#
|
||||
#
|
||||
# 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 datetime
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.exceptions import except_orm, ValidationError
|
||||
from odoo.addons.hotel import date_utils
|
||||
|
||||
|
||||
class Cardex(models.Model):
|
||||
_name = 'cardex'
|
||||
|
||||
# Validation for Departure date is after arrival date.
|
||||
@api.constrains('exit_date')
|
||||
def validation_dates(self):
|
||||
if self.exit_date < self.enter_date:
|
||||
raise models.ValidationError(
|
||||
_('Departure date (%s) is prior to arrival on %s') %
|
||||
(self.exit_date, self.enter_date))
|
||||
|
||||
def default_reservation_id(self):
|
||||
if 'reservation_id' in self.env.context:
|
||||
reservation = self.env['hotel.reservation'].search([
|
||||
('id', '=', self.env.context['reservation_id'])
|
||||
])
|
||||
return reservation
|
||||
return False
|
||||
|
||||
def default_enter_date(self):
|
||||
if 'reservation_id' in self.env.context:
|
||||
reservation = self.env['hotel.reservation'].search([
|
||||
('id', '=', self.env.context['reservation_id'])
|
||||
])
|
||||
return reservation.checkin
|
||||
return False
|
||||
|
||||
def default_exit_date(self):
|
||||
if 'reservation_id' in self.env.context:
|
||||
reservation = self.env['hotel.reservation'].search([
|
||||
('id', '=', self.env.context['reservation_id'])
|
||||
])
|
||||
return reservation.checkout
|
||||
return False
|
||||
|
||||
def default_partner_id(self):
|
||||
if 'reservation_id' in self.env.context:
|
||||
reservation = self.env['hotel.reservation'].search([
|
||||
('id', '=', self.env.context['reservation_id'])
|
||||
])
|
||||
return reservation.partner_id
|
||||
return False
|
||||
|
||||
@api.onchange('enter_date', 'exit_date')
|
||||
def check_change_dates(self):
|
||||
if self.exit_date <= self.enter_date:
|
||||
date_1 = date_utils.get_datetime(self.enter_date)
|
||||
date_2 = date_1 + datetime.timedelta(days=1)
|
||||
self.update({'exit_date': date_2, })
|
||||
raise ValidationError(
|
||||
_('Departure date, is prior to arrival. Check it now. %s') %
|
||||
(date_2))
|
||||
|
||||
partner_id = fields.Many2one('res.partner', default=default_partner_id,
|
||||
required=True)
|
||||
reservation_id = fields.Many2one(
|
||||
'hotel.reservation',
|
||||
default=default_reservation_id, readonly=True)
|
||||
enter_date = fields.Date(default=default_enter_date, required=True)
|
||||
exit_date = fields.Date(default=default_exit_date, required=True)
|
||||
150
hotel/models/currency_exchange.py
Normal file
150
hotel/models/currency_exchange.py
Normal file
@@ -0,0 +1,150 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Alexandre Díaz
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
from decimal import Decimal
|
||||
# For Python 3.0 and later
|
||||
from urllib.request import urlopen
|
||||
from openerp import models, fields, api, _
|
||||
|
||||
|
||||
class CurrencyExchangeRate(models.Model):
|
||||
|
||||
_name = "currency.exchange"
|
||||
_description = "currency"
|
||||
|
||||
name = fields.Char('Reg Number', readonly=True, default='New')
|
||||
today_date = fields.Datetime('Date Ordered',
|
||||
required=True,
|
||||
default=(lambda *a:
|
||||
time.strftime
|
||||
(DEFAULT_SERVER_DATETIME_FORMAT)))
|
||||
input_curr = fields.Many2one('res.currency', string='Input Currency',
|
||||
track_visibility='always')
|
||||
in_amount = fields.Float('Amount Taken', size=64, default=1.0)
|
||||
out_curr = fields.Many2one('res.currency', string='Output Currency',
|
||||
track_visibility='always')
|
||||
out_amount = fields.Float('Subtotal', size=64)
|
||||
folio_no = fields.Many2one('hotel.folio', 'Folio Number')
|
||||
guest_name = fields.Many2one('res.partner', string='Guest Name')
|
||||
room_number = fields.Char(string='Room Number')
|
||||
state = fields.Selection([('draft', 'Draft'), ('done', 'Done'),
|
||||
('cancel', 'Cancel')], 'State', default='draft')
|
||||
rate = fields.Float('Rate(per unit)', size=64)
|
||||
hotel_id = fields.Many2one('stock.warehouse', 'Hotel Name')
|
||||
type = fields.Selection([('cash', 'Cash')], 'Type', default='cash')
|
||||
tax = fields.Selection([('2', '2%'), ('5', '5%'), ('10', '10%')],
|
||||
'Service Tax', default='2')
|
||||
total = fields.Float('Amount Given')
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
"""
|
||||
Overrides orm create method.
|
||||
@param self: The object pointer
|
||||
@param vals: dictionary of fields value.
|
||||
"""
|
||||
if not vals:
|
||||
vals = {}
|
||||
if self._context is None:
|
||||
self._context = {}
|
||||
seq_obj = self.env['ir.sequence']
|
||||
vals['name'] = seq_obj.next_by_code('currency.exchange') or 'New'
|
||||
return super(CurrencyExchangeRate, self).create(vals)
|
||||
|
||||
@api.onchange('folio_no')
|
||||
def get_folio_no(self):
|
||||
'''
|
||||
When you change folio_no, based on that it will update
|
||||
the guest_name,hotel_id and room_number as well
|
||||
---------------------------------------------------------
|
||||
@param self: object pointer
|
||||
'''
|
||||
for rec in self:
|
||||
self.guest_name = False
|
||||
self.hotel_id = False
|
||||
self.room_number = False
|
||||
if rec.folio_no and len(rec.folio_no.room_lines) != 0:
|
||||
self.guest_name = rec.folio_no.partner_id.id
|
||||
self.hotel_id = rec.folio_no.warehouse_id.id
|
||||
self.room_number = rec.folio_no.room_lines[0].product_id.name
|
||||
|
||||
@api.multi
|
||||
def act_cur_done(self):
|
||||
"""
|
||||
This method is used to change the state
|
||||
to done of the currency exchange
|
||||
---------------------------------------
|
||||
@param self: object pointer
|
||||
"""
|
||||
self.write({'state': 'done'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def act_cur_cancel(self):
|
||||
"""
|
||||
This method is used to change the state
|
||||
to cancel of the currency exchange
|
||||
---------------------------------------
|
||||
@param self: object pointer
|
||||
"""
|
||||
self.write({'state': 'cancel'})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def act_cur_cancel_draft(self):
|
||||
"""
|
||||
This method is used to change the state
|
||||
to draft of the currency exchange
|
||||
---------------------------------------
|
||||
@param self: object pointer
|
||||
"""
|
||||
self.write({'state': 'draft'})
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def get_rate(self, a, b):
|
||||
'''
|
||||
Calculate rate between two currency
|
||||
-----------------------------------
|
||||
@param self: object pointer
|
||||
'''
|
||||
try:
|
||||
url = 'http://finance.yahoo.com/d/quotes.csv?s=%s%s=X&f=l1' % (a,
|
||||
b)
|
||||
rate = urllib2.urlopen(url).read().rstrip()
|
||||
return Decimal(rate)
|
||||
except:
|
||||
return Decimal('-1.00')
|
||||
|
||||
@api.onchange('input_curr', 'out_curr', 'in_amount')
|
||||
def get_currency(self):
|
||||
'''
|
||||
When you change input_curr, out_curr or in_amount
|
||||
it will update the out_amount of the currency exchange
|
||||
------------------------------------------------------
|
||||
@param self: object pointer
|
||||
'''
|
||||
self.out_amount = 0.0
|
||||
if self.input_curr:
|
||||
for rec in self:
|
||||
result = rec.get_rate(self.input_curr.name,
|
||||
self.out_curr.name)
|
||||
if self.out_curr:
|
||||
self.rate = result
|
||||
if self.rate == Decimal('-1.00'):
|
||||
raise except_orm(_('Warning'),
|
||||
_('Please Check Your \
|
||||
Network Connectivity.'))
|
||||
self.out_amount = (float(result) * float(self.in_amount))
|
||||
|
||||
@api.onchange('out_amount', 'tax')
|
||||
def tax_change(self):
|
||||
'''
|
||||
When you change out_amount or tax
|
||||
it will update the total of the currency exchange
|
||||
-------------------------------------------------
|
||||
@param self: object pointer
|
||||
'''
|
||||
if self.out_amount:
|
||||
ser_tax = ((self.out_amount) * (float(self.tax))) / 100
|
||||
self.total = self.out_amount - ser_tax
|
||||
251
hotel/models/hotel_dashboard.py
Normal file
251
hotel/models/hotel_dashboard.py
Normal file
@@ -0,0 +1,251 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2018 Alexandre Díaz
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from babel.dates import format_datetime, format_date
|
||||
from odoo import models, api, _, fields
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT as DF
|
||||
from odoo.tools.misc import formatLang
|
||||
|
||||
class HotelDashboard(models.Model):
|
||||
_name = "hotel.dashboard"
|
||||
|
||||
def _get_count(self):
|
||||
resevations_count = self.env['hotel.reservation'].search(
|
||||
[('sate', '=', 'confirm')])
|
||||
folios_count = self.env['hotel.folio'].search(
|
||||
[('sate', '=', 'sales_order')])
|
||||
next_arrivals_count = self.env['hotel.reservation'].search(
|
||||
[('is_checkin', '=', True)])
|
||||
|
||||
self.orders_count = len(orders_count)
|
||||
self.quotations_count = len(quotations_count)
|
||||
self.orders_done_count = len(orders_done_count)
|
||||
@api.one
|
||||
def _kanban_dashboard(self):
|
||||
if self.graph_type == 'bar':
|
||||
self.kanban_dashboard_graph = json.dumps(self.get_bar_graph_datas())
|
||||
elif self.graph_type == 'line':
|
||||
self.kanban_dashboard_graph = json.dumps(self.get_line_graph_datas())
|
||||
|
||||
@api.one
|
||||
def _kanban_dashboard_graph(self):
|
||||
self.kanban_dashboard_graph = json.dumps(self.get_bar_graph_datas())
|
||||
#~ if (self.type in ['sale', 'purchase']):
|
||||
#~ self.kanban_dashboard_graph = json.dumps(self.get_bar_graph_datas())
|
||||
#~ elif (self.type in ['cash', 'bank']):
|
||||
#~ self.kanban_dashboard_graph = json.dumps(self.get_line_graph_datas())
|
||||
|
||||
color = fields.Integer(string='Color Index')
|
||||
name = fields.Char(string="Name")
|
||||
type = fields.Char(default="sale")
|
||||
graph_type = fields.Selection([('line','Line'),('bar','Bar'),('none','None')])
|
||||
reservations_count = fields.Integer(compute = '_get_count')
|
||||
folios_count = fields.Integer(compute= '_get_count')
|
||||
next_arrivals_count = fields.Integer(compute= '_get_count')
|
||||
kanban_dashboard = fields.Text(compute='_kanban_dashboard')
|
||||
kanban_dashboard_graph = fields.Text(compute='_kanban_dashboard_graph')
|
||||
show_on_dashboard = fields.Boolean(string='Show journal on dashboard', help="Whether this journal should be displayed on the dashboard or not", default=True)
|
||||
|
||||
@api.multi
|
||||
def get_bar_graph_datas(self):
|
||||
data = []
|
||||
today = datetime.strptime(fields.Date.context_today(self), DF)
|
||||
day_of_week = int(format_datetime(today, 'e', locale=self._context.get('lang') or 'en_US'))
|
||||
for i in range(0,15):
|
||||
if i==0:
|
||||
label = _('Today')
|
||||
else:
|
||||
label = format_date(today + timedelta(days=i) , 'd', locale=self._context.get('lang') or 'en_US')
|
||||
data.append({'label':label,'value':0.0, 'type': 'past' if i<0 else 'future'})
|
||||
# Build SQL query to find amount aggregated by week
|
||||
select_sql_clause = """SELECT count(id) as total from hotel_reservation where state != 'cancelled'"""
|
||||
query = "("+select_sql_clause+" and date(checkin) = '"+today.strftime(DF)+"')"
|
||||
for i in range(1,15):
|
||||
next_date = today + timedelta(days=i)
|
||||
query += " UNION ALL ("+select_sql_clause+" and date(checkin) = '"+next_date.strftime(DF)+"')"
|
||||
|
||||
self.env.cr.execute(query)
|
||||
query_results = self.env.cr.dictfetchall()
|
||||
for index in range(0, len(query_results)):
|
||||
data[index]['value'] = query_results[index].get('total')
|
||||
return [{'values': data}]
|
||||
|
||||
@api.multi
|
||||
def get_journal_dashboard_datas(self):
|
||||
#~ currency = self.currency_id or self.company_id.currency_id
|
||||
#~ number_to_reconcile = last_balance = account_sum = 0
|
||||
#~ ac_bnk_stmt = []
|
||||
#~ title = ''
|
||||
#~ number_draft = number_waiting = number_late = 0
|
||||
#~ sum_draft = sum_waiting = sum_late = 0.0
|
||||
#~ if self.type in ['bank', 'cash']:
|
||||
#~ last_bank_stmt = self.env['account.bank.statement'].search([('journal_id', 'in', self.ids)], order="date desc, id desc", limit=1)
|
||||
#~ last_balance = last_bank_stmt and last_bank_stmt[0].balance_end or 0
|
||||
#~ #Get the number of items to reconcile for that bank journal
|
||||
#~ self.env.cr.execute("""SELECT COUNT(DISTINCT(statement_line_id))
|
||||
#~ FROM account_move where statement_line_id
|
||||
#~ IN (SELECT line.id
|
||||
#~ FROM account_bank_statement_line AS line
|
||||
#~ LEFT JOIN account_bank_statement AS st
|
||||
#~ ON line.statement_id = st.id
|
||||
#~ WHERE st.journal_id IN %s and st.state = 'open')""", (tuple(self.ids),))
|
||||
#~ already_reconciled = self.env.cr.fetchone()[0]
|
||||
#~ self.env.cr.execute("""SELECT COUNT(line.id)
|
||||
#~ FROM account_bank_statement_line AS line
|
||||
#~ LEFT JOIN account_bank_statement AS st
|
||||
#~ ON line.statement_id = st.id
|
||||
#~ WHERE st.journal_id IN %s and st.state = 'open'""", (tuple(self.ids),))
|
||||
#~ all_lines = self.env.cr.fetchone()[0]
|
||||
#~ number_to_reconcile = all_lines - already_reconciled
|
||||
#~ # optimization to read sum of balance from account_move_line
|
||||
#~ account_ids = tuple(filter(None, [self.default_debit_account_id.id, self.default_credit_account_id.id]))
|
||||
#~ if account_ids:
|
||||
#~ amount_field = 'balance' if (not self.currency_id or self.currency_id == self.company_id.currency_id) else 'amount_currency'
|
||||
#~ query = """SELECT sum(%s) FROM account_move_line WHERE account_id in %%s AND date <= %%s;""" % (amount_field,)
|
||||
#~ self.env.cr.execute(query, (account_ids, fields.Date.today(),))
|
||||
#~ query_results = self.env.cr.dictfetchall()
|
||||
#~ if query_results and query_results[0].get('sum') != None:
|
||||
#~ account_sum = query_results[0].get('sum')
|
||||
#~ #TODO need to check if all invoices are in the same currency than the journal!!!!
|
||||
#~ elif self.type in ['sale', 'purchase']:
|
||||
#~ title = _('Bills to pay') if self.type == 'purchase' else _('Invoices owed to you')
|
||||
#~ # optimization to find total and sum of invoice that are in draft, open state
|
||||
#~ query = """SELECT state, amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %s AND state NOT IN ('paid', 'cancel');"""
|
||||
#~ self.env.cr.execute(query, (self.id,))
|
||||
#~ query_results = self.env.cr.dictfetchall()
|
||||
#~ today = datetime.today()
|
||||
#~ query = """SELECT amount_total, currency_id AS currency, type FROM account_invoice WHERE journal_id = %s AND date < %s AND state = 'open';"""
|
||||
#~ self.env.cr.execute(query, (self.id, today))
|
||||
#~ late_query_results = self.env.cr.dictfetchall()
|
||||
#~ for result in query_results:
|
||||
#~ if result['type'] in ['in_refund', 'out_refund']:
|
||||
#~ factor = -1
|
||||
#~ else:
|
||||
#~ factor = 1
|
||||
#~ cur = self.env['res.currency'].browse(result.get('currency'))
|
||||
#~ if result.get('state') in ['draft', 'proforma', 'proforma2']:
|
||||
#~ number_draft += 1
|
||||
#~ sum_draft += cur.compute(result.get('amount_total'), currency) * factor
|
||||
#~ elif result.get('state') == 'open':
|
||||
#~ number_waiting += 1
|
||||
#~ sum_waiting += cur.compute(result.get('amount_total'), currency) * factor
|
||||
#~ for result in late_query_results:
|
||||
#~ if result['type'] in ['in_refund', 'out_refund']:
|
||||
#~ factor = -1
|
||||
#~ else:
|
||||
#~ factor = 1
|
||||
#~ cur = self.env['res.currency'].browse(result.get('currency'))
|
||||
#~ number_late += 1
|
||||
#~ sum_late += cur.compute(result.get('amount_total'), currency) * factor
|
||||
|
||||
#~ difference = currency.round(last_balance-account_sum) + 0.0
|
||||
return {
|
||||
'graph': self.graph_type,
|
||||
'number_to_reconcile': 11,
|
||||
'account_balance': 4314,
|
||||
'last_balance': 252,
|
||||
'difference': 432,
|
||||
'number_draft': 32,
|
||||
'number_waiting': 44,
|
||||
'number_late': 23,
|
||||
'sum_draft': 2424245,
|
||||
'sum_waiting': 3124312,
|
||||
'sum_late': 23123,
|
||||
'currency_id': 1,
|
||||
'bank_statements_source': 'fonte',
|
||||
'title': 'titulo',
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def get_line_graph_datas(self):
|
||||
data = []
|
||||
today = datetime.strptime(fields.Date.context_today(self), DF)
|
||||
days=30
|
||||
|
||||
for i in range(-1, days + 1):
|
||||
ndate = today + timedelta(days=i)
|
||||
ndate_str = ndate.strftime(DF)
|
||||
day_onboard = self.env['hotel.reservation.line'].search_count([('date','=',ndate)])
|
||||
locale = self._context.get('lang') or 'en_US'
|
||||
short_name = format_date(ndate, 'd', locale=locale)
|
||||
name = format_date(ndate, 'd LLLL Y', locale=locale)
|
||||
data.append({'x':short_name,'y':day_onboard, 'name':name})
|
||||
return [{'values': data, 'area': True}]
|
||||
|
||||
@api.multi
|
||||
def action_create_new(self):
|
||||
#~ ctx = self._context.copy()
|
||||
#~ model = 'account.invoice'
|
||||
#~ if self.type == 'sale':
|
||||
#~ ctx.update({'journal_type': self.type, 'default_type': 'out_invoice', 'type': 'out_invoice', 'default_journal_id': self.id})
|
||||
#~ if ctx.get('refund'):
|
||||
#~ ctx.update({'default_type':'out_refund', 'type':'out_refund'})
|
||||
#~ view_id = self.env.ref('account.invoice_form').id
|
||||
#~ elif self.type == 'purchase':
|
||||
#~ ctx.update({'journal_type': self.type, 'default_type': 'in_invoice', 'type': 'in_invoice', 'default_journal_id': self.id})
|
||||
#~ if ctx.get('refund'):
|
||||
#~ ctx.update({'default_type': 'in_refund', 'type': 'in_refund'})
|
||||
#~ view_id = self.env.ref('account.invoice_supplier_form').id
|
||||
#~ else:
|
||||
#~ ctx.update({'default_journal_id': self.id})
|
||||
#~ view_id = self.env.ref('account.view_move_form').id
|
||||
#~ model = 'account.move'
|
||||
model = "hotel.folio"
|
||||
view_id = self.env.ref('hotel.view_hotel_folio1_form').id
|
||||
ctx=''
|
||||
return {
|
||||
'name': _('Create invoice/bill'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': model,
|
||||
'view_id': view_id,
|
||||
'context': ctx,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def open_action(self):
|
||||
"""return action based on type for related journals"""
|
||||
#~ action_name = self._context.get('action_name', False)
|
||||
#~ if not action_name:
|
||||
#~ if self.type == 'bank':
|
||||
#~ action_name = 'action_bank_statement_tree'
|
||||
#~ elif self.type == 'cash':
|
||||
#~ action_name = 'action_view_bank_statement_tree'
|
||||
#~ elif self.type == 'sale':
|
||||
#~ action_name = 'action_invoice_tree1'
|
||||
#~ elif self.type == 'purchase':
|
||||
#~ action_name = 'action_invoice_tree2'
|
||||
#~ else:
|
||||
#~ action_name = 'action_move_journal_line'
|
||||
|
||||
#~ _journal_invoice_type_map = {
|
||||
#~ ('sale', None): 'out_invoice',
|
||||
#~ ('purchase', None): 'in_invoice',
|
||||
#~ ('sale', 'refund'): 'out_refund',
|
||||
#~ ('purchase', 'refund'): 'in_refund',
|
||||
#~ ('bank', None): 'bank',
|
||||
#~ ('cash', None): 'cash',
|
||||
#~ ('general', None): 'general',
|
||||
#~ }
|
||||
#~ invoice_type = _journal_invoice_type_map[(self.type, self._context.get('invoice_type'))]
|
||||
|
||||
#~ ctx = self._context.copy()
|
||||
#~ ctx.pop('group_by', None)
|
||||
#~ ctx.update({
|
||||
#~ 'journal_type': self.type,
|
||||
#~ 'default_journal_id': self.id,
|
||||
#~ 'search_default_journal_id': self.id,
|
||||
#~ 'default_type': invoice_type,
|
||||
#~ 'type': invoice_type
|
||||
#~ })
|
||||
|
||||
#~ [action] = self.env.ref('account.%s' % action_name).read()
|
||||
#~ action['context'] = ctx
|
||||
#~ action['domain'] = self._context.get('use_domain', [])
|
||||
#~ if action_name in ['action_bank_statement_tree', 'action_view_bank_statement_tree']:
|
||||
#~ action['views'] = False
|
||||
#~ action['view_id'] = False
|
||||
return False
|
||||
32
hotel/models/hotel_floor.py
Normal file
32
hotel/models/hotel_floor.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Dario Lodeiros <>
|
||||
#
|
||||
#
|
||||
# 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 HotelFloor(models.Model):
|
||||
|
||||
_name = "hotel.floor"
|
||||
_description = "Ubication"
|
||||
|
||||
name = fields.Char('Ubication Name', size=64, required=True, index=True)
|
||||
sequence = fields.Integer('Sequence', size=64)
|
||||
884
hotel/models/hotel_folio.py
Normal file
884
hotel/models/hotel_folio.py
Normal file
@@ -0,0 +1,884 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
|
||||
import datetime
|
||||
import time
|
||||
import pytz
|
||||
import logging
|
||||
from decimal import Decimal
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from odoo.exceptions import except_orm, UserError, ValidationError
|
||||
from odoo.tools import (
|
||||
misc,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT,
|
||||
DEFAULT_SERVER_DATE_FORMAT)
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.addons.hotel import date_utils
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
|
||||
class HotelFolio(models.Model):
|
||||
|
||||
@api.model
|
||||
def name_search(self, name='', args=None, operator='ilike', limit=100):
|
||||
if args is None:
|
||||
args = []
|
||||
args += ([('name', operator, name)])
|
||||
mids = self.search(args, limit=100)
|
||||
return mids.name_get()
|
||||
|
||||
@api.model
|
||||
def _needaction_count(self, domain=None):
|
||||
"""
|
||||
Show a count of draft state folio on the menu badge.
|
||||
@param self: object pointer
|
||||
"""
|
||||
return self.search_count([('state', '=', 'draft')])
|
||||
|
||||
@api.multi
|
||||
def copy(self, default=None):
|
||||
'''
|
||||
@param self: object pointer
|
||||
@param default: dict of default values to be set
|
||||
'''
|
||||
return super(HotelFolio, self).copy(default=default)
|
||||
|
||||
@api.multi
|
||||
def _invoiced(self, name, arg):
|
||||
'''
|
||||
@param self: object pointer
|
||||
@param name: Names of fields.
|
||||
@param arg: User defined arguments
|
||||
'''
|
||||
pass
|
||||
# return self.env['sale.order']._invoiced(name, arg)
|
||||
|
||||
@api.multi
|
||||
def _invoiced_search(self, obj, name, args):
|
||||
'''
|
||||
@param self: object pointer
|
||||
@param name: Names of fields.
|
||||
@param arg: User defined arguments
|
||||
'''
|
||||
pass
|
||||
# return self.env['sale.order']._invoiced_search(obj, name, args)
|
||||
|
||||
# @api.depends('invoice_lines.invoice_id.state', 'invoice_lines.quantity')
|
||||
def _get_invoice_qty(self):
|
||||
pass
|
||||
# @api.depends('product_id.invoice_policy', 'order_id.state')
|
||||
def _compute_qty_delivered_updateable(self):
|
||||
pass
|
||||
# @api.depends('state', 'order_line.invoice_status')
|
||||
def _get_invoiced(self):
|
||||
pass
|
||||
# @api.depends('state', 'product_uom_qty', 'qty_delivered', 'qty_to_invoice', 'qty_invoiced')
|
||||
def _compute_invoice_status(self):
|
||||
pass
|
||||
# @api.depends('product_uom_qty', 'discount', 'price_unit', 'tax_id')
|
||||
def _compute_amount(self):
|
||||
pass
|
||||
# @api.depends('order_line.price_total')
|
||||
def _amount_all(self):
|
||||
pass
|
||||
|
||||
_name = 'hotel.folio'
|
||||
_description = 'Hotel Folio'
|
||||
|
||||
_order = 'id'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin', 'portal.mixin']
|
||||
|
||||
name = fields.Char('Folio Number', readonly=True, index=True,
|
||||
default='New')
|
||||
partner_id = fields.Many2one('res.partner',
|
||||
track_visibility='onchange')
|
||||
# partner_invoice_id = fields.Many2one('res.partner',
|
||||
# string='Invoice Address',
|
||||
# readonly=True, required=True,
|
||||
# states={'draft': [('readonly', False)],
|
||||
# 'sent': [('readonly', False)]},
|
||||
# help="Invoice address for current sales order.")
|
||||
|
||||
# For being used directly in the Folio views
|
||||
email = fields.Char('E-mail', related='partner_id.email')
|
||||
mobile = fields.Char('Mobile', related='partner_id.mobile')
|
||||
phone = fields.Char('Phone', related='partner_id.phone')
|
||||
|
||||
state = fields.Selection([('draft', 'Pre-reservation'), ('confirm', 'Pending Entry'),
|
||||
('booking', 'On Board'), ('done', 'Out'),
|
||||
('cancelled', 'Cancelled')],
|
||||
'State', readonly=True,
|
||||
default=lambda *a: 'draft',
|
||||
track_visibility='onchange')
|
||||
|
||||
room_lines = fields.One2many('hotel.reservation', 'folio_id',
|
||||
readonly=False,
|
||||
states={'done': [('readonly', True)]},
|
||||
help="Hotel room reservation detail.",)
|
||||
|
||||
service_line_ids = fields.One2many('hotel.service', 'folio_id',
|
||||
readonly=False,
|
||||
states={'done': [('readonly', True)]},
|
||||
help="Hotel services detail provide to "
|
||||
"customer and it will include in "
|
||||
"main Invoice.")
|
||||
# service_line_ids = fields.One2many('hotel.service.line', 'folio_id',
|
||||
# readonly=False,
|
||||
# states={'done': [('readonly', True)]},
|
||||
# help="Hotel services detail provide to"
|
||||
# "customer and it will include in "
|
||||
# "main Invoice.")
|
||||
# has no sense used as this way
|
||||
hotel_invoice_id = fields.Many2one('account.invoice', 'Invoice')
|
||||
|
||||
company_id = fields.Many2one('res.company', 'Company')
|
||||
|
||||
# currency_id = fields.Many2one('res.currency', related='pricelist_id.currency_id',
|
||||
# string='Currency', readonly=True, required=True)
|
||||
|
||||
# pricelist_id = fields.Many2one('product.pricelist',
|
||||
# string='Pricelist',
|
||||
# required=True,
|
||||
# readonly=True,
|
||||
# states={'draft': [('readonly', False)],
|
||||
# 'sent': [('readonly', False)]},
|
||||
# help="Pricelist for current sales order.")
|
||||
# Monetary to Float
|
||||
invoices_amount = fields.Float(compute='compute_invoices_amount',
|
||||
store=True,
|
||||
string="Pending in Folio")
|
||||
# Monetary to Float
|
||||
refund_amount = fields.Float(compute='compute_invoices_amount',
|
||||
store=True,
|
||||
string="Payment Returns")
|
||||
# Monetary to Float
|
||||
invoices_paid = fields.Float(compute='compute_invoices_amount',
|
||||
store=True, track_visibility='onchange',
|
||||
string="Payments")
|
||||
|
||||
booking_pending = fields.Integer('Booking pending',
|
||||
compute='_compute_cardex_count')
|
||||
cardex_count = fields.Integer('Cardex counter',
|
||||
compute='_compute_cardex_count')
|
||||
cardex_pending = fields.Boolean('Cardex Pending',
|
||||
compute='_compute_cardex_count')
|
||||
cardex_pending_num = fields.Integer('Cardex Pending',
|
||||
compute='_compute_cardex_count')
|
||||
checkins_reservations = fields.Integer('checkins reservations')
|
||||
checkouts_reservations = fields.Integer('checkouts reservations')
|
||||
partner_internal_comment = fields.Text(string='Internal Partner Notes',
|
||||
related='partner_id.comment')
|
||||
internal_comment = fields.Text(string='Internal Folio Notes')
|
||||
cancelled_reason = fields.Text('Cause of cancelled')
|
||||
payment_ids = fields.One2many('account.payment', 'folio_id',
|
||||
readonly=True)
|
||||
return_ids = fields.One2many('payment.return', 'folio_id',
|
||||
readonly=True)
|
||||
prepaid_warning_days = fields.Integer(
|
||||
'Prepaid Warning Days',
|
||||
help='Margin in days to create a notice if a payment \
|
||||
advance has not been recorded')
|
||||
reservation_type = fields.Selection([('normal', 'Normal'),
|
||||
('staff', 'Staff'),
|
||||
('out', 'Out of Service')],
|
||||
'Type', default=lambda *a: 'normal')
|
||||
channel_type = fields.Selection([('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone'),
|
||||
('web', 'Web')], 'Sales Channel', default='door')
|
||||
num_invoices = fields.Integer(compute='_compute_num_invoices')
|
||||
rooms_char = fields.Char('Rooms', compute='_computed_rooms_char')
|
||||
segmentation_ids = fields.Many2many('res.partner.category',
|
||||
string='Segmentation')
|
||||
has_confirmed_reservations_to_send = fields.Boolean(
|
||||
compute='_compute_has_confirmed_reservations_to_send')
|
||||
has_cancelled_reservations_to_send = fields.Boolean(
|
||||
compute='_compute_has_cancelled_reservations_to_send')
|
||||
has_checkout_to_send = fields.Boolean(
|
||||
compute='_compute_has_checkout_to_send')
|
||||
# fix_price = fields.Boolean(compute='_compute_fix_price')
|
||||
date_order = fields.Datetime(
|
||||
string='Order Date',
|
||||
required=True, readonly=True, index=True,
|
||||
states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
|
||||
copy=False, default=fields.Datetime.now)
|
||||
|
||||
invoice_ids = fields.Many2many('account.invoice', string='Invoices',
|
||||
compute='_get_invoiced', readonly=True, copy=False)
|
||||
invoice_status = fields.Selection([('upselling', 'Upselling Opportunity'),
|
||||
('invoiced', 'Fully Invoiced'),
|
||||
('to invoice', 'To Invoice'),
|
||||
('no', 'Nothing to Invoice')],
|
||||
string='Invoice Status',
|
||||
compute='_compute_invoice_status',
|
||||
store=True, readonly=True, default='no')
|
||||
client_order_ref = fields.Char(string='Customer Reference', copy=False)
|
||||
note = fields.Text('Terms and conditions')
|
||||
# layout_category_id = fields.Many2one('sale.layout_category', string='Section')
|
||||
|
||||
user_id = fields.Many2one('res.users', string='Salesperson', index=True,
|
||||
track_visibility='onchange', default=lambda self: self.env.user)
|
||||
|
||||
sequence = fields.Integer(string='Sequence', default=10)
|
||||
# sale.order
|
||||
amount_total = fields.Float(string='Total', store=True, readonly=True,
|
||||
track_visibility='always')
|
||||
|
||||
|
||||
def _compute_fix_price(self):
|
||||
for record in self:
|
||||
for res in record.room_lines:
|
||||
if res.fix_total == True:
|
||||
record.fix_price = True
|
||||
break
|
||||
else:
|
||||
record.fix_price = False
|
||||
|
||||
def action_recalcule_payment(self):
|
||||
for record in self:
|
||||
for res in record.room_lines:
|
||||
res.on_change_checkin_checkout_product_id()
|
||||
|
||||
def _computed_rooms_char(self):
|
||||
for record in self:
|
||||
rooms = ', '.join(record.mapped('room_lines.room_id.name'))
|
||||
record.rooms_char = rooms
|
||||
|
||||
@api.model
|
||||
def recompute_amount(self):
|
||||
folios = self.env['hotel.folio']
|
||||
if folios:
|
||||
folios = folios.filtered(lambda x: (
|
||||
x.name == folio_name))
|
||||
folios.compute_invoices_amount()
|
||||
|
||||
@api.multi
|
||||
def _compute_num_invoices(self):
|
||||
pass
|
||||
# for fol in self:
|
||||
# fol.num_invoices = len(self.mapped('invoice_ids.id'))
|
||||
|
||||
@api.model
|
||||
def daily_plan(self):
|
||||
_logger.info('daily_plan')
|
||||
self._cr.execute("update hotel_folio set checkins_reservations = 0, \
|
||||
checkouts_reservations = 0 where checkins_reservations > 0 \
|
||||
or checkouts_reservations > 0")
|
||||
folios_in = self.env['hotel.folio'].search([
|
||||
('room_lines.is_checkin', '=', True)
|
||||
])
|
||||
folios_out = self.env['hotel.folio'].search([
|
||||
('room_lines.is_checkout', '=', True)
|
||||
])
|
||||
for fol in folios_in:
|
||||
count_checkin = fol.room_lines.search_count([
|
||||
('is_checkin', '=', True), ('folio_id.id', '=', fol.id)
|
||||
])
|
||||
fol.write({'checkins_reservations': count_checkin})
|
||||
for fol in folios_out:
|
||||
count_checkout = fol.room_lines.search_count([
|
||||
('is_checkout', '=', True),
|
||||
('folio_id.id', '=', fol.id)
|
||||
])
|
||||
fol.write({'checkouts_reservations': count_checkout})
|
||||
return True
|
||||
|
||||
# @api.depends('order_line.price_total', 'payment_ids', 'return_ids')
|
||||
@api.multi
|
||||
def compute_invoices_amount(self):
|
||||
_logger.info('compute_invoices_amount')
|
||||
|
||||
@api.multi
|
||||
def action_pay(self):
|
||||
self.ensure_one()
|
||||
partner = self.partner_id.id
|
||||
amount = self.invoices_amount
|
||||
view_id = self.env.ref('hotel.view_account_payment_folio_form').id
|
||||
return{
|
||||
'name': _('Register Payment'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'account.payment',
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_id': view_id,
|
||||
'context': {
|
||||
'default_folio_id': self.id,
|
||||
'default_amount': amount,
|
||||
'default_payment_type': 'inbound',
|
||||
'default_partner_type': 'customer',
|
||||
'default_partner_id': partner,
|
||||
'default_communication': self.name,
|
||||
},
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_payments(self):
|
||||
self.ensure_one()
|
||||
payments_obj = self.env['account.payment']
|
||||
payments = payments_obj.search([('folio_id','=',self.id)])
|
||||
payment_ids = payments.mapped('id')
|
||||
invoices = self.mapped('invoice_ids.id')
|
||||
return{
|
||||
'name': _('Payments'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.payment',
|
||||
'target': 'new',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', payment_ids)],
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def open_invoices_folio(self):
|
||||
invoices = self.mapped('invoice_ids')
|
||||
action = self.env.ref('account.action_invoice_tree1').read()[0]
|
||||
if len(invoices) > 1:
|
||||
action['domain'] = [('id', 'in', invoices.ids)]
|
||||
elif len(invoices) == 1:
|
||||
action['views'] = [(self.env.ref('account.invoice_form').id, 'form')]
|
||||
action['res_id'] = invoices.ids[0]
|
||||
else:
|
||||
action = {'type': 'ir.actions.act_window_close'}
|
||||
return action
|
||||
|
||||
@api.multi
|
||||
def action_return_payments(self):
|
||||
self.ensure_one()
|
||||
return_move_ids = []
|
||||
acc_pay_obj = self.env['account.payment']
|
||||
payments = acc_pay_obj.search([
|
||||
'|',
|
||||
('invoice_ids', 'in', self.invoice_ids.ids),
|
||||
('folio_id', '=', self.id)
|
||||
])
|
||||
return_move_ids += self.invoice_ids.filtered(
|
||||
lambda invoice: invoice.type == 'out_refund').mapped(
|
||||
'payment_move_line_ids.move_id.id')
|
||||
return_lines = self.env['payment.return.line'].search([(
|
||||
'move_line_ids','in',payments.mapped(
|
||||
'move_line_ids.id'))])
|
||||
return_move_ids += return_lines.mapped('return_id.move_id.id')
|
||||
|
||||
return{
|
||||
'name': _('Returns'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.move',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', return_move_ids)],
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_checks(self):
|
||||
self.ensure_one()
|
||||
rooms = self.mapped('room_lines.id')
|
||||
return {
|
||||
'name': _('Cardexs'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'cardex',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('reservation_id', 'in', rooms)],
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def action_folios_amount(self):
|
||||
now_utc_dt = date_utils.now()
|
||||
now_utc_str = now_utc_dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
reservations = self.env['hotel.reservation'].search([
|
||||
('checkout', '<=', now_utc_str)
|
||||
])
|
||||
folio_ids = reservations.mapped('folio_id.id')
|
||||
folios = self.env['hotel.folio'].search([('id', 'in', folio_ids)])
|
||||
folios = folios.filtered(lambda r: r.invoices_amount > 0)
|
||||
return {
|
||||
'name': _('Pending'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'hotel.folio',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', folios.ids)]
|
||||
}
|
||||
|
||||
@api.depends('room_lines')
|
||||
def _compute_has_confirmed_reservations_to_send(self):
|
||||
has_to_send = False
|
||||
for rline in self.room_lines:
|
||||
if rline.splitted:
|
||||
master_reservation = rline.parent_reservation or rline
|
||||
has_to_send = self.env['hotel.reservation'].search_count([
|
||||
('splitted', '=', True),
|
||||
('folio_id', '=', self.id),
|
||||
('to_send', '=', True),
|
||||
('state', 'in', ('confirm', 'booking')),
|
||||
'|',
|
||||
('parent_reservation', '=', master_reservation.id),
|
||||
('id', '=', master_reservation.id),
|
||||
]) > 0
|
||||
elif rline.to_send and rline.state in ('confirm', 'booking'):
|
||||
has_to_send = True
|
||||
break
|
||||
self.has_confirmed_reservations_to_send = has_to_send
|
||||
|
||||
@api.depends('room_lines')
|
||||
def _compute_has_cancelled_reservations_to_send(self):
|
||||
has_to_send = False
|
||||
for rline in self.room_lines:
|
||||
if rline.splitted:
|
||||
master_reservation = rline.parent_reservation or rline
|
||||
has_to_send = self.env['hotel.reservation'].search_count([
|
||||
('splitted', '=', True),
|
||||
('folio_id', '=', self.id),
|
||||
('to_send', '=', True),
|
||||
('state', '=', 'cancelled'),
|
||||
'|',
|
||||
('parent_reservation', '=', master_reservation.id),
|
||||
('id', '=', master_reservation.id),
|
||||
]) > 0
|
||||
elif rline.to_send and rline.state == 'cancelled':
|
||||
has_to_send = True
|
||||
break
|
||||
self.has_cancelled_reservations_to_send = has_to_send
|
||||
|
||||
@api.depends('room_lines')
|
||||
def _compute_has_checkout_to_send(self):
|
||||
has_to_send = True
|
||||
for rline in self.room_lines:
|
||||
if rline.splitted:
|
||||
master_reservation = rline.parent_reservation or rline
|
||||
nreservs = self.env['hotel.reservation'].search_count([
|
||||
('splitted', '=', True),
|
||||
('folio_id', '=', self.id),
|
||||
('to_send', '=', True),
|
||||
('state', '=', 'done'),
|
||||
'|',
|
||||
('parent_reservation', '=', master_reservation.id),
|
||||
('id', '=', master_reservation.id),
|
||||
])
|
||||
if nreservs != len(self.room_lines):
|
||||
has_to_send = False
|
||||
elif not rline.to_send or rline.state != 'done':
|
||||
has_to_send = False
|
||||
break
|
||||
self.has_checkout_to_send = has_to_send
|
||||
|
||||
@api.multi
|
||||
def _compute_cardex_count(self):
|
||||
_logger.info('_compute_cardex_amount')
|
||||
for fol in self:
|
||||
num_cardex = 0
|
||||
pending = False
|
||||
if fol.reservation_type == 'normal':
|
||||
for reser in fol.room_lines:
|
||||
if reser.state != 'cancelled' and \
|
||||
not reser.parent_reservation:
|
||||
num_cardex += len(reser.cardex_ids)
|
||||
fol.cardex_count = num_cardex
|
||||
pending = 0
|
||||
for reser in fol.room_lines:
|
||||
if reser.state != 'cancelled' and \
|
||||
not reser.parent_reservation:
|
||||
pending += (reser.adults + reser.children) \
|
||||
- len(reser.cardex_ids)
|
||||
if pending <= 0:
|
||||
fol.cardex_pending = False
|
||||
else:
|
||||
fol.cardex_pending = True
|
||||
fol.cardex_pending_num = pending
|
||||
|
||||
@api.multi
|
||||
def go_to_currency_exchange(self):
|
||||
'''
|
||||
when Money Exchange button is clicked then this method is called.
|
||||
-------------------------------------------------------------------
|
||||
@param self: object pointer
|
||||
'''
|
||||
_logger.info('go_to_currency_exchange')
|
||||
pass
|
||||
# cr, uid, context = self.env.args
|
||||
# context = dict(context)
|
||||
# for rec in self:
|
||||
# if rec.partner_id.id and len(rec.room_lines) != 0:
|
||||
# context.update({'folioid': rec.id, 'guest': rec.partner_id.id,
|
||||
# 'room_no': rec.room_lines[0].product_id.name})
|
||||
# self.env.args = cr, uid, misc.frozendict(context)
|
||||
# else:
|
||||
# raise except_orm(_('Warning'), _('Please Reserve Any Room.'))
|
||||
# return {'name': _('Currency Exchange'),
|
||||
# 'res_model': 'currency.exchange',
|
||||
# 'type': 'ir.actions.act_window',
|
||||
# 'view_id': False,
|
||||
# 'view_mode': 'form,tree',
|
||||
# 'view_type': 'form',
|
||||
# 'context': {'default_folio_no': context.get('folioid'),
|
||||
# 'default_hotel_id': context.get('hotel'),
|
||||
# 'default_guest_name': context.get('guest'),
|
||||
# 'default_room_number': context.get('room_no')
|
||||
# },
|
||||
# }
|
||||
|
||||
@api.model
|
||||
def create(self, vals, check=True):
|
||||
"""
|
||||
Overrides orm create method.
|
||||
@param self: The object pointer
|
||||
@param vals: dictionary of fields value.
|
||||
@return: new record set for hotel folio.
|
||||
"""
|
||||
_logger.info('create')
|
||||
if not 'service_line_ids' and 'folio_id' in vals:
|
||||
tmp_room_lines = vals.get('room_lines', [])
|
||||
vals['order_policy'] = vals.get('hotel_policy', 'manual')
|
||||
vals.update({'room_lines': []})
|
||||
for line in (tmp_room_lines):
|
||||
line[2].update({'folio_id': folio_id})
|
||||
vals.update({'room_lines': tmp_room_lines})
|
||||
folio_id = super(HotelFolio, self).create(vals)
|
||||
else:
|
||||
if not vals:
|
||||
vals = {}
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('hotel.folio')
|
||||
folio_id = super(HotelFolio, self).create(vals)
|
||||
|
||||
return folio_id
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if 'room_lines' in vals and vals['room_lines'][0][2] and 'reservation_line_ids' in vals['room_lines'][0][2] and vals['room_lines'][0][2]['reservation_line_ids'][0][0] == 5:
|
||||
del vals['room_lines']
|
||||
return super(HotelFolio, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
@api.onchange('partner_id')
|
||||
def onchange_partner_id(self):
|
||||
'''
|
||||
When you change partner_id it will update the partner_invoice_id,
|
||||
partner_shipping_id and pricelist_id of the hotel folio as well
|
||||
---------------------------------------------------------------
|
||||
@param self: object pointer
|
||||
'''
|
||||
_logger.info('onchange_partner_id')
|
||||
pass
|
||||
# self.update({
|
||||
# 'currency_id': self.env.ref('base.main_company').currency_id,
|
||||
# 'partner_invoice_id': self.partner_id and self.partner_id.id or False,
|
||||
# 'partner_shipping_id': self.partner_id and self.partner_id.id or False,
|
||||
# 'pricelist_id': self.partner_id and self.partner_id.property_product_pricelist.id or False,
|
||||
# })
|
||||
# """
|
||||
# Warning messajes saved in partner form to folios
|
||||
# """
|
||||
# if not self.partner_id:
|
||||
# return
|
||||
# warning = {}
|
||||
# title = False
|
||||
# message = False
|
||||
# partner = self.partner_id
|
||||
#
|
||||
# # If partner has no warning, check its company
|
||||
# if partner.sale_warn == 'no-message' and partner.parent_id:
|
||||
# partner = partner.parent_id
|
||||
#
|
||||
# if partner.sale_warn != 'no-message':
|
||||
# # Block if partner only has warning but parent company is blocked
|
||||
# if partner.sale_warn != 'block' and partner.parent_id \
|
||||
# and partner.parent_id.sale_warn == 'block':
|
||||
# partner = partner.parent_id
|
||||
# title = _("Warning for %s") % partner.name
|
||||
# message = partner.sale_warn_msg
|
||||
# warning = {
|
||||
# 'title': title,
|
||||
# 'message': message,
|
||||
# }
|
||||
# if self.partner_id.sale_warn == 'block':
|
||||
# self.update({
|
||||
# 'partner_id': False,
|
||||
# 'partner_invoice_id': False,
|
||||
# 'partner_shipping_id': False,
|
||||
# 'pricelist_id': False
|
||||
# })
|
||||
# return {'warning': warning}
|
||||
#
|
||||
# if warning:
|
||||
# return {'warning': warning}
|
||||
|
||||
@api.multi
|
||||
def button_dummy(self):
|
||||
'''
|
||||
@param self: object pointer
|
||||
'''
|
||||
# for folio in self:
|
||||
# folio.order_id.button_dummy()
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def action_done(self):
|
||||
for line in self.room_lines:
|
||||
if line.state == "booking":
|
||||
line.action_reservation_checkout()
|
||||
|
||||
@api.multi
|
||||
def action_invoice_create(self, grouped=False, states=None):
|
||||
'''
|
||||
@param self: object pointer
|
||||
'''
|
||||
pass
|
||||
# if states is None:
|
||||
# states = ['confirmed', 'done']
|
||||
# order_ids = [folio.order_id.id for folio in self]
|
||||
# sale_obj = self.env['sale.order'].browse(order_ids)
|
||||
# invoice_id = (sale_obj.action_invoice_create(grouped=False,
|
||||
# states=['confirmed',
|
||||
# 'done']))
|
||||
# for line in self:
|
||||
# values = {'invoiced': True,
|
||||
# 'state': 'progress' if grouped else 'progress',
|
||||
# 'hotel_invoice_id': invoice_id
|
||||
# }
|
||||
# line.write(values)
|
||||
# return invoice_id
|
||||
|
||||
@api.multi
|
||||
def advance_invoice(self):
|
||||
pass
|
||||
# order_ids = [folio.order_id.id for folio in self]
|
||||
# sale_obj = self.env['sale.order'].browse(order_ids)
|
||||
# invoices = action_invoice_create(self, grouped=True)
|
||||
# return invoices
|
||||
|
||||
@api.multi
|
||||
def action_cancel(self):
|
||||
'''
|
||||
@param self: object pointer
|
||||
'''
|
||||
pass
|
||||
# for sale in self:
|
||||
# if not sale.order_id:
|
||||
# raise ValidationError(_('Order id is not available'))
|
||||
# for invoice in sale.invoice_ids:
|
||||
# invoice.state = 'cancel'
|
||||
# sale.room_lines.action_cancel()
|
||||
# sale.order_id.action_cancel()
|
||||
|
||||
|
||||
@api.multi
|
||||
def action_confirm(self):
|
||||
_logger.info('action_confirm')
|
||||
|
||||
@api.multi
|
||||
def print_quotation(self):
|
||||
pass
|
||||
# self.order_id.filtered(lambda s: s.state == 'draft').write({
|
||||
# 'state': 'sent',
|
||||
# })
|
||||
# return self.env.ref('sale.report_saleorder').report_action(self, data=data)
|
||||
|
||||
@api.multi
|
||||
def action_cancel_draft(self):
|
||||
_logger.info('action_confirm')
|
||||
|
||||
@api.multi
|
||||
def send_reservation_mail(self):
|
||||
'''
|
||||
This function opens a window to compose an email,
|
||||
template message loaded by default.
|
||||
@param self: object pointer
|
||||
'''
|
||||
# Debug Stop -------------------
|
||||
# import wdb; wdb.set_trace()
|
||||
# Debug Stop -------------------
|
||||
self.ensure_one()
|
||||
ir_model_data = self.env['ir.model.data']
|
||||
try:
|
||||
template_id = (ir_model_data.get_object_reference
|
||||
('hotel',
|
||||
'mail_template_hotel_reservation')[1])
|
||||
except ValueError:
|
||||
template_id = False
|
||||
try:
|
||||
compose_form_id = (ir_model_data.get_object_reference
|
||||
('mail',
|
||||
'email_compose_message_wizard_form')[1])
|
||||
except ValueError:
|
||||
compose_form_id = False
|
||||
ctx = dict()
|
||||
ctx.update({
|
||||
'default_model': 'hotel.folio',
|
||||
'default_res_id': self._ids[0],
|
||||
'default_use_template': bool(template_id),
|
||||
'default_template_id': template_id,
|
||||
'default_composition_mode': 'comment',
|
||||
'force_send': True,
|
||||
'mark_so_as_sent': True
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.compose.message',
|
||||
'views': [(compose_form_id, 'form')],
|
||||
'view_id': compose_form_id,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
'force_send': True
|
||||
}
|
||||
|
||||
@api.multi
|
||||
def send_exit_mail(self):
|
||||
'''
|
||||
This function opens a window to compose an email,
|
||||
template message loaded by default.
|
||||
@param self: object pointer
|
||||
'''
|
||||
# Debug Stop -------------------
|
||||
# import wdb; wdb.set_trace()
|
||||
# Debug Stop -------------------
|
||||
self.ensure_one()
|
||||
ir_model_data = self.env['ir.model.data']
|
||||
try:
|
||||
template_id = (ir_model_data.get_object_reference
|
||||
('hotel',
|
||||
'mail_template_hotel_exit')[1])
|
||||
except ValueError:
|
||||
template_id = False
|
||||
try:
|
||||
compose_form_id = (ir_model_data.get_object_reference
|
||||
('mail',
|
||||
'email_compose_message_wizard_form')[1])
|
||||
except ValueError:
|
||||
compose_form_id = False
|
||||
ctx = dict()
|
||||
ctx.update({
|
||||
'default_model': 'hotel.reservation',
|
||||
'default_res_id': self._ids[0],
|
||||
'default_use_template': bool(template_id),
|
||||
'default_template_id': template_id,
|
||||
'default_composition_mode': 'comment',
|
||||
'force_send': True,
|
||||
'mark_so_as_sent': True
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.compose.message',
|
||||
'views': [(compose_form_id, 'form')],
|
||||
'view_id': compose_form_id,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
'force_send': True
|
||||
}
|
||||
|
||||
|
||||
@api.multi
|
||||
def send_cancel_mail(self):
|
||||
'''
|
||||
This function opens a window to compose an email,
|
||||
template message loaded by default.
|
||||
@param self: object pointer
|
||||
'''
|
||||
# Debug Stop -------------------
|
||||
#import wdb; wdb.set_trace()
|
||||
# Debug Stop -------------------
|
||||
self.ensure_one()
|
||||
ir_model_data = self.env['ir.model.data']
|
||||
try:
|
||||
template_id = (ir_model_data.get_object_reference
|
||||
('hotel',
|
||||
'mail_template_hotel_cancel')[1])
|
||||
except ValueError:
|
||||
template_id = False
|
||||
try:
|
||||
compose_form_id = (ir_model_data.get_object_reference
|
||||
('mail',
|
||||
'email_compose_message_wizard_form')[1])
|
||||
except ValueError:
|
||||
compose_form_id = False
|
||||
ctx = dict()
|
||||
ctx.update({
|
||||
'default_model': 'hotel.reservation',
|
||||
'default_res_id': self._ids[0],
|
||||
'default_use_template': bool(template_id),
|
||||
'default_template_id': template_id,
|
||||
'default_composition_mode': 'comment',
|
||||
'force_send': True,
|
||||
'mark_so_as_sent': True
|
||||
})
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.compose.message',
|
||||
'views': [(compose_form_id, 'form')],
|
||||
'view_id': compose_form_id,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
'force_send': True
|
||||
}
|
||||
|
||||
@api.model
|
||||
def reservation_reminder_24hrs(self):
|
||||
"""
|
||||
This method is for scheduler
|
||||
every 1day scheduler will call this method to
|
||||
find all tomorrow's reservations.
|
||||
----------------------------------------------
|
||||
@param self: The object pointer
|
||||
@return: send a mail
|
||||
"""
|
||||
now_str = time.strftime(dt)
|
||||
now_date = datetime.strptime(now_str, dt)
|
||||
ir_model_data = self.env['ir.model.data']
|
||||
template_id = (ir_model_data.get_object_reference
|
||||
('hotel_reservation',
|
||||
'mail_template_reservation_reminder_24hrs')[1])
|
||||
template_rec = self.env['mail.template'].browse(template_id)
|
||||
for reserv_rec in self.search([]):
|
||||
checkin_date = (datetime.strptime(reserv_rec.checkin, dt))
|
||||
difference = relativedelta(now_date, checkin_date)
|
||||
if(difference.days == -1 and reserv_rec.partner_id.email and
|
||||
reserv_rec.state == 'confirm'):
|
||||
template_rec.send_mail(reserv_rec.id, force_send=True)
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
# for record in self:
|
||||
# record.order_id.unlink()
|
||||
return super(HotelFolio, self).unlink()
|
||||
|
||||
@api.multi
|
||||
def get_grouped_reservations_json(self, state, import_all=False):
|
||||
self.ensure_one()
|
||||
info_grouped = []
|
||||
for rline in self.room_lines:
|
||||
if (import_all or rline.to_send) and not rline.parent_reservation and rline.state == state:
|
||||
dates = rline.get_real_checkin_checkout()
|
||||
vals = {
|
||||
'num': len(
|
||||
self.room_lines.filtered(lambda r: r.get_real_checkin_checkout()[0] == dates[0] and r.get_real_checkin_checkout()[1] == dates[1] and r.room_type_id.id == rline.room_type_id.id and (r.to_send or import_all) and not r.parent_reservation and r.state == rline.state)
|
||||
),
|
||||
'room_type': {
|
||||
'id': rline.room_type_id.id,
|
||||
'name': rline.room_type_id.name,
|
||||
},
|
||||
'checkin': dates[0],
|
||||
'checkout': dates[1],
|
||||
'nights': len(rline.reservation_line_ids),
|
||||
'adults': rline.adults,
|
||||
'childrens': rline.children,
|
||||
}
|
||||
founded = False
|
||||
for srline in info_grouped:
|
||||
if srline['num'] == vals['num'] and srline['room_type']['id'] == vals['room_type']['id'] and srline['checkin'] == vals['checkin'] and srline['checkout'] == vals['checkout']:
|
||||
founded = True
|
||||
break
|
||||
if not founded:
|
||||
info_grouped.append(vals)
|
||||
return sorted(sorted(info_grouped, key=lambda k: k['num'], reverse=True), key=lambda k: k['room_type']['id'])
|
||||
1298
hotel/models/hotel_reservation.py
Normal file
1298
hotel/models/hotel_reservation.py
Normal file
File diff suppressed because it is too large
Load Diff
35
hotel/models/hotel_reservation_line.py
Normal file
35
hotel/models/hotel_reservation_line.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
#
|
||||
# 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 odoo import models, fields, api, _
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
class HotelReservationLine(models.Model):
|
||||
_name = "hotel.reservation.line"
|
||||
_order = "date"
|
||||
|
||||
reservation_id = fields.Many2one('hotel.reservation', string='Reservation',
|
||||
ondelete='cascade', required=True,
|
||||
copy=False)
|
||||
date = fields.Date('Date')
|
||||
price = fields.Float('Price')
|
||||
discount = fields.Float(
|
||||
string='Discount (%)',
|
||||
digits=dp.get_precision('Discount'), default=0.0)
|
||||
105
hotel/models/hotel_room.py
Normal file
105
hotel/models/hotel_room.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# -*- 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 odoo import models, fields, api, _
|
||||
|
||||
|
||||
class HotelRoom(models.Model):
|
||||
""" The rooms for lodging can be for sleeping, usually called rooms, and also
|
||||
for speeches (conference rooms), parking, relax with cafe con leche, spa...
|
||||
"""
|
||||
_name = 'hotel.room'
|
||||
_description = 'Hotel Room'
|
||||
# The record's name
|
||||
name = fields.Char('Room Name', required=True)
|
||||
# Used for activate records
|
||||
active = fields.Boolean('Active', default=True)
|
||||
# Used for ordering
|
||||
sequence = fields.Integer('Sequence', default=0)
|
||||
|
||||
_order = "sequence, room_type_id, name"
|
||||
|
||||
# each room has only one type (Many2one)
|
||||
room_type_id = fields.Many2one('hotel.room.type', 'Hotel Room Type')
|
||||
|
||||
floor_id = fields.Many2one('hotel.floor', 'Ubication',
|
||||
help='At which floor the room is located.')
|
||||
# TODO Q. Should the amenities be on the Room Type ? -
|
||||
room_amenities = fields.Many2many('hotel.room.amenities', 'temp_tab',
|
||||
'room_amenities', 'rcateg_id',
|
||||
string='Room Amenities',
|
||||
help='List of room amenities.')
|
||||
|
||||
# default price for this room
|
||||
list_price = fields.Float(store=True,
|
||||
string='Room Rate',
|
||||
help='The room rate is fixed unless a room type'
|
||||
' is selected, in which case the rate is taken from'
|
||||
' the room type.')
|
||||
# how to manage the price
|
||||
# sale_price_type = fields.Selection([
|
||||
# ('fixed', 'Fixed Price'),
|
||||
# ('vroom', 'Room Type'),
|
||||
# ], 'Price Type', default='fixed', required=True)
|
||||
# max number of adults and children per room
|
||||
max_adult = fields.Integer('Max Adult')
|
||||
max_child = fields.Integer('Max Child')
|
||||
# maximum capacity of the room
|
||||
capacity = fields.Integer('Capacity')
|
||||
# FIXME not used
|
||||
to_be_cleaned = fields.Boolean('To be Cleaned', default=False)
|
||||
|
||||
shared_room = fields.Boolean('Shared Room', default=False)
|
||||
|
||||
description_sale = fields.Text(
|
||||
'Sale Description', translate=True,
|
||||
help="A description of the Product that you want to communicate to "
|
||||
" your customers. This description will be copied to every Sales "
|
||||
" Order, Delivery Order and Customer Invoice/Credit Note")
|
||||
|
||||
|
||||
# In case the price is managed from a specific type of room
|
||||
# price_virtual_room = fields.Many2one(
|
||||
# 'hotel.virtual.room',
|
||||
# 'Price Virtual Room',
|
||||
# help='Price will be based on selected Virtual Room')
|
||||
|
||||
# virtual_rooms = fields.Many2many('hotel.virtual.room',
|
||||
# string='Virtual Rooms')
|
||||
# categ_id = fields.Selection([('room', 'Room '),
|
||||
# ('shared_room', 'Shared Room'),
|
||||
# ('parking', 'Parking')],
|
||||
# string='Hotel Lodging Type',
|
||||
# store=True, default='room')
|
||||
|
||||
# price_virtual_room_domain = fields.Char(
|
||||
# compute=_compute_price_virtual_room_domain,
|
||||
# readonly=True,
|
||||
# store=False,
|
||||
# )
|
||||
|
||||
# @api.multi
|
||||
# @api.depends('categ_id')
|
||||
# def _compute_price_virtual_room_domain(self):
|
||||
# for rec in self:
|
||||
# rec.price_virtual_room_domain = json.dumps(
|
||||
# ['|', ('room_ids.id', '=', rec.id), ('room_type_ids.cat_id.id', '=', rec.categ_id.id)]
|
||||
# )
|
||||
|
||||
# @api.onchange('categ_id')
|
||||
# def price_virtual_room_domain(self):
|
||||
# return {
|
||||
# 'domain': {
|
||||
# 'price_virtual_room': [
|
||||
# '|', ('room_ids.id', '=', self._origin.id),
|
||||
# ('room_type_ids.cat_id.id', '=', self.categ_id.id)
|
||||
# ]
|
||||
# }
|
||||
# }
|
||||
|
||||
# @api.multi
|
||||
# def unlink(self):
|
||||
# for record in self:
|
||||
# record.product_id.unlink()
|
||||
# return super(HotelRoom, self).unlink()
|
||||
30
hotel/models/hotel_room_amenities.py
Normal file
30
hotel/models/hotel_room_amenities.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- 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 odoo import models, fields, api, _
|
||||
|
||||
|
||||
class HotelRoomAmenities(models.Model):
|
||||
|
||||
_name = 'hotel.room.amenities'
|
||||
_description = 'Room amenities'
|
||||
# The record's name
|
||||
name = fields.Char('Amenity Name', required=True)
|
||||
# Used for activate records
|
||||
active = fields.Boolean('Active', default=True)
|
||||
|
||||
default_code = fields.Char('Internal Reference', store=True)
|
||||
|
||||
# room_categ_id = fields.Many2one('product.product', 'Product Category',
|
||||
# required=True, delegate=True,
|
||||
# ondelete='cascade')
|
||||
room_amenities_type_id = fields.Many2one('hotel.room.amenities.type',
|
||||
'Amenity Catagory')
|
||||
|
||||
# room_ids = fields.Many2man('hotel.room','Rooms')
|
||||
|
||||
# @api.multi
|
||||
# def unlink(self):
|
||||
# # self.room_categ_id.unlink()
|
||||
# return super(HotelRoomAmenities, self).unlink()
|
||||
27
hotel/models/hotel_room_amenities_type.py
Normal file
27
hotel/models/hotel_room_amenities_type.py
Normal file
@@ -0,0 +1,27 @@
|
||||
# -*- 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 odoo import models, fields, api, _
|
||||
|
||||
|
||||
class HotelRoomAmenitiesType(models.Model):
|
||||
|
||||
_name = 'hotel.room.amenities.type'
|
||||
_description = 'Amenities Type'
|
||||
# The record's name
|
||||
name = fields.Char('Amenity Name', required=True)
|
||||
# Used for activate records
|
||||
active = fields.Boolean('Active', default=True)
|
||||
|
||||
room_amenities_ids = fields.One2many('hotel.room.amenities',
|
||||
'room_amenities_type_id',
|
||||
'Amenities in this category')
|
||||
|
||||
# cat_id = fields.Many2one('product.category', 'category', required=True,
|
||||
# delegate=True, ondelete='cascade')
|
||||
|
||||
# @api.multi
|
||||
# def unlink(self):
|
||||
# # self.cat_id.unlink()
|
||||
# return super(HotelRoomAmenitiesType, self).unlink()
|
||||
141
hotel/models/hotel_room_type.py
Normal file
141
hotel/models/hotel_room_type.py
Normal file
@@ -0,0 +1,141 @@
|
||||
# -*- 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 decimal import Decimal
|
||||
from datetime import datetime, timedelta
|
||||
import dateutil.parser
|
||||
# For Python 3.0 and later
|
||||
from urllib.request import urlopen
|
||||
import time
|
||||
from odoo.exceptions import except_orm, UserError, ValidationError
|
||||
from odoo.tools import (
|
||||
misc,
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.addons.hotel import date_utils
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
class HotelRoomType(models.Model):
|
||||
""" Before creating a 'room type', you need to consider the following:
|
||||
With the term 'room type' is meant a type of residential accommodation: for
|
||||
example, a Double Room, a Economic Room, an Apartment, a Tent, a Caravan...
|
||||
"""
|
||||
_name = "hotel.room.type"
|
||||
_description = "Room Type"
|
||||
|
||||
_inherits = {'product.product': 'product_id'}
|
||||
# Relationship between models
|
||||
product_id = fields.Many2one('product.product', 'Product Room Type',
|
||||
required=True, delegate=True,
|
||||
ondelete='cascade')
|
||||
# cat_id = fields.Many2one('product.category', 'category', required=True,
|
||||
# delegate=True, index=True, ondelete='cascade')
|
||||
room_ids = fields.One2many('hotel.room', 'room_type_id', 'Rooms')
|
||||
|
||||
# TODO Hierarchical relationship for parent-child tree ?
|
||||
# parent_id = fields.Many2one ...
|
||||
|
||||
# Used for activate records
|
||||
active = fields.Boolean('Active', default=True,
|
||||
help="The active field allows you to hide the \
|
||||
category without removing it.")
|
||||
# Used for ordering
|
||||
sequence = fields.Integer('Sequence', default=0)
|
||||
|
||||
code_type = fields.Char('Code')
|
||||
|
||||
_order = "sequence, code_type, name"
|
||||
|
||||
_sql_constraints = [('code_type_unique', 'unique(code_type)',
|
||||
'code must be unique!')]
|
||||
# total number of rooms in this type
|
||||
total_rooms_count = fields.Integer(compute='_compute_total_rooms')
|
||||
# FIXING rename to default rooms ?
|
||||
max_real_rooms = fields.Integer('Default Max Room Allowed')
|
||||
|
||||
@api.depends('room_ids')
|
||||
def _compute_total_rooms(self):
|
||||
for record in self:
|
||||
count = 0
|
||||
count += len(record.room_ids) # Rooms linked directly
|
||||
# room_categories = r.room_type_ids.mapped('room_ids.id')
|
||||
# count += self.env['hotel.room'].search_count([
|
||||
# ('categ_id.id', 'in', room_categories)
|
||||
# ]) # Rooms linked through room type
|
||||
record.total_rooms_count = count
|
||||
|
||||
def _check_duplicated_rooms(self):
|
||||
# FIXME Using a Many2one relationship duplicated should not been possible
|
||||
pass
|
||||
|
||||
@api.constrains('max_real_rooms', 'room_ids')
|
||||
def _check_max_rooms(self):
|
||||
warning_msg = ""
|
||||
# for r in self:
|
||||
if self.max_real_rooms > self.total_rooms_count:
|
||||
warning_msg += _('The Maxime rooms allowed can not be greate \
|
||||
than total rooms count')
|
||||
raise models.ValidationError(warning_msg)
|
||||
|
||||
@api.multi
|
||||
def get_capacity(self):
|
||||
# WARNING use selg.capacity directly ?
|
||||
pass
|
||||
# self.ensure_one()
|
||||
# hotel_room_obj = self.env['hotel.room']
|
||||
# room_categories = self.room_type_ids.mapped('room_ids.id')
|
||||
# room_ids = self.room_ids + hotel_room_obj.search([
|
||||
# ('categ_id.id', 'in', room_categories)
|
||||
# ])
|
||||
# capacities = room_ids.mapped('capacity')
|
||||
# return any(capacities) and min(capacities) or 0
|
||||
|
||||
@api.model
|
||||
def check_availability_virtual_room(self, checkin, checkout,
|
||||
room_type_id=False, notthis=[]):
|
||||
"""
|
||||
Check the avalability for an specific type of room
|
||||
@return: A recordset of free rooms ?
|
||||
"""
|
||||
occupied = self.env['hotel.reservation'].occupied(checkin, checkout)
|
||||
rooms_occupied = occupied.mapped('product_id.id')
|
||||
free_rooms = self.env['hotel.room'].search([
|
||||
('product_id.id', 'not in', rooms_occupied),
|
||||
('id', 'not in', notthis)
|
||||
])
|
||||
if room_type_id:
|
||||
# hotel_room_obj = self.env['hotel.room']
|
||||
room_type_id = self.env['hotel.room.type'].search([
|
||||
('id', '=', room_type_id)
|
||||
])
|
||||
# room_categories = virtual_room.room_type_ids.mapped('room_ids.id')
|
||||
# rooms_linked = virtual_room.room_ids | hotel_room_obj.search([
|
||||
# ('categ_id.id', 'in', room_categories)])
|
||||
# rooms_linked = room_type_id.room_ids
|
||||
rooms_linked = self.room_ids
|
||||
free_rooms = free_rooms & rooms_linked
|
||||
return free_rooms.sorted(key=lambda r: r.sequence)
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
"""
|
||||
Overrides orm create method.
|
||||
@param self: The object pointer
|
||||
@param vals: dictionary of fields value.
|
||||
@return: new record set for hotel room type.
|
||||
"""
|
||||
vals.update({'is_room_type': True})
|
||||
vals.update({'purchase_ok': False})
|
||||
vals.update({'type': 'service'})
|
||||
return super().create(vals)
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for record in self:
|
||||
# Set fixed price to rooms with price from this virtual rooms
|
||||
# Remove product.product
|
||||
record.product_id.unlink()
|
||||
return super().unlink()
|
||||
78
hotel/models/hotel_service.py
Normal file
78
hotel/models/hotel_service.py
Normal file
@@ -0,0 +1,78 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.addons.hotel import date_utils
|
||||
from odoo.addons import decimal_precision as dp
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class HotelService(models.Model):
|
||||
|
||||
@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):
|
||||
if 'room_lines' in self.env.context and self.env.context['room_lines']:
|
||||
ids = [item[1] for item in self.env.context['room_lines']]
|
||||
return self.env['hotel.reservation'].search([('id', 'in', ids)],
|
||||
limit=1)
|
||||
return False
|
||||
|
||||
_name = 'hotel.service'
|
||||
_description = 'Hotel Services and its charges'
|
||||
|
||||
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')
|
||||
|
||||
ser_room_line = fields.Many2one('hotel.reservation', 'Room',
|
||||
default=_default_ser_room_line)
|
||||
|
||||
list_price = fields.Float(
|
||||
related='product_id.list_price')
|
||||
|
||||
channel_type = fields.Selection([
|
||||
('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone'),
|
||||
('call', 'Call Center'),
|
||||
('web', 'Web')], 'Sales Channel')
|
||||
|
||||
ser_checkin = fields.Datetime('From Date', required=True,
|
||||
default=_service_checkin)
|
||||
ser_checkout = fields.Datetime('To Date', required=True,
|
||||
default=_service_checkout)
|
||||
|
||||
|
||||
# TODO Hierarchical relationship for parent-child tree
|
||||
# parent_id = fields.Many2one ...
|
||||
|
||||
# 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()
|
||||
246
hotel/models/hotel_service_line.py
Normal file
246
hotel/models/hotel_service_line.py
Normal file
@@ -0,0 +1,246 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Alexandre Díaz
|
||||
# Copyright 2017 Dario Lodeiros
|
||||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
||||
import time
|
||||
import datetime
|
||||
import logging
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.addons.hotel import date_utils
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
from odoo.addons import decimal_precision as dp
|
||||
|
||||
|
||||
class HotelServiceLine(models.Model):
|
||||
|
||||
@api.one
|
||||
def copy(self, default=None):
|
||||
'''
|
||||
@param self: object pointer
|
||||
@param default: dict of default values to be set
|
||||
'''
|
||||
line_id = self.service_line_id.id
|
||||
sale_line_obj = self.env['sale.order.line'].browse(line_id)
|
||||
return sale_line_obj.copy(default=default)
|
||||
|
||||
@api.multi
|
||||
def _amount_line(self, field_name, arg):
|
||||
'''
|
||||
@param self: object pointer
|
||||
@param field_name: Names of fields.
|
||||
@param arg: User defined arguments
|
||||
'''
|
||||
for folio in self:
|
||||
line = folio.service_line_id
|
||||
x = line._amount_line(field_name, arg)
|
||||
return x
|
||||
|
||||
@api.multi
|
||||
def _number_packages(self, field_name, arg):
|
||||
'''
|
||||
@param self: object pointer
|
||||
@param field_name: Names of fields.
|
||||
@param arg: User defined arguments
|
||||
'''
|
||||
for folio in self:
|
||||
line = folio.service_line_id
|
||||
x = line._number_packages(field_name, arg)
|
||||
return x
|
||||
|
||||
@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):
|
||||
if 'room_lines' in self.env.context and self.env.context['room_lines']:
|
||||
ids = [item[1] for item in self.env.context['room_lines']]
|
||||
return self.env['hotel.reservation'].search([('id', 'in', ids)],
|
||||
limit=1)
|
||||
return False
|
||||
|
||||
_name = 'hotel.service.line'
|
||||
_description = 'hotel Service line'
|
||||
# The record's name
|
||||
name = fields.Char('Service line', required=True)
|
||||
# services in the hotel are products
|
||||
product_id = fields.Many2one('product.product', 'Service')
|
||||
|
||||
list_price = fields.Float(
|
||||
related='product_id.list_price')
|
||||
|
||||
# TODO refactor to services_ids
|
||||
# services_line_id = fields.Many2one('hotel.services', 'Service Line',
|
||||
# ondelete='cascade')
|
||||
# FIXME You can add services to a folio ?
|
||||
folio_id = fields.Many2one('hotel.folio', 'Folio', ondelete='cascade')
|
||||
|
||||
channel_type = fields.Selection([
|
||||
('door', 'Door'),
|
||||
('mail', 'Mail'),
|
||||
('phone', 'Phone'),
|
||||
('call', 'Call Center'),
|
||||
('web','Web')], 'Sales Channel')
|
||||
|
||||
ser_checkin = fields.Datetime('From Date', required=True,
|
||||
default=_service_checkin)
|
||||
ser_checkout = fields.Datetime('To Date', required=True,
|
||||
default=_service_checkout)
|
||||
ser_room_line = fields.Many2one('hotel.reservation','Room', default=_default_ser_room_line)
|
||||
|
||||
@api.model
|
||||
def create(self, vals, check=True):
|
||||
"""
|
||||
Overrides orm create method.
|
||||
@param self: The object pointer
|
||||
@param vals: dictionary of fields value.
|
||||
@return: new record set for hotel service line.
|
||||
"""
|
||||
if 'folio_id' in vals:
|
||||
folio = self.env['hotel.folio'].browse(vals['folio_id'])
|
||||
vals.update({'order_id': folio.order_id.id})
|
||||
user = self.env['res.users'].browse(self.env.uid)
|
||||
if user.has_group('hotel.group_hotel_call'):
|
||||
vals.update({'channel_type': 'call'})
|
||||
return super(HotelServiceLine, self).create(vals)
|
||||
|
||||
# ~ @api.multi
|
||||
# ~ def unlink(self):
|
||||
# ~ """
|
||||
# ~ Overrides orm unlink method.
|
||||
# ~ @param self: The object pointer
|
||||
# ~ @return: True/False.
|
||||
# ~ """
|
||||
# ~ s_line_obj = self.env['sale.order.line']
|
||||
# ~ for line in self:
|
||||
# ~ if line.service_line_id:
|
||||
# ~ sale_unlink_obj = s_line_obj.browse([line.service_line_id.id])
|
||||
# ~ sale_unlink_obj.unlink()
|
||||
# ~ return super(HotelServiceLine, self).unlink()
|
||||
|
||||
@api.onchange('product_id')
|
||||
def product_id_change_hotel(self):
|
||||
'''
|
||||
@param self: object pointer
|
||||
'''
|
||||
if self.product_id:
|
||||
if not (self.folio_id and self.folio_id.partner_id) and \
|
||||
self.ser_room_line:
|
||||
self.folio_id = self.ser_room_line.folio_id
|
||||
|
||||
self.name = self.product_id.name
|
||||
self.price_unit = self.product_id.lst_price
|
||||
self.product_uom = self.product_id.uom_id
|
||||
self.price_unit = self.product_id.price
|
||||
|
||||
#~ self.price_unit = tax_obj._fix_tax_included_price(prod.price,
|
||||
#~ prod.taxes_id,
|
||||
#~ self.tax_id)
|
||||
|
||||
# ~ _logger.info(self._context)
|
||||
# ~ if 'folio_id' in self._context:
|
||||
# ~ _logger.info(self._context)
|
||||
# ~ domain_rooms = []
|
||||
# ~ rooms_lines = self.env['hotel.reservation'].search([('folio_id','=',folio_id)])
|
||||
# ~ room_ids = room_lines.mapped('id')
|
||||
# ~ domain_rooms.append(('id','in',room_ids))
|
||||
# ~ return {'domain': {'ser_room_line': domain_rooms}}
|
||||
#
|
||||
# ~ @api.onchange('folio_id')
|
||||
# ~ def folio_id_change(self):
|
||||
# ~ self.ensure_one()
|
||||
# ~ _logger.info(self.mapped('folio_id.room_lines'))
|
||||
# ~ rooms = self.mapped('folio_id.room_lines.id')
|
||||
# ~ return {'domain': {'ser_room_line': rooms}}
|
||||
|
||||
#~ @api.onchange('product_uom')
|
||||
#~ def product_uom_change(self):
|
||||
#~ '''
|
||||
#~ @param self: object pointer
|
||||
#~ '''
|
||||
# ~ if not self.product_uom:
|
||||
# ~ self.price_unit = 0.0
|
||||
# ~ return
|
||||
# ~ self.price_unit = self.product_id.lst_price
|
||||
# ~ if self.folio_id.partner_id:
|
||||
# ~ prod = self.product_id.with_context(
|
||||
# ~ lang=self.folio_id.partner_id.lang,
|
||||
# ~ partner=self.folio_id.partner_id.id,
|
||||
# ~ quantity=1,
|
||||
# ~ date_order=self.folio_id.date_order,
|
||||
# ~ pricelist=self.folio_id.pricelist_id.id,
|
||||
# ~ uom=self.product_uom.id
|
||||
# ~ )
|
||||
# ~ tax_obj = self.env['account.tax']
|
||||
# ~ self.price_unit = tax_obj._fix_tax_included_price(prod.price,
|
||||
# ~ prod.taxes_id,
|
||||
# ~ self.tax_id)
|
||||
|
||||
@api.onchange('ser_checkin', 'ser_checkout')
|
||||
def on_change_checkout(self):
|
||||
'''
|
||||
When you change checkin or checkout it will checked it
|
||||
and update the qty of hotel service line
|
||||
-----------------------------------------------------------------
|
||||
@param self: object pointer
|
||||
'''
|
||||
now_utc_dt = date_utils.now()
|
||||
if not self.ser_checkin:
|
||||
self.ser_checkin = now_utc_dt.strftime(
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
if not self.ser_checkout:
|
||||
self.ser_checkout = now_utc_dt.strftime(
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
chkin_utc_dt = date_utils.get_datetime(self.ser_checkin)
|
||||
chkout_utc_dt = date_utils.get_datetime(self.ser_checkout)
|
||||
if chkout_utc_dt < chkin_utc_dt:
|
||||
raise UserError(_('Checkout must be greater or equal checkin date'))
|
||||
if self.ser_checkin and self.ser_checkout:
|
||||
diffDate = date_utils.date_diff(self.ser_checkin,
|
||||
self.ser_checkout, hours=False) + 1
|
||||
|
||||
@api.multi
|
||||
def button_confirm(self):
|
||||
'''
|
||||
@param self: object pointer
|
||||
'''
|
||||
for folio in self:
|
||||
line = folio.service_line_id
|
||||
x = line.button_confirm()
|
||||
return x
|
||||
|
||||
@api.multi
|
||||
def button_done(self):
|
||||
'''
|
||||
@param self: object pointer
|
||||
'''
|
||||
for folio in self:
|
||||
line = folio.service_line_id
|
||||
x = line.button_done()
|
||||
return x
|
||||
|
||||
@api.one
|
||||
def copy_data(self, default=None):
|
||||
'''
|
||||
@param self: object pointer
|
||||
@param default: dict of default values to be set
|
||||
'''
|
||||
sale_line_obj = self.env['sale.order.line'
|
||||
].browse(self.service_line_id.id)
|
||||
return sale_line_obj.copy_data(default=default)
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for record in self:
|
||||
record.service_line_id.unlink()
|
||||
return super(HotelServiceLine, self).unlink()
|
||||
25
hotel/models/hotel_service_type.py
Normal file
25
hotel/models/hotel_service_type.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# -*- 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 odoo import models, fields, api, _
|
||||
|
||||
|
||||
class HotelServiceType(models.Model):
|
||||
|
||||
_name = "hotel.service.type"
|
||||
_description = "Service Type"
|
||||
# The record's name
|
||||
name = fields.Char('Service Type', required=True)
|
||||
# Used for activate records
|
||||
active = fields.Boolean('Active?', default=True)
|
||||
|
||||
# ser_id = fields.Many2one('product.category', 'category', required=True,
|
||||
# delegate=True, index=True, ondelete='cascade')
|
||||
service_ids = fields.One2many('hotel.services', 'service_type_id',
|
||||
'Services in this category')
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
# self.ser_id.unlink()
|
||||
return super(HotelServiceType, self).unlink()
|
||||
70
hotel/models/hotel_virtual_room_availability.py
Normal file
70
hotel/models/hotel_virtual_room_availability.py
Normal file
@@ -0,0 +1,70 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Alexandre Díaz <alex@aloxa.eu>
|
||||
#
|
||||
# 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 odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HotelVirtualRoomAvailability(models.Model):
|
||||
_inherit = 'mail.thread'
|
||||
_name = 'hotel.virtual.room.availability'
|
||||
|
||||
# virtual_room_id = fields.Many2one('hotel.virtual.room', 'Virtual Room',
|
||||
# required=True, track_visibility='always',
|
||||
# ondelete='cascade')
|
||||
room_type_id = fields.Many2one('hotel.room.type', 'Room Type',
|
||||
required=True, track_visibility='always',
|
||||
ondelete='cascade')
|
||||
avail = fields.Integer('Avail', default=0, track_visibility='always')
|
||||
no_ota = fields.Boolean('No OTA', default=False, track_visibility='always')
|
||||
booked = fields.Boolean('Booked', default=False, readonly=True,
|
||||
track_visibility='always')
|
||||
date = fields.Date('Date', required=True, track_visibility='always')
|
||||
|
||||
_sql_constraints = [('vroom_registry_unique', 'unique(room_type_id, date)',
|
||||
'Only can exists one availability in the same day for the same room type!')]
|
||||
|
||||
@api.constrains('avail')
|
||||
def _check_avail(self):
|
||||
if self.avail < 0:
|
||||
self.avail = 0
|
||||
|
||||
vroom_obj = self.env['hotel.room.type']
|
||||
cavail = len(vroom_obj.check_availability_virtual_room(
|
||||
self.date,
|
||||
self.date,
|
||||
room_type_id=self.room_type_id.id))
|
||||
max_avail = min(cavail,
|
||||
self.room_type_id.total_rooms_count)
|
||||
if self.avail > max_avail:
|
||||
self.avail = max_avail
|
||||
|
||||
@api.constrains('date', 'room_type_id')
|
||||
def _check_date_virtual_room_id(self):
|
||||
count = self.search_count([
|
||||
('date', '=', self.date),
|
||||
('room_type_id', '=', self.room_type_id.id)
|
||||
])
|
||||
if count > 1:
|
||||
raise ValidationError(_("can't assign the same date to more than \
|
||||
one room type"))
|
||||
50
hotel/models/hotel_virtual_room_restriction.py
Normal file
50
hotel/models/hotel_virtual_room_restriction.py
Normal file
@@ -0,0 +1,50 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Alexandre Díaz <alex@aloxa.eu>
|
||||
#
|
||||
# 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 odoo import models, fields, api
|
||||
|
||||
|
||||
class VirtualRoomRestriction(models.Model):
|
||||
_name = 'hotel.virtual.room.restriction'
|
||||
|
||||
name = fields.Char('Restriction Plan Name', required=True)
|
||||
item_ids = fields.One2many('hotel.virtual.room.restriction.item',
|
||||
'restriction_id', string='Restriction Items',
|
||||
copy=True)
|
||||
active = fields.Boolean('Active',
|
||||
help='If unchecked, it will allow you to hide the \
|
||||
restriction plan without removing it.',
|
||||
default=True)
|
||||
|
||||
@api.multi
|
||||
@api.depends('name')
|
||||
def name_get(self):
|
||||
restriction_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_restrictions_id')
|
||||
if restriction_id:
|
||||
restriction_id = int(restriction_id)
|
||||
names = []
|
||||
for record in self:
|
||||
if record.id == restriction_id:
|
||||
names.append((record.id, '%s (Parity)' % record.name))
|
||||
else:
|
||||
names.append((record.id, record.name))
|
||||
return names
|
||||
90
hotel/models/hotel_virtual_room_restriction_item.py
Normal file
90
hotel/models/hotel_virtual_room_restriction_item.py
Normal file
@@ -0,0 +1,90 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Alexandre Díaz <alex@aloxa.eu>
|
||||
#
|
||||
# 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
|
||||
from odoo import models, fields, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import DEFAULT_SERVER_DATE_FORMAT
|
||||
from odoo.addons.hotel import date_utils
|
||||
|
||||
|
||||
class HotelVirtualRoomRestrictionItem(models.Model):
|
||||
_name = 'hotel.virtual.room.restriction.item'
|
||||
|
||||
restriction_id = fields.Many2one('hotel.virtual.room.restriction',
|
||||
'Restriction Plan', ondelete='cascade',
|
||||
index=True)
|
||||
# virtual_room_id = fields.Many2one('hotel.virtual.room', 'Virtual Room',
|
||||
# required=True, ondelete='cascade')
|
||||
room_type_id = fields.Many2one('hotel.room.type', 'Room Type',
|
||||
required=True, ondelete='cascade')
|
||||
date_start = fields.Date('From')
|
||||
date_end = fields.Date("To")
|
||||
applied_on = fields.Selection([
|
||||
('1_global', 'Global'),
|
||||
# ('0_virtual_room', 'Virtual Room')], string="Apply On", required=True,
|
||||
# default='0_virtual_room',
|
||||
('0_room_type', 'Room Type')], string="Apply On", required=True,
|
||||
default='0_room_type',
|
||||
help='Pricelist Item applicable on selected option')
|
||||
|
||||
min_stay = fields.Integer("Min. Stay")
|
||||
min_stay_arrival = fields.Integer("Min. Stay Arrival")
|
||||
max_stay = fields.Integer("Max. Stay")
|
||||
max_stay_arrival = fields.Integer("Max. Stay Arrival")
|
||||
closed = fields.Boolean('Closed')
|
||||
closed_departure = fields.Boolean('Closed Departure')
|
||||
closed_arrival = fields.Boolean('Closed Arrival')
|
||||
|
||||
_sql_constraints = [('vroom_registry_unique',
|
||||
'unique(restriction_id, room_type_id, date_start, date_end)',
|
||||
'Only can exists one restriction in the same day for the same room type!')]
|
||||
|
||||
@api.constrains('min_stay', 'min_stay_arrival', 'max_stay',
|
||||
'max_stay_arrival')
|
||||
def _check_min_stay_min_stay_arrival_max_stay(self):
|
||||
if self.min_stay < 0:
|
||||
raise ValidationError(_("Min. Stay can't be less than zero"))
|
||||
elif self.min_stay_arrival < 0:
|
||||
raise ValidationError(
|
||||
("Min. Stay Arrival can't be less than zero"))
|
||||
elif self.max_stay < 0:
|
||||
raise ValidationError(_("Max. Stay can't be less than zero"))
|
||||
elif self.max_stay_arrival < 0:
|
||||
raise ValidationError(
|
||||
("Max. Stay Arrival can't be less than zero"))
|
||||
|
||||
@api.constrains('date_start', 'date_end')
|
||||
def _check_date_start_date_end(self):
|
||||
if self.applied_on == '1_global':
|
||||
self.date_start = False
|
||||
self.date_end = False
|
||||
elif self.date_start and self.date_end:
|
||||
date_start_dt = date_utils.get_datetime(self.date_start)
|
||||
date_end_dt = date_utils.get_datetime(self.date_end)
|
||||
if date_end_dt < date_start_dt:
|
||||
raise ValidationError(_("Invalid Dates"))
|
||||
|
||||
@api.constrains('applied_on')
|
||||
def _check_applied_on(self):
|
||||
count = self.search_count([('applied_on', '=', '1_global')])
|
||||
if count > 1:
|
||||
raise ValidationError(_("Already exists an global rule"))
|
||||
87
hotel/models/inherit_account_invoice.py
Normal file
87
hotel/models/inherit_account_invoice.py
Normal file
@@ -0,0 +1,87 @@
|
||||
# -*- 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, _
|
||||
from openerp.exceptions import UserError, ValidationError
|
||||
|
||||
import logging
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountInvoice(models.Model):
|
||||
|
||||
_inherit = 'account.invoice'
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
cr, uid, context = self.env.args
|
||||
context = dict(context)
|
||||
if context.get('invoice_origin', False):
|
||||
vals.update({'origin': context['invoice_origin']})
|
||||
return super(AccountInvoice, self).create(vals)
|
||||
|
||||
@api.multi
|
||||
def action_folio_payments(self):
|
||||
self.ensure_one()
|
||||
sales = self.mapped('invoice_line_ids.sale_line_ids.order_id')
|
||||
folios = self.env['hotel.folio'].search([('order_id.id','in',sales.ids)])
|
||||
payments_obj = self.env['account.payment']
|
||||
payments = payments_obj.search([('folio_id','in',folios.ids)])
|
||||
payment_ids = payments.mapped('id')
|
||||
return{
|
||||
'name': _('Payments'),
|
||||
'view_type': 'form',
|
||||
'view_mode': 'tree,form',
|
||||
'res_model': 'account.payment',
|
||||
'target': 'new',
|
||||
'type': 'ir.actions.act_window',
|
||||
'domain': [('id', 'in', payment_ids)],
|
||||
}
|
||||
|
||||
dif_customer_payment = fields.Boolean(compute='_compute_dif_customer_payment')
|
||||
from_folio = fields.Boolean(compute='_compute_dif_customer_payment')
|
||||
sale_ids = fields.Many2many(
|
||||
'sale.order', 'sale_order_invoice_rel', 'invoice_id',
|
||||
'order_id', 'Sale Orders', readonly=True,
|
||||
help="This is the list of sale orders related to this invoice.")
|
||||
folio_ids = fields.Many2many(
|
||||
comodel_name='hotel.folio', compute='_compute_dif_customer_payment')
|
||||
|
||||
@api.multi
|
||||
def _compute_dif_customer_payment(self):
|
||||
for inv in self:
|
||||
sales = inv.mapped('invoice_line_ids.sale_line_ids.order_id')
|
||||
folios = self.env['hotel.folio'].search([('order_id.id','in',sales.ids)])
|
||||
if folios:
|
||||
inv.from_folio = True
|
||||
inv.folio_ids = [(6, 0, folios.ids)]
|
||||
payments_obj = self.env['account.payment']
|
||||
payments = payments_obj.search([('folio_id','in',folios.ids)])
|
||||
for pay in payments:
|
||||
if pay.partner_id != inv.partner_id:
|
||||
inv.dif_customer_payment = True
|
||||
|
||||
@api.multi
|
||||
def action_invoice_open(self):
|
||||
to_open_invoices_without_vat = self.filtered(lambda inv: inv.state != 'open' and inv.partner_id.vat == False)
|
||||
if to_open_invoices_without_vat:
|
||||
vat_error = _("We need the VAT of the following companies")
|
||||
for invoice in to_open_invoices_without_vat:
|
||||
vat_error += ", " + invoice.partner_id.name
|
||||
raise ValidationError(vat_error)
|
||||
return super(AccountInvoice, self).action_invoice_open()
|
||||
|
||||
# ~ @api.multi
|
||||
# ~ def confirm_paid(self):
|
||||
# ~ '''
|
||||
# ~ This method change pos orders states to done when folio invoice
|
||||
# ~ is in done.
|
||||
# ~ ----------------------------------------------------------
|
||||
# ~ @param self: object pointer
|
||||
# ~ '''
|
||||
# ~ pos_order_obj = self.env['pos.order']
|
||||
# ~ res = super(AccountInvoice, self).confirm_paid()
|
||||
# ~ pos_odr_rec = pos_order_obj.search([('invoice_id', 'in', self._ids)])
|
||||
# ~ pos_odr_rec and pos_odr_rec.write({'state': 'done'})
|
||||
# ~ return res
|
||||
94
hotel/models/inherit_account_payment.py
Normal file
94
hotel/models/inherit_account_payment.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# -*- 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 decimal import Decimal
|
||||
import datetime
|
||||
# For Python 3.0 and later
|
||||
from urllib.request import urlopen
|
||||
import time
|
||||
import logging
|
||||
from openerp.exceptions import except_orm, UserError, ValidationError
|
||||
from openerp.tools import misc, DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from openerp import models, fields, api, _
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountPayment(models.Model):
|
||||
|
||||
_inherit = 'account.payment'
|
||||
|
||||
folio_id = fields.Many2one('hotel.folio', string='Folio')
|
||||
amount_total_folio = fields.Float(
|
||||
compute="_compute_folio_amount", store=True,
|
||||
string="Total amount in folio",
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def return_payment_folio(self):
|
||||
journal = self.journal_id
|
||||
partner = self.partner_id
|
||||
amount = self.amount
|
||||
reference = self.communication
|
||||
account_move_lines = self.move_line_ids.filtered(lambda x: (
|
||||
x.account_id.internal_type == 'receivable'))
|
||||
return_line_vals = {
|
||||
'move_line_ids': [(6, False, [x.id for x in account_move_lines])],
|
||||
'partner_id': partner.id,
|
||||
'amount': amount,
|
||||
'reference': reference,
|
||||
}
|
||||
return_vals = {
|
||||
'journal_id': journal.id,
|
||||
'line_ids': [(0,0,return_line_vals)],
|
||||
}
|
||||
return_pay = self.env['payment.return'].create(return_vals)
|
||||
return {
|
||||
'name': 'Folio Payment Return',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'payment.return',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_id': return_pay.id,
|
||||
}
|
||||
@api.multi
|
||||
def modify(self):
|
||||
self.cancel()
|
||||
vals = {
|
||||
'journal_id': self.journal_id,
|
||||
'partner_id': self.partner_id,
|
||||
'amount': self.amount,
|
||||
'payment_date': self.payment_date,
|
||||
'communication': self.communication,
|
||||
'folio_id': self.folio_id}
|
||||
self.update(vals)
|
||||
self.post()
|
||||
|
||||
@api.multi
|
||||
def delete(self):
|
||||
self.cancel()
|
||||
self.move_name = ''
|
||||
self.unlink()
|
||||
|
||||
@api.multi
|
||||
@api.depends('state')
|
||||
def _compute_folio_amount(self):
|
||||
res = []
|
||||
fol = ()
|
||||
for payment in self:
|
||||
amount_pending = 0
|
||||
total_amount = 0
|
||||
if payment.folio_id:
|
||||
fol = payment.env['hotel.folio'].search([
|
||||
('id', '=', payment.folio_id.id)
|
||||
])
|
||||
else:
|
||||
return
|
||||
if len(fol) == 0:
|
||||
return
|
||||
elif len(fol) > 1:
|
||||
raise except_orm(_('Warning'), _('This pay is related with \
|
||||
more than one Reservation.'))
|
||||
else:
|
||||
fol.compute_invoices_amount()
|
||||
return res
|
||||
39
hotel/models/inherit_payment_return.py
Normal file
39
hotel/models/inherit_payment_return.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# --------------------------------------------------------------------------
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2018-Darío Lodeiros Vázquez
|
||||
#
|
||||
# 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 PaymentReturn(models.Model):
|
||||
|
||||
_inherit = 'payment.return'
|
||||
|
||||
folio_id = fields.Many2one('hotel.folio', string='Folio')
|
||||
|
||||
@api.multi
|
||||
def action_confirm(self):
|
||||
pay = super(PaymentReturn,self).action_confirm()
|
||||
if pay:
|
||||
folio_ids = []
|
||||
for line in self.line_ids:
|
||||
payments = self.env['account.payment'].search([('move_line_ids','in',line.move_line_ids.ids)])
|
||||
folio_ids += payments.mapped('folio_id.id')
|
||||
folios = self.env['hotel.folio'].browse(folio_ids)
|
||||
folios.compute_invoices_amount()
|
||||
14
hotel/models/inherit_product_category.py
Normal file
14
hotel/models/inherit_product_category.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- 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 ProductCategory(models.Model):
|
||||
|
||||
_inherit = "product.category"
|
||||
|
||||
# isroomtype = fields.Boolean('Is Room Type')
|
||||
isamenitytype = fields.Boolean('Is Amenities Type')
|
||||
isservicetype = fields.Boolean('Is Service Type')
|
||||
41
hotel/models/inherit_product_pricelist.py
Normal file
41
hotel/models/inherit_product_pricelist.py
Normal file
@@ -0,0 +1,41 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
#
|
||||
# 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, api
|
||||
|
||||
|
||||
class ProductPricelist(models.Model):
|
||||
_inherit = 'product.pricelist'
|
||||
|
||||
@api.multi
|
||||
@api.depends('name')
|
||||
def name_get(self):
|
||||
pricelist_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_pricelist_id')
|
||||
if pricelist_id:
|
||||
pricelist_id = int(pricelist_id)
|
||||
org_names = super(ProductPricelist, self).name_get()
|
||||
names = []
|
||||
for name in org_names:
|
||||
if name[0] == pricelist_id:
|
||||
names.append((name[0], '%s (Parity)' % name[1]))
|
||||
else:
|
||||
names.append((name[0], name[1]))
|
||||
return names
|
||||
14
hotel/models/inherit_product_product.py
Normal file
14
hotel/models/inherit_product_product.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- 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 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')
|
||||
19
hotel/models/inherit_res_company.py
Normal file
19
hotel/models/inherit_res_company.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# -*- 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 ResCompany(models.Model):
|
||||
|
||||
_inherit = 'res.company'
|
||||
|
||||
additional_hours = fields.Integer('Additional Hours',
|
||||
help="Provide the min hours value for \
|
||||
check in, checkout days, whatever \
|
||||
the hours will be provided here based \
|
||||
on that extra days will be \
|
||||
calculated.")
|
||||
default_cancel_policy_days = fields.Integer('Cancelation Days')
|
||||
default_cancel_policy_percent = fields.Integer('Percent to pay')
|
||||
28
hotel/models/inherit_res_partner.py
Normal file
28
hotel/models/inherit_res_partner.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- 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 ResPartner(models.Model):
|
||||
|
||||
_inherit = 'res.partner'
|
||||
|
||||
reservations_count = fields.Integer('Reservations',
|
||||
compute='_compute_reservations_count')
|
||||
folios_count = fields.Integer('Folios', compute='_compute_folios_count')
|
||||
|
||||
def _compute_reservations_count(self):
|
||||
hotel_reservation_obj = self.env['hotel.reservation']
|
||||
for partner in self:
|
||||
partner.reservations_count = hotel_reservation_obj.search_count([
|
||||
('partner_id.id', '=', partner.id)
|
||||
])
|
||||
|
||||
def _compute_folios_count(self):
|
||||
hotel_folio_obj = self.env['hotel.folio']
|
||||
for partner in self:
|
||||
partner.folios_count = hotel_folio_obj.search_count([
|
||||
('partner_id.id', '=', partner.id)
|
||||
])
|
||||
45
hotel/models/inherited_mail_compose_message.py
Normal file
45
hotel/models/inherited_mail_compose_message.py
Normal file
@@ -0,0 +1,45 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2018 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 odoo import api, models
|
||||
|
||||
|
||||
class MailComposeMessage(models.TransientModel):
|
||||
_inherit = 'mail.compose.message'
|
||||
|
||||
@api.multi
|
||||
def send_mail(self, auto_commit=False):
|
||||
if self._context.get('default_model') == 'hotel.folio' and self._context.get('default_res_id') and self._context.get('mark_so_as_sent'):
|
||||
folio = self.env['hotel.folio'].browse([
|
||||
self._context['default_res_id']
|
||||
])
|
||||
if folio:
|
||||
cmds = []
|
||||
for lid in folio.room_lines._ids:
|
||||
cmds.append((
|
||||
1,
|
||||
lid,
|
||||
{'to_send': False}
|
||||
))
|
||||
if cmds:
|
||||
folio.room_lines = cmds
|
||||
return super(MailComposeMessage, self).send_mail(auto_commit=auto_commit)
|
||||
104
hotel/models/res_config.py
Normal file
104
hotel/models/res_config.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2017 Solucións Aloxa S.L. <info@aloxa.eu>
|
||||
# Alexandre Díaz <alex@aloxa.eu>
|
||||
#
|
||||
# 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 re
|
||||
import pytz
|
||||
from openerp import models, fields, api, _
|
||||
from openerp.exceptions import ValidationError
|
||||
|
||||
|
||||
@api.model
|
||||
def _tz_get(self):
|
||||
# put POSIX 'Etc/*' entries at the end to avoid confusing users
|
||||
# see bug 1086728
|
||||
return [(tz, tz) for tz in sorted(pytz.all_timezones,
|
||||
key=lambda tz: tz
|
||||
if not tz.startswith('Etc/') else '_')]
|
||||
|
||||
|
||||
class HotelConfiguration(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
parity_pricelist_id = fields.Many2one('product.pricelist',
|
||||
'Product Pricelist')
|
||||
parity_restrictions_id = fields.Many2one('hotel.virtual.room.restriction',
|
||||
'Restrictions')
|
||||
default_arrival_hour = fields.Char('Default Arrival Hour (GMT)',
|
||||
help="HH:mm Format", default="14:00")
|
||||
default_departure_hour = fields.Char('Default Departure Hour (GMT)',
|
||||
help="HH:mm Format", default="12:00")
|
||||
tz_hotel = fields.Selection(_tz_get, string='Timezone',
|
||||
default=lambda self: self._context.get('tz'),
|
||||
help="The hotel's timezone, used to manage \
|
||||
date and time values in reservations \
|
||||
It is important to set a value for this \
|
||||
field.")
|
||||
|
||||
@api.multi
|
||||
def set_values(self):
|
||||
super(HotelConfiguration, self).set_values()
|
||||
|
||||
self.env['ir.default'].sudo().set(
|
||||
'res.config.settings', 'parity_pricelist_id',
|
||||
self.parity_pricelist_id.id)
|
||||
self.env['ir.default'].sudo().set(
|
||||
'res.config.settings', 'parity_restrictions_id',
|
||||
self.parity_restrictions_id.id)
|
||||
self.env['ir.default'].sudo().set(
|
||||
'res.config.settings', 'tz_hotel', self.tz_hotel)
|
||||
self.env['ir.default'].sudo().set(
|
||||
'res.config.settings', 'default_arrival_hour',
|
||||
self.default_arrival_hour)
|
||||
self.env['ir.default'].sudo().set(
|
||||
'res.config.settings', 'default_departure_hour',
|
||||
self.default_departure_hour)
|
||||
|
||||
@api.model
|
||||
def get_values(self):
|
||||
res = super(HotelConfiguration, self).get_values()
|
||||
|
||||
# ONLY FOR v11. DO NOT FORWARD-PORT
|
||||
parity_pricelist_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_pricelist_id')
|
||||
parity_restrictions_id = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'parity_restrictions_id')
|
||||
tz_hotel = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'tz_hotel')
|
||||
default_arrival_hour = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'default_arrival_hour')
|
||||
default_departure_hour = self.env['ir.default'].sudo().get(
|
||||
'res.config.settings', 'default_departure_hour')
|
||||
res.update(
|
||||
parity_pricelist_id=parity_pricelist_id,
|
||||
parity_restrictions_id=parity_restrictions_id,
|
||||
tz_hotel=tz_hotel,
|
||||
default_arrival_hour=default_arrival_hour,
|
||||
default_departure_hour=default_departure_hour,
|
||||
)
|
||||
return res
|
||||
|
||||
@api.constrains('default_arrival_hour', 'default_departure_hour')
|
||||
def _check_hours(self):
|
||||
r = re.compile('[0-2][0-9]:[0-5][0-9]')
|
||||
if not r.match(self.default_arrival_hour):
|
||||
raise ValidationError(_("Invalid arrival hour (Format: HH:mm)"))
|
||||
if not r.match(self.default_departure_hour):
|
||||
raise ValidationError(_("Invalid departure hour (Format: HH:mm)"))
|
||||
137
hotel/models/virtual_room.py
Normal file
137
hotel/models/virtual_room.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# -*- 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 decimal import Decimal
|
||||
from datetime import datetime, timedelta
|
||||
import dateutil.parser
|
||||
# For Python 3.0 and later
|
||||
from urllib.request import urlopen
|
||||
import time
|
||||
from openerp.exceptions import except_orm, UserError, ValidationError
|
||||
from openerp.tools import (
|
||||
misc,
|
||||
DEFAULT_SERVER_DATE_FORMAT,
|
||||
DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
from openerp import models, fields, api, _
|
||||
from odoo.addons.hotel import date_utils
|
||||
|
||||
|
||||
class VirtualRoom(models.Model):
|
||||
_name = 'hotel.virtual.room'
|
||||
_inherits = {'product.product': 'product_id'}
|
||||
|
||||
@api.depends('room_ids', 'room_type_ids')
|
||||
def _compute_total_rooms(self):
|
||||
for r in self:
|
||||
count = 0
|
||||
count += len(r.room_ids) # Rooms linked directly
|
||||
room_categories = r.room_type_ids.mapped('room_ids.id')
|
||||
count += self.env['hotel.room'].search_count([
|
||||
('categ_id.id', 'in', room_categories)
|
||||
]) # Rooms linked through room type
|
||||
r.total_rooms_count = count
|
||||
|
||||
@api.constrains('room_ids', 'room_type_ids')
|
||||
def _check_duplicated_rooms(self):
|
||||
warning_msg = ""
|
||||
for r in self:
|
||||
room_categories = self.room_type_ids.mapped('room_ids.id')
|
||||
if self.room_ids & self.env['hotel.room'].search([
|
||||
('categ_id.id', 'in', room_categories)]):
|
||||
room_ids = self.room_ids & self.env['hotel.room'].search([
|
||||
('categ_id.id', 'in', room_categories)
|
||||
])
|
||||
rooms_name = ','.join(str(x.name) for x in room_ids)
|
||||
warning_msg += _('You can not enter the same room in duplicate \
|
||||
(check the room types) %s') % rooms_name
|
||||
raise models.ValidationError(warning_msg)
|
||||
|
||||
@api.constrains('max_real_rooms', 'room_ids', 'room_type_ids')
|
||||
def _check_max_rooms(self):
|
||||
warning_msg = ""
|
||||
for r in self:
|
||||
if self.max_real_rooms > self.total_rooms_count:
|
||||
warning_msg += _('The Maxime rooms allowed can not be greate \
|
||||
than total rooms count')
|
||||
raise models.ValidationError(warning_msg)
|
||||
|
||||
virtual_code = fields.Char('Code') # not used
|
||||
room_ids = fields.Many2many('hotel.room', string='Rooms')
|
||||
room_type_ids = fields.Many2many('hotel.room.type', string='Room Types')
|
||||
total_rooms_count = fields.Integer(compute='_compute_total_rooms')
|
||||
product_id = fields.Many2one('product.product', 'Product_id',
|
||||
required=True, delegate=True,
|
||||
ondelete='cascade')
|
||||
# FIXME services are related to real rooms
|
||||
service_ids = fields.Many2many('hotel.services',
|
||||
string='Included Services')
|
||||
max_real_rooms = fields.Integer('Default Max Room Allowed')
|
||||
product_id = fields.Many2one(
|
||||
'product.product', required=True,
|
||||
ondelete='cascade')
|
||||
active = fields.Boolean(default=True, help="The active field allows you to hide the category without removing it.")
|
||||
|
||||
@api.multi
|
||||
def get_capacity(self):
|
||||
self.ensure_one()
|
||||
hotel_room_obj = self.env['hotel.room']
|
||||
room_categories = self.room_type_ids.mapped('room_ids.id')
|
||||
room_ids = self.room_ids + hotel_room_obj.search([
|
||||
('categ_id.id', 'in', room_categories)
|
||||
])
|
||||
capacities = room_ids.mapped('capacity')
|
||||
return any(capacities) and min(capacities) or 0
|
||||
|
||||
@api.model
|
||||
def check_availability_virtual_room(self, checkin, checkout,
|
||||
virtual_room_id=False, notthis=[]):
|
||||
occupied = self.env['hotel.reservation'].occupied(checkin, checkout)
|
||||
rooms_occupied = occupied.mapped('product_id.id')
|
||||
free_rooms = self.env['hotel.room'].search([
|
||||
('product_id.id', 'not in', rooms_occupied),
|
||||
('id', 'not in', notthis)
|
||||
])
|
||||
if virtual_room_id:
|
||||
hotel_room_obj = self.env['hotel.room']
|
||||
virtual_room = self.env['hotel.virtual.room'].search([
|
||||
('id', '=', virtual_room_id)
|
||||
])
|
||||
room_categories = virtual_room.room_type_ids.mapped('room_ids.id')
|
||||
rooms_linked = virtual_room.room_ids | hotel_room_obj.search([
|
||||
('categ_id.id', 'in', room_categories)])
|
||||
free_rooms = free_rooms & rooms_linked
|
||||
return free_rooms.sorted(key=lambda r: r.sequence)
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
for record in self:
|
||||
# Set fixed price to rooms with price from this virtual rooms
|
||||
rooms = self.env['hotel.room'].search([
|
||||
('sale_price_type', '=', 'vroom'),
|
||||
('price_virtual_room', '=', record.id)
|
||||
])
|
||||
for room in rooms:
|
||||
room.sale_price_type = 'fixed'
|
||||
# Remove product.product
|
||||
record.product_id.unlink()
|
||||
return super(VirtualRoom, self).unlink()
|
||||
Reference in New Issue
Block a user