From c25314760caeeb233b7a78b5fe7f779d47771b49 Mon Sep 17 00:00:00 2001 From: Oscar Date: Sat, 9 Oct 2021 12:00:26 -0500 Subject: [PATCH] Refactory 06 --- __init__.py | 47 ++- booking.fodt | Bin 22840 -> 21417 bytes booking.py | 745 +---------------------------------- operation.fodt => folio.fodt | Bin operation.py => folio.py | 738 +++++++++++++++++++++++++++++++++- operation.xml => folio.xml | 24 +- tryton.cfg | 2 +- view/board_folio_tree.xml | 9 +- view/booking_form.xml | 26 +- 9 files changed, 780 insertions(+), 811 deletions(-) rename operation.fodt => folio.fodt (100%) rename operation.py => folio.py (66%) rename operation.xml => folio.xml (97%) diff --git a/__init__.py b/__init__.py index 3b0bc46..9d1cd29 100644 --- a/__init__.py +++ b/__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, diff --git a/booking.fodt b/booking.fodt index 0d81d5191bed41973d9585cbbbb277bc91feecd2..da9a3cab3c700f2dcc113102801c970db65fad30 100644 GIT binary patch delta 17978 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 delta 19481 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 diff --git a/booking.py b/booking.py index a684eca..308b345 100644 --- a/booking.py +++ b/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 diff --git a/operation.fodt b/folio.fodt similarity index 100% rename from operation.fodt rename to folio.fodt diff --git a/operation.py b/folio.py similarity index 66% rename from operation.py rename to folio.py index 48acb06..1f1dd5c 100644 --- a/operation.py +++ b/folio.py @@ -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): diff --git a/operation.xml b/folio.xml similarity index 97% rename from operation.xml rename to folio.xml index 34ed84d..43628cc 100644 --- a/operation.xml +++ b/folio.xml @@ -15,6 +15,18 @@ this repository contains the full copyright notices and license terms. --> folio_charge_form + + Folio Statement + hotel.folio + hotel.folio + hotel/folio.fodt + + + form_print + hotel.folio,-1 + + + - - Statement Operation - hotel.operation - hotel.operation - hotel/operation.fodt - - - form_print - hotel.operation,-1 - - - Operation Check Out hotel.operation.check_out diff --git a/tryton.cfg b/tryton.cfg index 7a46426..88abe3d 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -21,7 +21,7 @@ xml: data_category.xml location.xml booking.xml - operation.xml + folio.xml housekeeping.xml company.xml city.xml diff --git a/view/board_folio_tree.xml b/view/board_folio_tree.xml index 5ba87f0..1b41370 100644 --- a/view/board_folio_tree.xml +++ b/view/board_folio_tree.xml @@ -2,17 +2,16 @@ + + - - - - - + + diff --git a/view/booking_form.xml b/view/booking_form.xml index 9262737..09d302a 100644 --- a/view/booking_form.xml +++ b/view/booking_form.xml @@ -20,12 +20,12 @@ this repository contains the full copyright notices and license terms. -->