trytonpsk-hotel/service.py

400 lines
13 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from datetime import datetime
from trytond.model import ModelView, Workflow, ModelSQL, fields
from trytond.pool import Pool
from trytond.pyson import Eval, In, If, Get
from trytond.modules.company import CompanyReport
from trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
STATES = {
'readonly': Eval('state') == 'checked'
}
STATES_LINE = {
'readonly': Eval('state') == 'loaded'
}
class ServiceKind(ModelSQL, ModelView):
'Service Kind'
__name__ = 'hotel.service.kind'
name = fields.Char('Name', required=True)
product = fields.Many2One('product.product', 'Product')
salable = fields.Boolean('Salable')
category = fields.Selection([
('breakfast', 'Breakfast'),
('lunch', 'Lunch'),
('dinner', 'Dinner'),
('restaurant', 'Restaurant'),
('laundry', 'Laundry'),
('spa', 'Spa'),
], 'Category')
class Service(Workflow, ModelSQL, ModelView):
'Service'
__name__ = 'hotel.service'
_rec_name = 'kind.name'
number = fields.Char('Number', required=False)
kind = fields.Many2One('hotel.service.kind', 'Kind', required=True,
states={
'readonly': Eval('state').in_(['confirmed', 'checked'])
})
service_date = fields.Date('Date', required=True, states={
'readonly': Eval('state').in_(['confirmed', 'checked'])
})
lines = fields.One2Many('hotel.service.line', 'service', 'Lines',
states={'readonly': Eval('state') != 'open'}
)
state = fields.Selection([
('draft', 'Draft'),
('open', 'Open'),
('confirmed', 'Confirmed'),
('checked', 'Checked'),
], 'State', readonly=True, states=STATES)
state_string = state.translated('state')
space = fields.Many2One('analytic_account.space', 'Space', required=False)
description = fields.Char('Description', states=STATES)
count_services = fields.Function(fields.Integer('Count Services'),
'get_count_services')
company = fields.Many2One('company.company', 'Company', required=True,
states=STATES, domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
Get(Eval('context', {}), 'company', 0))
])
@classmethod
def __setup__(cls):
super(Service, cls).__setup__()
cls._order.insert(0, ('service_date', 'DESC'))
cls._transitions |= set((
('draft', 'open'),
('open', 'confirmed'),
('confirmed', 'open'),
('confirmed', 'checked'),
))
cls._buttons.update({
'draft': {
'invisible': Eval('state') != 'open',
},
'open': {
'invisible': Eval('state') != 'draft',
},
'confirm': {
'invisible': Eval('state') != 'open',
},
'checked': {
'invisible': Eval('state') != 'confirmed',
},
})
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_company():
return Transaction().context.get('company')
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('open')
def open(cls, records):
cls.set_number(records)
@classmethod
@ModelView.button
@Workflow.transition('confirmed')
def confirm(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('checked')
def checked(cls, records):
pass
def get_count_services(self, name=None):
return sum([line.quantity for line in self.lines])
@classmethod
def set_number(cls, services):
"""
Fill the number field with the service sequence
"""
Config = Pool().get('hotel.configuration')
config = Config.get_configuration()
for service in services:
if service.number or not config.hotel_service_sequence:
continue
number = config.hotel_service_sequence.get()
cls.write([service], {'number': number})
class ServiceLine(Workflow, ModelSQL, ModelView):
'Service Line'
__name__ = 'hotel.service.line'
service = fields.Many2One('hotel.service', 'Service', required=True)
room = fields.Many2One('hotel.room', 'Room', required=True,
states=STATES_LINE)
time_service = fields.Time('Time Service', states=STATES_LINE)
product = fields.Many2One('product.product', 'Product',
domain=[
('salable', '=', True),
('active', '=', True),
], required=True)
quantity = fields.Integer('Quantity', required=True, states=STATES_LINE)
description = fields.Char('Description', states=STATES_LINE)
order = fields.Char('Order', states=STATES_LINE)
folio_line = fields.Many2One('hotel.folio', 'Folio Line',
states={'readonly': True})
guest = fields.Char('Guest', states=STATES_LINE)
charge = fields.Many2One('hotel.folio.charge', 'Operation Line',
states={'readonly': True})
folio = fields.Many2One('hotel.folio', 'Folio', domain=[
('room', '=', Eval('room')),
('registration_state', '=', 'check_in')
], depends=['room'])
state = fields.Selection([
('draft', 'Draft'),
('loaded', 'Loaded'),
('deleted', 'Deleted'),
('done', 'Done'),
], 'State', readonly=True)
state_string = state.translated('state')
@classmethod
def __setup__(cls):
super(ServiceLine, cls).__setup__()
cls._transitions |= set((
('draft', 'invoiced'),
('draft', 'loaded'),
('draft', 'done'),
('loaded', 'draft'),
))
cls._buttons.update({
'draft': {
'invisible': ~Eval('state').in_(['invoiced']),
},
'load': {
'invisible': Eval('state') != 'draft',
},
'done': {
'invisible': Eval('state') != 'draft',
},
})
@staticmethod
def default_quantity():
return 1
@staticmethod
def default_state():
return 'draft'
@staticmethod
def default_time_service():
Company = Pool().get('company.company')
local_date = Company.convert_timezone(datetime.now())
return local_date.time()
@fields.depends('product', 'description')
def on_change_product(self):
if self.product:
self.description = self.product.name
@classmethod
@ModelView.button
@Workflow.transition('loaded')
def load(cls, records):
for record in records:
record.add_product_to_room()
@classmethod
@ModelView.button
@Workflow.transition('draft')
def draft(cls, records):
pass
@classmethod
@ModelView.button
@Workflow.transition('done')
def done(cls, records):
pass
def add_product_to_room(self):
FolioCharge = Pool().get('hotel.folio.charge')
Folio = Pool().get('hotel.folio')
if self.folio_line:
return
folios = Folio.search([
('room', '=', self.room.id),
('registration_state', '=', 'check_in'),
])
if not folios:
raise UserError(
gettext('hotel.msg_room_not_occupied', s=self.room.name)
)
# elif len(folios) > 1:
# raise UserError(
# gettext('hotel.msg_multiple_rooms_active', s=self.room.name)
# )
# if not folios:
# return
# folio = folios[0]
folio = self.folio
invoice_to = None
if folio.charges_blocked:
raise UserError(
gettext('hotel.msg_folio_charges_blocked')
)
if folio.booking.channel:
invoice_to = self.folio.main_guest.id
analytic_account = None
if self.service.space:
analytic_account = self.service.space.analytic_account.id
record = {
'folio': self.folio.id,
'date_service': self.service.service_date,
'order': self.order,
'description': self.description,
'quantity': int(self.quantity),
'invoice_to': invoice_to,
'unit_price': self.product.template.list_price,
'product': self.product.id,
'to_invoice': True,
'analytic_account': analytic_account,
'state': '',
}
charge, = FolioCharge.create([record])
self.write([self], {'charge': charge.id})
if self.product.template.type == 'goods':
charge.do_stock_move()
class ServiceReport(CompanyReport):
__name__ = 'hotel.service'
# @classmethod
# def get_context(cls, records, header, data):
# report_context = super().get_context(records, header, data)
# return report_context
class CreateDailyServicesStart(ModelView):
'Create Daily Services Start'
__name__ = 'hotel.daily_services.start'
kind = fields.Many2One('hotel.service.kind', 'Kind', required=True,
domain=[
('category', 'in', ['breakfast', 'dinner', 'lunch']),
('product', '!=', None)
])
date = fields.Date('Date', required=True)
company = fields.Many2One('company.company', 'Company', required=True)
description = fields.Char('Description')
@staticmethod
def default_company():
return Transaction().context.get('company')
class CreateDailyServices(Wizard):
'Create Daily Services'
__name__ = 'hotel.daily_services'
start = StateView(
'hotel.daily_services.start',
'hotel.create_daily_services_start_view_form', [
Button('Cancel', 'end', 'tryton-cancel'),
Button('Accept', 'accept_', 'tryton-ok', default=True),
])
accept_ = StateTransition()
def transition_accept_(self):
pool = Pool()
Service = pool.get('hotel.service')
Folio = pool.get('hotel.folio')
kind = self.start.kind
if kind.category == 'breakfast':
dom = [
('arrival_date', '<', self.start.date),
('departure_date', '>=', self.start.date),
('registration_state', '=', 'check_in'),
]
elif kind.category == 'lunch':
dom = [
('arrival_date', '<', self.start.date),
('departure_date', '>', self.start.date),
('registration_state', 'in', ['check_in', 'check_out']),
]
elif kind.category == 'dinner':
dom = [
('arrival_date', '<=', self.start.date),
('departure_date', '>', self.start.date),
('registration_state', 'in', ['check_in', 'pending']),
]
if not dom:
return 'end'
folios = Folio.search(dom)
services = Service.search_read([
('state', '=', 'open'),
('kind', '=', kind.id),
])
if services:
raise UserError(gettext('hotel.msg_services_not_confirmed'))
lines_to_create = []
service, = Service.create([{
'service_date': self.start.date,
'state': 'draft',
'kind': kind.id,
}])
Service.open([service])
product = kind.product
for fol in folios:
booking = fol.booking
if booking.plan == 'no_breakfast':
continue
if kind == 'lunch' and booking.plan in [
'half_american', 'bed_breakfast']:
continue
if kind == 'dinner' and booking.plan == 'bed_breakfast':
continue
if len(fol.guests) >= fol.pax:
guests = [guest.name for guest in fol.guests]
else:
if not fol.main_guest:
raise UserError(gettext(
'hotel.msg_missing_guest', number=booking.number))
guests = [fol.main_guest.name] * fol.pax
for guest in guests:
lines_to_create.append({
'folio': fol.id,
'service': service.id,
'room': fol.room.id,
'guest': guest,
'product': product.id,
'quantity': 1,
})
if lines_to_create:
ServiceLine.create(lines_to_create)
return 'end'