# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

from decimal import Decimal
from datetime import timedelta, datetime, date
from collections import OrderedDict
import calendar

from trytond.model import ModelView, Workflow, ModelSQL, fields
from trytond.pyson import Eval, Bool
from trytond.pool import Pool
from trytond.report import Report
from trytond.wizard import (
    Wizard, StateView, StateAction, Button, StateTransition, StateReport
)
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
from .constants import (
    REGISTRATION_STATE, COMPLEMENTARY, INVOICE_STATES, TYPE_DOCUMENT, PLAN,
)
from .room import ROOM_STATUS


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'])
}

STATES_MNT = {'readonly': Eval('state') != 'draft'}
_ZERO = Decimal('0')


class Folio(ModelSQL, ModelView):
    'Folio'
    __name__ = 'hotel.folio'
    STATES = {
        'required': Eval('registration_state') == 'check_in',
        'readonly': Eval('registration_state') == 'check_in',
    }
    _CHECKOUT = {
        'readonly': Eval('registration_state').in_(
            ['check_out', 'no_show', 'cancelled']
        ),
        'required': Eval('registration_state') == 'check_in',
    }
    booking = fields.Many2One('hotel.booking', 'Booking', ondelete='CASCADE')
    reference = fields.Char('Reference', states={
        'readonly': Eval('registration_state').in_(['check_in', 'check_out'])
    })
    registration_card = fields.Char('Registration Card', readonly=True,
        help="Unique sequence for card guest registration.")
    room = fields.Many2One('hotel.room', 'Room', states=_CHECKOUT)
    arrival_date = fields.Date('Arrival Date', required=True,
        states={
            'readonly': Eval('registration_state').in_(['check_out', 'check_in'])
        })
    departure_date = fields.Date('Departure Date', required=True,
        states={
            'readonly': Eval('registration_state').in_(['check_out', 'check_in'])
        })
    product = fields.Many2One('product.product', 'Product',
        domain=[
            ('template.type', '=', 'service'),
            ('template.kind', '=', 'accommodation'),
        ], required=True, states={'readonly': True})
    unit_price = fields.Numeric('Unit Price', digits=(16, 2),
        states={
            'required': Bool(Eval('product')),
            'readonly': Eval('registration_state') == 'check_out',
        })
    unit_price_w_tax = fields.Function(fields.Numeric('Unit Price w Tax',
        digits=(16, 2)), 'get_unit_price_w_tax')
    uom = fields.Many2One('product.uom', 'UOM', readonly=True)
    main_guest = fields.Many2One('party.party', 'Main Guest',
        states={'readonly': True})
    contact = fields.Char('Contact', states={
        'readonly': Eval('registration_state').in_(['check_in', 'check_out'])}
    )
    party_holder = fields.Function(fields.Many2One('party.party',
        'Party Holder'), 'get_party_holder')
    num_children = fields.Function(fields.Integer('No. Children'),
        'get_num_children')
    nights_quantity = fields.Integer('Nights', states={'readonly': True},
        depends=['arrival_date', 'departure_date'])
    host_quantity = fields.Integer('Host', states=STATES_CHECKIN)
    estimated_arrival_time = fields.Time('Estimated Arrival Time', states={
            'readonly': Eval('registration_state').in_(
                ['check_out', 'no_show', 'cancelled']
            )})
    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')
    commission_amount = fields.Numeric('Commission Amount', digits=(16, 2),
        depends=['nights_quantity', 'unit_price'], states={'readonly': True}
    )
    guests = fields.One2Many('hotel.folio.guest', 'folio', 'Guests',
        states={'readonly': Eval('registration_state').in_(['check_out'])})
    storage = fields.Many2One('stock.location', 'Storage',
        domain=[('type', '=', 'storage')], states={
            'readonly': Eval('registration_state') != 'check_in',
            },
        depends=['registration_state'])
    registration_state = fields.Selection(REGISTRATION_STATE,
        'Registration State', readonly=True)
    registration_state_string = registration_state.translated(
        'registration_state')
    charges = fields.One2Many('hotel.folio.charge', 'folio', 'Charges',
        states={
            'readonly': ~Eval('registration_state').in_(
                ['pending', 'check_in']),
        })
    party = fields.Many2One('party.party', 'Party to Bill',
        states={
            'readonly': Eval('registration_state').in_(['check_out']),
        })
    invoice_line = fields.Many2One('account.invoice.line', 'Invoice Line')
    to_invoice = fields.Boolean('To Invoice', states={
            'invisible': Bool(Eval('invoice_line')),
        }, depends=['invoice_line'],
        help='Mark this checkbox if you want to invoice this item')
    invoice = fields.Function(fields.Many2One('account.invoice', 'Invoice',
        depends=['invoice_line']), 'get_invoice')
    invoice_state = fields.Function(fields.Selection(
        INVOICE_STATES, 'Invoice State'), 'get_invoice_state')
    invoice_state_string = invoice_state.translated('invoice_state')
    type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
        states={
            'invisible': ~Bool(Eval('complementary')),
            'required': Bool(Eval('complementary')),
    })
    breakfast_included = fields.Boolean('Breakfast Included', states={
        'readonly': Eval('registration_state') != 'check_in',
    })
    room_amount = fields.Function(fields.Numeric('Room Amount',
        digits=(16, 2), depends=['nights_quantity', 'unit_price']
        ), 'on_change_with_room_amount')
    channel = fields.Function(fields.Many2One('hotel.channel', 'Channel'),
        'get_channel')
    num_children = fields.Function(fields.Integer('Num. Children'),
        'get_num_guests')
    num_adults = fields.Function(fields.Integer('Num. Adults'),
        'get_num_guests')
    stock_moves = fields.Function(fields.One2Many('stock.move', 'origin',
        'Moves'), 'get_stock_moves')
    vehicle_plate = fields.Char('Vehicle Plate')
    room_status = fields.Function(fields.Selection(ROOM_STATUS,
        'Room Status'), 'get_room_status')
    payment_status = fields.Selection([
            (None, ''),
            ('pending', 'Pending'),
            ('paid', 'Paid'),
        ], 'Acco. Payment Status', readonly=True)
    payments = fields.One2Many('account.statement.line', 'source',
        'Payments', readonly=True)
    occupancy = fields.One2Many('hotel.folio.occupancy', 'folio', 'Occupancy',
        readonly=True)
    pending_accommodation = fields.Function(fields.Numeric('Pending to Pay',
        digits=(16, 2)), 'get_pending_to_pay')
    pending_charges = fields.Function(fields.Numeric('Pending to Pay',
        digits=(16, 2)), 'get_pending_to_pay')
    pending_total = fields.Function(fields.Numeric('Pending to Pay',
        digits=(16, 2)), 'get_pending_to_pay')
    total_advances = fields.Function(fields.Numeric('Total Advances',
        digits=(16, 2)), 'get_total_advances')
    pax = fields.Integer('PAX', states={
        'readonly': Eval('registration_state').in_(
            ['check_out', 'no_show', 'cancelled']
        )}, help="Number of persons in house setted manually.")
    invoices = fields.Function(fields.Many2Many('account.invoice',
        None, None, 'Invoices'), 'get_invoices')
    charges_blocked = fields.Boolean('Charges Blocked')
    taxes_exception = fields.Boolean('Taxes Exception')
    plan = fields.Function(fields.Selection(PLAN, 'Commercial Plan'),
        'get_plan')
    group = fields.Function(fields.Boolean('Group'),
        'get_group')

    @classmethod
    def __setup__(cls):
        super(Folio, cls).__setup__()
        cls._buttons.update({
            'check_in': {
                'invisible': Eval('registration_state').in_(
                    ['check_in', 'check_out']
                ),
            },
            'check_out': {
                'invisible': Eval('registration_state') != 'check_in',
            },
            'bill': {
                'invisible': Eval('registration_state') != 'check_out',
            },
            'load_accommodation': {
                'invisible': ~Eval('registration_state'),
            },
            'do_payment': {
                'invisible': Eval('registration_state').in_([
                    'no_show', 'cancelled', 'pending'
                ]),
            }
        })

    @staticmethod
    def default_to_invoice():
        return True

    @staticmethod
    def default_payment_status():
        return 'pending'

    @staticmethod
    def default_registration_state():
        return 'pending'

    @staticmethod
    def default_pax():
        return 1

    @classmethod
    def delete(cls, records):
        for folio in records:
            for occ in folio.occupancy:
                if occ.charge:
                    raise UserError(gettext('hotel.msg_folio_with_charges'))
        super().delete(records)

    @classmethod
    @ModelView.button_action('hotel.wizard_statement_payment_form')
    def do_payment(cls, records):
        pass

    # @classmethod
    # def _get_origin(cls):
    #     'Return list of Model names for origin Reference'
    #     return []

    # @classmethod
    # def get_origin(cls):
    #     Model = Pool().get('ir.model')
    #     get_name = Model.get_name
    #     models = cls._get_origin()
    #     return [(None, '')] + [(m, get_name(m)) for m in models]

    def get_group(self, name=None):
        if self.booking:
            return self.booking.group

    def get_total_advances(self, name=None):
        res = []
        for pay in self.payments:
            res.append(pay.amount)
        return sum(res)

    def pending_occupancy(self, name=None):
        res = []
        for occ in self.occupancy:
            if occ.charge:
                continue
            res.append({
                'name': self.product.template.name,
                'date': occ.occupancy_date,
                'quantity': 1,
                'unit_price': occ.unit_price,
                'amount': occ.unit_price,
            })
        return res

    def get_plan(self, name=None):
        if self.booking:
            return self.booking.plan

    def get_pending_to_pay(self, name=None):
        res = 0
        if name == 'pending_accommodation':
            # if self.payment_status != 'paid':
            res = self.get_total_accommodation()
        if name == 'pending_charges':
            total_charges = self.get_total_products()
            charge_paid = 0
            for charge in self.charges:
                if charge.status == 'paid':
                    charge_paid += charge.amount
            res = total_charges - charge_paid
        if name == 'pending_total':
            res = self.total_amount - self.total_advances
        res = round(res, 2)
        return res

    def get_num_guests(self, name):
        res = []

        for guest in self.guests:
            if name == 'num_children' and guest.type_guest == 'child':
                res.append(1)
            elif name == 'num_adults' and guest.type_guest == 'adult':
                res.append(1)
        return sum(res)

    def get_party_holder(self, name=None):
        if self.booking.party:
            return self.booking.party.id

    def get_rec_name(self, name=None):
        if self.room:
            name = f'{self.room.code}:'
        if self.main_guest:
            name = name + self.main_guest.name
        return name

    @classmethod
    def search_rec_name(cls, name, clause):
        _, operator, value = clause
        if operator.startswith('!') or operator.startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        domain = [
            bool_op,
            ('main_guest', operator, value),
            ('registration_card', operator, value),
            ('product.name', operator, value),
            ('party.name', operator, value),
            ('booking.party.name', operator, value),
            ('reference', operator, value),
        ]
        return domain

    def get_invoices(self, name=None):
        res = []
        for charge in self.charges:
            if charge.invoice_line:
                res.append(charge.invoice_line.invoice.id)
        return list(set(res))

    @classmethod
    def create(cls, values):
        folios = super(Folio, cls).create(values)
        for folio in folios:
            folio.update_nights(folio.arrival_date, folio.departure_date)
            folio.update_commission()
            folio.save()

    @classmethod
    @ModelView.button
    def check_in(cls, records):
        for record in records:
            record.validate_check_in()
            if record.booking.state == 'offer':
                raise UserError(gettext('hotel.msg_missing_confirm_booking'))
            if record.main_guest is None:
                raise UserError(gettext('hotel.msg_missing_main_guest'))
            if record.room is None:
                raise UserError(gettext('hotel.msg_missing_select_room'))
            record.set_registration_number()
            record.check_room()
            record.add_occupancy('check_in')
            cls.update_room(record, 'check_in')
        cls.write(records, {'registration_state': 'check_in'})

    @classmethod
    @ModelView.button
    def load_accommodation(cls, records):
        for folio in records:
            folio.add_charge_occupancy(ctx='allow')

    def check_room(self):
        if self.room.state != 'clean':
            raise UserError(
                gettext('hotel.msg_room_no_clean', room=self.room.name)
            )

    def get_room_status(self, name):
        if self.room:
            return self.room.state

    def validate_check_in(self):
        if self.arrival_date > date.today():
            raise UserError(gettext('hotel.msg_cannot_check_in_future'))

    def try_reconcile(self):
        # Try mark as paid all charges of the folio if there is
        # not amount pending to pay
        if self.booking.pending_to_pay == 0:
            for charge in self.charges:
                charge.status = 'paid'
                charge.save()

    def validate_check_out(self):
        bk = self.booking
        for occ in self.occupancy:
            if not occ.charge:
                raise UserError(
                    gettext('hotel.msg_the_accommodation_not_charged'))

        def _check_accommodation(folio):
            if folio.booking.complementary:
                return
            for charge in folio.charges:
                if charge.status != 'paid':
                    raise UserError(
                        gettext('hotel.msg_accommodation_not_paid'))

        def _check_charges(folio):
            complementary = folio.booking.complementary
            for charge in folio.charges:
                if complementary and charge.product.template.kind == 'accommodation':
                    return
                if charge.status != 'paid':
                    raise UserError(gettext('hotel.msg_charges_not_paid'))

        if bk.responsible_payment == 'guest':
            _check_charges(self)
            _check_accommodation(self)
        elif bk.responsible_payment == 'holder':
            if bk.payment_term and bk.payment_term.payment_type == 2:
                return

            if self.main_guest == bk.party:
                for folio in bk.lines:
                    if bk.pending_to_pay == 0:
                        for charge in folio.charges:
                            charge.status = 'paid'
                            charge.save()
                        continue
                    _check_charges(folio)
                    _check_accommodation(folio)
            else:
                _check_charges(self)
                _check_accommodation(self)
        else:
            _check_charges(self)

    @classmethod
    @ModelView.button
    def check_out(cls, records):
        for record in records:
            record.try_reconcile()
            record.validate_check_out()
            cls.write([record], {'registration_state': 'check_out'})
            cls.update_room(record, 'check_out')

    @classmethod
    @ModelView.button_action('hotel.wizard_bill_booking')
    def bill(cls, records):
        pass

    @classmethod
    @ModelView.button_action('hotel.wizard_booking_advance_voucher')
    def pay(cls, records):
        pass

    @classmethod
    def update_room(cls, folio, status):
        pool = Pool()
        Configuration = pool.get('hotel.configuration')
        config = Configuration.get_configuration()
        cleaning_type_id = None
        room = folio.room
        if status in 'check_in' and config.cleaning_occupied:
            cleaning_type_id = config.cleaning_occupied.id
            room.last_check_in = datetime.now()
            room.last_check_out = None
            room.check_in_today = datetime.now().time()
        elif status in 'check_out' and config.cleaning_check_out:
            cleaning_type_id = config.cleaning_check_out.id
            room.last_check_out = datetime.now()
            room.last_check_in = None
            room.check_out_today = datetime.now().time()

        room.state = 'dirty'
        if cleaning_type_id:
            room.cleaning_type = cleaning_type_id
        room.save()

    def get_invoice_state(self, name=None):
        if self.invoice_line:
            return self.invoice_line.invoice.state

    def get_channel(self, name=None):
        if self.booking and self.booking.channel:
            return self.booking.channel.id

    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()

    def get_stock_moves(self, name=None):
        moves = []
        for charge in self.charges:
            if charge.move:
                moves.append(charge.move.id)
        return moves

    def get_unit_price_w_tax(self, name=None):
        Tax = Pool().get('account.tax')
        res = self.unit_price or 0
        if self.unit_price and not self.taxes_exception:
            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

    @fields.depends('unit_price', 'nights_quantity')
    def on_change_with_room_amount(self, name=None):
        res = 0
        if self.unit_price and self.nights_quantity:
            res = self.unit_price * self.nights_quantity
        return Decimal(round(res, Folio.total_amount.digits[1]))

    @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

    @staticmethod
    def default_breakfast_included():
        return True

    @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:
            return self.invoice_line.invoice.id

    def get_room_info(self):
        description = ' \n'.join([
            self.product.rec_name,
            'Huesped Principal: ' + self.main_guest.name if self.main_guest else '',
            'Habitacion: ' + self.room.name,
            'Llegada: ' + str(self.arrival_date),
            'Salida: ' + str(self.departure_date),
        ])
        return description

    @fields.depends('product', 'unit_price', 'uom', 'booking',
        'nights_quantity', 'commission_amount', 'occupancy')
    def on_change_product(self):
        Product = Pool().get('product.product')
        unit_price = None
        if self.product and self.booking:
            self.uom = self.product.default_uom.id
            with Transaction().set_context(
                self.booking.get_context_price(self.product)):
                unit_price = Product.get_sale_price([self.product],
                        self.nights_quantity or 0)[self.product.id]
            # self.taxes = [tax.id for tax in self.product.customer_taxes_used]
        else:
            unit_price = self.product.list_price
        if unit_price and self.booking:
            unit_price = self.booking.currency.round(self.unit_price)
            self.update_occupancy(price=unit_price)
        self.update_commission()

    def update_commission(self):
        if self.nights_quantity and self.unit_price and self.booking:
            if not self.booking.channel:
                return
            amount = self.on_change_with_room_amount()
            self.commission_amount = self.booking.channel.compute(amount)

    @fields.depends('arrival_date', 'departure_date', 'unit_price', 'booking')
    def on_change_arrival_date(self):
        if self.arrival_date and self.departure_date:
            self.update_nights(self.arrival_date, self.departure_date)
            self.update_commission()

    @fields.depends('arrival_date', 'departure_date', 'unit_price', 'booking')
    def on_change_departure_date(self):
        if self.arrival_date and self.departure_date:
            self.update_nights(self.arrival_date, self.departure_date)
            self.update_commission()

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

    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.
        """
        Maintenance = Pool().get('hotel.maintenance')
        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]
        print('rooms_not_available_ids', rooms_not_available_ids)
        dom = ['AND', ['OR',
            [
                ('start_date', '>=', start_date),
                ('start_date', '<=', end_date),
            ], [
                ('end_date', '<=', end_date),
                ('end_date', '>=', start_date),
            ], [
                ('start_date', '<=', start_date),
                ('end_date', '>=', end_date),
            ]],
            ('state', 'in', ('in_progress', 'finished')),
        ]
        maintenance = Maintenance.search(dom)
        rooms_not_available_ids.extend(mnt.room.id for mnt in maintenance)

        rooms_available_ids = set(rooms_ids) - set(rooms_not_available_ids)
        print('rooms_available_ids >> ', rooms_available_ids)
        return list(rooms_available_ids)

    def update_nights(self, arrival_date, departure_date):
        self.nights_quantity = (departure_date - arrival_date).days

    def add_charge_occupancy(self, ctx=None):
        pool = Pool()
        Charge = pool.get('hotel.folio.charge')
        Date = pool.get('ir.date')
        # Add transaction context for hotel price list for current day
        unit_price = Decimal(round(self.unit_price, 2))
        space = self.booking.space
        today = Date.today()
        for occ in self.occupancy:
            if occ.charge:
                continue
            if not ctx and occ.occupancy_date > today:
                continue
            analytic_account = space.analytic_account if space else None
            description = f"{self.room.name} | {self.registration_card}"
            rec = {
                'folio': self.id,
                'product': self.product.id,
                'unit_price': unit_price,
                'date_service': occ.occupancy_date,
                'quantity': 1,
                'status': 'pending',
                'state': '',
                'analytic_account': analytic_account,
                'kind': 'accommodation',
                'description': description,
                'taxes': [],
            }
            if not self.taxes_exception:
                rec['taxes'] = [('add', [
                        tax.id for tax in self.product.customer_taxes_used
                    ])
                ]
            charge, = Charge.create([rec])
            occ.charge = charge.id
            occ.save()

    def add_occupancy(self, next=None):
        FolioOccupancy = Pool().get('hotel.folio.occupancy')
        if self.occupancy:
            return

        res = []
        delta = (self.departure_date - self.arrival_date).days
        current = [oc.occupancy_date for oc in self.occupancy]
        unit_price = round(self.unit_price, 2)
        for day in range(delta):
            _date = self.arrival_date + timedelta(days=day)
            if _date in current:
                continue
            res.append({
                'folio': self.id,
                'occupancy_date': _date,
                'unit_price': unit_price,
                'state': 'draft',
            })
        FolioOccupancy.create(res)

    @classmethod
    def get_folios(cls, args):
        pool = Pool()
        Maintenance = pool.get('hotel.maintenance')
        Room = pool.get('hotel.room')
        start = datetime.strptime(args['start_date'], "%Y-%m-%d")
        start_date = (start + timedelta(days=-4)).date()
        end_date = (start + timedelta(days=30)).date()
        dom = ['OR', [
                ('arrival_date', '>=', start_date),
                ('arrival_date', '<=', end_date)
            ], [
                ('departure_date', '>=', start_date),
                ('departure_date', '<=', end_date)
            ]]

        fields_names = [
          "room.name",
          "arrival_date",
          "departure_date",
          "main_guest.name",
          "product.rec_name",
          "unit_price_w_tax",
          "pending_total",
          "plan",
          "guests",
          "charges",
          "booking.contact",
          "booking.party.name",
          "booking.number",
          "booking.media",
          "booking.channel.rec_name",
          "booking.channel.code",
          "registration_state",
          "registration_card",
          "nights_quantity",
          "notes",
          "vehicle_plate",
        ]
        rooms = Room.search_read([], fields_names=['code', 'name'])
        folios = cls.search_read(dom, fields_names=fields_names)
        occ_rate = cls.get_occupation_rate(start_date, end_date)
        dom_maint = ['OR', [
                ('start_date', '>=', start_date),
                ('start_date', '<=', end_date),
                ('state', 'in', ('in_progress', 'finished', 'confirmed')),
                ('start_date', '!=', None),
                ('end_date', '!=', None),
            ], [
                ('end_date', '>=', start_date),
                ('end_date', '<=', end_date),
                ('state', 'in', ('in_progress', 'finished', 'confirmed')),
                ('start_date', '!=', None),
                ('end_date', '!=', None),
            ],
        ]
        fields_mnt = [
            'start_date', 'end_date', 'room', 'room.name', 'issue',
            'create_uid.name'
        ]
        mnts = Maintenance.search_read(dom_maint, fields_names=fields_mnt)
        res = {
            'rooms': rooms,
            'folios': folios,
            'mnts': mnts,
            'occ_rate': occ_rate
        }
        return res

    def get_total_accommodation(self):
        res = []
        if self.booking.channel and \
                self.booking.channel_payment_method == 'ota_collect':
            return 0
        if self.registration_state in ['check_in', 'check_out']:
            for charge in self.charges:
                if charge.kind == 'accommodation':
                    taxed_amount = charge.taxed_amount or 0
                    res.append(taxed_amount)
            for occ in self.occupancy:
                if not occ.charge:
                    unit_price_w_tax = occ.unit_price_w_tax or 0
                    res.append(unit_price_w_tax)
        else:
            if self.nights_quantity and self.unit_price_w_tax:
                res = [self.nights_quantity * self.unit_price_w_tax]
        res = round(sum(res), 2)
        return res

    @classmethod
    def get_occupation_rate(cls, start_date, end_date):
        Room = Pool().get('hotel.room')
        alldays = OrderedDict()
        rooms = Room.search_read([], fields_names=['code', 'name'])
        n_rooms = len(rooms)
        delta = (end_date - start_date).days
        _date = start_date
        for nd in range(delta):
            alldays[_date] = {
                'occ_rate': 0
            }
            _date = _date + timedelta(days=1)

        dom = [
            ['OR', [
                ('arrival_date', '<=', start_date),
                ('departure_date', '>=', end_date),
            ], [
                ('arrival_date', '>=', start_date),
                ('departure_date', '<=', end_date),
            ], [
                ('arrival_date', '<=', end_date),
                ('departure_date', '>=', end_date),
            ], [
                ('arrival_date', '<=', start_date),
                ('departure_date', '>=', end_date),
            ]],
            ('booking.state', 'not in', ['no_show', 'cancelled'])
        ]

        nfields = ['arrival_date', 'departure_date', 'nights_quantity']
        folios = cls.search_read(dom, fields_names=nfields)
        bag_data = []
        extend = bag_data.extend
        for folio in folios:
            extend([folio['arrival_date'] + timedelta(days=n)
                for n in range(folio['nights_quantity'])
            ])

        for _day, dvalue in alldays.items():
            dvalue['occ_rate'] = int(bag_data.count(_day) * 100 / n_rooms)
        return alldays

    @classmethod
    def set_offset_commission_move(cls, folios_lines, bk):
        """
        Create Offset Move (Asiento de cruce) in order to cross the advance
        anticipated to the  channel (supplier) against the account
        receivable in the sale invoice
        """
        pool = Pool()
        Move = pool.get('account.move')
        Period = pool.get('account.period')
        MoveLine = pool.get('account.move.line')
        Config = pool.get('hotel.configuration')
        config = Config.get_configuration()

        period_id = Period.find(bk.company.id, date=date.today())
        description = bk.number
        if bk.ota_booking_code:
            description += ' | ' + bk.ota_booking_code

        move, = Move.create([{
            'journal': config.offset_journal.id,
            'period': period_id,
            'date': date.today(),
            # 'origin': str(self),
            'state': 'draft',
            'description': description,
        }])

        lines_ids = []
        party_channel = bk.channel.agent.party
        party = bk.party

        for folio, inv_line in folios_lines:
            if not folio.commission_amount:
                return

            move_line, = MoveLine.create([{
                'description': folio.registration_card,
                'account': party.account_receivable_used,
                'party': party.id,
                'debit': 0,
                'credit': folio.commission_amount,
                'move': move.id,
            }])
            lines_ids.append(move_line.id)
            move_line, = MoveLine.create([{
                'description': folio.registration_card,
                'account': party_channel.account_payable_used,
                'party': party.id,
                'debit': folio.commission_amount,
                'credit': 0,
                'move': move.id,
            }])
            inv_line.offset_move_line = move_line.id
            inv_line.save()

        Move.post([move])
        return lines_ids

    def get_total_products(self):
        res = []
        for charge in self.charges:
            if charge.kind == 'product':
                res.append(charge.amount)
        return sum(res)

    def get_total_amount(self, name):
        """
        The total amount of folio 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 = []
        res.append(self.get_total_accommodation())
        res.append(self.get_total_products())
        res = round(sum(res), Folio.total_amount.digits[1])
        return res

    @fields.depends('nights_quantity', 'unit_price', 'booking')
    def on_change_with_commission_amount(self):
        """
        Compute of commission amount for channel based on booking
        """
        res = Decimal(0)
        if self.nights_quantity and self.unit_price and self.booking and \
                self.booking.channel:
            amount = self.on_change_with_room_amount()
            res = self.booking.channel.compute(amount)
            return res

    def _create_occ(self, occ_date, unit_price):
        return {
            'occupancy_date': occ_date,
            'folio': self.id,
            'unit_price': unit_price,
            'state': 'draft',
        }

    @classmethod
    def write(cls, records, *args):
        super(Folio, cls).write(records, *args)
        for values in args:
            if not isinstance(values, dict):
                continue
            arr_date = values.get('arrival_date', None)
            dep_date = values.get('departure_date', None)
            unit_price = values.get('unit_price', None)
            if isinstance(arr_date, str):
                arr_date = datetime.strptime(arr_date, '%Y-%m-%d').date()
            if isinstance(dep_date, str):
                dep_date = datetime.strptime(dep_date, '%Y-%m-%d').date()
            if arr_date or dep_date or unit_price:
                for folio in records:
                    folio.update_occupancy(
                        arr_date=arr_date,
                        dep_date=dep_date,
                        price=unit_price)

    def update_occupancy(self, arr_date=None, dep_date=None, price=None):
        Occupancy = Pool().get('hotel.folio.occupancy')
        to_delete = []
        to_create = []
        price = price or self.unit_price
        arr_date = arr_date or self.arrival_date
        dep_date = dep_date or self.departure_date
        if dep_date or arr_date:
            self.update_nights(arr_date, dep_date)
            delta = (dep_date - arr_date).days
            current_dates = {occ.occupancy_date: occ for occ in self.occupancy}
            new_dates = []
            for day in range(delta):
                new_dates.append(arr_date + timedelta(day))

            for new_date in new_dates:
                if new_date not in current_dates.keys():
                    to_create.append(self._create_occ(new_date, price))

            for curr_date, occ in current_dates.items():
                if curr_date not in new_dates:
                    if not occ.charge:
                        to_delete.append(occ)
                        continue
                    raise UserError(gettext(
                        'hotel.msg_can_not_change_departure'))

            if to_create:
                self.occupancy
                Occupancy.create(to_create)
            if to_delete:
                Occupancy.delete(to_delete)

        if price:
            self.unit_price = price
            for occ in self.occupancy:
                occ.unit_price = price
                occ.save()
                if occ.charge:
                    occ.charge.unit_price = price
                    occ.charge.save()

    @classmethod
    def _get_check_rooms(cls, kind):
        # Dash Report
        # kind : arrival or departure
        field_target = kind + '_date'
        folios = cls.search_read([
            (field_target, '=', date.today())
        ], fields_names=['id'])
        return len(folios)

    @classmethod
    def report_check_in_today(cls, args):
        # Dash Report
        value = cls._get_check_rooms('arrival')
        today = date.today()
        res = {
            'value': value,
            'header_meta': str(today),
        }
        return res

    @classmethod
    def report_check_out_today(cls, args):
        # Dash Report
        value = cls._get_check_rooms('departure')
        today = date.today()
        res = {
            'value': value,
            'header_meta': str(today),
        }
        return res

    @classmethod
    def _get_occupation(cls, today):
        dom = [
            ('arrival_date', '<=', today),
            ('departure_date', '>', today),
        ]
        folios = cls.search_read(dom, fields_names=['id', 'guests'])
        return folios

    @classmethod
    def report_occupation_by_room(cls, args):
        # Dash Report
        today = date.today()
        folios = cls._get_occupation(today)
        value = len(folios)
        res = {
            'value': value,
            'header_meta': str(today),
        }
        return res

    @classmethod
    def report_number_guests(cls, args):
        # Dash Report
        today = date.today()
        folios = cls._get_occupation(today)
        value = 0
        for folio in folios:
            value += len(folio['guests'])

        res = {
            'value': value,
            'header_meta': str(today),
        }
        return res

    @classmethod
    def _get_range_dates(cls, folio, mode=None):
        start_date = folio['arrival_date']
        res = []
        _append = res.append
        for dt in range(folio['nights_quantity']):
            _date = start_date + timedelta(days=dt)
            if mode == 'string':
                _date = str(_date)
            _append(_date)
        return set(res)

    @classmethod
    def report_month_occupancy_rate(cls, args):
        pool = Pool()
        Room = pool.get('hotel.room')
        Folio = pool.get('hotel.folio')
        today = date.today()
        first, last = calendar.monthrange(today.year, today.month)
        labels = []
        alldays = OrderedDict()
        dates_of_month = []
        today = date.today()
        for nd in range(last):
            _day = str(date(today.year, today.month, nd + 1))
            labels.append(str(nd + 1))
            dates_of_month.append(_day)
            alldays[_day] = []

        date_start = date(today.year, today.month, 1)
        date_end = date(today.year, today.month, last)

        dom = [
            ['OR', [
                ('arrival_date', '<=', date_start),
                ('departure_date', '>=', date_start),
            ], [
                ('arrival_date', '>=', date_start),
                ('departure_date', '<=', date_end),
            ], [
                ('arrival_date', '<=', date_end),
                ('departure_date', '>=', date_end),
            ], [
                ('arrival_date', '<=', date_start),
                ('departure_date', '>=', date_end),
            ]],
            ('booking.state', 'not in', ['no_show', 'cancelled'])
        ]

        folios = Folio.search_read(dom, fields_names=[
            'arrival_date', 'nights_quantity'])

        for folio in folios:
            folio_dates = cls._get_range_dates(folio, mode='string')
            dates_target = set(dates_of_month) & folio_dates
            for dt in dates_target:
                alldays[dt].append(1)

        rooms = len(Room.search_read([]))
        values = [sum(fo_num) * 100 / rooms for _, fo_num in alldays.items()]
        month_name = today.strftime("%b %Y")
        label_item = '% Occupancy Rate'
        res = {
            'label_item': label_item,
            'labels': labels,
            'values': values,
            'header_meta': month_name,
            'min_value': 1,
            'max_value': 100,
        }
        return res

    @classmethod
    def report_current_occupancy_rate(cls, args):
        pool = Pool()
        Room = pool.get('hotel.room')
        rooms = Room.search_read([
            ('active', '=', True),
        ])
        today = date.today()
        dates_arrival = []
        dates_departure = []
        first, last = calendar.monthrange(today.year, today.month)
        day_one = date(today.year, today.month, 1)
        for md in range(last):
            dates_arrival.append(day_one + timedelta(days=(md+1)))
            dates_departure.append(day_one + timedelta(days=(md+2)))

        rooms_available = len(rooms) * last
        dom = ['OR', [
                ('arrival_date', 'in', dates_arrival),
                ('booking.state', 'not in', ['no_show', 'cancelled']),
            ], [
                ('departure_date', 'in', dates_departure),
                ('booking.state', 'not in', ['no_show', 'cancelled']),
            ]
        ]
        folios = cls.search_read(dom, fields_names=[
            'guests', 'arrival_date', 'nights_quantity'])
        occupation = []
        month_dates = set(dates_arrival)
        for folio in folios:
            _dates = cls._get_range_dates(folio)
            occupation.append(len(_dates & month_dates))

        value = (sum(occupation) * 100) / rooms_available
        month_name = today.strftime("%b %Y")
        value = f'{round(value, 2)}%'
        res = {
            'value': value,
            'header_meta': month_name,
        }
        return res


class FolioGuest(ModelSQL, ModelView):
    'Folio Guest'
    __name__ = 'hotel.folio.guest'
    REQ_MAIN_GUEST = {
        'required': Eval('main_guest', False)
    }
    folio = fields.Many2One('hotel.folio', 'Folio')
    main_guest = fields.Boolean('Main Guest')
    type_guest = fields.Selection([
            ('adult', 'Adult'),
            ('child', 'Child'),
        ], 'Type Guest', required=True)
    type_guest_string = type_guest.translated('type_guest')
    nationality = fields.Many2One('country.country', 'Nationality')
    origin_country = fields.Many2One('country.country', 'Origin Country')
    target_country = fields.Many2One('country.country', 'Target Country')
    # New fields for speed reason
    type_document = fields.Selection(TYPE_DOCUMENT, 'Document Type',
        required=True)
    type_document_string = type_document.translated('type_document')
    id_number = fields.Char('Id Number', required=True)
    name = fields.Char('Name', required=True)
    mobile = fields.Char('Mobile', states=REQ_MAIN_GUEST)
    email = fields.Char('Email', states=REQ_MAIN_GUEST)
    address = fields.Char('Address', states=REQ_MAIN_GUEST)
    profession = fields.Char('Profession')
    birthday = fields.Date('Birthday')
    visa_date = fields.Date('Visa Date')
    visa_category = fields.Char('Visa Category')
    visa_number = fields.Char('Visa Number')
    sex = fields.Selection([
        ('female', 'Female'),
        ('male', 'Male'),
        ('', ''),
        ], 'Sex', required=True)
    sex_string = sex.translated('sex')
    first_name = fields.Char('First Name')
    second_name = fields.Char('Second Name')
    first_family_name = fields.Char('First Family Name')
    second_family_name = fields.Char('Second Family Name')
    notes = fields.Text('Notes')
    country = fields.Many2One('party.country_code', 'Country',
        states=REQ_MAIN_GUEST)
    subdivision = fields.Many2One('party.department_code', 'Subdivision')
    city = fields.Many2One('party.city_code', 'City',
        domain=[('department', '=', Eval('subdivision'))])
    tags = fields.Many2Many('hotel.tag.guest', 'guest', 'tag', 'Tags')

    @classmethod
    def search_rec_name(cls, name, clause):
        _, operator, value = clause
        if operator.startswith('!') or operator.startswith('not '):
            bool_op = 'AND'
        else:
            bool_op = 'OR'
        domain = [
            bool_op,
            ('id_number', operator, value),
            ('name', operator, value),
            ('mobile', operator, value),
        ]
        return domain

    @classmethod
    def get_splitted_name(cls, name):
        first_name = None
        first_family_name = None
        second_name = None
        second_family_name = None
        full_names = {
            'second_family_name': None,
            'first_family_name': None,
            'second_name': None,
            'first_name': None,
        }
        if name:
            names = name.split(' ')
            first_name = names[0]
            second_family_name = names[-1]
            if len(names) > 1:
                first_family_name = names[-2]
            if len(names) == 2:
                second_family_name = None
                first_family_name = names[1]
            elif len(names) == 5:
                second_name = names[1] + ' ' + names[2]
            elif len(names) == 4:
                second_name = names[1]

        full_names['second_family_name'] = second_family_name
        full_names['first_family_name'] = first_family_name
        full_names['second_name'] = second_name
        full_names['first_name'] = first_name
        return full_names

    @fields.depends('name', 'first_name', 'second_name',
        'first_family_name', 'second_family_name')
    def on_change_name(self):
        names = self.get_splitted_name(self.name)
        self.second_family_name = names['second_family_name']
        self.first_family_name = names['first_family_name']
        self.second_name = names['second_name']
        self.first_name = names['first_name']

    @staticmethod
    def default_type_guest():
        return 'adult'

    @staticmethod
    def default_nationality():
        Config = Pool().get('hotel.configuration')
        config = Config.get_configuration()
        if config.nationality:
            return config.nationality.id

    @staticmethod
    def default_country():
        Config = Pool().get('hotel.configuration')
        config = Config.get_configuration()
        if config and config.country:
            return config.country.id

    @staticmethod
    def default_type_document():
        return '13'

    @fields.depends('nationality', 'origin_country', 'target_country')
    def on_change_nationality(self):
        if self.nationality:
            self.target_country = self.nationality.id
            self.origin_country = self.nationality.id

    @fields.depends('id_number', 'name', 'sex', 'email', 'mobile',
        'visa_number', 'visa_date', 'birthday', 'nationality')
    def on_change_id_number(self):
        if self.id_number:
            Guest = Pool().get('hotel.folio.guest')
            Party = Pool().get('party.party')
            guest = None
            guests = Guest.search([
                ('id_number', '=', self.id_number)
            ])
            if guests:
                guest = guests[0]
            else:
                parties = Party.search([
                    ('id_number', '=', self.id_number)
                ])
                if parties:
                    guest = parties[0]
            if guest:
                self.name = guest.name.upper()
                self.sex = guest.sex
                self.type_document = guest.type_document
                self.email = guest.email and guest.email.lower()
                self.mobile = guest.mobile
                self.visa_number = guest.visa_number
                self.visa_date = guest.visa_date
                self.birthday = guest.birthday
                self.nationality = guest.nationality and guest.nationality.id
                self.profession = guest.profession
                self.first_name = guest.first_name
                self.second_name = guest.second_name
                self.first_family_name = guest.first_family_name
                self.second_family_name = guest.second_family_name
                self.subdivision = guest.subdivision and guest.subdivision.id
                self.city = guest.city and guest.city.id
                address = ''
                if hasattr(guest, 'address'):
                    address = guest.address and guest.address.upper()
                elif hasattr(guest, 'addresses') and guest.addresses:
                    address = guest.addresses[0].street
                self.address = address and address.upper()

    @classmethod
    def manage_party(cls, v):
        Party = Pool().get('party.party')
        is_main_guest = v.pop('main_guest', False)
        folio = v.pop('folio', None)
        for val in ('type_guest', 'origin_country', 'target_country', 'profession'):
            _ = v.pop(val, None)

        if is_main_guest:
            parties = Party.search([
                ('id_number', '=', v['id_number']),
            ])
            email = v.pop('email', '').lower()
            mobile = v.pop('mobile', '').upper()
            address = v.pop('address', '').upper()
            nationality = v.pop('nationality')
            country = v.pop('country', None)
            subdivision = v.pop('subdivision', None)
            city = v.pop('city', None)
            v['type_person'] = 'persona_natural'
            _ = v.pop('tags', None)
            if not parties:
                v['contact_mechanisms'] = [
                    ('create', [
                        {'type': 'email', 'value': email},
                        {'type': 'mobile', 'value': mobile},
                    ])
                ]
                v['addresses'] = [
                    ('create', [{
                        'street': address,
                        'country_code': country,
                        'department_code': subdivision,
                        'city_code': city,
                    }])
                ]
                # FIXME add tags to party and remove this lines
                party, = Party.create([v])
            else:
                Party.write(parties, v)
                party = parties[0]
                has_email = False
                has_mobile = False
                to_write = {}
                if party.addresses:
                    for addr in party.addresses:
                        addr.street = address
                        addr.country_code = country
                        addr.department_code = subdivision
                        addr.city_code = city
                        addr.save()
                else:
                    to_write['addresses'] = [('create', [{
                        'street': address,
                        'country_code': nationality,
                    }])]

                for cm in party.contact_mechanisms:
                    if email and cm.type == 'email':
                        cm.value = email
                        has_email = True
                    elif mobile and cm.type == 'mobile':
                        cm.value = mobile
                        has_mobile = True
                    cm.save()
                to_write_cm = []
                if not has_mobile and mobile:
                    to_write_cm.append({'type': 'mobile', 'value': mobile})
                if not has_email and email:
                    to_write_cm.append({'type': 'email', 'value': email})

                if to_write_cm:
                    to_write['contact_mechanisms'] = [('create', to_write_cm)]
                if to_write:
                    Party.write(parties, to_write)

            if folio:
                Folio = Pool().get('hotel.folio')
                folio = Folio(folio)
                folio.main_guest = party.id
                folio.save()
                booking = folio.booking
                if not booking.party:
                    booking.party = party.id
                    booking.save()

    @classmethod
    def create(cls, vlist):
        for values in vlist:
            first_name = values.get('first_name', None)
            if not first_name:
                names = cls.get_splitted_name(values['name'])
                values.update(names)
        res = super(FolioGuest, cls).create(vlist)

        for v in vlist:
            cls.manage_party(v)
        return res

    @classmethod
    def write(cls, records, values):
        super(FolioGuest, cls).write(records, values)
        fields = cls.fields_get()
        for val in ('id', 'rec_name', 'write_date', 'write_uid', 'create_date'):
            fields.pop(val)
        data = {}
        fields_list = fields.keys()
        rec = records[0]
        for field in fields_list:
            data[field] = getattr(rec, field)

        data.update(values)
        cls.manage_party(data)


class FolioCharge(Workflow, ModelSQL, ModelView):
    'Folio Charge'
    __name__ = 'hotel.folio.charge'
    folio = fields.Many2One('hotel.folio', 'Folio', required=True,
        ondelete='CASCADE')
    date_service = fields.Date('Date Service', 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')
    unit_price = fields.Numeric('Unit Price', digits=(16, 2),
        required=True)
    unit_price_w_tax = fields.Function(fields.Numeric('Unit Price w Tax',
        digits=(16, 2)), 'get_unit_price_w_tax')
    order = fields.Char('Order')
    description = fields.Char('Description', depends=['product'])
    state = fields.Selection(INVOICE_STATES, 'Invoice State', readonly=True)
    state_string = state.translated('state')
    invoice_line = fields.Many2One('account.invoice.line', 'Invoice 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')
    kind = fields.Selection([
        ('accommodation', 'Accommodation'),
        ('product', 'Product'),
        ], 'Kind')
    # Remove for deprecation
    to_invoice = fields.Boolean('To Invoice', states={
            'invisible': Bool(Eval('invoice_line')),
        }, depends=['invoice_line'])
    ############################
    storage = fields.Many2One('stock.location', 'Storage', domain=[
        ('type', '=', 'storage')], states={
            'readonly': Bool(Eval('invoice_line')),
        })
    move = fields.Many2One('stock.move', 'Move')
    status = fields.Selection([
        ('paid', 'Paid'),
        ('pending', 'Pending'),
        ], 'Status', help="Status of payment")
    line_move = fields.Many2One('account.move.line', 'Line Acc. Move')
    analytic_account = fields.Many2One('analytic_account.account',
        'Analytic Account', domain=[
            ('type', '=', 'normal')
        ])
    taxes = fields.Many2Many('hotel.folio_charge-account.tax', 'charge', 'tax',
        'Taxes', order=[('tax.sequence', 'ASC'), ('tax.id', 'ASC')],
        domain=[
            ('parent', '=', None), [
                'OR',
                ('group', '=', None),
                ('group.kind', 'in', ['sale', 'both'])
            ],
        ],
    )

    # @classmethod
    # def delete(cls, records):
    #     target = []
    #     for rec in records:
    #         if rec.invoice_line or rec.move:
    #             continue
    #         target.append(rec)
    #     super().delete(target)

    @classmethod
    def __setup__(cls):
        super(FolioCharge, cls).__setup__()
        cls._buttons.update({
            'bill': {
                'invisible': Eval('invoice_state') is not None,
            },
        })

    @classmethod
    def _add_taxes(cls, vlist):
        pool = Pool()
        Folio = pool.get('hotel.folio')
        Product = pool.get('product.product')
        for value in vlist:
            folio_id = value.get('folio', None)
            if folio_id:
                folio = Folio(folio_id)
                if folio.taxes_exception:
                    continue
                product = Product(value['product'])
                value['taxes'] = [('add', [
                    tax.id for tax in product.customer_taxes_used
                ])]

    @classmethod
    def _check_create(cls, vlist):
        Folio = Pool().get('hotel.folio')
        for value in vlist:
            folio_id = value.get('folio', None)
            if folio_id:
                folio = Folio(folio_id)
                if folio.charges_blocked:
                    raise UserError(gettext('hotel.msg_folio_charges_blocked'))

    @classmethod
    def create(cls, vlist):
        cls._check_create(vlist)
        cls._add_taxes(vlist)
        return super().create(vlist)

    @staticmethod
    def default_quantity():
        return 1

    @staticmethod
    def default_kind():
        return 'product'

    @staticmethod
    def default_status():
        return 'pending'

    @staticmethod
    def default_to_invoice():
        return True

    @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
        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 and not self.folio.taxes_exception:
            values = Tax.compute(self.product.template.customer_taxes_used,
                self.unit_price, 1)
            if values:
                value = values[0]
                res = value['base'] + value['amount']
        res = round(res, 2)
        return res

    def do_stock_move(self, name=None):
        pool = Pool()
        Location = pool.get('stock.location')
        Move = pool.get('stock.move')
        Config = Pool().get('hotel.configuration')
        # FIXME add origin

        locs_customer = Location.search([
            ('type', '=', 'customer')
        ])
        customer_loc_id = locs_customer[0].id

        config = Config.get_configuration()
        if not self.storage and config.storage_by_default:
            storage_id = config.storage_by_default.id
        else:
            storage_id = self.storage.id

        move, = Move.create([{
            'product': self.product.id,
            'effective_date': self.date_service,
            'quantity': self.quantity,
            'unit_price': self.product.cost_price,
            'uom': self.product.default_uom.id,
            'from_location': storage_id,
            'to_location': customer_loc_id,
            'origin': str(self),
        }])
        self.move = move.id
        Move.do([move])
        self.save()

    def get_taxed_amount(self, name=None):
        if self.quantity and self.unit_price_w_tax:
            return self.quantity * self.unit_price_w_tax

    @classmethod
    @ModelView.button
    def bill(cls, records):
        cls.create_sales(records)

    @fields.depends('unit_price', 'folio', 'product', 'description', 'taxes')
    def on_change_product(self):
        if self.product:
            self.unit_price = round(self.product.template.list_price, 2)
            self.description = self.product.template.name
            if self.folio and not self.folio.taxes_exception:
                self.taxes = [
                    tax.id for tax in self.product.customer_taxes_used
                ]

    def get_move_line(self, account, party, amount):
        debit = credit = _ZERO
        if amount[0] == 'debit':
            debit = amount[1]
        else:
            credit = amount[1]
        res = {
            'description': self.description,
            'debit': debit,
            'credit': credit,
            'account': account.id,
            'party': party.id,
        }
        line = self._get_entry(res)
        return line

    def get_analytic_lines(self, account, line, date):
        "Yield analytic lines for the accounting line and the date"
        lines = []
        amount = line['debit'] or line['credit']
        for account, amount in account.distribute(amount):
            analytic_line = {}
            analytic_line['debit'] = amount if line['debit'] else Decimal(0)
            analytic_line['credit'] = amount if line['credit'] else Decimal(0)
            analytic_line['account'] = account
            analytic_line['date'] = date
            lines.append(analytic_line)
        return lines

    def _get_entry(self, line_move):
        line_move['analytic_lines'] = []
        if self.analytic_account:
            entry = self.analytic_account
            to_create = []
            # Just debits must to create entries, credits not
            if entry.account and line_move['credit'] > 0:
                to_create.extend(self.get_analytic_lines(
                        entry.account,
                        line_move, self.date_service
                    ))
            if to_create:
                line_move['analytic_lines'] = [('create', to_create)]
        return line_move


class FolioOccupancy(ModelSQL, ModelView):
    'Folio Occupancy'
    __name__ = 'hotel.folio.occupancy'
    folio = fields.Many2One('hotel.folio', 'Folio', required=True,
        ondelete='CASCADE')
    occupancy_date = fields.Date('Occupancy Date', required=True)
    charge = fields.Many2One('hotel.folio.charge', 'Charge')
    price_list = fields.Many2One('hotel.price_list', 'Price List')
    unit_price = fields.Numeric('Unit Price', digits=(16, 2), states={
            'readonly': True,
        })
    unit_price_w_tax = fields.Function(
        fields.Numeric('Unit Price W Tax'), 'get_unit_price_w_tax')
    state = fields.Selection([
        ('draft', 'Draft'),
        ('confirmed', 'Confirmed'),
        ('invoiced', 'Invoiced'),
    ], 'State', readonly=True)

    def get_unit_price_w_tax(self, name=None):
        Tax = Pool().get('account.tax')
        res = self.unit_price or 0
        if self.unit_price and not self.folio.taxes_exception:
            values = Tax.compute(
                self.folio.product.template.customer_taxes_used,
                self.unit_price, 1)
            if values:
                value = values[0]
                res = value['base'] + value['amount']
        return res


class OpenMigrationStart(ModelView):
    'Open Migration Start'
    __name__ = 'hotel.open_migration.start'
    start_date = fields.Date('Start Date', required=True)
    end_date = fields.Date('End Date', required=True)
    company = fields.Many2One('company.company', 'Company', required=True)

    @staticmethod
    def default_company():
        return Transaction().context.get('company')


class OpenMigration(Wizard):
    'Open Migration'
    __name__ = 'hotel.open_migration'
    start = StateView(
        'hotel.open_migration.start',
        'hotel.open_migration_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Open', 'print_', 'tryton-print', default=True),
        ])
    print_ = StateAction('hotel.report_migration')

    def do_print_(self, action):
        data = {
            'start_date': self.start.start_date,
            'end_date': self.start.end_date,
            'company': self.start.company.id,
        }
        return action, data

    def transition_print_(self):
        return 'end'


class Migration(Report):
    'Hotel Migration'
    __name__ = 'hotel.migration'

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        pool = Pool()
        Folio = pool.get('hotel.folio')
        Company = pool.get('company.company')
        records = Folio.search([
            ('arrival_date', '>=', data['start_date']),
            ('arrival_date', '<=', data['end_date']),
            ('main_guest', '!=', None),
            ('registration_state', 'in', ['check_in', 'check_out']),
        ], order=[('arrival_date', 'ASC')])
        end_date = data['end_date']
        _records = []
        company = Company(data['company'])
        mig_code = company.mig_code
        city_code = company.city_code
        for folio in records:
            for guest in folio.guests:
                for move in ('E', 'S'):
                    if move == 'S' and folio.departure_date > end_date:
                        continue

                    if move == 'E':
                        move_date = folio.arrival_date
                    else:
                        move_date = folio.departure_date

                    if not all([
                            guest.origin_country,
                            guest.target_country,
                            guest.nationality
                            ]):
                        continue
                    _records.append({
                        'mig_code': mig_code,
                        'city_code': city_code,
                        'type_doc': guest.type_document,
                        'id_number': guest.id_number,
                        'nationality': guest.nationality.code_numeric,
                        'first_family_name': guest.first_family_name,
                        'second_family_name': guest.second_family_name,
                        'first_name': guest.first_name,
                        'move': move,
                        'move_date': move_date,
                        'origin': guest.origin_country.code_numeric,
                        'target': guest.target_country.code_numeric,
                        'birthday': guest.birthday,
                    })
        report_context['records'] = _records
        return report_context


class OperationReport(Report):
    __name__ = 'hotel.operation'

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        user = Pool().get('res.user')(Transaction().user)
        report_context['company'] = user.company
        return report_context


class CheckOutOperationFailed(ModelView):
    'Check Out Operation Failed'
    __name__ = 'hotel.operation.check_out.failed'


class CheckOutOperation(Wizard):
    'Check Out Operation'
    __name__ = 'hotel.operation.check_out'
    start = StateTransition()
    failed = StateView(
        'hotel.operation.check_out.failed',
        'hotel.operation_check_out_failed_view_form', [
            Button('Force Check Out', 'force', 'tryton-forward'),
            Button('Cancel', 'end', 'tryton-cancel', True),
        ])
    force = StateTransition()

    def transition_start(self):
        Operation = Pool().get('hotel.operation')
        active_id = Transaction().context['active_id']
        operations = Operation.browse([active_id])
        if Operation.validate_check_out_date(operations):
            return 'end'
        else:
            return 'failed'

    def transition_force(self):
        Operation = Pool().get('hotel.operation')
        active_id = Transaction().context['active_id']
        operations = Operation.browse([active_id])
        Operation.check_out(operations)
        return 'end'


class UpdateOccupancyStart(ModelView):
    'Update Occupancy'
    __name__ = 'hotel.folio_update_occupancy.start'
    departure_date = fields.Date('Departure Date', states={
        'readonly': Bool(Eval('room'))
    })
    room = fields.Many2One('hotel.room', 'New Room', domain=[
        ('id', 'in', Eval('targets')),
    ])
    unit_price = fields.Numeric('Unit Price', digits=(16, 2))
    accommodation = fields.Many2One('product.product', 'Accommodation',
        domain=[
            ('template.kind', '=', 'accommodation'),
        ])
    targets = fields.Function(fields.Many2Many('hotel.room', None, None,
        'Targets', depends=['departure_date', 'accommodation', 'room']),
        'on_change_with_targets')
    # tranfer_charges = fields.Boolean('Transfer Charges')

    @fields.depends('departure_date', 'accommodation', 'room')
    def on_change_with_targets(self, name=None):
        pool = Pool()
        RoomTemplate = pool.get('hotel.room-product.template')
        Folio = pool.get('hotel.folio')
        folio = Folio(Transaction().context.get('active_id'))
        res = []

        acco = folio.product
        if self.accommodation:
            acco = self.accommodation

        departure_date = folio.departure_date
        if self.departure_date:
            departure_date = self.departure_date

        room_templates = RoomTemplate.search([
            ('template.accommodation_capacity', '>=', acco.accommodation_capacity),
            ('template', '=', acco.template.id)
        ])
        rooms_ids = [t.room.id for t in room_templates]

        res = Folio.get_available_rooms(
            folio.arrival_date,
            departure_date,
            rooms_ids=rooms_ids
        )
        return res


class UpdateOccupancy(Wizard):
    'Update Occupancy'
    __name__ = 'hotel.folio_update_occupancy'
    """
    this is the wizard that allows to the frontdesk employee to change
    original room o departure date.
    """
    start = StateView(
        'hotel.folio_update_occupancy.start',
        'hotel.folio_update_occupancy_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Change', 'update', 'tryton-ok'),
        ]
    )
    update = StateTransition()

    def default_start(self, fields):
        targets = self.start.on_change_with_targets()
        res = {
            'targets': targets
        }
        return res

    def transition_update(self):
        pool = Pool()
        Folio = pool.get('hotel.folio')
        folio, = Folio.browse([Transaction().context.get('active_id')])
        dep_date = self.start.departure_date
        price = self.start.unit_price
        room = self.start.room
        folio.update_occupancy(dep_date=dep_date, price=price)
        if dep_date:
            folio.departure_date = dep_date
        if price:
            folio.unit_price = price
        if room:
            folio.room = room.id
        folio.save()
        return 'end'


class OperationByConsumerReport(Report):
    __name__ = 'hotel.folio.charge'

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        pool = Pool()
        Folio = pool.get('hotel.folio')
        user = pool.get('res.user')(Transaction().user)
        consumer_lines = []
        total_amount = 0
        # if records:
        #     line = records[0]
        #     folio = Folio(line.folio.id)
        #     total_amount = folio.room_amount
        #     for ch in folio.lines:
        #         if ch.invoice_to and ch.invoice_to.id == line.invoice_to.id:
        #             consumer_lines.append(l)
        #             total_amount += l.amount
        #     setattr(folio, 'lines', consumer_lines)
        #     setattr(folio, 'total_amount', total_amount)
        #
        # report_context['records'] = [folio]
        report_context['company'] = user.company
        return report_context


class OperationBill(Wizard):
    'Operation Bill'
    __name__ = 'hotel.folio.bill'
    start_state = 'create_bill'
    create_bill = StateTransition()

    def transition_create_bill(self):
        Folio = Pool().get('hotel.folio')
        ids = Transaction().context['active_ids']
        folios = Folio.browse(ids)
        Folio.bill(folios)
        return 'end'


class OperationVoucher(ModelSQL):
    'Operation - Voucher'
    __name__ = 'hotel.folio-account.voucher'
    _table = 'folio_vouchers_rel'
    folio = fields.Many2One('hotel.folio', 'Folio', ondelete='CASCADE',
        required=True)
    voucher = fields.Many2One('account.voucher', 'Voucher', required=True,
        domain=[('voucher_type', '=', 'receipt')], ondelete='RESTRICT')

    @classmethod
    def set_voucher_origin(cls, voucher_id, folio_id):
        cls.create([{
            'voucher': voucher_id,
            'folio': folio_id,
        }])


class ReverseCheckout(Wizard):
    'Reverse Checkout'
    __name__ = 'hotel.folio.reverse_checkout'
    start_state = 'reverse_checkout'
    reverse_checkout = StateTransition()

    def transition_reverse_checkout(self):
        Folio = Pool().get('hotel.folio')
        folio_id = Transaction().context['active_id']
        if folio_id:
            Folio.check_in([Folio(folio_id)])
        return 'end'


class FolioStockMove(ModelSQL):
    'Folio - Stock Move'
    __name__ = 'hotel.folio-stock.move'
    _table = 'hotel_folio_stock_move_rel'
    folio = fields.Many2One('hotel.folio', 'Folio',
        ondelete='CASCADE', required=True)
    move = fields.Many2One('stock.move', 'Move',
        ondelete='RESTRICT', required=True)


class StatisticsByMonthStart(ModelView):
    'Statistics By Month Start'
    __name__ = 'hotel.statistics_by_month.start'
    period = fields.Many2One('account.period', 'Period', required=True)
    company = fields.Many2One('company.company', 'Company', required=True)

    @staticmethod
    def default_company():
        return Transaction().context.get('company')


class StatisticsByMonth(Wizard):
    'Statistics By Month'
    __name__ = 'hotel.statistics_by_month'
    start = StateView(
        'hotel.statistics_by_month.start',
        'hotel.print_hotel_statistics_by_month_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Open', 'print_', 'tryton-print', default=True),
        ])
    print_ = StateAction('hotel.statistics_by_month_report')

    def do_print_(self, action):
        data = {
            'period': self.start.period.id,
            'company': self.start.company.id,
        }
        return action, data

    def transition_print_(self):
        return 'end'


class StatisticsByMonthReport(Report):
    'Hotel Statistics By Month'
    __name__ = 'hotel.statistics_by_month.report'

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        Operation = Pool().get('hotel.folio')
        Period = Pool().get('account.period')
        Company = Pool().get('company.company')
        Rooms = Pool().get('hotel.room')
        period = Period(data['period'])
        company = Company(data['company'])
        month_days = (period.end_date - period.start_date).days + 1
        rooms_available = []
        rooms_sold = []
        beds_available = []
        beds_sold = []
        local_guests = []
        foreign_guests = []
        room1_sold = []
        room2_sold = []
        room_suite_sold = []
        room_other_sold = []
        guests_one_ng = []
        guests_two_ng = []
        guests_three_ng = []
        guests_four_ng = []
        guests_five_ng = []
        guests_six_ng = []
        guests_seven_ng = []
        guests_eight_ng = []
        guests_permanent_ng = []
        reason_bussiness = []
        reason_bussiness_foreign = []
        reason_leisure = []
        reason_leisure_foreign = []
        reason_convention = []
        reason_convention_foreign = []
        reason_health = []
        reason_health_foreign = []
        reason_transport = []
        reason_transport_foreign = []
        reason_other = []
        reason_other_foreign = []
        total_guests = []
        folios = Operation.search(['OR', [
                ('start_date', '>=', period.start_date),
                ('start_date', '<=', period.end_date),
                ('kind', '=', 'occupancy'),
            ], [
                ('end_date', '>=', period.start_date),
                ('end_date', '<=', period.end_date),
                ('kind', '=', 'occupancy'),
            ]
        ])
        rooms = Rooms.search([])
        rooms_available = [len(rooms) * month_days]
        for r in rooms:
            beds_available.append(
                r.main_accommodation.accommodation_capacity * month_days
            )

        def _get_ctx_dates(op):
            start_date = op.start_date
            end_date = op.end_date
            if op.start_date < period.start_date:
                start_date = period.start_date
            if op.end_date > period.end_date:
                end_date = period.end_date

            sub_dates = [
                str(start_date + timedelta(n))
                for n in range((end_date - start_date).days)
            ]
            return sub_dates

        for op in folios:
            room_capacity = op.room.main_accommodation.accommodation_capacity
            ctx_dates = _get_ctx_dates(op)
            len_ctx_dates = len(ctx_dates)
            rooms_sold.append(len_ctx_dates)
            beds_sold.append(len_ctx_dates * room_capacity)
            qty_guests = len(op.guests)
            if not qty_guests:
                qty_guests = 1
            total_guests.append(qty_guests)
            is_local = True
            if op.main_guest.type_document == '41':
                is_local = False
                foreign_guests.append(qty_guests)
            else:
                local_guests.append(qty_guests)

            if room_capacity == 1:
                room1_sold.append(qty_guests)
            elif room_capacity == 2:
                room2_sold.append(qty_guests)
            elif room_capacity > 2:
                room_suite_sold.append(qty_guests)

            if len_ctx_dates == 1:
                guests_one_ng.append(qty_guests)
            elif len_ctx_dates == 2:
                guests_two_ng.append(qty_guests)
            elif len_ctx_dates == 3:
                guests_three_ng.append(qty_guests)
            elif len_ctx_dates == 4:
                guests_four_ng.append(qty_guests)
            elif len_ctx_dates == 5:
                guests_five_ng.append(qty_guests)
            elif len_ctx_dates == 6:
                guests_six_ng.append(qty_guests)
            elif len_ctx_dates == 7:
                guests_seven_ng.append(qty_guests)
            elif len_ctx_dates >= 8:
                guests_eight_ng.append(qty_guests)

            segment = 'bussiness'
            if op.origin:
                if hasattr(op.origin, 'booking'):
                    segment = op.origin.booking.segment

            if segment == 'bussiness':
                if is_local:
                    reason_bussiness.append(qty_guests)
                else:
                    reason_bussiness_foreign.append(qty_guests)
            elif segment == 'convention':
                if is_local:
                    reason_convention.append(qty_guests)
                else:
                    reason_convention_foreign.append(qty_guests)
            elif segment == 'health':
                if is_local:
                    reason_health.append(qty_guests)
                else:
                    reason_health_foreign.append(qty_guests)
            else:
                if is_local:
                    reason_leisure.append(qty_guests)
                else:
                    reason_leisure_foreign.append(qty_guests)

        def _get_rate(val):
            res = 0
            if sum_total_guests > 0:
                res = round(float(sum(val)) / sum_total_guests, 4)
            return res

        sum_total_guests = sum(total_guests)
        rate_guests_one_ng = _get_rate(guests_one_ng)
        rate_guests_two_ng = _get_rate(guests_two_ng)
        rate_guests_three_ng = _get_rate(guests_three_ng)
        rate_guests_four_ng = _get_rate(guests_four_ng)
        rate_guests_five_ng = _get_rate(guests_five_ng)
        rate_guests_six_ng = _get_rate(guests_six_ng)
        rate_guests_seven_ng = _get_rate(guests_seven_ng)
        rate_guests_eight_ng = _get_rate(guests_eight_ng)

        report_context['period'] = period.name
        report_context['company'] = company.party.name
        report_context['rooms_available'] = sum(rooms_available)
        report_context['rooms_sold'] = sum(rooms_sold)
        report_context['beds_available'] = sum(beds_available)
        report_context['beds_sold'] = sum(beds_sold)
        report_context['local_guests'] = sum(local_guests)
        report_context['foreign_guests'] = sum(foreign_guests)
        report_context['room1_sold'] = sum(room1_sold)
        report_context['room2_sold'] = sum(room2_sold)
        report_context['room_suite_sold'] = sum(room_suite_sold)
        report_context['room_other_sold'] = sum(room_other_sold)
        report_context['guests_one_ng'] = rate_guests_one_ng
        report_context['guests_two_ng'] = rate_guests_two_ng
        report_context['guests_three_ng'] = rate_guests_three_ng
        report_context['guests_four_ng'] = rate_guests_four_ng
        report_context['guests_five_ng'] = rate_guests_five_ng
        report_context['guests_six_ng'] = rate_guests_six_ng
        report_context['guests_seven_ng'] = rate_guests_seven_ng
        report_context['guests_eight_ng'] = rate_guests_eight_ng
        report_context['guests_permanent_ng'] = sum(guests_permanent_ng)
        report_context['reason_bussiness'] = sum(reason_bussiness)
        report_context['reason_bussiness_foreign'] = sum(reason_bussiness_foreign)
        report_context['reason_leisure'] = sum(reason_leisure)
        report_context['reason_leisure_foreign'] = sum(reason_leisure_foreign)
        report_context['reason_convention'] = sum(reason_convention)
        report_context['reason_convention_foreign'] = sum(reason_convention_foreign)
        report_context['reason_health'] = sum(reason_health)
        report_context['reason_health_foreign'] = sum(reason_health_foreign)
        report_context['reason_transport'] = sum(reason_transport)
        report_context['reason_transport_foreign'] = sum(reason_transport_foreign)
        report_context['reason_other_foreign'] = sum(reason_other_foreign)
        report_context['reason_other'] = sum(reason_other)

        return report_context


class GuestsListStart(ModelView):
    'Guests List Start'
    __name__ = 'hotel.print_guests_list.start'
    date = fields.Date('Date', required=True)
    company = fields.Many2One('company.company', 'Company', required=True)

    @staticmethod
    def default_date():
        Date_ = Pool().get('ir.date')
        return Date_.today()

    @staticmethod
    def default_company():
        return Transaction().context.get('company')


class GuestsList(Wizard):
    'Guests List'
    __name__ = 'hotel.print_guests_list'
    start = StateView(
        'hotel.print_guests_list.start',
        'hotel.print_guests_list_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Open', 'print_', 'tryton-print', default=True),
        ])
    print_ = StateReport('hotel.guests_list.report')

    def do_print_(self, action):
        company = self.start.company
        data = {
            'date': self.start.date,
            'company': company.id,
        }
        return action, data

    def transition_print_(self):
        return 'end'


class GuestsListReport(Report):
    __name__ = 'hotel.guests_list.report'

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        pool = Pool()
        Company = pool.get('company.company')
        Folio = pool.get('hotel.folio')
        Room = pool.get('hotel.room')
        User = pool.get('res.user')
        user_id = Transaction().user
        user = User(user_id)
        dom = [
            ('arrival_date', '<=', data['date']),
            ('departure_date', '>', data['date']),
        ]
        folios = Folio.search(dom, order=[('room.code', 'ASC')])

        total_guests = []
        rooms_occupied = []
        for op in folios:
            total_guests.append(len(op.guests))
            rooms_occupied.append(op.room.id)

        rooms = Room.search_read([])

        _rooms_occupied = len(set(rooms_occupied))
        num_rooms = len(rooms)
        if num_rooms:
            occupancy_rate = round(_rooms_occupied / num_rooms, 2)
        else:
            occupancy_rate = 0

        guests = []
        for fo in folios:
            for guest in op.guests:
                guests.append({
                    'room': fo.room.code,
                    'guest': guest.name,
                    'party': fo.booking.party.name if fo.booking.party else '',
                    'arrival_date': fo.arrival_date,
                    'departure_date': fo.departure_date,
                    'nights_quantity': fo.nights_quantity,
                    'state': fo.registration_state,
                })
        report_context['records'] = guests
        report_context['total_rooms'] = _rooms_occupied
        report_context['total_guests'] = sum(total_guests)
        report_context['occupancy_rate'] = occupancy_rate
        report_context['company'] = Company(data['company'])
        report_context['date'] = data['date']
        report_context['print_date'] = datetime.now()
        report_context['user'] = user.name
        return report_context


class RegistrationCardReport(Report):
    __name__ = 'hotel.folio.registration_card'

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        user = Pool().get('res.user')(Transaction().user)
        report_context['company'] = user.company
        _records = []
        for rec in records:
            if not rec.registration_card:
                continue
            _records.append(rec)
        report_context['records'] = _records
        return report_context


class HotelFolioTax(ModelSQL):
    'Hotel Folio - Tax'
    __name__ = 'hotel.folio-account.tax'
    _table = 'hotel_folio_account_tax'
    folio = fields.Many2One('hotel.folio', 'Folio', ondelete='CASCADE',
        required=True)
    tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
        required=True)


class HotelFolioChargeTax(ModelSQL):
    'Hotel Folio Charge - Tax'
    __name__ = 'hotel.folio_charge-account.tax'
    _table = 'hotel_folio_charge_account_tax'
    charge = fields.Many2One('hotel.folio.charge', 'Charge',
        ondelete='CASCADE', required=True)
    tax = fields.Many2One('account.tax', 'Tax', ondelete='RESTRICT',
        required=True)
