# 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'