Refactory 06
This commit is contained in:
parent
27151e1bb4
commit
c25314760c
47
__init__.py
47
__init__.py
|
@ -5,7 +5,7 @@ from . import location
|
|||
from . import configuration
|
||||
from . import room
|
||||
from . import booking
|
||||
from . import operation
|
||||
from . import folio
|
||||
from . import housekeeping
|
||||
from . import company
|
||||
from . import city
|
||||
|
@ -31,18 +31,17 @@ def register():
|
|||
product.PriceList,
|
||||
city.MigrationCity,
|
||||
booking.Booking,
|
||||
booking.Folio,
|
||||
booking.BookingDailyStart,
|
||||
configuration.ConfigurationProduct,
|
||||
housekeeping.Housekeeping,
|
||||
housekeeping.HousekeepingCleaningType,
|
||||
company.Company,
|
||||
operation.FolioCharge,
|
||||
# operation.Operation,
|
||||
# operation.OperationMaintenance,
|
||||
# operation.OperationGuest,
|
||||
# operation.TransferOperationStart,
|
||||
# operation.TransferChargeStart,
|
||||
folio.Folio,
|
||||
folio.FolioCharge,
|
||||
# folio.folioMaintenance,
|
||||
# folio.folioGuest,
|
||||
# folio.TransferfolioStart,
|
||||
# folio.TransferChargeStart,
|
||||
party.Party,
|
||||
channel.ChannelTax,
|
||||
booking.Guest,
|
||||
|
@ -50,11 +49,11 @@ def register():
|
|||
booking.BookingVoucher,
|
||||
booking.RoomsOccupancyStart,
|
||||
booking.BookingForecastStart,
|
||||
# operation.OpenMigrationStart,
|
||||
# operation.CheckOutOperationFailed,
|
||||
# operation.ChangeRoomStart,
|
||||
# operation.OperationVoucher,
|
||||
# operation.StatisticsByMonthStart,
|
||||
# folio.OpenMigrationStart,
|
||||
# folio.CheckOutfolioFailed,
|
||||
# folio.ChangeRoomStart,
|
||||
# folio.folioVoucher,
|
||||
# folio.StatisticsByMonthStart,
|
||||
sale.InvoiceIncomeDailyStart,
|
||||
service.Service,
|
||||
service.ServiceLine,
|
||||
|
@ -72,10 +71,10 @@ def register():
|
|||
booking.BookingForecastReport,
|
||||
booking.RoomsOccupancyReport,
|
||||
booking.BookingDailyReport,
|
||||
# operation.Migration,
|
||||
# operation.OperationReport,
|
||||
# operation.OperationByConsumerReport,
|
||||
# operation.StatisticsByMonthReport,
|
||||
# folio.Migration,
|
||||
# folio.folioReport,
|
||||
# folio.folioByConsumerReport,
|
||||
# folio.StatisticsByMonthReport,
|
||||
sale.InvoiceIncomeDailyReport,
|
||||
sale.InvoiceSimplifiedReport,
|
||||
service.ServiceReport,
|
||||
|
@ -88,13 +87,13 @@ def register():
|
|||
booking.BookingForecast,
|
||||
booking.RoomsOccupancy,
|
||||
booking.BookingDaily,
|
||||
# operation.OpenMigration,
|
||||
# operation.CheckOutOperation,
|
||||
# operation.OperationBill,
|
||||
# operation.ChangeRoom,
|
||||
# operation.TransferOperation,
|
||||
# operation.TransferCharge,
|
||||
# operation.StatisticsByMonth,
|
||||
# folio.OpenMigration,
|
||||
# folio.CheckOutfolio,
|
||||
# folio.folioBill,
|
||||
# folio.ChangeRoom,
|
||||
# folio.Transferfolio,
|
||||
# folio.TransferCharge,
|
||||
# folio.StatisticsByMonth,
|
||||
service.CreateDailyServices,
|
||||
housekeeping.HousekeepingService,
|
||||
booking.GuestsList,
|
||||
|
|
BIN
booking.fodt
BIN
booking.fodt
Binary file not shown.
745
booking.py
745
booking.py
|
@ -125,10 +125,10 @@ class Booking(Workflow, ModelSQL, ModelView):
|
|||
vehicle_plate = fields.Integer('Vehicle Plate', states=STATES)
|
||||
travel_cause = fields.Char('Travel Cause', states=STATES)
|
||||
taxes_exception = fields.Boolean('Taxes Exception', states=STATES)
|
||||
total_advance = fields.Function(fields.Numeric('Total Advance'),
|
||||
'get_total_advance')
|
||||
pending_to_pay = fields.Function(fields.Numeric('Pending to Pay'),
|
||||
'get_pending_to_pay')
|
||||
total_advance = fields.Function(fields.Numeric('Total Advance',
|
||||
digits=(16, 2)), 'get_total_advance')
|
||||
pending_to_pay = fields.Function(fields.Numeric('Pending to Pay',
|
||||
digits=(16, 2)),'get_pending_to_pay')
|
||||
breakfast_included = fields.Boolean('Breakfast Included')
|
||||
|
||||
@classmethod
|
||||
|
@ -137,13 +137,11 @@ class Booking(Workflow, ModelSQL, ModelView):
|
|||
cls._order.insert(0, ('create_date', 'DESC'))
|
||||
cls._transitions |= set((
|
||||
('offer', 'confirmed'),
|
||||
('confirmed', 'offer'),
|
||||
('offer', 'not_confirmed'),
|
||||
('offer', 'cancelled'),
|
||||
('confirmed', 'offer'),
|
||||
('cancelled', 'offer'),
|
||||
('confirmed', 'cancelled'),
|
||||
('not_confirmed', 'confirmed'),
|
||||
('not_confirmed', 'cancelled'),
|
||||
('confirmed', 'not_show'),
|
||||
))
|
||||
cls._buttons.update({
|
||||
'select_rooms': {
|
||||
|
@ -153,30 +151,16 @@ class Booking(Workflow, ModelSQL, ModelView):
|
|||
'invisible': Eval('state') != 'offer',
|
||||
},
|
||||
'cancel': {
|
||||
'invisible': Eval('state').in_(['cancel', '']) and
|
||||
Eval('registration_state') == 'check_out',
|
||||
'invisible': Eval('state').in_(['cancel', ''])
|
||||
},
|
||||
'offer': {
|
||||
'invisible': Eval('state').in_(['offer', 'confirmed'])
|
||||
or Eval('registration_state').in_(['check_in', 'check_out']),
|
||||
},
|
||||
'confirm': {
|
||||
'invisible': ~Eval('state').in_(['offer', 'not_confirmed'])
|
||||
'invisible': ~Eval('state').in_(['offer', 'not_show'])
|
||||
},
|
||||
'not_confirm': {
|
||||
'invisible': Eval('state') != 'offer',
|
||||
},
|
||||
'check_in': {
|
||||
'invisible': Eval('state') != 'confirmed' and
|
||||
Eval('registration_state').in_(['check_in', 'check_out']),
|
||||
},
|
||||
'no_show': {
|
||||
'invisible': Eval('state') != 'confirmed' and
|
||||
Eval('registration_state').in_(['check_in', 'check_out']),
|
||||
},
|
||||
'check_out': {
|
||||
'invisible': Eval('state') != 'confirmed' and
|
||||
Eval('registration_state') == 'check_out',
|
||||
'not_show': {
|
||||
'invisible': Eval('state') != 'confirmed',
|
||||
},
|
||||
'pay_advance': {
|
||||
'invisible':
|
||||
|
@ -276,8 +260,8 @@ class Booking(Workflow, ModelSQL, ModelView):
|
|||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
@Workflow.transition('not_confirmed')
|
||||
def not_confirm(cls, records):
|
||||
@Workflow.transition('not_show')
|
||||
def not_show(cls, records):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
|
@ -446,611 +430,6 @@ class Booking(Workflow, ModelSQL, ModelView):
|
|||
self.breakfast_included = self.price_list.breakfast_included
|
||||
|
||||
|
||||
class Folio(ModelSQL, ModelView):
|
||||
'Folio'
|
||||
__name__ = 'hotel.folio'
|
||||
booking = fields.Many2One('hotel.booking', 'Booking', ondelete='CASCADE',
|
||||
select=True)
|
||||
registration_card = fields.Char('Registration Card', readonly=True,
|
||||
select=True, help="Unique sequence for card guest registration.")
|
||||
room = fields.Many2One('hotel.room', 'Room', select=True, states={
|
||||
'required': Eval('registration_state') == 'check_in',
|
||||
'readonly': Eval('registration_state') == 'check_in',
|
||||
})
|
||||
arrival_date = fields.Date('Arrival Date', required=True,
|
||||
states=STATES_CHECKIN)
|
||||
departure_date = fields.Date('Departure Date', required=True,
|
||||
states=STATES_CHECKIN)
|
||||
product = fields.Many2One('product.product', 'Product',
|
||||
select=True, domain=[
|
||||
('template.type', '=', 'service'),
|
||||
('template.kind', '=', 'accommodation'),
|
||||
], required=True, states=STATES_CHECKIN)
|
||||
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
|
||||
states={
|
||||
'required': Bool(Eval('product')),
|
||||
'readonly': Eval('registration_state') == 'check_in',
|
||||
})
|
||||
uom = fields.Many2One('product.uom', 'UOM', readonly=True)
|
||||
main_guest = fields.Many2One('party.party', 'Main Guest', select=True,
|
||||
states={
|
||||
'required': Eval('registration_state') == 'check_in',
|
||||
'readonly': Eval('registration_state') == 'check_in',
|
||||
}
|
||||
)
|
||||
contact = fields.Char('Contact', states=STATES_CHECKIN)
|
||||
num_children = fields.Function(fields.Integer('No. Children'),
|
||||
'get_num_children')
|
||||
nights_quantity = fields.Function(fields.Integer('Nights'),
|
||||
'get_nights_quantity')
|
||||
host_quantity = fields.Integer('Host', states=STATES_CHECKIN)
|
||||
unit_digits = fields.Function(fields.Integer('Unit Digits'), 'get_unit_digits')
|
||||
notes = fields.Text('Notes')
|
||||
total_amount = fields.Function(fields.Numeric('Total Amount',
|
||||
digits=(16, 2)), 'get_total_amount')
|
||||
total_commission = fields.Function(fields.Numeric('Channel Commission',
|
||||
digits=(16, 2)), 'get_channel_commission')
|
||||
guests = fields.One2Many('hotel.booking.guest', 'booking_line', 'Guests',
|
||||
states={
|
||||
'readonly': ~Eval('registration_state').in_(['check_in']),
|
||||
})
|
||||
nationality = fields.Many2One('party.nationality', 'Nationality',
|
||||
states=STATES_CHECKIN)
|
||||
origin_country = fields.Many2One('party.nationality', 'Origin Country',
|
||||
select=True, states=STATES_CHECKIN)
|
||||
target_country = fields.Many2One('party.nationality', 'Target Country',
|
||||
select=True, states=STATES_CHECKIN)
|
||||
registration_state = fields.Selection(REGISTRATION_STATE,
|
||||
'Registration State', readonly=True)
|
||||
charges = fields.One2Many('hotel.folio.charge', 'folio', 'Charges',
|
||||
states={
|
||||
'readonly': ~Eval('registration_state').in_(['check_in']),
|
||||
})
|
||||
party = fields.Many2One('party.party', 'Party to Bill', select=True,
|
||||
states={
|
||||
'readonly': Eval('registration_state').in_(['check_out']),
|
||||
})
|
||||
invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line')
|
||||
invoice = fields.Function(fields.Many2One('account.invoice', 'Invoice'),
|
||||
'get_invoice')
|
||||
invoice_state = fields.Selection(INVOICE_STATES, 'Invoice State', readonly=True)
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Folio, cls).__setup__()
|
||||
cls._check_modify_exclude = [
|
||||
'nationality', 'origin_country', 'target_country',
|
||||
'registration_state', 'guests'
|
||||
],
|
||||
cls._buttons.update({
|
||||
'check_in': {
|
||||
'invisible': Eval('registration_state').in_(['check_in', 'check_out']),
|
||||
},
|
||||
'check_out': {
|
||||
'invisible': Eval('registration_state').in_(['check_out', 'pending']),
|
||||
},
|
||||
'bill': {
|
||||
'invisible': Eval('registration_state') != 'check_out',
|
||||
}
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def check_in(cls, records):
|
||||
config_party = Pool().get('party.configuration')(1)
|
||||
validate_party = config_party.validate_party
|
||||
|
||||
if not validate_party:
|
||||
config_party.validate_party = True
|
||||
config_party.save()
|
||||
booking = records[0].booking
|
||||
# booking.party.pre_validate()
|
||||
|
||||
for rec in records:
|
||||
rec.set_registration_card_number()
|
||||
line = records[0]
|
||||
# if line.state == 'offer':
|
||||
# raise UserError(gettext('hotel.msg_missing_confirm_booking'))
|
||||
if line.main_guest is None:
|
||||
raise UserError(gettext('hotel.msg_missing_main_guest'))
|
||||
if line.room is None:
|
||||
raise UserError(gettext('hotel.msg_missing_select_room'))
|
||||
|
||||
booking.check_rooms()
|
||||
cls.write([records[0]], {'registration_state': 'check_in'})
|
||||
|
||||
change_state = all(
|
||||
[rl.registration_state == 'check_in' for rl in booking.lines]
|
||||
)
|
||||
print('change_state..', change_state)
|
||||
if change_state:
|
||||
booking.registration_state = 'check_in'
|
||||
booking.save()
|
||||
if config_party.validate_party != validate_party:
|
||||
config_party.validate_party = validate_party
|
||||
config_party.save()
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def check_out(cls, records):
|
||||
for record in records:
|
||||
cls.write([record], {'registration_state': 'check_out'})
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def bill(cls, records):
|
||||
for rec in records:
|
||||
# if rec.complementary:
|
||||
# return
|
||||
cls.create_invoice(rec)
|
||||
|
||||
def set_registration_card_number(self):
|
||||
"""
|
||||
Fill the number field for registration card with sequence
|
||||
"""
|
||||
pool = Pool()
|
||||
Config = pool.get('hotel.configuration')
|
||||
config = Config.get_configuration()
|
||||
|
||||
if self.registration_card:
|
||||
return
|
||||
if not config.registration_card_sequence:
|
||||
raise UserError(gettext('hotel.msg_missing_sequence_registration'))
|
||||
number = config.registration_card_sequence.get()
|
||||
self.registration_card = number
|
||||
self.save()
|
||||
|
||||
@classmethod
|
||||
def create_invoice(cls, record):
|
||||
pool = Pool()
|
||||
Date = pool.get('ir.date')
|
||||
InvoiceLine = pool.get('account.invoice.line')
|
||||
FolioCharge = pool.get('hotel.folio.charge')
|
||||
Configuration = pool.get('hotel.configuration')
|
||||
configuration = Configuration.get_configuration()
|
||||
# SaleVoucher = pool.get('sale.sale-account.voucher')
|
||||
# date_ = Date.today()
|
||||
# ctx = {}
|
||||
invoice_to_create = cls.get_grouped_invoices([record])
|
||||
print(invoice_to_create)
|
||||
for rec in invoice_to_create.values():
|
||||
# if rec.get('price_list'):
|
||||
# ctx['price_list'] = rec.get('price_list')
|
||||
# ctx['sale_date'] = date_
|
||||
# ctx['currency'] = rec['currency']
|
||||
# ctx['customer'] = rec['party'].id
|
||||
|
||||
invoice = cls._get_new_invoice(rec)
|
||||
invoice.save()
|
||||
|
||||
# Here add payments to invoice
|
||||
# if rec.get('vouchers'):
|
||||
# for v in rec['vouchers']:
|
||||
# SaleVoucher.create([{
|
||||
# 'voucher': v.id,
|
||||
# 'sale': sale.id
|
||||
# }])
|
||||
|
||||
# Add and create default charges lines if exists
|
||||
if rec.get('guests_qty') and rec.get('add_default_charges'):
|
||||
for product in configuration.default_charges:
|
||||
if rec['party']:
|
||||
taxes_ids = cls.get_taxes(invoice, product, rec)
|
||||
new_line = {
|
||||
'invoice': invoice.id,
|
||||
'type': 'line',
|
||||
'unit': product.template.default_uom.id,
|
||||
'quantity': rec['guests_qty'],
|
||||
'unit_price': product.template.list_price,
|
||||
'product': product.id,
|
||||
'description': product.rec_name,
|
||||
}
|
||||
if taxes_ids:
|
||||
new_line.update({'taxes': [('add', taxes_ids)]})
|
||||
if new_line:
|
||||
InvoiceLine.create([new_line])
|
||||
|
||||
print('XXXXX', rec)
|
||||
for _line in rec['lines']:
|
||||
line, = InvoiceLine.create([
|
||||
cls._get_invoice_line(invoice, _line, rec)
|
||||
])
|
||||
if _line.get('folios'):
|
||||
cls.write(_line.get('folios'), {
|
||||
'invoice_line': line.id,
|
||||
# 'invoice_state': 'in_process'
|
||||
})
|
||||
else:
|
||||
FolioCharge.write([_line.get('charge')], {
|
||||
'invoice_line': line.id,
|
||||
'state': 'invoiced',
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _get_invoice_line(cls, invoice, line, record):
|
||||
product = line['product']
|
||||
new_line = {
|
||||
# 'invoice_line': invoice.id,
|
||||
'type': 'line',
|
||||
'invoice': invoice.id,
|
||||
'unit': product.template.default_uom.id,
|
||||
'account': product.template.account_category.account_revenue_used.id,
|
||||
'invoice_type': 'out',
|
||||
'operation_center': 1,
|
||||
'quantity': line['quantity'],
|
||||
'unit_price': line['unit_price'],
|
||||
'product': product.id,
|
||||
'party': invoice.party.id,
|
||||
'description': line['description'],
|
||||
}
|
||||
if not line['taxes_exception']:
|
||||
taxes_ids = cls.get_taxes(invoice, line['product'], record)
|
||||
if taxes_ids:
|
||||
new_line.update({'taxes': [('add', taxes_ids)]})
|
||||
return new_line
|
||||
|
||||
@classmethod
|
||||
def get_taxes(cls, invoice, product, rec):
|
||||
ctx = cls.get_context_price(invoice, product, rec)
|
||||
return ctx['taxes']
|
||||
|
||||
@classmethod
|
||||
def get_context_price(cls, invoice, product, rec):
|
||||
context = {}
|
||||
context['currency'] = rec['currency']
|
||||
context['customer'] = rec['party'].id
|
||||
context['price_list'] = rec['price_list']
|
||||
context['uom'] = product.template.default_uom.id
|
||||
|
||||
# Set taxes before unit_price to have taxes in context of sale price
|
||||
taxes = []
|
||||
pattern = {}
|
||||
for tax in product.customer_taxes_used:
|
||||
if invoice.party and invoice.party.customer_tax_rule:
|
||||
tax_ids = invoice.party.customer_tax_rule.apply(tax, pattern)
|
||||
if tax_ids:
|
||||
taxes.extend(tax_ids)
|
||||
continue
|
||||
taxes.append(tax.id)
|
||||
if invoice.party and invoice.party.customer_tax_rule:
|
||||
tax_ids = invoice.party.customer_tax_rule.apply(None, pattern)
|
||||
if tax_ids:
|
||||
taxes.extend(tax_ids)
|
||||
|
||||
context['taxes'] = taxes
|
||||
return context
|
||||
|
||||
@staticmethod
|
||||
def default_main_guest():
|
||||
party = Transaction().context.get('party')
|
||||
return party
|
||||
|
||||
@staticmethod
|
||||
def default_host_quantity():
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def default_accommodation():
|
||||
Configuration = Pool().get('hotel.configuration')
|
||||
configuration = Configuration.get_configuration()
|
||||
|
||||
if configuration.default_accommodation:
|
||||
return configuration.default_accommodation.id
|
||||
|
||||
@classmethod
|
||||
def validate(cls, lines):
|
||||
super(Folio, cls).validate(lines)
|
||||
for line in lines:
|
||||
# line.check_method()
|
||||
pass
|
||||
|
||||
def get_invoice(self, name=None):
|
||||
if self.invoice_line and self.invoice_line.invoice:
|
||||
self.invoice_line.invoice.id
|
||||
|
||||
# @fields.depends('accommodation', 'product')
|
||||
# def on_change_accommodation(self):
|
||||
# if not self.accommodation:
|
||||
# self.product = None
|
||||
|
||||
@classmethod
|
||||
def get_room_info(cls, fo):
|
||||
description = ' \n'.join([
|
||||
fo.product.rec_name,
|
||||
'Huesped Principal: ' + fo.main_guest.name,
|
||||
'Habitacion: ' + fo.room.name,
|
||||
'Llegada: ' + str(fo.arrival_date),
|
||||
'Salida: ' + str(fo.departure_date),
|
||||
])
|
||||
return description
|
||||
|
||||
@classmethod
|
||||
def get_grouped_invoices(cls, folios):
|
||||
res = {}
|
||||
# transfered = []
|
||||
# for op in folios:
|
||||
# for top in op.transfered_operations:
|
||||
# top.party = op.party
|
||||
# transfered.append(top)
|
||||
#
|
||||
# if transfered:
|
||||
# folios.extend(transfered)
|
||||
ffols = [fol for fol in folios if not fol.invoice_line]
|
||||
for fo in ffols:
|
||||
if fo.party:
|
||||
party = fo.party
|
||||
else:
|
||||
party = fo.main_guest
|
||||
|
||||
booking = fo.booking
|
||||
agent_id = booking.party_seller.party.id if booking.party_seller else None
|
||||
if party.id not in res.keys():
|
||||
# Add room product to sale
|
||||
res[party.id] = {
|
||||
'party': party,
|
||||
'currency': booking.currency.id,
|
||||
'payment_term': None,
|
||||
'guests_qty': len(fo.guests) + 1,
|
||||
'reference': '',
|
||||
'agent': agent_id,
|
||||
'rooms': fo.room.name,
|
||||
'company': booking.company.id,
|
||||
'price_list': booking.price_list.id if booking.price_list else None,
|
||||
'add_default_charges': False,
|
||||
'vouchers': booking.vouchers,
|
||||
'lines': [{
|
||||
'folios': [fo],
|
||||
'description': cls.get_room_info(fo),
|
||||
'quantity': fo.nights_quantity,
|
||||
'product': fo.product,
|
||||
'unit_price': fo.unit_price,
|
||||
'taxes_exception': booking.taxes_exception,
|
||||
}]
|
||||
}
|
||||
else:
|
||||
res[party.id]['rooms'] += ' ' + fo.room.name
|
||||
res[party.id]['lines'].append({
|
||||
'folios': [fo],
|
||||
'description': cls.get_room_info(fo),
|
||||
'quantity': fo.nights_quantity,
|
||||
'product': fo.accommodation,
|
||||
'unit_price': fo.unit_price,
|
||||
'taxes_exception': booking.taxes_exception,
|
||||
})
|
||||
for charge in fo.charges:
|
||||
if charge.invoice_line:
|
||||
continue
|
||||
invoice_party_id = charge.invoice_to.id
|
||||
unit_price = booking.currency.round(charge.unit_price)
|
||||
if invoice_party_id != party.id:
|
||||
if invoice_party_id not in res.keys():
|
||||
res[invoice_party_id] = {
|
||||
'party': charge.invoice_to.id,
|
||||
'currency': booking.currency.id,
|
||||
'payment_term': None,
|
||||
'lines': [],
|
||||
}
|
||||
res[invoice_party_id]['lines'].append({
|
||||
'description': ' | '.join([
|
||||
str(charge.date_service),
|
||||
charge.order or '',
|
||||
charge.description or ''
|
||||
]),
|
||||
'quantity': charge.quantity,
|
||||
'product': charge.product,
|
||||
'unit_price': unit_price,
|
||||
'charge': charge,
|
||||
'taxes_exception': booking.taxes_exception,
|
||||
})
|
||||
return res
|
||||
|
||||
@fields.depends('product', 'unit_price', 'uom', 'booking', 'nights_quantity')
|
||||
def on_change_product(self):
|
||||
Product = Pool().get('product.product')
|
||||
if self.product and self.booking:
|
||||
self.uom = self.product.default_uom.id
|
||||
with Transaction().set_context(self.booking.get_context_price(self.product)):
|
||||
self.unit_price = Product.get_sale_price([self.product],
|
||||
self.nights_quantity or 0)[self.product.id]
|
||||
if self.unit_price:
|
||||
self.unit_price = self.booking.currency.round(self.unit_price)
|
||||
else:
|
||||
self.unit_price = self.product.list_price
|
||||
|
||||
@fields.depends('arrival_date', 'departure_date')
|
||||
def on_change_arrival_date(self):
|
||||
if not self.arrival_date or (
|
||||
self.departure_date and self.departure_date > self.arrival_date):
|
||||
return
|
||||
self.departure_date = self.arrival_date + timedelta(days=1)
|
||||
|
||||
def check_method(self):
|
||||
"""
|
||||
Check the methods.
|
||||
"""
|
||||
Date = Pool().get('ir.date')
|
||||
if self.registration_state in (['check_in', 'check_out']):
|
||||
raise UserError(gettext('hotel.msg_reservation_checkin'))
|
||||
if self.arrival_date < Date.today():
|
||||
raise UserError(gettext('hotel.msg_invalid_arrival_date'))
|
||||
if self.arrival_date >= self.departure_date:
|
||||
raise UserError(gettext('hotel.msg_invalid_date'))
|
||||
# Operation = Pool().get('hotel.operation')
|
||||
# operations = Operation.search([
|
||||
# ('room', '=', self.room.id),
|
||||
# ['AND',
|
||||
# ['OR', [
|
||||
# ('start_date', '>=', self.arrival_date),
|
||||
# ('end_date', '<=', self.arrival_date),
|
||||
# ], [
|
||||
# ('start_date', '>=', self.departure_date),
|
||||
# ('end_date', '<=', self.departure_date),
|
||||
# ]]
|
||||
# ]
|
||||
# ])
|
||||
# if operations:
|
||||
# raise AccessError(gettext('hotel.msg_occupied_room', s=self.departure_date))
|
||||
# config = Pool().get('hotel.configuration')(1)
|
||||
# quarantine_days = config.quarantine_rooms
|
||||
# room_id = self.room.id
|
||||
# if quarantine_days:
|
||||
# delta = timedelta(days=int(quarantine_days))
|
||||
# _date = self.arrival_date - delta
|
||||
# operations = Operation.search([
|
||||
# ['AND',
|
||||
# ['OR', [
|
||||
# ('room', '=', room_id),
|
||||
# ('end_date', '=', _date)
|
||||
# ],
|
||||
# [
|
||||
# ('room', '=', room_id),
|
||||
# ('end_date', '=', self.arrival_date)
|
||||
# ]]]])
|
||||
# if operations:
|
||||
# raise AccessError(gettext('hotel.msg_restring_room', s=self.room.name))
|
||||
|
||||
def get_state(self, name):
|
||||
if self.booking:
|
||||
return self.booking.state
|
||||
|
||||
def get_host_quantity(self, name):
|
||||
res = 0
|
||||
if self.num_adult:
|
||||
res += self.num_adult
|
||||
if self.num_children:
|
||||
res += self.num_children
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_available_rooms(cls, start_date, end_date, rooms_ids=[], oper=None):
|
||||
"""
|
||||
Look for available rooms.
|
||||
|
||||
given the date interval, return a list of room ids.
|
||||
|
||||
a room is available if it has no operation that overlaps
|
||||
with the given date interval.
|
||||
|
||||
the optional 'rooms' list is a list of room instance, is an
|
||||
additional filter, specifying the ids of the desirable rooms.
|
||||
|
||||
the optional 'oper' is an operation object that has to be
|
||||
filtered out of the test. it is useful for validating an already
|
||||
existing operation.
|
||||
"""
|
||||
|
||||
if start_date >= end_date:
|
||||
raise UserError(gettext('hotel.msg_invalid_date_range'))
|
||||
|
||||
# define the domain of the operations that find a
|
||||
# room to be available
|
||||
dom = ['AND', ['OR',
|
||||
[
|
||||
('arrival_date', '>=', start_date),
|
||||
('arrival_date', '<', end_date),
|
||||
], [
|
||||
('departure_date', '<=', end_date),
|
||||
('departure_date', '>', start_date),
|
||||
], [
|
||||
('arrival_date', '<=', start_date),
|
||||
('departure_date', '>=', end_date),
|
||||
],
|
||||
]]
|
||||
|
||||
## If oper was specified, do not compare the operations with it
|
||||
if oper is not None:
|
||||
dom.append(('id', '!=', oper.id))
|
||||
|
||||
if rooms_ids:
|
||||
dom.append(('room', 'in', rooms_ids))
|
||||
|
||||
folios = cls.search(dom)
|
||||
rooms_not_available_ids = [folio.room.id for folio in folios]
|
||||
rooms_available_ids = set(rooms_ids) - set(rooms_not_available_ids)
|
||||
return list(rooms_available_ids)
|
||||
|
||||
@fields.depends('arrival_date', 'departure_date')
|
||||
def get_nights_quantity(self, name=None):
|
||||
"""
|
||||
Compute nights between start and end
|
||||
return a integer the mean days of occupancy.
|
||||
"""
|
||||
nights = 0
|
||||
if not self.arrival_date or not self.departure_date:
|
||||
return nights
|
||||
nights = (self.departure_date - self.arrival_date).days
|
||||
return nights
|
||||
|
||||
def get_total_amount(self, name):
|
||||
"""
|
||||
The total amount of booking based on room flat price.
|
||||
TODO: If room fee is applied should be used for total price calculation
|
||||
instead of flat price. Fee is linked to channel management.
|
||||
"""
|
||||
res = _ZERO
|
||||
if self.nights_quantity and self.unit_price:
|
||||
res = self.nights_quantity * self.unit_price
|
||||
return res
|
||||
|
||||
def get_channel_commission(self, name):
|
||||
"""
|
||||
Calculation of sale commission for channel based on booking total amount
|
||||
"""
|
||||
res = Decimal(0)
|
||||
if self.total_amount and self.booking.party_seller and \
|
||||
self.booking.party_seller.party.sale_commission:
|
||||
res = self.total_amount * self.booking.party_seller.sale_commission / 100
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _get_new_invoice(cls, data):
|
||||
pool = Pool()
|
||||
Invoice = pool.get('account.invoice')
|
||||
Party = pool.get('party.party')
|
||||
Agent = pool.get('commission.agent')
|
||||
Journal = pool.get('account.journal')
|
||||
PaymentTerm = pool.get('account.invoice.payment_term')
|
||||
Date = pool.get('ir.date')
|
||||
date_ = Date.today()
|
||||
|
||||
price_list_id = None
|
||||
if data.get('price_list'):
|
||||
price_list_id = data['price_list']
|
||||
|
||||
company_id = Transaction().context.get('company')
|
||||
party = data['party']
|
||||
description = data.get('rooms')
|
||||
reference = data.get('reference')
|
||||
agent = None
|
||||
if data.get('agent'):
|
||||
agent = Agent(data['agent'])
|
||||
|
||||
journal, = Journal.search([
|
||||
('type', '=', 'revenue'),
|
||||
], limit=1)
|
||||
|
||||
address = Party.address_get(party, type='invoice')
|
||||
payment_term = data.get('payment_term', None)
|
||||
if not payment_term:
|
||||
payment_terms = PaymentTerm.search([])
|
||||
payment_term = payment_terms[0]
|
||||
return Invoice(
|
||||
company=company_id,
|
||||
payment_term=payment_term.id,
|
||||
party=party.id,
|
||||
account=party.account_receivable_used.id,
|
||||
invoice_date=date_,
|
||||
description=description,
|
||||
state='draft',
|
||||
reference=reference,
|
||||
agent=agent,
|
||||
journal=journal,
|
||||
type='out',
|
||||
invoice_type='1',
|
||||
invoice_address=address.id,
|
||||
)
|
||||
|
||||
|
||||
class BookingReport(Report):
|
||||
__name__ = 'hotel.booking'
|
||||
|
||||
|
@ -1707,103 +1086,3 @@ class BookingDailyReport(Report):
|
|||
report_context['company'] = Company(data['company']).party.name
|
||||
report_context['date'] = data['date']
|
||||
return report_context
|
||||
|
||||
|
||||
class HotelCharge(Workflow, ModelSQL, ModelView):
|
||||
'Hotel Charge'
|
||||
__name__ = 'hotel.charge'
|
||||
|
||||
booking_line = fields.Many2One('', 'Booking Line',
|
||||
required=True)
|
||||
service_date = fields.Date('Service Date', select=True, required=True)
|
||||
product = fields.Many2One('product.product', 'Product',
|
||||
domain=[('salable', '=', True)], required=True)
|
||||
quantity = fields.Integer('Quantity', required=True)
|
||||
invoice_to = fields.Many2One('party.party', 'Invoice To', required=True)
|
||||
unit_price = fields.Numeric('Unit Price', required=True)
|
||||
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price'),
|
||||
'get_unit_price_w_tax')
|
||||
order = fields.Char('Order', select=True)
|
||||
description = fields.Char('Description', select=True)
|
||||
state = fields.Selection(INVOICE_STATES, 'State', readonly=True)
|
||||
state_string = state.translated('state')
|
||||
sale_line = fields.Many2One('sale.line', 'Sale Line', readonly=True)
|
||||
amount = fields.Function(fields.Numeric('Amount',
|
||||
digits=(16, 2)), 'get_amount')
|
||||
taxed_amount = fields.Function(fields.Numeric('Amount with Tax',
|
||||
digits=(16, 2)), 'get_taxed_amount')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(HotelCharge, cls).__setup__()
|
||||
cls._buttons.update({
|
||||
'transfer': {
|
||||
'invisible': True,
|
||||
},
|
||||
# 'bill': {
|
||||
# 'invisible': Eval('invoice_state') is not None,
|
||||
# },
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def default_quantity():
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def default_date_service():
|
||||
today = Pool().get('ir.date').today()
|
||||
return today
|
||||
|
||||
def get_amount(self, name=None):
|
||||
if self.quantity and self.unit_price:
|
||||
return self.quantity * self.unit_price_w_tax
|
||||
return 0
|
||||
|
||||
def get_unit_price_w_tax(self, name=None):
|
||||
Tax = Pool().get('account.tax')
|
||||
res = self.unit_price or 0
|
||||
if self.unit_price:
|
||||
values = Tax.compute(
|
||||
self.product.template.customer_taxes_used,
|
||||
self.unit_price, 1)
|
||||
if values:
|
||||
value = values[0]
|
||||
res = value['base'] + value['amount']
|
||||
return res
|
||||
|
||||
def get_taxed_amount(self, name=None):
|
||||
if self.quantity and self.unit_price:
|
||||
return self.quantity * self.unit_price
|
||||
|
||||
# def get_sale(self, name=None):
|
||||
# if self.sale_line:
|
||||
# return self.sale_line.sale.id
|
||||
|
||||
# def compute_amount_with_tax(line):
|
||||
# tax_amount = _ZERO
|
||||
# amount = _ZERO
|
||||
# if line.taxes:
|
||||
# tax_list = Tax.compute(line.taxes, line.unit_price or _ZERO,
|
||||
# line.quantity or 0.0)
|
||||
#
|
||||
# tax_amount = sum([t['amount'] for t in tax_list], _ZERO)
|
||||
#
|
||||
# if line.unit_price:
|
||||
# amount = line.unit_price * Decimal(line.quantity)
|
||||
# return amount + tax_amount
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def bill(cls, records):
|
||||
cls.create_sales(records)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button_action('hotel.wizard_operation_line_transfer')
|
||||
def transfer(cls, records):
|
||||
pass
|
||||
|
||||
@fields.depends('unit_price', 'product')
|
||||
def on_change_product(self):
|
||||
if self.product:
|
||||
self.unit_price = self.product.template.list_price
|
||||
self.description = self.product.description
|
||||
|
|
|
@ -10,7 +10,16 @@ from trytond.report import Report
|
|||
from trytond.wizard import Wizard, StateView, StateAction, Button, StateTransition
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.model.exceptions import AccessError
|
||||
from trytond.exceptions import UserError
|
||||
from trytond.i18n import gettext
|
||||
from constants import REGISTRATION_STATE, COMPLEMENTARY, INVOICE_STATES
|
||||
|
||||
STATES_CHECKIN = {
|
||||
'readonly': Eval('registration_state').in_(
|
||||
['check_in', 'no_show', 'cancelled']
|
||||
),
|
||||
'required': Eval('registration_state') == 'check_in',
|
||||
}
|
||||
|
||||
STATES_OP = {
|
||||
'readonly': Eval('state').in_(['check_out', 'done', 'cancelled'])
|
||||
|
@ -33,23 +42,712 @@ COLOR_MNT = {
|
|||
'done': '#d45757',
|
||||
}
|
||||
|
||||
INVOICE_STATES = [
|
||||
('', ''),
|
||||
('pending', 'Pending'),
|
||||
('in_process', 'In Process'),
|
||||
('invoiced', 'Invoiced'),
|
||||
('paid', 'Paid')
|
||||
]
|
||||
|
||||
COMPLEMENTARY = [
|
||||
('', ''),
|
||||
('in_house', 'In House'),
|
||||
('courtesy', 'Courtesy')
|
||||
]
|
||||
|
||||
_ZERO = Decimal('0')
|
||||
|
||||
|
||||
class Folio(ModelSQL, ModelView):
|
||||
'Folio'
|
||||
__name__ = 'hotel.folio'
|
||||
booking = fields.Many2One('hotel.booking', 'Booking', ondelete='CASCADE',
|
||||
select=True)
|
||||
registration_card = fields.Char('Registration Card', readonly=True,
|
||||
select=True, help="Unique sequence for card guest registration.")
|
||||
room = fields.Many2One('hotel.room', 'Room', select=True, states={
|
||||
'required': Eval('registration_state') == 'check_in',
|
||||
'readonly': Eval('registration_state') == 'check_in',
|
||||
})
|
||||
arrival_date = fields.Date('Arrival Date', required=True,
|
||||
states=STATES_CHECKIN)
|
||||
departure_date = fields.Date('Departure Date', required=True,
|
||||
states=STATES_CHECKIN)
|
||||
product = fields.Many2One('product.product', 'Product',
|
||||
select=True, domain=[
|
||||
('template.type', '=', 'service'),
|
||||
('template.kind', '=', 'accommodation'),
|
||||
], required=True, states=STATES_CHECKIN)
|
||||
unit_price = fields.Numeric('Unit Price', digits=(16, 4),
|
||||
states={
|
||||
'required': Bool(Eval('product')),
|
||||
'readonly': Eval('registration_state') == 'check_in',
|
||||
})
|
||||
uom = fields.Many2One('product.uom', 'UOM', readonly=True)
|
||||
main_guest = fields.Many2One('party.party', 'Main Guest', select=True,
|
||||
states={
|
||||
'required': Eval('registration_state') == 'check_in',
|
||||
'readonly': Eval('registration_state') == 'check_in',
|
||||
}
|
||||
)
|
||||
contact = fields.Char('Contact', states=STATES_CHECKIN)
|
||||
num_children = fields.Function(fields.Integer('No. Children'),
|
||||
'get_num_children')
|
||||
nights_quantity = fields.Function(fields.Integer('Nights'),
|
||||
'get_nights_quantity')
|
||||
host_quantity = fields.Integer('Host', states=STATES_CHECKIN)
|
||||
unit_digits = fields.Function(fields.Integer('Unit Digits'), 'get_unit_digits')
|
||||
notes = fields.Text('Notes')
|
||||
total_amount = fields.Function(fields.Numeric('Total Amount',
|
||||
digits=(16, 2)), 'get_total_amount')
|
||||
total_commission = fields.Function(fields.Numeric('Channel Commission',
|
||||
digits=(16, 2)), 'get_channel_commission')
|
||||
guests = fields.One2Many('hotel.booking.guest', 'booking_line', 'Guests',
|
||||
states={'readonly': ~Eval('registration_state').in_(['check_in'])})
|
||||
nationality = fields.Many2One('party.nationality', 'Nationality',
|
||||
states=STATES_CHECKIN)
|
||||
origin_country = fields.Many2One('party.nationality', 'Origin Country',
|
||||
select=True, states=STATES_CHECKIN)
|
||||
target_country = fields.Many2One('party.nationality', 'Target Country',
|
||||
select=True, states=STATES_CHECKIN)
|
||||
registration_state = fields.Selection(REGISTRATION_STATE,
|
||||
'Registration State', readonly=True)
|
||||
charges = fields.One2Many('hotel.folio.charge', 'folio', 'Charges',
|
||||
states={
|
||||
'readonly': ~Eval('registration_state').in_(['check_in']),
|
||||
})
|
||||
party = fields.Many2One('party.party', 'Party to Bill', select=True,
|
||||
states={
|
||||
'readonly': Eval('registration_state').in_(['check_out']),
|
||||
})
|
||||
invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line')
|
||||
invoice = fields.Function(fields.Many2One('account.invoice', 'Invoice'),
|
||||
'get_invoice')
|
||||
invoice_state = fields.Selection(INVOICE_STATES, 'Invoice State', readonly=True)
|
||||
type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
|
||||
states={
|
||||
'invisible': ~Bool(Eval('complementary')),
|
||||
'required': Bool(Eval('complementary')),
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(Folio, cls).__setup__()
|
||||
cls._check_modify_exclude = [
|
||||
'nationality', 'origin_country', 'target_country',
|
||||
'registration_state', 'guests'
|
||||
],
|
||||
cls._buttons.update({
|
||||
'check_in': {
|
||||
'invisible': Eval('registration_state').in_(['check_in', 'check_out']),
|
||||
},
|
||||
'check_out': {
|
||||
'invisible': Eval('registration_state').in_(['check_out', 'pending']),
|
||||
},
|
||||
'bill': {
|
||||
'invisible': Eval('registration_state') != 'check_out',
|
||||
}
|
||||
})
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def check_in(cls, records):
|
||||
config_party = Pool().get('party.configuration')(1)
|
||||
validate_party = config_party.validate_party
|
||||
if not validate_party:
|
||||
config_party.validate_party = True
|
||||
config_party.save()
|
||||
|
||||
for rec in records:
|
||||
# rec.booking.party.pre_validate()
|
||||
if rec.booking.state == 'offer':
|
||||
raise UserError(gettext('hotel.msg_missing_confirm_booking'))
|
||||
if rec.main_guest is None:
|
||||
raise UserError(gettext('hotel.msg_missing_main_guest'))
|
||||
if rec.room is None:
|
||||
raise UserError(gettext('hotel.msg_missing_select_room'))
|
||||
rec.set_registration_number()
|
||||
rec.booking.check_rooms()
|
||||
|
||||
cls.write(records, {'registration_state': 'check_in'})
|
||||
# change_state = all(
|
||||
# [rl.registration_state == 'check_in' for rl in booking.lines]
|
||||
# )
|
||||
# print('change_state..', change_state)
|
||||
# if change_state:
|
||||
# booking.registration_state = 'check_in'
|
||||
# booking.save()
|
||||
if config_party.validate_party != validate_party:
|
||||
config_party.validate_party = validate_party
|
||||
config_party.save()
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def check_out(cls, records):
|
||||
for record in records:
|
||||
cls.write([record], {'registration_state': 'check_out'})
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def bill(cls, records):
|
||||
for rec in records:
|
||||
# if rec.complementary:
|
||||
# return
|
||||
cls.create_invoice(rec)
|
||||
|
||||
def set_registration_number(self):
|
||||
"""
|
||||
Fill the number field for registration card with sequence
|
||||
"""
|
||||
pool = Pool()
|
||||
Config = pool.get('hotel.configuration')
|
||||
config = Config.get_configuration()
|
||||
|
||||
if self.registration_card:
|
||||
return
|
||||
if not config.registration_card_sequence:
|
||||
raise UserError(gettext('hotel.msg_missing_sequence_registration'))
|
||||
number = config.registration_card_sequence.get()
|
||||
self.registration_card = number
|
||||
self.save()
|
||||
|
||||
@classmethod
|
||||
def create_invoice(cls, record):
|
||||
pool = Pool()
|
||||
Date = pool.get('ir.date')
|
||||
InvoiceLine = pool.get('account.invoice.line')
|
||||
FolioCharge = pool.get('hotel.folio.charge')
|
||||
Configuration = pool.get('hotel.configuration')
|
||||
configuration = Configuration.get_configuration()
|
||||
# SaleVoucher = pool.get('sale.sale-account.voucher')
|
||||
# date_ = Date.today()
|
||||
# ctx = {}
|
||||
invoice_to_create = cls.get_grouped_invoices([record])
|
||||
print(invoice_to_create)
|
||||
for rec in invoice_to_create.values():
|
||||
# if rec.get('price_list'):
|
||||
# ctx['price_list'] = rec.get('price_list')
|
||||
# ctx['sale_date'] = date_
|
||||
# ctx['currency'] = rec['currency']
|
||||
# ctx['customer'] = rec['party'].id
|
||||
|
||||
invoice = cls._get_new_invoice(rec)
|
||||
invoice.save()
|
||||
|
||||
# Here add payments to invoice
|
||||
# if rec.get('vouchers'):
|
||||
# for v in rec['vouchers']:
|
||||
# SaleVoucher.create([{
|
||||
# 'voucher': v.id,
|
||||
# 'sale': sale.id
|
||||
# }])
|
||||
|
||||
# Add and create default charges lines if exists
|
||||
if rec.get('guests_qty') and rec.get('add_default_charges'):
|
||||
for product in configuration.default_charges:
|
||||
if rec['party']:
|
||||
taxes_ids = cls.get_taxes(invoice, product, rec)
|
||||
new_line = {
|
||||
'invoice': invoice.id,
|
||||
'type': 'line',
|
||||
'unit': product.template.default_uom.id,
|
||||
'quantity': rec['guests_qty'],
|
||||
'unit_price': product.template.list_price,
|
||||
'product': product.id,
|
||||
'description': product.rec_name,
|
||||
}
|
||||
if taxes_ids:
|
||||
new_line.update({'taxes': [('add', taxes_ids)]})
|
||||
if new_line:
|
||||
InvoiceLine.create([new_line])
|
||||
|
||||
print('XXXXX', rec)
|
||||
for _line in rec['lines']:
|
||||
line, = InvoiceLine.create([
|
||||
cls._get_invoice_line(invoice, _line, rec)
|
||||
])
|
||||
if _line.get('folios'):
|
||||
cls.write(_line.get('folios'), {
|
||||
'invoice_line': line.id,
|
||||
# 'invoice_state': 'in_process'
|
||||
})
|
||||
else:
|
||||
FolioCharge.write([_line.get('charge')], {
|
||||
'invoice_line': line.id,
|
||||
'state': 'invoiced',
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def _get_invoice_line(cls, invoice, line, record):
|
||||
product = line['product']
|
||||
new_line = {
|
||||
'type': 'line',
|
||||
'invoice': invoice.id,
|
||||
'unit': product.template.default_uom.id,
|
||||
'account': product.template.account_category.account_revenue_used.id,
|
||||
'invoice_type': 'out',
|
||||
'operation_center': 1,
|
||||
'quantity': line['quantity'],
|
||||
'unit_price': line['unit_price'],
|
||||
'product': product.id,
|
||||
'party': invoice.party.id,
|
||||
'description': line['description'],
|
||||
}
|
||||
if not line['taxes_exception']:
|
||||
taxes_ids = cls.get_taxes(invoice, line['product'], record)
|
||||
if taxes_ids:
|
||||
new_line.update({'taxes': [('add', taxes_ids)]})
|
||||
return new_line
|
||||
|
||||
@classmethod
|
||||
def get_taxes(cls, invoice, product, rec):
|
||||
ctx = cls.get_context_price(invoice, product, rec)
|
||||
return ctx['taxes']
|
||||
|
||||
@classmethod
|
||||
def get_context_price(cls, invoice, product, rec):
|
||||
context = {}
|
||||
context['currency'] = rec['currency']
|
||||
context['customer'] = rec['party'].id
|
||||
context['price_list'] = rec['price_list']
|
||||
context['uom'] = product.template.default_uom.id
|
||||
|
||||
# Set taxes before unit_price to have taxes in context of sale price
|
||||
taxes = []
|
||||
pattern = {}
|
||||
for tax in product.customer_taxes_used:
|
||||
if invoice.party and invoice.party.customer_tax_rule:
|
||||
tax_ids = invoice.party.customer_tax_rule.apply(tax, pattern)
|
||||
if tax_ids:
|
||||
taxes.extend(tax_ids)
|
||||
continue
|
||||
taxes.append(tax.id)
|
||||
if invoice.party and invoice.party.customer_tax_rule:
|
||||
tax_ids = invoice.party.customer_tax_rule.apply(None, pattern)
|
||||
if tax_ids:
|
||||
taxes.extend(tax_ids)
|
||||
|
||||
context['taxes'] = taxes
|
||||
return context
|
||||
|
||||
@staticmethod
|
||||
def default_main_guest():
|
||||
party = Transaction().context.get('party')
|
||||
return party
|
||||
|
||||
@staticmethod
|
||||
def default_host_quantity():
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def default_accommodation():
|
||||
Configuration = Pool().get('hotel.configuration')
|
||||
configuration = Configuration.get_configuration()
|
||||
|
||||
if configuration.default_accommodation:
|
||||
return configuration.default_accommodation.id
|
||||
|
||||
@classmethod
|
||||
def validate(cls, lines):
|
||||
super(Folio, cls).validate(lines)
|
||||
for line in lines:
|
||||
# line.check_method()
|
||||
pass
|
||||
|
||||
def get_invoice(self, name=None):
|
||||
if self.invoice_line and self.invoice_line.invoice:
|
||||
self.invoice_line.invoice.id
|
||||
|
||||
# @fields.depends('accommodation', 'product')
|
||||
# def on_change_accommodation(self):
|
||||
# if not self.accommodation:
|
||||
# self.product = None
|
||||
|
||||
@classmethod
|
||||
def get_room_info(cls, fo):
|
||||
description = ' \n'.join([
|
||||
fo.product.rec_name,
|
||||
'Huesped Principal: ' + fo.main_guest.name,
|
||||
'Habitacion: ' + fo.room.name,
|
||||
'Llegada: ' + str(fo.arrival_date),
|
||||
'Salida: ' + str(fo.departure_date),
|
||||
])
|
||||
return description
|
||||
|
||||
@classmethod
|
||||
def get_grouped_invoices(cls, folios):
|
||||
res = {}
|
||||
# transfered = []
|
||||
# for op in folios:
|
||||
# for top in op.transfered_operations:
|
||||
# top.party = op.party
|
||||
# transfered.append(top)
|
||||
#
|
||||
# if transfered:
|
||||
# folios.extend(transfered)
|
||||
ffols = [fol for fol in folios if not fol.invoice_line]
|
||||
for fo in ffols:
|
||||
if fo.party:
|
||||
party = fo.party
|
||||
else:
|
||||
party = fo.main_guest
|
||||
|
||||
booking = fo.booking
|
||||
agent_id = booking.party_seller.party.id if booking.party_seller else None
|
||||
if party.id not in res.keys():
|
||||
# Add room product to sale
|
||||
res[party.id] = {
|
||||
'party': party,
|
||||
'currency': booking.currency.id,
|
||||
'payment_term': None,
|
||||
'guests_qty': len(fo.guests) + 1,
|
||||
'reference': '',
|
||||
'agent': agent_id,
|
||||
'rooms': fo.room.name,
|
||||
'company': booking.company.id,
|
||||
'price_list': booking.price_list.id if booking.price_list else None,
|
||||
'add_default_charges': False,
|
||||
'vouchers': booking.vouchers,
|
||||
'lines': [{
|
||||
'folios': [fo],
|
||||
'description': cls.get_room_info(fo),
|
||||
'quantity': fo.nights_quantity,
|
||||
'product': fo.product,
|
||||
'unit_price': fo.unit_price,
|
||||
'taxes_exception': booking.taxes_exception,
|
||||
}]
|
||||
}
|
||||
else:
|
||||
res[party.id]['rooms'] += ' ' + fo.room.name
|
||||
res[party.id]['lines'].append({
|
||||
'folios': [fo],
|
||||
'description': cls.get_room_info(fo),
|
||||
'quantity': fo.nights_quantity,
|
||||
'product': fo.accommodation,
|
||||
'unit_price': fo.unit_price,
|
||||
'taxes_exception': booking.taxes_exception,
|
||||
})
|
||||
for charge in fo.charges:
|
||||
if charge.invoice_line:
|
||||
continue
|
||||
invoice_party_id = charge.invoice_to.id
|
||||
unit_price = booking.currency.round(charge.unit_price)
|
||||
if invoice_party_id != party.id:
|
||||
if invoice_party_id not in res.keys():
|
||||
res[invoice_party_id] = {
|
||||
'party': charge.invoice_to.id,
|
||||
'currency': booking.currency.id,
|
||||
'payment_term': None,
|
||||
'lines': [],
|
||||
}
|
||||
res[invoice_party_id]['lines'].append({
|
||||
'description': ' | '.join([
|
||||
str(charge.date_service),
|
||||
charge.order or '',
|
||||
charge.description or ''
|
||||
]),
|
||||
'quantity': charge.quantity,
|
||||
'product': charge.product,
|
||||
'unit_price': unit_price,
|
||||
'charge': charge,
|
||||
'taxes_exception': booking.taxes_exception,
|
||||
})
|
||||
return res
|
||||
|
||||
@fields.depends('product', 'unit_price', 'uom', 'booking', 'nights_quantity')
|
||||
def on_change_product(self):
|
||||
Product = Pool().get('product.product')
|
||||
if self.product and self.booking:
|
||||
self.uom = self.product.default_uom.id
|
||||
with Transaction().set_context(
|
||||
self.booking.get_context_price(self.product)):
|
||||
self.unit_price = Product.get_sale_price([self.product],
|
||||
self.nights_quantity or 0)[self.product.id]
|
||||
if self.unit_price:
|
||||
self.unit_price = self.booking.currency.round(self.unit_price)
|
||||
else:
|
||||
self.unit_price = self.product.list_price
|
||||
|
||||
@fields.depends('arrival_date', 'departure_date')
|
||||
def on_change_arrival_date(self):
|
||||
if not self.arrival_date or (
|
||||
self.departure_date and self.departure_date > self.arrival_date):
|
||||
return
|
||||
self.departure_date = self.arrival_date + timedelta(days=1)
|
||||
|
||||
def check_method(self):
|
||||
"""
|
||||
Check the methods.
|
||||
"""
|
||||
Date = Pool().get('ir.date')
|
||||
if self.registration_state in (['check_in', 'check_out']):
|
||||
raise UserError(gettext('hotel.msg_reservation_checkin'))
|
||||
if self.arrival_date < Date.today():
|
||||
raise UserError(gettext('hotel.msg_invalid_arrival_date'))
|
||||
if self.arrival_date >= self.departure_date:
|
||||
raise UserError(gettext('hotel.msg_invalid_date'))
|
||||
# Operation = Pool().get('hotel.operation')
|
||||
# operations = Operation.search([
|
||||
# ('room', '=', self.room.id),
|
||||
# ['AND',
|
||||
# ['OR', [
|
||||
# ('start_date', '>=', self.arrival_date),
|
||||
# ('end_date', '<=', self.arrival_date),
|
||||
# ], [
|
||||
# ('start_date', '>=', self.departure_date),
|
||||
# ('end_date', '<=', self.departure_date),
|
||||
# ]]
|
||||
# ]
|
||||
# ])
|
||||
# if operations:
|
||||
# raise AccessError(gettext('hotel.msg_occupied_room', s=self.departure_date))
|
||||
# config = Pool().get('hotel.configuration')(1)
|
||||
# quarantine_days = config.quarantine_rooms
|
||||
# room_id = self.room.id
|
||||
# if quarantine_days:
|
||||
# delta = timedelta(days=int(quarantine_days))
|
||||
# _date = self.arrival_date - delta
|
||||
# operations = Operation.search([
|
||||
# ['AND',
|
||||
# ['OR', [
|
||||
# ('room', '=', room_id),
|
||||
# ('end_date', '=', _date)
|
||||
# ],
|
||||
# [
|
||||
# ('room', '=', room_id),
|
||||
# ('end_date', '=', self.arrival_date)
|
||||
# ]]]])
|
||||
# if operations:
|
||||
# raise AccessError(gettext('hotel.msg_restring_room', s=self.room.name))
|
||||
|
||||
def get_state(self, name):
|
||||
if self.booking:
|
||||
return self.booking.state
|
||||
|
||||
def get_host_quantity(self, name):
|
||||
res = 0
|
||||
if self.num_adult:
|
||||
res += self.num_adult
|
||||
if self.num_children:
|
||||
res += self.num_children
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def get_available_rooms(cls, start_date, end_date, rooms_ids=[], oper=None):
|
||||
"""
|
||||
Look for available rooms.
|
||||
|
||||
given the date interval, return a list of room ids.
|
||||
|
||||
a room is available if it has no operation that overlaps
|
||||
with the given date interval.
|
||||
|
||||
the optional 'rooms' list is a list of room instance, is an
|
||||
additional filter, specifying the ids of the desirable rooms.
|
||||
|
||||
the optional 'oper' is an operation object that has to be
|
||||
filtered out of the test. it is useful for validating an already
|
||||
existing operation.
|
||||
"""
|
||||
|
||||
if start_date >= end_date:
|
||||
raise UserError(gettext('hotel.msg_invalid_date_range'))
|
||||
|
||||
# define the domain of the operations that find a
|
||||
# room to be available
|
||||
dom = ['AND', ['OR',
|
||||
[
|
||||
('arrival_date', '>=', start_date),
|
||||
('arrival_date', '<', end_date),
|
||||
], [
|
||||
('departure_date', '<=', end_date),
|
||||
('departure_date', '>', start_date),
|
||||
], [
|
||||
('arrival_date', '<=', start_date),
|
||||
('departure_date', '>=', end_date),
|
||||
],
|
||||
]]
|
||||
|
||||
## If oper was specified, do not compare the operations with it
|
||||
if oper is not None:
|
||||
dom.append(('id', '!=', oper.id))
|
||||
|
||||
if rooms_ids:
|
||||
dom.append(('room', 'in', rooms_ids))
|
||||
|
||||
folios = cls.search(dom)
|
||||
rooms_not_available_ids = [folio.room.id for folio in folios]
|
||||
rooms_available_ids = set(rooms_ids) - set(rooms_not_available_ids)
|
||||
return list(rooms_available_ids)
|
||||
|
||||
@fields.depends('arrival_date', 'departure_date')
|
||||
def get_nights_quantity(self, name=None):
|
||||
"""
|
||||
Compute nights between start and end
|
||||
return a integer the mean days of occupancy.
|
||||
"""
|
||||
nights = 0
|
||||
if not self.arrival_date or not self.departure_date:
|
||||
return nights
|
||||
nights = (self.departure_date - self.arrival_date).days
|
||||
return nights
|
||||
|
||||
def get_total_amount(self, name):
|
||||
"""
|
||||
The total amount of booking based on room flat price.
|
||||
TODO: If room fee is applied should be used for total price calculation
|
||||
instead of flat price. Fee is linked to channel management.
|
||||
"""
|
||||
res = _ZERO
|
||||
if self.nights_quantity and self.unit_price:
|
||||
res = self.nights_quantity * self.unit_price
|
||||
return res
|
||||
|
||||
def get_channel_commission(self, name):
|
||||
"""
|
||||
Calculation of sale commission for channel based on booking total amount
|
||||
"""
|
||||
res = Decimal(0)
|
||||
if self.total_amount and self.booking.party_seller and \
|
||||
self.booking.party_seller.party.sale_commission:
|
||||
res = self.total_amount * self.booking.party_seller.sale_commission / 100
|
||||
return res
|
||||
|
||||
@classmethod
|
||||
def _get_new_invoice(cls, data):
|
||||
pool = Pool()
|
||||
Invoice = pool.get('account.invoice')
|
||||
Party = pool.get('party.party')
|
||||
Agent = pool.get('commission.agent')
|
||||
Journal = pool.get('account.journal')
|
||||
PaymentTerm = pool.get('account.invoice.payment_term')
|
||||
Date = pool.get('ir.date')
|
||||
date_ = Date.today()
|
||||
|
||||
price_list_id = None
|
||||
if data.get('price_list'):
|
||||
price_list_id = data['price_list']
|
||||
|
||||
company_id = Transaction().context.get('company')
|
||||
party = data['party']
|
||||
description = data.get('rooms')
|
||||
reference = data.get('reference')
|
||||
agent = None
|
||||
if data.get('agent'):
|
||||
agent = Agent(data['agent'])
|
||||
|
||||
journal, = Journal.search([
|
||||
('type', '=', 'revenue'),
|
||||
], limit=1)
|
||||
|
||||
address = Party.address_get(party, type='invoice')
|
||||
payment_term = data.get('payment_term', None)
|
||||
if not payment_term:
|
||||
payment_terms = PaymentTerm.search([])
|
||||
payment_term = payment_terms[0]
|
||||
return Invoice(
|
||||
company=company_id,
|
||||
payment_term=payment_term.id,
|
||||
party=party.id,
|
||||
account=party.account_receivable_used.id,
|
||||
invoice_date=date_,
|
||||
description=description,
|
||||
state='draft',
|
||||
reference=reference,
|
||||
agent=agent,
|
||||
journal=journal,
|
||||
type='out',
|
||||
invoice_type='1',
|
||||
invoice_address=address.id,
|
||||
)
|
||||
|
||||
|
||||
class HotelCharge(Workflow, ModelSQL, ModelView):
|
||||
'Hotel Charge'
|
||||
__name__ = 'hotel.charge'
|
||||
|
||||
folio = fields.Many2One('', 'Booking Line', required=True)
|
||||
service_date = fields.Date('Service Date', select=True, required=True)
|
||||
product = fields.Many2One('product.product', 'Product',
|
||||
domain=[('salable', '=', True)], required=True)
|
||||
quantity = fields.Integer('Quantity', required=True)
|
||||
invoice_to = fields.Many2One('party.party', 'Invoice To', required=True)
|
||||
unit_price = fields.Numeric('Unit Price', required=True)
|
||||
unit_price_w_tax = fields.Function(fields.Numeric('Unit Price'),
|
||||
'get_unit_price_w_tax')
|
||||
order = fields.Char('Order', select=True)
|
||||
description = fields.Char('Description', select=True)
|
||||
state = fields.Selection(INVOICE_STATES, 'State', readonly=True)
|
||||
state_string = state.translated('state')
|
||||
sale_line = fields.Many2One('sale.line', 'Sale Line', readonly=True)
|
||||
amount = fields.Function(fields.Numeric('Amount',
|
||||
digits=(16, 2)), 'get_amount')
|
||||
taxed_amount = fields.Function(fields.Numeric('Amount with Tax',
|
||||
digits=(16, 2)), 'get_taxed_amount')
|
||||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super(HotelCharge, cls).__setup__()
|
||||
cls._buttons.update({
|
||||
'transfer': {
|
||||
'invisible': True,
|
||||
},
|
||||
# 'bill': {
|
||||
# 'invisible': Eval('invoice_state') is not None,
|
||||
# },
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def default_quantity():
|
||||
return 1
|
||||
|
||||
@staticmethod
|
||||
def default_date_service():
|
||||
today = Pool().get('ir.date').today()
|
||||
return today
|
||||
|
||||
def get_amount(self, name=None):
|
||||
if self.quantity and self.unit_price:
|
||||
return self.quantity * self.unit_price_w_tax
|
||||
return 0
|
||||
|
||||
def get_unit_price_w_tax(self, name=None):
|
||||
Tax = Pool().get('account.tax')
|
||||
res = self.unit_price or 0
|
||||
if self.unit_price:
|
||||
values = Tax.compute(
|
||||
self.product.template.customer_taxes_used,
|
||||
self.unit_price, 1)
|
||||
if values:
|
||||
value = values[0]
|
||||
res = value['base'] + value['amount']
|
||||
return res
|
||||
|
||||
def get_taxed_amount(self, name=None):
|
||||
if self.quantity and self.unit_price:
|
||||
return self.quantity * self.unit_price
|
||||
|
||||
# def get_sale(self, name=None):
|
||||
# if self.sale_line:
|
||||
# return self.sale_line.sale.id
|
||||
|
||||
# def compute_amount_with_tax(line):
|
||||
# tax_amount = _ZERO
|
||||
# amount = _ZERO
|
||||
# if line.taxes:
|
||||
# tax_list = Tax.compute(line.taxes, line.unit_price or _ZERO,
|
||||
# line.quantity or 0.0)
|
||||
#
|
||||
# tax_amount = sum([t['amount'] for t in tax_list], _ZERO)
|
||||
#
|
||||
# if line.unit_price:
|
||||
# amount = line.unit_price * Decimal(line.quantity)
|
||||
# return amount + tax_amount
|
||||
|
||||
@classmethod
|
||||
@ModelView.button
|
||||
def bill(cls, records):
|
||||
cls.create_sales(records)
|
||||
|
||||
@classmethod
|
||||
@ModelView.button_action('hotel.wizard_operation_line_transfer')
|
||||
def transfer(cls, records):
|
||||
pass
|
||||
|
||||
@fields.depends('unit_price', 'product')
|
||||
def on_change_product(self):
|
||||
if self.product:
|
||||
self.unit_price = self.product.template.list_price
|
||||
self.description = self.product.description
|
||||
|
||||
|
||||
# class Operation(Workflow, ModelSQL, ModelView):
|
||||
# 'Operation'
|
||||
# __name__ = 'hotel.operation'
|
||||
|
@ -833,12 +1531,12 @@ class OperationMaintenance(Workflow, ModelSQL, ModelView):
|
|||
start_date = values.get('start_date') or rec.start_date
|
||||
end_date = values.get('end_date') or rec.end_date
|
||||
room = Room(values.get('room') or rec.room)
|
||||
Operation().check_dates(start_date, end_date, room, rec.operation)
|
||||
rec.update_operation({
|
||||
'start_date': start_date,
|
||||
'end_date': end_date,
|
||||
'room': room.id,
|
||||
})
|
||||
# Operation().check_dates(start_date, end_date, room, rec.operation)
|
||||
# rec.update_operation({
|
||||
# 'start_date': start_date,
|
||||
# 'end_date': end_date,
|
||||
# 'room': room.id,
|
||||
# })
|
||||
super(OperationMaintenance, cls).write(records, values)
|
||||
|
||||
def check_method(self):
|
|
@ -15,6 +15,18 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<field name="name">folio_charge_form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_folio">
|
||||
<field name="name">Folio Statement</field>
|
||||
<field name="model">hotel.folio</field>
|
||||
<field name="report_name">hotel.folio</field>
|
||||
<field name="report">hotel/folio.fodt</field>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="report_folio_keyword">
|
||||
<field name="keyword">form_print</field>
|
||||
<field name="model">hotel.folio,-1</field>
|
||||
<field name="action" ref="report_folio"/>
|
||||
</record>
|
||||
|
||||
<!-- <record model="ir.ui.view" id="operation_view_tree">
|
||||
<field name="model">hotel.operation</field>
|
||||
<field name="type">tree</field>
|
||||
|
@ -214,18 +226,6 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<field name="act_window" ref="act_operation_tree"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_operation">
|
||||
<field name="name">Statement Operation</field>
|
||||
<field name="model">hotel.operation</field>
|
||||
<field name="report_name">hotel.operation</field>
|
||||
<field name="report">hotel/operation.fodt</field>
|
||||
</record>
|
||||
<record model="ir.action.keyword" id="report_operation_keyword">
|
||||
<field name="keyword">form_print</field>
|
||||
<field name="model">hotel.operation,-1</field>
|
||||
<field name="action" ref="report_operation"/>
|
||||
</record>
|
||||
|
||||
<record model="ir.action.wizard" id="wizard_operation_check_out">
|
||||
<field name="name">Operation Check Out</field>
|
||||
<field name="wiz_name">hotel.operation.check_out</field>
|
|
@ -21,7 +21,7 @@ xml:
|
|||
data_category.xml
|
||||
location.xml
|
||||
booking.xml
|
||||
operation.xml
|
||||
folio.xml
|
||||
housekeeping.xml
|
||||
company.xml
|
||||
city.xml
|
||||
|
|
|
@ -2,17 +2,16 @@
|
|||
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
|
||||
this repository contains the full copyright notices and license terms. -->
|
||||
<tree>
|
||||
<field name="booking"/>
|
||||
<field name="room"/>
|
||||
<field name="product"/>
|
||||
<field name="registration_card"/>
|
||||
<field name="main_guest"/>
|
||||
<field name="room"/>
|
||||
<field name="nights_quantity"/>
|
||||
<field name="arrival_date"/>
|
||||
<field name="departure_date"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="total_amount"/>
|
||||
<field name="host_quantity"/>
|
||||
<field name="registration_state"/>
|
||||
<field name="invoice_state"/>
|
||||
<field name="booking"/>
|
||||
<field name="unit_price"/>
|
||||
<field name="total_amount"/>
|
||||
</tree>
|
||||
|
|
|
@ -20,12 +20,12 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<field name="media"/>
|
||||
<label name="party_seller"/>
|
||||
<field name="party_seller" widget="selection"/>
|
||||
<label name="taxes_exception"/>
|
||||
<field name="taxes_exception"/>
|
||||
<label name="plan"/>
|
||||
<field name="plan"/>
|
||||
<label name="breakfast_included"/>
|
||||
<field name="breakfast_included"/>
|
||||
<label name="taxes_exception"/>
|
||||
<field name="taxes_exception"/>
|
||||
<button name="select_rooms" string="Select Rooms"
|
||||
icon="tryton-open" colspan="2"/>
|
||||
<button name="send_email" string="Send Email"
|
||||
|
@ -86,15 +86,11 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<group col="7" colspan="6" id="advances">
|
||||
<button name="pay_advance" string="Add Advance" icon="tryton-open"
|
||||
colspan="2"/>
|
||||
<label name="total_advance"/>
|
||||
<field name="total_advance"/>
|
||||
<label name="registration_state"/>
|
||||
<field name="registration_state"/>
|
||||
<!-- <button name="check_in" string="Check In"
|
||||
icon="tryton-forward"/>
|
||||
<button name="check_out" string="Check Out"
|
||||
icon="tryton-ok"/> -->
|
||||
<button name="no_show" string="No Show" icon="tryton-cancel"/>
|
||||
<label name="total_advance"/>
|
||||
<field name="total_advance"/>
|
||||
<button name="no_show" string="No Show" icon="tryton-cancel"/>
|
||||
<label name="state"/>
|
||||
<field name="state"/>
|
||||
</group>
|
||||
<group col="8" colspan="6" id="amounts">
|
||||
<label name="untaxed_amount"/>
|
||||
|
@ -106,15 +102,13 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<label name="pending_to_pay"/>
|
||||
<field name="pending_to_pay"/>
|
||||
</group>
|
||||
<group col="8" colspan="6" id="buttons">
|
||||
<label name="state"/>
|
||||
<field name="state"/>
|
||||
<group col="4" colspan="6" id="buttons">
|
||||
<button name="offer" string="Offer"
|
||||
icon="tryton-clear"/>
|
||||
<button name="cancel" string="Cancel"
|
||||
icon="tryton-cancel"/>
|
||||
<button name="not_confirm" string="Not Confirm"
|
||||
icon="tryton-forward"/>
|
||||
<button name="not_show" string="Not Show"
|
||||
icon="tryton-cancel"/>
|
||||
<button name="confirm" string="Confirm"
|
||||
icon="tryton-forward"/>
|
||||
<!-- <label name="satisfaction"/>
|
||||
|
|
Loading…
Reference in New Issue