trytonpsk-hotel/service.py

400 lines
13 KiB
Python
Raw Permalink Normal View History

2020-04-16 14:45:13 +02:00
# 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
2023-10-04 05:31:09 +02:00
2020-04-16 14:45:13 +02:00
from trytond.model import ModelView, Workflow, ModelSQL, fields
from trytond.pool import Pool
2021-07-31 18:17:54 +02:00
from trytond.pyson import Eval, In, If, Get
2020-04-16 14:45:13 +02:00
from trytond.modules.company import CompanyReport
from trytond.wizard import Wizard, StateView, Button, StateTransition
from trytond.transaction import Transaction
2021-10-11 19:03:43 +02:00
from trytond.exceptions import UserError
2021-07-21 14:56:53 +02:00
from trytond.i18n import gettext
2020-04-16 14:45:13 +02:00
STATES = {
'readonly': Eval('state') == 'checked'
}
2020-07-11 17:21:01 +02:00
STATES_LINE = {
'readonly': Eval('state') == 'loaded'
}
2020-04-16 14:45:13 +02:00
2021-07-21 14:56:53 +02:00
2020-04-16 14:45:13 +02:00
class ServiceKind(ModelSQL, ModelView):
'Service Kind'
__name__ = 'hotel.service.kind'
2023-10-04 05:31:09 +02:00
name = fields.Char('Name', required=True)
2020-04-16 14:45:13 +02:00
product = fields.Many2One('product.product', 'Product')
salable = fields.Boolean('Salable')
category = fields.Selection([
('breakfast', 'Breakfast'),
2022-08-09 22:11:25 +02:00
('lunch', 'Lunch'),
('dinner', 'Dinner'),
('restaurant', 'Restaurant'),
('laundry', 'Laundry'),
('spa', 'Spa'),
], 'Category')
2020-04-16 14:45:13 +02:00
class Service(Workflow, ModelSQL, ModelView):
'Service'
__name__ = 'hotel.service'
_rec_name = 'kind.name'
2023-10-04 05:31:09 +02:00
number = fields.Char('Number', required=False)
2020-04-16 14:45:13 +02:00
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')
2022-11-19 17:40:54 +01:00
space = fields.Many2One('analytic_account.space', 'Space', required=False)
2023-10-04 05:31:09 +02:00
description = fields.Char('Description', states=STATES)
count_services = fields.Function(fields.Integer('Count Services'),
'get_count_services')
2021-07-31 18:17:54 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
2022-10-21 14:54:37 +02:00
states=STATES, domain=[
('id', If(In('company', Eval('context', {})), '=', '!='),
Get(Eval('context', {}), 'company', 0))
])
2020-04-16 14:45:13 +02:00
@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'
2021-07-31 18:17:54 +02:00
@staticmethod
def default_company():
2021-07-31 18:19:43 +02:00
return Transaction().context.get('company')
2021-07-31 18:17:54 +02:00
2020-04-16 14:45:13 +02:00
@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):
2022-08-09 22:11:25 +02:00
return sum([line.quantity for line in self.lines])
2020-04-16 14:45:13 +02:00
@classmethod
def set_number(cls, services):
"""
Fill the number field with the service sequence
"""
2021-07-30 17:34:10 +02:00
Config = Pool().get('hotel.configuration')
2020-04-16 14:45:13 +02:00
config = Config.get_configuration()
for service in services:
if service.number or not config.hotel_service_sequence:
continue
2021-07-30 17:34:10 +02:00
number = config.hotel_service_sequence.get()
2020-04-16 14:45:13 +02:00
cls.write([service], {'number': number})
class ServiceLine(Workflow, ModelSQL, ModelView):
'Service Line'
__name__ = 'hotel.service.line'
service = fields.Many2One('hotel.service', 'Service', required=True)
2023-10-04 05:31:09 +02:00
room = fields.Many2One('hotel.room', 'Room', required=True,
2021-10-11 19:03:43 +02:00
states=STATES_LINE)
2023-10-04 05:31:09 +02:00
time_service = fields.Time('Time Service', states=STATES_LINE)
2020-04-16 14:45:13 +02:00
product = fields.Many2One('product.product', 'Product',
domain=[
('salable', '=', True),
('active', '=', True),
], required=True)
2020-07-11 17:21:01 +02:00
quantity = fields.Integer('Quantity', required=True, states=STATES_LINE)
description = fields.Char('Description', states=STATES_LINE)
2023-10-04 05:31:09 +02:00
order = fields.Char('Order', states=STATES_LINE)
2021-10-12 07:18:02 +02:00
folio_line = fields.Many2One('hotel.folio', 'Folio Line',
2021-10-11 19:03:43 +02:00
states={'readonly': True})
2021-10-12 07:18:02 +02:00
guest = fields.Char('Guest', states=STATES_LINE)
2021-10-11 19:03:43 +02:00
charge = fields.Many2One('hotel.folio.charge', 'Operation Line',
2020-04-16 14:45:13 +02:00
states={'readonly': True})
2021-10-12 07:43:22 +02:00
folio = fields.Many2One('hotel.folio', 'Folio', domain=[
('room', '=', Eval('room')),
2021-10-14 06:41:15 +02:00
('registration_state', '=', 'check_in')
2021-10-12 17:25:14 +02:00
], depends=['room'])
2020-04-16 14:45:13 +02:00
state = fields.Selection([
('draft', 'Draft'),
('loaded', 'Loaded'),
('deleted', 'Deleted'),
('done', 'Done'),
2021-10-12 17:25:14 +02:00
], 'State', readonly=True)
2020-04-16 14:45:13 +02:00
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):
2021-10-12 07:43:22 +02:00
FolioCharge = Pool().get('hotel.folio.charge')
2022-06-15 04:23:38 +02:00
Folio = Pool().get('hotel.folio')
2021-10-12 07:18:02 +02:00
if self.folio_line:
2020-04-16 14:45:13 +02:00
return
2022-06-15 04:23:38 +02:00
folios = Folio.search([
('room', '=', self.room.id),
('registration_state', '=', 'check_in'),
])
if not folios:
2022-07-07 15:31:42 +02:00
raise UserError(
gettext('hotel.msg_room_not_occupied', s=self.room.name)
)
2022-11-21 18:30:51 +01:00
# elif len(folios) > 1:
# raise UserError(
# gettext('hotel.msg_multiple_rooms_active', s=self.room.name)
# )
2022-07-07 15:31:42 +02:00
# if not folios:
# return
2022-06-15 04:23:38 +02:00
2022-11-21 18:30:51 +01:00
# folio = folios[0]
folio = self.folio
2022-07-23 01:07:58 +02:00
invoice_to = None
2022-10-28 18:44:14 +02:00
if folio.charges_blocked:
raise UserError(
gettext('hotel.msg_folio_charges_blocked')
)
2022-07-23 01:07:58 +02:00
if folio.booking.channel:
invoice_to = self.folio.main_guest.id
2022-11-21 18:30:51 +01:00
analytic_account = None
if self.service.space:
analytic_account = self.service.space.analytic_account.id
2022-07-21 17:17:35 +02:00
record = {
2021-10-12 07:18:02 +02:00
'folio': self.folio.id,
2020-04-16 14:45:13 +02:00
'date_service': self.service.service_date,
'order': self.order,
'description': self.description,
'quantity': int(self.quantity),
2022-07-23 01:07:58 +02:00
'invoice_to': invoice_to,
2020-04-16 14:45:13 +02:00
'unit_price': self.product.template.list_price,
'product': self.product.id,
2021-12-28 00:02:47 +01:00
'to_invoice': True,
2022-11-21 18:30:51 +01:00
'analytic_account': analytic_account,
2021-10-14 06:41:15 +02:00
'state': '',
2020-04-16 14:45:13 +02:00
}
2022-07-21 17:17:35 +02:00
charge, = FolioCharge.create([record])
self.write([self], {'charge': charge.id})
2022-07-23 01:07:58 +02:00
if self.product.template.type == 'goods':
charge.do_stock_move()
2020-04-16 14:45:13 +02:00
class ServiceReport(CompanyReport):
__name__ = 'hotel.service'
2023-10-04 05:31:09 +02:00
# @classmethod
# def get_context(cls, records, header, data):
# report_context = super().get_context(records, header, data)
# return report_context
2020-04-16 14:45:13 +02:00
class CreateDailyServicesStart(ModelView):
'Create Daily Services Start'
__name__ = 'hotel.daily_services.start'
kind = fields.Many2One('hotel.service.kind', 'Kind', required=True,
domain=[
2022-08-09 22:11:25 +02:00
('category', 'in', ['breakfast', 'dinner', 'lunch']),
('product', '!=', None)
])
2020-04-16 14:45:13 +02:00
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'
2022-10-21 14:54:37 +02:00
start = StateView(
'hotel.daily_services.start',
2020-04-16 14:45:13 +02:00
'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')
2021-10-11 19:03:43 +02:00
Folio = pool.get('hotel.folio')
2022-08-09 22:11:25 +02:00
kind = self.start.kind
2022-10-26 19:28:15 +02:00
if kind.category == 'breakfast':
2022-08-09 22:11:25 +02:00
dom = [
('arrival_date', '<', self.start.date),
('departure_date', '>=', self.start.date),
2022-10-26 16:03:55 +02:00
('registration_state', '=', 'check_in'),
2022-08-09 22:11:25 +02:00
]
2022-10-26 19:28:15 +02:00
elif kind.category == 'lunch':
2022-08-09 22:11:25 +02:00
dom = [
('arrival_date', '<', self.start.date),
('departure_date', '>', self.start.date),
('registration_state', 'in', ['check_in', 'check_out']),
]
2022-10-26 19:28:15 +02:00
elif kind.category == 'dinner':
2022-08-09 22:11:25 +02:00
dom = [
('arrival_date', '<=', self.start.date),
('departure_date', '>', self.start.date),
('registration_state', 'in', ['check_in', 'pending']),
]
2022-10-26 19:28:15 +02:00
if not dom:
return 'end'
2022-08-09 22:11:25 +02:00
folios = Folio.search(dom)
2020-04-16 14:45:13 +02:00
2022-11-21 18:30:51 +01:00
services = Service.search_read([
('state', '=', 'open'),
('kind', '=', kind.id),
])
if services:
raise UserError(gettext('hotel.msg_services_not_confirmed'))
2020-04-16 14:45:13 +02:00
lines_to_create = []
service, = Service.create([{
'service_date': self.start.date,
'state': 'draft',
2022-08-09 22:11:25 +02:00
'kind': kind.id,
2020-04-16 14:45:13 +02:00
}])
Service.open([service])
2022-08-09 22:11:25 +02:00
product = kind.product
2021-10-11 19:03:43 +02:00
for fol in folios:
2022-08-09 22:11:25 +02:00
booking = fol.booking
if booking.plan == 'no_breakfast':
continue
2022-10-21 14:54:37 +02:00
if kind == 'lunch' and booking.plan in [
'half_american', 'bed_breakfast']:
2022-08-09 22:11:25 +02:00
continue
if kind == 'dinner' and booking.plan == 'bed_breakfast':
continue
2022-10-21 14:54:37 +02:00
if len(fol.guests) >= fol.pax:
guests = [guest.name for guest in fol.guests]
else:
2022-10-26 15:57:17 +02:00
if not fol.main_guest:
raise UserError(gettext(
'hotel.msg_missing_guest', number=booking.number))
2022-10-21 14:54:37 +02:00
guests = [fol.main_guest.name] * fol.pax
for guest in guests:
lines_to_create.append({
2021-10-12 07:18:02 +02:00
'folio': fol.id,
'service': service.id,
2021-10-11 19:03:43 +02:00
'room': fol.room.id,
2022-10-21 14:54:37 +02:00
'guest': guest,
'product': product.id,
'quantity': 1,
})
if lines_to_create:
ServiceLine.create(lines_to_create)
2020-04-16 14:45:13 +02:00
return 'end'