# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from datetime import datetime, timedelta, date
from decimal import Decimal
import copy

from trytond.model import Workflow, ModelView, ModelSQL, fields
from trytond.wizard import (
    Wizard, StateView, Button, StateTransition, StateReport
)
from trytond.report import Report
from trytond.pyson import Eval, If, In, Get, Bool
from trytond.transaction import Transaction
from trytond.pool import Pool
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.modules.account.tax import TaxableMixin
from .constants import (
    STATE_BOOKING, REASON, GUARANTEE, SATISFACTION, MEDIA, PLAN,
    COMPLEMENTARY, PAYMENT_METHOD_CHANNEL, TYPE_DOCUMENT
)

SEPARATOR = ' | '
_ZERO = Decimal('0.0')
ADJUST_CURRENCY = 100

STATES = {
    'readonly': Eval('state') != 'offer',
}

NO_CHANGES = ['finished', 'cancelled', 'not_show']

STATES_BLOCKED = {
    'readonly': Eval('state').in_(NO_CHANGES),
}

STATES_CONFIRMED = {
    'readonly': Eval('state') != 'offer',
    'required': Eval('state') == 'confirmed',
}


class Booking(Workflow, ModelSQL, ModelView):
    'Booking'
    __name__ = 'hotel.booking'
    _rec_name = 'number'
    number = fields.Char('Number', readonly=True, select=True,
        help="Sequence of reservation.")
    party = fields.Many2One('party.party', 'Customer', required=False,
        select=True, states=STATES_BLOCKED,
        help="Person or company owner of the booking.")
    contact = fields.Char('Contact', states=STATES_BLOCKED,
        help='Main contact or person how request booking')
    payment_term = fields.Many2One('account.invoice.payment_term',
        'Payment Term', states=STATES_BLOCKED)
    booking_date = fields.DateTime('Booking Date', readonly=True)
    guests_num = fields.Function(fields.Integer('Person Number'),
        'get_person_num')
    adult_num = fields.Function(fields.Integer('Adult Number'),
        'get_person_num')
    children_num = fields.Function(fields.Integer('Children Number'),
        'get_person_num')
    group = fields.Boolean('Group', states=STATES_BLOCKED)
    corporative = fields.Boolean('Corporative', states=STATES_BLOCKED)
    complementary = fields.Boolean('Complementary', states=STATES_BLOCKED)
    type_complementary = fields.Selection(COMPLEMENTARY, 'Type Complementary',
        states=STATES_BLOCKED)
    channel = fields.Many2One('hotel.channel', 'Channel',
        states={
            'invisible': Eval('media') != 'ota',
            'readonly': Eval('state').in_(['finished', 'cancelled']),
        },
        help="Agency or channel that do reservation.")
    state = fields.Selection(STATE_BOOKING, 'State', readonly=True,
        required=True)
    state_string = state.translated('state')
    price_list = fields.Many2One('product.price_list', 'Price List',
        states={
            'readonly': Bool(Eval('lines')),
        }, depends=['state', 'company', 'lines'])
    rate_plan = fields.Many2One('hotel.rate_plan', 'Rate Plan',
        states={
            'readonly': Bool(Eval('lines')),
        }, depends=['state', 'company', 'lines'])
    company = fields.Many2One('company.company', 'Company', required=True,
        states=STATES, domain=[('id', If(In('company',
        Eval('context', {})), '=', '!='), Get(Eval('context', {}),
        'company', 0))], readonly=True)
    # 'required': Eval('state') == 'confirmed',
    lines = fields.One2Many('hotel.folio', 'booking', 'Folios',
        states={
            'readonly': Eval('state').in_(['finished', 'cancelled']),
        }, depends=['state', 'party'], context={'party': Eval('party')})
    cancellation_policy = fields.Many2One('hotel.policy.cancellation',
        'Cancellation Policy', states=STATES_BLOCKED)
    currency = fields.Many2One('currency.currency', 'Currency',
        required=True, states={
            'readonly': (Eval('state') != 'offer') |
                (Eval('lines', [0]) & Eval('currency', 0)),
            }, depends=['state'])
    satisfaction = fields.Selection(SATISFACTION, 'Satisfaction')
    media = fields.Selection(MEDIA, 'Media', states=STATES_BLOCKED,
        help="Way through which the booking arrives.")
    media_string = media.translated('media')
    lead_origin = fields.Many2One('crm.lead_origin', 'Lead Origin')
    plan = fields.Selection(PLAN, 'Commercial Plan', states=STATES_BLOCKED,
        help="Plans offered by hotel and selected by guest for booking.")
    plan_string = plan.translated('plan')
    comments = fields.Text('Comments', states=STATES_BLOCKED)
    reason = fields.Selection(REASON, 'Tourism Segment', states=STATES_BLOCKED)
    reason_string = reason.translated('segment')
    guarantee = fields.Selection(GUARANTEE, 'Guarantee', states=STATES_BLOCKED)
    guarantee_string = guarantee.translated('guarantee')
    untaxed_amount = fields.Function(fields.Numeric('Untaxed Amount',
        digits=(16, 2), depends=['lines']), 'get_untaxed_amount')
    tax_amount = fields.Function(fields.Numeric('Tax Amount',
        digits=(16, 2), depends=['lines']), 'get_tax_amount')
    total_amount = fields.Function(fields.Numeric('Total Amount',
        digits=(16, 2), depends=['lines']), 'get_total_amount')
    collect_amount = fields.Function(fields.Numeric('Collect Amount',
        digits=(16, 2), depends=['lines']), 'get_collect_amount')
    code = fields.Char('Code', states={'readonly': True})
    booker = fields.Many2One('party.party', 'Booker',
        states={'readonly': True})
    created_channel = fields.DateTime('Created Channel',
        states={'readonly': True})
    vouchers = fields.Many2Many('hotel.booking-account.voucher', 'booking',
        'voucher', 'Vouchers', states={'readonly': False})
    ota_booking_code = fields.Char('OTA Code', select=True,
        states={'invisible': Eval('media') != 'ota'}
    )
    payments = fields.One2Many('account.statement.line', 'source',
        'Payments', states=STATES_BLOCKED, readonly=True)
    vehicles_num = fields.Integer('Vehicles Number', states=STATES_BLOCKED,
        help="Number of vehicles that bring with guests.")
    travel_cause = fields.Char('Travel Cause', states=STATES_BLOCKED)
    taxes_exception = fields.Boolean('Taxes Exception', states=STATES_BLOCKED)
    total_advances = fields.Function(fields.Numeric('Total Advance',
        digits=(16, 2)), 'get_total_advances')
    pending_to_pay = fields.Function(fields.Numeric('Pending to Pay',
        digits=(16, 2)), 'get_pending_to_pay')
    breakfast_included = fields.Boolean('Breakfast Included',
        states=STATES_BLOCKED)
    channel_commission = fields.Function(fields.Numeric('Channel Commission',
        digits=(16, 2), depends=['lines']), 'get_channel_commission')
    commission = fields.Many2One('commission', 'Commission')
    agent = fields.Many2One('commission.agent', 'Agent')
    channel_invoice = fields.Many2One('account.invoice', 'Channel Invoice',
        states={
            'invisible': ~Eval('channel'),
            'readonly': True
        })
    channel_payment_method = fields.Selection(PAYMENT_METHOD_CHANNEL,
        'Channel Payment Method', states={'invisible': ~Eval('channel')},
        depends=['channel']
    )
    invoices = fields.Function(fields.Many2Many('account.invoice',
        None, None, 'Invoices'), 'get_invoices')
    extra_commissions = fields.Many2Many('hotel.booking-channel.commission',
        'booking', 'commission', 'Channel Commission', domain=[
            ('channel', '=', Eval('channel'))
        ], states=STATES_BLOCKED)
    stock_moves = fields.Function(fields.One2Many('stock.move', 'origin', 'Moves',
        readonly=True), 'get_stock_moves')
    channel_icon = fields.Function(fields.Char('Channel Icon'),
        'get_channel_icon')
    emails = fields.One2Many('email.activity', 'origin', 'Emails',
        readonly=True)
    responsible_payment = fields.Selection([
        ('holder', 'Holder'),
        ('holder_guest', 'Holder / Guest'),
        ('guest', 'Guest'),
        ], 'Responsible for Payment', states={
            'required': True,
            'readonly': Eval('state').in_(NO_CHANGES),
        })
    credit_card = fields.Char('Credit Card', states=STATES_BLOCKED)
    vip = fields.Boolean('V.I.P. Customer', states=STATES_BLOCKED)
    pending_acco = 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')
    income_moves = fields.One2Many('account.move', 'origin', 'Income Moves',
        states={
            'readonly': Eval('state').in_(['finished', 'cancelled']),
        })
    space = fields.Many2One('analytic_account.space', 'Space',
        states=STATES_BLOCKED)
    collection_mode = fields.Selection([
        ('', ''),
        ('anticipated', 'Anticipated'),
        ('post_checkin', 'Post Checkin'),
        ], 'Collection Mode', required=False,
        help="Commission collection mode")
    link_web_checkin = fields.Function(fields.Char('Link Web Check-in'),
        'get_link_web_checkin')

    @classmethod
    def __setup__(cls):
        super(Booking, cls).__setup__()
        cls._order.insert(0, ('create_date', 'DESC'))
        cls._transitions |= set((
            ('offer', 'confirmed'),
            ('offer', 'cancelled'),
            ('confirmed', 'offer'),
            ('cancelled', 'offer'),
            ('confirmed', 'cancelled'),
            ('confirmed', 'not_show'),
            ('confirmed', 'finished'),
            ('not_show', 'confirmed'),
        ))
        cls._buttons.update({
            'select_rooms': {
                'invisible': Eval('state').in_(NO_CHANGES),
            },
            'update_holder': {
                'invisible': Eval('state').in_(NO_CHANGES),
            },
            'cancel': {
                'invisible': Eval('state').in_(NO_CHANGES)
            },
            'offer': {
                'invisible': Eval('state').in_(['offer', 'finished'])
            },
            'confirm': {
                'invisible': ~Eval('state').in_(['offer', 'not_show'])
            },
            'not_show': {
                'invisible': Eval('state') != 'confirmed',
            },
            'do_payment': {
                'invisible': Eval('state').in_(NO_CHANGES),
            },
            'send_email_booking': {
                'invisible': Eval('state').in_(['finished'])
            },
            'send_email_checkin': {
                'invisible': Eval('state').in_(['finished'])
            },
            'bill': {
                'invisible': ~Eval('state').in_(['confirmed', 'not_show']),
            },
        })

    @staticmethod
    def default_responsible_payment():
        return 'holder'

    @staticmethod
    def default_collection_mode():
        return 'post_checkin'

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

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

    def get_channel_icon(self, name):
        name_icon = 'hotel-channel-house'
        if self.channel and self.channel.code:
            name_icon = f'hotel-channel-{self.channel.code}'
        return name_icon

    @classmethod
    def trigger_create(cls, records):
        cls.set_number(records)

    @classmethod
    def delete(cls, records):
        for record in records:
            if record.number:
                raise UserError(gettext('hotel.msg_can_no_delete_booking'))
        super(Booking, cls).delete(records)

    @classmethod
    def copy(cls, bookings, default=None):
        if default is None:
            default = {}
        default = default.copy()
        default['number'] = None
        default['booking_date'] = datetime.now()
        default['payments'] = None
        return super(Booking, cls).copy(bookings, default=default)

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

    @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,
            ('number', operator, value),
            ('ota_booking_code', operator, value),
            ('party.name', operator, value),
            ('contact', operator, value),
        ]
        return domain

    @staticmethod
    def default_currency():
        Company = Pool().get('company.company')
        company = Transaction().context.get('company')
        if company:
            return Company(company).currency.id

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

    @staticmethod
    def default_state():
        return 'offer'

    @staticmethod
    def default_plan():
        return 'bed_breakfast'

    @staticmethod
    def default_booking_date():
        now = datetime.now()
        return now

    def _round_taxes(self, taxes):
        if not self.currency:
            return
        for taxline in taxes.values():
            taxline['amount'] = self.currency.round(taxline['amount'])

    def _get_taxes(self):
        pool = Pool()
        Tax = pool.get('account.tax')

        taxes = {}
        with Transaction().set_context({}):
            def compute(_taxes, unit_price, quantity):
                l_taxes = Tax.compute(list(_taxes), unit_price, quantity)
                for tax in l_taxes:
                    taxline = TaxableMixin._compute_tax_line(**tax)
                    # Base must always be rounded per folio as there will
                    # be one tax folio per taxable_lines
                    if self.currency:
                        taxline['base'] = self.currency.round(taxline['base'])
                    if taxline not in taxes:
                        taxes[taxline] = taxline
                    else:
                        taxes[taxline]['base'] += taxline['base']
                        taxes[taxline]['amount'] += taxline['amount']

            for folio in self.lines:
                if folio.taxes_exception:
                    continue
                _taxes = folio.product.customer_taxes_used
                for occ in folio.occupancy:
                    if not occ.charge:
                        compute(_taxes, occ.unit_price, 1)

                for charge in folio.charges:
                    compute(charge.taxes, charge.unit_price, charge.quantity)

        return taxes

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

    def get_person_num(self, name):
        res = {
            'guests_num': [],
            'adult_num': [],
            'children_num': [],
        }

        for line in self.lines:
            res['guests_num'].append(line.num_adults + line.num_children)
            if name == 'adult_num' and line.num_adults:
                res[name].append(line.num_adults)
            elif name == 'children_num' and line.num_children:
                res[name].append(line.num_children)
        return sum(res[name])

    @fields.depends('ota_booking_code', 'lines')
    def on_change_ota_booking_code(self):
        if self.ota_booking_code:
            for line in self.lines:
                line.reference = self.ota_booking_code

    def is_person(self):
        if self.party.type_document in (
                '12', '13', '21', '22', '41', '42', '47'):
            return True

    @fields.depends('party', 'price_list', 'lines')
    def on_change_party(self):
        if self.party:
            if self.party.sale_price_list:
                self.price_list = self.party.sale_price_list.id
                self.price_list.rec_name = self.party.sale_price_list.rec_name
            for folio in self.lines:
                if self.is_person():
                    folio.main_guest = self.party.id

    @fields.depends('channel')
    def on_change_channel(self):
        if self.channel:
            if self.channel.invoice_to == 'channel':
                self.party = self.channel.agent.party.id
                self.responsible_payment = 'holder_guest'
            if self.channel.payment_method == 'ota_collect':
                self.responsible_payment = 'holder_guest'

            self.channel_payment_method = self.channel.payment_method
            self.price_list = self.channel.price_list
            self.collection_mode = self.channel.collection_mode
        else:
            self.channel_payment_method = None
            self.price_list = None
            self.collection_mode = None

    @classmethod
    @ModelView.button_action('hotel.wizard_select_rooms')
    def select_rooms(cls, records):
        pass

    @classmethod
    @ModelView.button_action('hotel.wizard_update_holder')
    def update_holder(cls, records):
        pass

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

    @classmethod
    @ModelView.button
    @Workflow.transition('offer')
    def offer(cls, records):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('cancelled')
    def cancel(cls, records):
        for rec in records:
            for folio in rec.lines:
                if folio.registration_state in ['check_in', 'check_out']:
                    raise UserError(gettext('hotel.msg_no_delete_folios'))
                else:
                    folio.delete([folio])

    @classmethod
    @ModelView.button
    @Workflow.transition('not_show')
    def not_show(cls, records):
        pass

    @classmethod
    @ModelView.button
    @Workflow.transition('confirmed')
    def confirm(cls, records):
        for rec in records:
            rec.update_folio('pending')
            rec.state = 'confirmed'
            rec.save()
            rec.send_email('booking_email')

    @classmethod
    def add_payment(cls, args):
        pool = Pool()
        StatementLine = pool.get('account.statement.line')
        Folio = pool.get('hotel.folio')
        Booking = pool.get('hotel.booking')
        Party = pool.get('party.party')
        Date = pool.get('ir.date')

        amount = args['amount_to_pay']

        if not amount:
            return 'end'

        statement_id = args['statement']
        number = args.get('number', None)
        folio_id = args.get('folio', None)
        kind = args.get('kind', 'booking')
        party_id = args['party']
        party = Party(party_id)

        try:
            account = party.account_receivable.id
        except:
            raise UserError(gettext('hotel.party_without_account_receivable'))

        booking = Booking(args['booking'])
        pending = booking.pending_to_pay
        record = booking
        description = booking.number

        if folio_id and kind == 'folio':
            folio = Folio(folio_id)
            record = folio
            description = folio.registration_card
            if booking.responsible_payment == 'guest':
                pending = folio.pending_total
            elif booking.responsible_payment == 'holder_guest':
                pending = folio.pending_charges

        if abs(pending - amount) <= ADJUST_CURRENCY:
            amount = pending
        line = StatementLine(
            statement=statement_id,
            date=Date.today(),
            amount=amount,
            party=party_id,
            account=account,
            number=number,
            description=description,
            source=str(record),
        )
        line.save()
        line.create_move()

        # We keep to mark all concepts charges/folios as paid if
        # the customer to pay all pending values
        def _set_paid_charges(_folio):
            for charge in _folio.charges:
                if charge.status == 'paid':
                    continue
                charge.status = 'paid'
                charge.save()
            _folio.payment_status = 'paid'
            _folio.save()

        if folio_id and kind == 'folio':
            if amount >= pending:
                _set_paid_charges(folio)
        else:
            if amount >= pending:
                for folio in booking.lines:
                    _set_paid_charges(folio)

        record.save()

    @classmethod
    @ModelView.button
    def no_show(cls, records):
        for record in records:
            cls.write([record], {'state': 'no_show'})

    @classmethod
    @ModelView.button
    def send_email_booking(cls, records):
        for booking in records:
            if booking.state == 'confirmed':
                booking.send_email('booking_email')

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

    @classmethod
    def create_channel_voucher(cls, bk):
        Voucher = Pool().get('account.voucher')
        # If ota_collect is paymode for folios
        if not bk.channel or bk.responsible_payment != 'ota_collect':
            return

        payment_mode = bk.channel.payment_mode
        if not payment_mode:
            raise UserError(gettext('hotel.msg_missing_payment_mode'))

        party = bk.channel.agent.party
        account_id = Voucher.get_account('receipt', payment_mode)

        amount = bk.collect_amount - bk.channel_commission
        lines = [{
            'detail': f'OTA Code {bk.ota_booking_code}',
            'amount': amount,
            'amount_original': amount,
            'account': party.account_receivable.id,
        }]

        to_create = {
            'party': party.id,
            'voucher_type': 'receipt',
            'date': date.today(),
            'description': f'Booking {bk.number}',
            'payment_mode': payment_mode.id,
            'account': account_id,
            'journal': payment_mode.journal.id,
            'lines': [('create', lines)],
            'amount_to_pay': amount,
            'method_counterpart': 'one_line',
            # 'state': 'draft',
        }
        voucher, = Voucher.create([to_create])
        Voucher.process([voucher])
        cls.write([bk], {'vouchers': [('add', [voucher])]})

    @classmethod
    def concile_charges(cls, records):
        '''
        We need mark charges pending to pay as paid, if the customer
        already paid all booking charges
        '''
        for booking in records:
            if booking.pending_to_pay != 0:
                continue
            for folio in booking.lines:
                for charge in folio.charges:
                    if charge.status == 'pending':
                        charge.status = 'paid'
                        charge.save()

    @classmethod
    def check_finished(cls, records):
        for rec in records:
            _folios, _charges = cls.pending_to_invoice(rec.lines)
            if not _folios and not _charges:
                cls.write([rec], {'state': 'finished'})

    @classmethod
    def set_number(cls, bookings):
        """
        Fill the number field with the booking sequence
        """
        pool = Pool()
        Config = pool.get('hotel.configuration')
        config = Config.get_configuration()

        for booking in bookings:
            if booking.number or not config.booking_sequence:
                continue
            number = config.booking_sequence.get()
            cls.write([booking], {'number': number})

    @classmethod
    def reconcile(cls, booking, voucher):
        pool = Pool()
        VoucherConfig = pool.get('account.voucher_configuration')
        MoveLine = pool.get('account.move.line')
        invoice = None
        config = VoucherConfig.get_configuration()
        account = config.customer_advance_account
        for folio in booking.lines:
            invoice = folio.invoice
            if invoice and invoice.state == 'paid':
                continue
            advances = []
            payments = []
            for voucher in booking.vouchers:
                for line in voucher.move.lines:
                    if line.account.id == account.id and not line.reconciliation:
                        advances.append(voucher)
                        break
                    elif invoice and line.account.id == invoice.account.id:
                        payments.append(line)

        if invoice:
            to_reconcile_lines = []
            if advances:
                invoice.create_move_advance(advances)
                invoice.save()
            if payments:
                invoice.add_payment_lines({invoice: payments})
                invoice.save()
            if invoice.amount_to_pay == Decimal(0):
                for ml in invoice.payment_lines:
                    if not ml.reconciliation:
                        to_reconcile_lines.append(ml)
                for ml in invoice.move.lines:
                    if not ml.reconciliation and ml.account.id == invoice.account.id:
                        to_reconcile_lines.append(ml)
                if to_reconcile_lines:
                    MoveLine.reconcile(to_reconcile_lines)

    @classmethod
    def _get_charge_line(cls, cha, bk):
        description = cha.product.template.name
        if cha.order:
            description = cha.description + ' | ' + cha.order
        if cha.kind == 'accommodation':
            description = cha.description + ' | ' + str(cha.date_service)

        return {
            'description': description,
            'quantity': cha.quantity,
            'product': cha.product,
            'unit_price': bk.currency.round(cha.unit_price),
            'charge': cha,
            'origin': str(cha.folio),
            'taxes': cha.taxes,
            'taxes_exception': bk.taxes_exception,
            'analytic_account': cha.analytic_account,
        }

    @classmethod
    def _get_accommodation_line(cls, fo, bk):
        return {
            'folios': [fo],
            'description': fo.get_room_info(),
            'quantity': fo.nights_quantity,
            'product': fo.product,
            'unit_price': fo.unit_price,
            'taxes': fo.taxes,
            'origin': str(fo),
            'taxes_exception': bk.taxes_exception,
        }

    @classmethod
    def add_invoice_charges(cls, kind, charge, res):
        pass

    @classmethod
    def get_grouped_invoices(cls, folios, kind=None, party=None):
        res = {}
        for folio in folios:
            bk = folio.booking
            price_list = bk.price_list
            agent_id = bk.channel.agent.id if bk.channel else bk.agent
            for ch in folio.charges:
                if ch.invoice_line:
                    continue
                if kind == 'only_products' and ch.kind == 'accommodation':
                    continue
                if kind == 'only_accommodation' and ch.kind == 'product':
                    continue

                if bk.responsible_payment == 'holder':
                    party = bk.party
                elif bk.responsible_payment == 'guest':
                    party = folio.main_guest
                else:
                    if ch.kind == 'accommodation':
                        party = bk.party
                    else:
                        party = folio.main_guest

                if not party:
                    raise UserError(
                        gettext('hotel.msg_missing_main_guest'))

                if party.id not in res.keys():
                    res[party.id] = {
                        'party': party,
                        'currency': bk.currency.id,
                        'payment_term': bk.payment_term.id,
                        'number': bk.number,
                        'reference': [folio.registration_card],
                        'rooms': [folio.room.name],
                        'price_list': price_list.id if price_list else None,
                        'company': bk.company.id,
                        'lines': [],
                    }
                    if ch.kind == 'accommodation' and agent_id:
                        res[party.id]['agent'] = agent_id
                        res[party.id]['ota_booking_code'] = bk.ota_booking_code or ''
                        res[party.id]['guests_qty'] = len(folio.guests)
                line = cls._get_charge_line(ch, bk)
                res[party.id]['lines'].append(line)
        return res

    @classmethod
    def _get_invoice_line(cls, invoice, line, record=None):
        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',
            'quantity': line['quantity'],
            'unit_price': line['unit_price'],
            'product': product.id,
            'party': invoice.party.id,
            'description': line['description'],
            'taxes': [('add', line['taxes'])],
            'origin': line['origin'],
        }
        analytic_account = line.get('analytic_account', None)
        if analytic_account:
            new_line['analytic_accounts'] = [('create', [{
                'account': analytic_account.id,
                'root':  analytic_account.root.id,
            }])]
        return new_line

    def do_moves(self):
        Move = Pool().get('stock.move')
        moves = {}
        to_create = []
        for folio in self.lines:
            for charge in folio.charges:
                move = self.get_move(charge)
                if move:
                    moves[folio.id] = move
            for m in moves:
                moves[m].state = 'draft'
                to_create.append(moves[m]._save_values)

            Move.create(to_create)
            Move.do(self.moves)

    def get_move(self, charge):
        '''
        Return stock move for charge according a storage
        '''
        pool = Pool()
        Move = pool.get('stock.move')
        product = self.product
        if not product or product.type != 'goods' and self.quantity <= 0:
            return

        if not charge.folio.storage:
            return None
        customer_location = self.party.customer_location
        move = Move()
        move.quantity = self.quantity
        move.uom = product.default_uom
        move.product = product
        # Add on create charge take default storage
        # from folio if isn not present
        move.from_location = charge.folio.storage
        move.to_location = customer_location
        move.state = 'draft'
        move.company = self.company
        move.unit_price = self.unit_price
        move.currency = self.company.currency
        move.planned_date = self.date_service
        move.origin = charge
        return move

    @classmethod
    def get_taxes(cls, product, party=None, currency=None):
        ctx = cls.get_context_price(product, party, currency)
        return ctx['taxes']

    @classmethod
    def pending_to_invoice(cls, folios):
        _folios = []
        _charges = []
        for folio in folios:
            if not folio.invoice_line or not folio.to_invoice:
                _folios.append(folio)
            for charge in folio.charges:
                if not charge.invoice_line and charge.to_invoice:
                    _charges.append(charge)
        return _folios, _charges

    @classmethod
    def create_channel_pay(cls, bk):
        pool = Pool()
        Note = pool.get('account.note')
        NoteLine = pool.get('account.note.line')
        Invoice = pool.get('account.invoice')
        Config = pool.get('account.voucher_configuration')
        config = Config.get_configuration()
        commission = bk.currency.round(bk.channel_commission)
        note, = Note.create([{
            'description': bk.number,
            'journal': config.default_journal_note.id,
            'date': date.today(),
            'state': 'draft',
        }])
        line1, = NoteLine.create([{
            'note': note.id,
            'debit': 0,
            'credit': commission,
            'party': bk.channel.agent.party.id,
            'account': bk.channel_invoice.account.id,
            'description': bk.ota_booking_code,
        }])
        line2, = NoteLine.create([{
            'note': note.id,
            'debit': commission,
            'credit': 0,
            'party': bk.channel.agent.party.id,
            'account': bk.channel.debit_account.id,
            'description': bk.number,
        }])
        Note.write([note], {'lines': [('add', [line1, line2])]})
        Note.post([note])
        lines_to_add = []
        for line in note.move.lines:
            if line.account.id == bk.channel_invoice.account.id:
                lines_to_add.append(line.id)
        Invoice.write([bk.channel_invoice], {
            'payment_lines': [('add', lines_to_add)],
        })
        bk.channel_invoice.save()

    @classmethod
    def create_channel_invoice(cls, bk):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        InvoiceLine = pool.get('account.invoice.line')
        Folio = pool.get('hotel.folio')
        if not bk.channel:
            return
        data = {
            'party': bk.channel.agent.party,
            'reference': bk.number,
            'description': f"{bk.ota_booking_code} | {bk.party.name}",
            'payment_term': bk.payment_term,
            'number': bk.number,
        }
        invoice = cls._get_new_invoice(data)
        invoice.on_change_invoice_type()
        invoice.save()
        for folio in bk.lines:
            if folio.invoice_line:
                continue
            _folio = {
                'product': folio.product,
                'quantity': folio.nights_quantity,
                'unit_price': folio.unit_price,
                'description': folio.room.name,
                'origin': str(folio),
                'taxes_exception': bk.taxes_exception,
            }
            line, = InvoiceLine.create([
                cls._get_invoice_line(invoice, _folio)
            ])
            Folio.write([folio], {'invoice_line': line.id})
        invoice.save()
        invoice.update_taxes([invoice])
        cls.write([bk], {'channel_invoice': invoice.id})
        Invoice.validate([invoice])
        invoice.save()
        invoice, = Invoice.browse([invoice.id])
        try:
            Invoice.submit([invoice])
            invoice.save()
        except Exception as e:
            print(e)
        try:
            invoice = Invoice(invoice.id)
            Invoice.post([invoice])
        except Exception as e:
            print(e)

    @classmethod
    def create_invoice(cls, booking_id, folios_ids, party_id=None, kind=None):
        pool = Pool()
        Folio = pool.get('hotel.folio')
        Party = pool.get('party.party')
        FolioCharge = pool.get('hotel.folio.charge')
        InvoiceLine = pool.get('account.invoice.line')
        bookings = cls.browse([booking_id])
        folios = Folio.browse(folios_ids)
        for folio in folios:
            folio.add_charge_occupancy(ctx='invoice')
        invoice = {}
        party = Party(party_id)

        invoice_to_create = cls.get_grouped_invoices(folios, kind, party)
        if not invoice_to_create:
            return

        for rec in invoice_to_create.values():
            invoice = cls._get_new_invoice(rec)
            invoice.on_change_invoice_type()
            invoice.save()

            for _line in rec['lines']:
                line, = InvoiceLine.create([
                    cls._get_invoice_line(invoice, _line)
                ])
                FolioCharge.write([_line.get('charge')], {
                    'invoice_line': line.id,
                })
            invoice.save()
            invoice.update_taxes([invoice])
        Booking.concile_charges(bookings)
        Booking.check_finished(bookings)

    def get_link_web_checkin(self, name=None):
        transaction = Transaction().context
        pool = Pool()
        host, _ = transaction['_request']['http_host'].split(":")
        db = pool.database_name
        link = f'http://{host}/app/{db}/web_checkin/{self.id}'
        return link

    @classmethod
    def _get_new_invoice(cls, data):
        pool = Pool()
        Invoice = pool.get('account.invoice')
        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()
        party = data['party']

        ota_code = data.get('ota_booking_code', '')
        description = []
        if data.get('description'):
            description.extend(data['description'])
        else:
            description.append(data['number'])
        if ota_code:
            description.append(ota_code)
        if data.get('rooms'):
            description.extend(data.get('rooms'))
        description = SEPARATOR.join(description)
        reference = data.get('reference')
        if reference:
            reference = SEPARATOR.join(ref for ref in reference if ref)
        agent = None
        if data.get('agent'):
            agent = Agent(data['agent'])

        journal, = Journal.search([('type', '=', 'revenue')], limit=1)

        address = party.address_get(type='invoice')
        payment_term_id = data.get('payment_term', None)
        if not payment_term_id:
            payment_terms = PaymentTerm.search([])
            payment_term_id = payment_terms[0].id

        return Invoice(
            company=data['company'],
            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,
        )

    def update_folio(self, state):
        Folio = Pool().get('hotel.folio')
        Folio.write(list(self.lines), {'registration_state': state})
        if state == 'pending':
            for folio in self.lines:
                folio.add_occupancy()

    @classmethod
    def add_charges_occupancy(cls, bookings):
        for booking in bookings:
            for folio in booking.lines:
                folio.add_charge_occupancy()

    @classmethod
    def do_revenue_accounting(cls, bookings):
        pool = Pool()
        Date = pool.get('ir.date')
        Move = pool.get('account.move')
        MoveLine = pool.get('account.move.line')
        Journal = pool.get('account.journal')
        Period = pool.get('account.period')
        Config = pool.get('hotel.configuration')
        journal, = Journal.search([
            ('type', '=', 'revenue'),
        ], limit=1)
        config = Config.get_configuration()
        today = Date.today()
        for bk in bookings:
            period_id = Period.find(bk.company.id, date=today)
            _move, = Move.create([{
                'date': today,
                'journal': journal.id,
                'period': period_id,
                'origin': str(bk),
                'state': 'draft',
                'description': '',
            }])
            debit = 0
            for folio in bk.lines:
                reference = folio.registration_card
                for charge in folio.charges:
                    if charge.line_move:
                        continue
                    product = charge.product
                    account_id = product.account_revenue_used.id
                    credit = charge.amount
                    debit += credit
                    desc = f'{product.rec_name} | {str(charge.date_service)}'
                    _line = {
                        'description': desc,
                        'party': bk.party.id,
                        'debit': 0,
                        'credit': credit,
                        'reference': reference,
                        'account': account_id,
                        'move': _move.id
                    }
                    mline, = MoveLine.create([_line])
                    charge.line_move = mline.id
                    charge.save()
            if not config.recognise_account:
                raise UserError(gettext('hotel.msg_missing_recognise_account'))
            recognise_account_id = config.recognise_account.id
            move_line, = MoveLine.create([{
                'description': bk.number,
                'party': bk.party.id,
                'debit': debit,
                'credit': 0,
                'reference': reference,
                'account': recognise_account_id,
                'move': _move.id
            }])
            charge.line_move = move_line.id
            charge.save()
            Move.post([_move])

    @classmethod
    def get_context_price(
        cls, product, party=None, currency=None,
        _date=None, price_list=None, taxes_exception=False
    ):
        context = {}
        if currency:
            context['currency'] = currency.id
        if party:
            context['customer'] = party.id
        if _date:
            context['sale_date'] = _date
        if price_list:
            context['price_list'] = price_list.id

        context['uom'] = product.template.default_uom.id

        # Set taxes before unit_price to have taxes in context of sale price
        taxes = []
        pattern = {}
        if not taxes_exception and party:
            for tax in product.customer_taxes_used:
                if party and party.customer_tax_rule:
                    tax_ids = party.customer_tax_rule.apply(tax, pattern)
                    if tax_ids:
                        taxes.extend(tax_ids)
                    continue
                taxes.append(tax.id)
            if party and party.customer_tax_rule:
                tax_ids = party.customer_tax_rule.apply(None, pattern)
                if tax_ids:
                    taxes.extend(tax_ids)

        context['taxes'] = taxes
        return context

    def get_total_advances(self, name):
        Advance = Pool().get('hotel.booking-account.voucher')
        advances = Advance.search([('booking', '=', self.id)])
        vouchers = sum([ad.voucher.amount_to_pay for ad in advances])
        payments = sum([pay.amount for pay in self.payments])
        folios_payments = sum([fol.total_advances for fol in self.lines])
        return self.currency.round(vouchers + payments + folios_payments)

    def get_pending_to_pay(self, name):
        res = 0
        if name == 'pending_to_pay':
            if self.total_amount:
                res = self.total_amount - (self.total_advances or 0)
                if res > -1 and res < 1:
                    res = 0
        elif name == 'pending_acco':
            res = sum(fol.pending_accommodation for fol in self.lines)
        elif name == 'pending_charges':
            res = sum(fol.pending_charges for fol in self.lines)
        return self.currency.round(res)

    def get_collect_amount(self, name):
        res = sum(folio.room_amount for folio in self.lines)
        return self.currency.round(res)

    def get_total_amount(self, name):
        res = 0
        if self.tax_amount or self.untaxed_amount:
            res = self.tax_amount + self.untaxed_amount
        return self.currency.round(res)

    def get_tax_amount(self, name):
        taxes_computed = self._get_taxes()
        res = sum([t['amount'] for t in taxes_computed], _ZERO)
        return self.currency.round(res)

    def get_untaxed_amount(self, name):
        res = []
        for folio in self.lines:
            if folio.occupancy:
                for occ in folio.occupancy:
                    if not occ.charge:
                        res.append(occ.unit_price)
            else:
                res.append(folio.room_amount)
            for charge in folio.charges:
                res.append(charge.amount)
        return self.currency.round(sum(res))

    def get_channel_commission(self, name):
        res = [
            line.commission_amount for line in self.lines
                if line.commission_amount]
        base_comm = [folio.on_change_with_room_amount() for folio in self.lines]
        for comm in self.extra_commissions:
            extras = sum(base_comm) * Decimal(comm.commission / 100)
            res.append(extras)
        return self.currency.round(sum(res))

    def send_email(self, name):
        pool = Pool()
        config = pool.get('hotel.configuration')(1)
        Template = pool.get('email.template')
        Activity = pool.get('email.activity')
        if not self.party or not self.party.email:
            return
        email = self.party.email
        template = getattr(config, name)
        if not template:
            return
        template.subject = f'{template.subject} No. {self.number}'
        if email:
            Template.send(template, self, email, attach=True)
            Activity.create([{
                'template': template.id,
                'origin': str(self),
                'status': 'sended',
            }])
        else:
            raise UserError(gettext('hotel.msg_missing_party_email'))

    @fields.depends('price_list', 'breakfast_included')
    def on_change_price_list(self):
        if self.price_list:
            self.breakfast_included = self.price_list.breakfast_included

    @classmethod
    @ModelView.button
    def send_email_checkin(cls, records):
        for booking in records:
            if booking.state == 'confirmed':
                booking.send_email('check_in_email')

    @classmethod
    @ModelView.button
    def send_email_customer_experience(cls, records):
        for booking in records:
            if booking.state == 'confirmed':
                booking.send_email('customer_experience')


class BookingReport(Report):
    __name__ = 'hotel.booking'

    @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 BookingStatementReport(Report):
    __name__ = 'hotel.booking_statement'

    @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 SelectRoomsAsk(ModelView):
    'Select Rooms Assistant'
    __name__ = 'hotel.booking.select_rooms.ask'
    arrival_date = fields.Date('Arrival Date', required=True)
    departure_date = fields.Date('Departure Date', required=True)
    accommodation = fields.Many2One('product.product',
        'Accommodation', domain=[
            ('template.kind', '=', 'accommodation'),
        ])
    rooms = fields.Many2Many('hotel.room', None, None,
        'Rooms', domain=[
            ('id', 'in', Eval('targets')),
        ])
    overbooking = fields.Boolean('Overbooking')
    targets = fields.Function(fields.Many2Many('hotel.room', None, None,
        'Targets'), 'on_change_with_targets')
    unit_price = fields.Numeric('Unit Price', digits=(16, 2), required=True)

    @fields.depends('accommodation', 'departure_date', 'arrival_date')
    def on_change_with_unit_price(self):
        Booking = Pool().get('hotel.booking')
        booking = Booking(Transaction().context.get('active_id'))
        ctx = {}
        rate_plan = booking.rate_plan
        if rate_plan:
            ctx['price_list'] = rate_plan.price_list
            ctx['sale_date'] = self.arrival_date
            ctx['currency'] = booking.currency.id
            if booking.party:
                ctx['customer'] = booking.party.id
        if self.accommodation and self.departure_date and self.arrival_date:
            product = self.accommodation
            unit_price = product.template.list_price
            quantity = (self.departure_date - self.arrival_date).days
            if rate_plan and rate_plan.price_list:
                with Transaction().set_context(ctx):
                    unit_price = rate_plan.price_list.compute(
                        booking.party, product, unit_price,
                        quantity, product.default_uom)
                    unit_price = booking.currency.round(unit_price)
            return unit_price

    @fields.depends('arrival_date', 'departure_date', 'accommodation',
        'overbooking')
    def on_change_with_targets(self, name=None):
        pool = Pool()
        RoomTemplate = pool.get('hotel.room-product.template')
        Room = pool.get('hotel.room')
        Folio = pool.get('hotel.folio')
        res = []
        if not self.accommodation or not self.arrival_date or not self.departure_date:
            return res

        if self.overbooking:
            return [r.id for r in Room.search([])]

        room_templates = RoomTemplate.search([
            ('template.accommodation_capacity', '>=', self.accommodation.accommodation_capacity)
        ])
        rooms_ids = [t.room.id for t in room_templates]
        rooms_available_ids = Folio.get_available_rooms(
            self.arrival_date,
            self.departure_date,
            rooms_ids=rooms_ids
        )

        return rooms_available_ids


class SelectRooms(Wizard):
    'Select Rooms'
    __name__ = 'hotel.booking.select_rooms'
    """
    this is the wizard that allows the front desk employee to select
    rooms, based on the requirements listed by the customer.
    """
    start = StateView(
        'hotel.booking.select_rooms.ask',
        'hotel.view_select_rooms_form', [
            Button('Exit', 'end', 'tryton-cancel'),
            Button('Add and Continue', 'add_continue', 'tryton-forward'),
            Button('Add', 'add_rooms', 'tryton-ok'),
        ]
    )
    add_rooms = StateTransition()
    add_continue = StateTransition()

    def transition_add_rooms(self):
        self._add_rooms()
        return 'end'

    def transition_add_continue(self):
        self._add_rooms()
        return 'start'

    def _add_rooms(self):
        pool = Pool()
        Folio = pool.get('hotel.folio')
        Booking = pool.get('hotel.booking')
        booking = Booking(Transaction().context.get('active_id'))
        lines_to_create = []
        product = self.start.accommodation

        for room in self.start.rooms:
            values = {
                'booking': booking.id,
                'product': product.id,
                'reference': booking.ota_booking_code,
                'contact': booking.contact,
                'room': room.id,
                'arrival_date': self.start.arrival_date,
                'departure_date': self.start.departure_date,
                'unit_price': self.start.unit_price,
            }
            if booking.party and booking.is_person():
                values['main_guest'] = booking.party.id
            values.update({'product': product.id})
            lines_to_create.append(values)
        Folio.create(lines_to_create)
        booking.save()


class BookingVoucher(ModelSQL):
    'Booking - Voucher'
    __name__ = 'hotel.booking-account.voucher'
    _table = 'booking_vouchers_rel'
    booking = fields.Many2One('hotel.booking', 'Booking',
            ondelete='CASCADE', select=True, required=True)
    voucher = fields.Many2One('account.voucher', 'Voucher', select=True,
            domain=[('voucher_type', '=', 'receipt')], ondelete='RESTRICT',
            required=True)

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


class BookingStatementLine(ModelSQL):
    'Booking - Statement Line'
    __name__ = 'hotel.booking-statement.line'
    _table = 'hotel_booking_statement_line_rel'
    booking = fields.Many2One('hotel.booking', 'Booking',
        ondelete='CASCADE', select=True, required=True)
    statement_line = fields.Many2One('account.statement.line',
        'Statement Line', ondelete='CASCADE', required=True)


class RevenueForecastStart(ModelView):
    'Revenue Forecast Start'
    __name__ = 'hotel.print_revenue_forecast.start'
    company = fields.Many2One('company.company', 'Company', required=True)
    fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear',
            required=True)
    period = fields.Many2One('account.period', 'Period', required=True,
            domain=[
                ('fiscalyear', '=', Eval('fiscalyear')),
            ])

    @staticmethod
    def default_fiscalyear():
        Fiscalyear = Pool().get('account.fiscalyear')
        fiscalyear, = Fiscalyear.search([],
            limit=1, order=[('start_date', 'DESC')])
        return fiscalyear.id

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


class RevenueForecast(Wizard):
    'Revenue Forecast'
    __name__ = 'hotel.print_revenue_forecast'
    start = StateView(
            'hotel.print_revenue_forecast.start',
            'hotel.print_revenue_forecast_start_view_form', [
                Button('Cancel', 'end', 'tryton-cancel'),
                Button('Print', 'print_', 'tryton-print', default=True),
            ])
    print_ = StateReport('hotel.revenue_forecast.report')

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

    def transition_print_(self):
        return 'end'


class RevenueForecastReport(Report):
    __name__ = 'hotel.revenue_forecast.report'

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        pool = Pool()
        Company = pool.get('company.company')
        Period = pool.get('account.period')
        Room = pool.get('hotel.room')
        BookingFolio = pool.get('hotel.folio')

        period = Period(data['period'])
        start_date = period.start_date
        end_date = period.end_date
        max_day = (end_date - start_date).days

        rooms = Room.search([])
        alldays = {}
        alldays_convert = {}
        data['revenue'] = 0
        data['occupancy_rate'] = 0
        for nd in range(31):
            day_n = 'day' + str((nd + 1))
            tdate = start_date + timedelta(nd)
            zero = 0
            if nd > max_day:
                tdate = ''
                zero = ''
            data[day_n] = tdate
            data['total_' + day_n] = zero
            data[('revenue_' + day_n)] = zero
            data[('rate_' + day_n)] = zero
            alldays[day_n] = ''
            alldays_convert[tdate] = day_n

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

        lines = BookingFolio.search(dom)
        drooms = {}

        available_nights = len(rooms) * max_day
        nights = 0
        for room in rooms:
            drooms[room.id] = {'name': room.name}
            drooms[room.id].update(alldays.copy())

        for line in lines:
            _delta = (line.departure_date - line.arrival_date).days
            for i in range(_delta):
                dt = line.arrival_date + timedelta(i)
                if dt >= start_date and dt < end_date \
                    and dt >= start_date:
                    revenue_day = float(line.unit_price) / 1000000
                    dayn = alldays_convert[dt]
                    drooms[line.room.id][dayn] = revenue_day
                    data['total_' + dayn] += 1
                    data['revenue_' + dayn] += revenue_day
                    data['revenue'] += float(line.unit_price)
                    nights += 1

        for i in range(max_day):
            day_n = 'day' + str((i + 1))
            data['rate_' + day_n] = (data['total_' + day_n] * 100.0) / len(rooms)

        if nights > 0:
            data['occupancy_rate'] = float(nights / available_nights) * 100

        report_context['records'] = list(drooms.values())
        report_context['period'] = period.name
        report_context['company'] = Company(data['company'])
        return report_context


class RoomsOccupancyStart(ModelView):
    'Rooms Occupancy Start'
    __name__ = 'hotel.print_rooms_occupancy.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 RoomsOccupancy(Wizard):
    'Rooms Occupancy'
    __name__ = 'hotel.print_rooms_occupancy'
    start = StateView(
        'hotel.print_rooms_occupancy.start',
        'hotel.print_rooms_occupancy_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Open', 'print_', 'tryton-print', default=True),
        ])
    print_ = StateReport('hotel.rooms_occupancy.report')

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

    def transition_print_(self):
        return 'end'


class RoomsOccupancyReport(Report):
    __name__ = 'hotel.rooms_occupancy.report'

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

        def _get_default_room(r):
            res = {
                'room': r.name,
                'guest': None,
                'num_guest': None,
                'party': None,
                'arrival': None,
                'departure': None,
                'booking': None,
                'registration_card': None,
                'amount': 0,
                'registration_state': None,
                'pending_total': 0,
            }
            return res

        rooms_map = {room.id: _get_default_room(room) for room in all_rooms}
        occupancy_rooms = 0
        for fo in folios:
            rooms_map[fo.room.id].update({
                'guest': fo.main_guest.name,
                'num_guest': len(fo.guests),
                'party': fo.booking.party.name if fo.booking.party else '',
                'arrival': fo.arrival_date,
                'departure': fo.departure_date,
                'registration_card': fo.registration_card,
                'amount': fo.total_amount,
                'booking': fo.booking.number,
                'registration_state': fo.registration_state_string,
                'pending_total': fo.pending_total,
            })
            occupancy_rooms += 1

        if all_rooms:
            occupancy_rate = (float(len(folios)) / len(all_rooms)) * 100
        else:
            occupancy_rate = 0

        report_context['records'] = rooms_map.values()
        report_context['occupancy_rate'] = occupancy_rate
        report_context['occupancy_rooms'] = occupancy_rooms
        report_context['company'] = Company(data['company'])
        report_context['date'] = data['date']
        report_context['user'] = User(Transaction().user).rec_name
        return report_context


class BookingStatusStart(ModelView):
    'Booking Status Start'
    __name__ = 'hotel.print_booking_status.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)
    grouped = fields.Boolean('Grouped')
    state = fields.Selection([
        ('offer', 'Offer'),
        ('confirmed', 'Confirmed'),
        ('cancelled', 'Cancelled'),
        ('not_show', 'Not Show'),
        ('finished', 'Finished'),
        ('', ''),
        ], 'State')

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

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


class BookingStatus(Wizard):
    'Booking Status'
    __name__ = 'hotel.booking_status'
    start = StateView(
        'hotel.print_booking_status.start',
        'hotel.print_booking_status_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Open', 'print_', 'tryton-print', default=True),
        ])
    print_ = StateReport('hotel.booking_status.report')

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

    def transition_print_(self):
        return 'end'


class BookingStatusReport(Report):
    __name__ = 'hotel.booking_status.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')
        dom = [
            ('arrival_date', '>=', data['start_date']),
            ('arrival_date', '<=', data['end_date']),
        ]
        if data['state']:
            dom.append(
                ('booking.state', '=', data['state'])
            )
        records = Folio.search(dom, order=[('arrival_date', 'ASC')])

        _records = {}
        for fol in records:
            bk = fol.booking
            if bk not in _records.keys():
                _records[bk] = []
            _records[bk].append(fol)

        report_context['grouped'] = data['grouped']
        report_context['records'] = _records
        report_context['company'] = Company(data['company'])
        return report_context


class InvoicePaymentForm(ModelView):
    'Invoice Payment Form'
    __name__ = 'invoice.payment.form'
    payment_mode = fields.Many2One('account.voucher.paymode', 'Payment Mode',
        required=True)
    payment_amount = fields.Numeric('Payment amount', digits=(16, 2),
        required=True)
    party = fields.Many2One('party.party', 'Party', required=True)
    pay_date = fields.Date('Advance Date', required=True)
    reference = fields.Char('Reference')

    @classmethod
    def default_advance_date(cls):
        Date = Pool().get('ir.date')
        return Date.today()


class UpdateHolderStart(ModelView):
    'Update Holder Start'
    __name__ = 'hotel.update_holder.start'
    name = fields.Char('Name', required=True)
    sex = fields.Selection([
            ('', ''),
            ('male', 'Male'),
            ('female', 'Female'),
        ], 'Sex')
    email = fields.Char('Email', required=True)
    mobile = fields.Char('Mobile')
    phone = fields.Char('Phone')
    birthday = fields.Date('Birthday')
    nationality = fields.Many2One('country.country', 'Nationality')
    origin_country = fields.Many2One('party.country_code', 'Origin Country')
    target_country = fields.Many2One('party.country_code', 'Target Country')
    country = fields.Many2One('party.country_code', 'Country')
    subdivision = fields.Many2One('party.department_code', 'Subdivision')
    city = fields.Many2One('party.city_code', 'City', domain=[
        ('department', '=', Eval('subdivision'))
    ])
    address = fields.Char('Address', required=True)
    type_document = fields.Selection(TYPE_DOCUMENT, 'Tipo de Documento',
        required=True)
    id_number = fields.Char('Id Number', required=True)
    visa_number = fields.Char('Visa Number')
    visa_date = fields.Date('Visa Date')
    notes = fields.Text('Notes')

    customer = fields.Many2One('party.party', 'Party')
    customer_id_number = fields.Char('Customer Id Number')
    customer_name = fields.Char('Customer Name')
    customer_country = fields.Many2One('party.country_code', 'Customer Country')
    customer_subdivision = fields.Many2One('party.department_code',
        'Customer Subdivision')
    customer_city = fields.Many2One('party.city_code', 'Customer City')
    customer_address = fields.Char('Customer Address')
    customer_phone = fields.Char('Customer Phone')
    customer_email = fields.Char('Customer Email')
    customer_type_document = fields.Selection(TYPE_DOCUMENT,
        'Customer Type Doc.')
    main_guest = fields.Boolean('Main Guest')
    vehicle_plate = fields.Char('Vehicle Plate')

    @fields.depends('id_number', 'name', 'sex', 'email', 'mobile',
        'visa_number', 'visa_date', 'address', 'birthday', 'nationality')
    def on_change_id_number(self):
        if self.id_number:
            pool = Pool()
            Party = pool.get('party.party')
            Guest = pool.get('hotel.folio.guest')
            clause = [('id_number', '=', self.id_number)]
            parties = Party.search(clause)
            if not parties:
                parties = Guest.search(clause)
            if not parties:
                return

            address = ''
            country = None
            subdivision = None
            party = parties[0]
            if hasattr(party, 'addresses') and party.addresses:
                _address = party.addresses[0]
                address = _address.street
                country = _address.country_code and _address.country_code.id
                subdivision = _address.department_code and _address.department_code.id
            elif hasattr(party, 'address') and party.address:
                address = party.address

            self.name = party.name
            self.sex = party.sex
            self.email = party.email
            self.mobile = party.mobile
            self.visa_number = party.visa_number
            self.visa_date = party.visa_date
            self.birthday = party.birthday
            self.nationality = party.nationality and party.nationality.id
            self.country = country
            self.subdivision = subdivision
            self.address = address


class UpdateHolder(Wizard):
    'Update Holder'
    __name__ = 'hotel.update_holder'
    start = StateView(
        'hotel.update_holder.start',
        'hotel.update_holder_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Update', 'update', 'tryton-ok', default=True),
        ])
    update = StateTransition()

    def default_start(self, fields):
        pool = Pool()
        Booking = pool.get('hotel.booking')
        Config = pool.get('hotel.configuration')
        config = Config.get_configuration()
        booking = Booking(Transaction().context['active_id'])
        party = booking.party
        nationality_id = None
        country_id = None
        if config.nationality:
            nationality_id = config.nationality.id
        if config.country:
            country_id = config.country.id

        res = {
            'nationality': nationality_id,
            'country': country_id,
        }
        if party and party.id > 0:
            if party.nationality:
                nationality_id = party.nationality.id

            address = party.addresses[0] if party.addresses else None
            res = {
                'name': party.name.upper(),
                'nationality': nationality_id,
                'id_number': party.id_number,
                'type_document': party.type_document,
                'sex': party.sex,
                'mobile': party.mobile,
                'phone': party.phone,
                'email': party.email,
                'visa_number': party.visa_number,
                'visa_date': party.visa_date,
                'birthday': party.birthday,
                'notes': party.notes,
            }
            if address:
                res['country'] = address.country_code.id if address.country_code else None
                res['city'] = address.city_code.id if address.city_code else None
                res['subdivision'] = address.department_code.id if address.department_code else None
        elif booking.contact:
            res['name'] = booking.contact.upper()
        return res

    def _set_cms(self, action, rec, email, mobile=None, phone=None):
        cms = []  # contact_mechanisms
        if mobile:
            cms.append({'type': 'mobile', 'value': mobile})
        if email:
            cms.append({'type': 'email', 'value': email})
        if phone:
            cms.append({'type': 'phone', 'value': phone})
        if cms:
            rec['contact_mechanisms'] = [(action, cms)]
        return

    def transition_update(self):
        pool = Pool()
        Booking = pool.get('hotel.booking')
        Folio = pool.get('hotel.folio')
        Guest = pool.get('hotel.folio.guest')
        Party = pool.get('party.party')
        Address = pool.get('party.address')
        CM = pool.get('party.contact_mechanism')
        active_id = Transaction().context.get('active_id', False)
        edit = True
        booking = Booking(active_id)
        _party = self.start
        to_folio = {}
        if _party.vehicle_plate:
            to_folio['vehicle_plate'] = _party.vehicle_plate

        nationality_id = _party.nationality.id if _party.nationality else None
        rec = {
            'name': _party.name.upper(),
            'nationality': nationality_id,
            'sex': _party.sex,
            'birthday': _party.birthday,
            'type_document': _party.type_document,
            'id_number': _party.id_number,
            'visa_number': _party.visa_number,
            'visa_date': _party.visa_date,
            'notes': _party.notes,
        }
        country_code = _party.country.id if _party.country else None
        city_code = _party.city.id if _party.city else None
        subdivision_code = _party.subdivision.id if _party.subdivision else None
        street = _party.address.upper() if _party.address else ''
        address = {}
        address['country_code'] = country_code
        address['city_code'] = city_code
        address['department_code'] = subdivision_code
        address['street'] = street
        party = None
        if not booking.party:
            edit = False
            parties = Party.search([
                ('id_number', '=', _party.id_number),
            ])
            if not parties:
                self._set_cms(
                    'create', rec, _party.email, _party.mobile, _party.phone
                )
                rec['addresses'] = [('create', [address])]
                # raise UserError('Este cliente ya existe!')
            else:
                party = parties[0]
                edit = True
        else:
            party = booking.party

        if party:
            if party.addresses:
                Address.write(list(party.addresses), address)
            else:
                Address.create([address])
            cms_add = {}
            if _party.mobile:
                cms_add['mobile'] = _party.mobile
            if _party.phone:
                cms_add['phone'] = _party.phone
            if _party.email:
                cms_add['email'] = _party.email
            if party.contact_mechanisms:
                for cm in party.contact_mechanisms:
                    if cm.type == 'mobile' and _party.mobile:
                        cm.value = cms_add.pop('mobile')
                    elif cm.type == 'phone' and _party.phone:
                        cm.value = cms_add.pop('phone')
                    elif cm.type == 'email' and _party.email:
                        cm.value = cms_add.pop('email')
                    cm.save()
                if cms_add:
                    for (key, value) in cms_add.items():
                        if not value:
                            continue
                        cm = CM(party=party.id, type=key, value=value)
                        cm.save()
            else:
                self._set_cms('create', rec, _party.email, _party.mobile, _party.phone)

        rec_ = None
        if _party.customer_id_number and _party.customer_name and _party.customer_type_document:
            rec_ = {
                'name': _party.customer_name,
                'id_number': _party.customer_id_number,
                'type_document': _party.customer_type_document,
            }
            address_cust = {
                'country_code': None,
                'city_code': None,
                'street': '',
            }
            if _party.customer_country:
                address_cust['country_code'] = _party.customer_country.id
            if _party.customer_city:
                address_cust['city_code'] = _party.customer_city.id
            if _party.customer_address:
                address_cust['street'] = _party.customer_address.upper()

            rec_['addresses'] = [('create', [address_cust])]
            self._set_cms(
                'create',
                rec_,
                _party.customer_email,
                phone=_party.customer_phone,
            )
            party, = Party.create([rec_])
        if edit:
            Party.write([party], rec)
            if not booking.party:
                booking.party = party.id
        else:
            party, = Party.create([rec])
            Booking.write([booking], {'party': party.id})

        booking.save()
        if _party.type_document != '31':
            for folio in booking.lines:
                if not folio.main_guest:
                    folio.main_guest = party.id
                    folio.save()
                    _ = rec.pop('contact_mechanisms', None)
                    _ = rec.pop('addresses', None)
                    rec['address'] = address['street']
                    rec['country'] = country_code
                    rec['nationality'] = nationality_id
                    rec['folio'] = folio.id
                    rec['main_guest'] = True
                    rec['email'] = _party.email
                    rec['mobile'] = _party.mobile
                    Guest.create([rec])
                break

        for folio in booking.lines:
            Folio.write([folio], to_folio)
        return 'end'


class ManagerStart(ModelView):
    'Manager Start'
    __name__ = 'hotel.print_manager.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 Manager(Wizard):
    'Manager'
    __name__ = 'hotel.print_manager'
    start = StateView(
        'hotel.print_manager.start',
        'hotel.print_manager_start_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Open', 'print_', 'tryton-print', default=True),
        ])
    print_ = StateReport('hotel.manager.report')

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

    def transition_print_(self):
        return 'end'


class ManagerReport(Report):
    __name__ = 'hotel.manager.report'

    @classmethod
    def get_location(self, party, field):
        for address in party.addresses:
            if hasattr(address, field):
                value = getattr(address, field + '_code')
                if value:
                    return value

    @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')
        rooms = Room.search([])
        total_rooms = len(rooms)
        delta_days = (data['end_date'] - data['start_date']).days + 1
        folios = Folio.search([
            ('arrival_date', '>=', data['start_date']),
            ('arrival_date', '<=', data['end_date']),
        ])
        list_channels = [
            ('ota', 'OTAS'),
            ('direct', 'DIRECTOS'),
            ('special_price', 'TARIFA ESPECIAL'),
            ('courtesy', 'CORTESIA'),
            ('house_use', 'USO DE CASA'),
        ]

        channels = {}
        rooms_occupied = []
        for ch, desc in list_channels:
            channels[ch] = {
                'name': desc,
                'room_qty': [],
                'room_rate': 0,
                'num_adults': [],
                'num_children': [],
                'income': [],
                'adr': 0,
                'rev_par': 0,
            }

        local_guests = []
        foreign_guests = []
        total_income = []
        rooms_saled = []
        guests_by_country = {}
        guests_by_city = {}
        for folio in folios:
            if folio.booking.channel:
                type_ = 'ota'
            else:
                type_ = 'direct'
            channels[type_]['room_qty'].append(folio.nights_quantity)
            channels[type_]['num_adults'].append(folio.num_adults)
            channels[type_]['num_children'].append(folio.num_children)
            channels[type_]['income'].append(folio.total_amount)
            total_income.append(folio.total_amount)
            for guest in folio.guests:
                if not guest or not guest.nationality:
                    continue
                if guest.nationality.name == 'COLOMBIA':
                    city = cls.get_location(folio.main_guest, 'city')
                    local_guests.append(1)
                    if not city:
                        continue
                    if city not in guests_by_city.keys():
                        guests_by_city[city] = {
                            'name': city.name,
                            'persons': [],
                            'nights': [],
                        }
                    guests_by_city[city]['persons'].append(1)
                    guests_by_city[city]['nights'].append(folio.nights_quantity)
                else:
                    country = cls.get_location(folio.main_guest, 'country')
                    foreign_guests.append(1)
                    if not country:
                        continue
                    if country not in guests_by_country.keys():
                        guests_by_country[country] = {
                            'name': country.name,
                            'persons': [],
                            'nights': [],
                        }

                    guests_by_country[country]['persons'].append(1)
                    guests_by_country[country]['nights'].append(folio.nights_quantity)

        for k, v in channels.items():
            room_qty = sum(v['room_qty'])
            income = sum(v['income'])
            v['room_qty'] = room_qty
            v['room_rate'] = room_qty / (total_rooms * delta_days)
            v['num_adults'] = sum(v['num_adults'])
            v['num_children'] = sum(v['num_children'])
            v['income'] = income
            if income > 0:
                v['adr'] = income / room_qty
                v['rev_par'] = income / delta_days
            rooms_occupied.append(room_qty)
            if k in ('direct', 'ota'):
                rooms_saled.append(room_qty)

        available_nights = total_rooms * delta_days
        beds_capacity = []
        for room in rooms:
            beds_capacity.append(room.main_accommodation.accommodation_capacity or 0)

        available_beds = sum(beds_capacity) * delta_days
        average_price = sum(total_income) / sum(rooms_saled)
        report_context['records'] = channels.values()
        report_context['rooms_occupied'] = sum(rooms_occupied)
        report_context['rooms_saled'] = sum(rooms_saled)
        report_context['gross_occupancy'] = sum(rooms_occupied) / available_nights
        report_context['net_occupancy'] = sum(rooms_saled) / available_nights
        report_context['total_income'] = sum(total_income)
        report_context['local_guests'] = sum(local_guests)
        report_context['foreign_guests'] = sum(foreign_guests)
        report_context['available_nights'] = available_nights
        report_context['available_beds'] = available_beds
        report_context['average_price'] = average_price
        report_context['guests_by_country'] = guests_by_country.values()
        report_context['guests_by_city'] = guests_by_city.values()
        report_context['company'] = Company(data['company'])
        user_id = Transaction().context.get('user')
        report_context['user'] = User(user_id)
        return report_context


class BookingChannelCommision(ModelSQL):
    'Booking Channel Commision'
    __name__ = 'hotel.booking-channel.commission'
    _table = 'hotel_booking_channel_commission_rel'
    commission = fields.Many2One('hotel.channel.commission',
        'Channel Commission', ondelete='CASCADE', required=True)
    booking = fields.Many2One('hotel.booking', 'Booking', ondelete='RESTRICT',
        required=True)


class StatementPaymentForm(ModelView):
    'Statement Payment Form'
    __name__ = 'hotel.payment_form.start'
    statement = fields.Many2One('account.statement', 'Statement',
        required=True, domain=['OR', [
            ('create_uid.login', '=', Eval('user')),
            ('state', '=', 'draft')
        ], [
            Eval('user') == 'admin',
            ('state', '=', 'draft'),
        ]])
    amount_to_pay = fields.Numeric('Amount to Pay', required=True,
        digits=(16, Eval('currency_digits', 2)),
        depends=['currency_digits'])
    currency_digits = fields.Integer('Currency Digits')
    kind = fields.Selection([
        ('booking', 'Booking'),
        ('folio', 'Folio'),
        ], 'Kind', required=True)
    folio = fields.Many2One('hotel.folio', 'Folio', domain=[
            ('id', 'in', Eval('folios')),
        ], states={
            'invisible': Eval('kind') == 'booking',
            'required': Eval('kind') == 'folio',
        }, depends=['folios', 'kind']
    )
    voucher = fields.Char('Voucher', states={
            'required': Bool(Eval('require_voucher')),
        }, depends=['require_voucher'])
    party = fields.Many2One('party.party', 'Party', domain=[
        ('id', 'in', Eval('parties'))
        ], required=True)
    user = fields.Many2One('res.user', 'User', states={'readonly': True})
    require_voucher = fields.Boolean('Require Voucher', depends=['statement'])
    parties = fields.Many2Many('party.party', None, None, 'Parties')
    folios = fields.Many2Many('hotel.folio', None, None, 'Folios')

    @classmethod
    def default_require_voucher(cls):
        return False

    @classmethod
    def default_user(cls):
        user = Pool().get('res.user')(Transaction().user)
        return user.id

    @classmethod
    def default_kind(cls):
        model = Transaction().context['active_model']
        if model == "hotel.folio":
            return "folio"
        else:
            return "booking"

    @fields.depends('statement', 'require_voucher')
    def on_change_with_require_voucher(self):
        if self.statement:
            return self.statement.journal.require_voucher
        return False


class WizardStatementPayment(Wizard):
    'Wizard Statement Payment'
    __name__ = 'hotel.payment_form'
    start = StateView(
        'hotel.payment_form.start',
        'hotel.statement_payment_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Pay', 'pay_', 'tryton-ok', default=True),
        ])
    pay_ = StateTransition()

    def default_start(self, fields):
        pool = Pool()
        Booking = pool.get('hotel.booking')
        Folio = pool.get('hotel.folio')
        model = Transaction().context['active_model']
        active_id = Transaction().context['active_id']
        parties_ids = []
        folios_ids = []
        folio_id = None
        party_id = None
        pending_to_pay = 0
        kind = 'booking'
        if model == "hotel.booking":
            booking = Booking(active_id)
            if not booking.party:
                raise UserError(gettext('hotel.msg_missing_party_holder'))

            if booking.responsible_payment in ('holder', 'holder_guest'):
                parties_ids.append(booking.party.id)
            if booking.responsible_payment in ('guest', 'holder_guest'):
                for folio in booking.lines:
                    parties_ids.append(folio.main_guest.id)
                    folios_ids.append(folio.id)
            pending_to_pay = booking.pending_to_pay
        else:
            kind = 'folio'
            folio = Folio(active_id)
            folios_ids.append(folio.id)
            parties_ids.append(folio.main_guest.id)
            if folio.booking.responsible_payment == 'guest':
                pending_to_pay = folio.pending_total
            elif folio.booking.responsible_payment == 'holder_guest':
                pending_to_pay = folio.pending_charges
            if folio.booking.party == folio.main_guest and \
                    folio.booking.responsible_payment == 'holder':
                pending_to_pay = folio.booking.pending_to_pay

        return {
            'currency_digits': 2,
            'party': party_id,
            'folio': folio_id,
            'parties': parties_ids,
            'folios': folios_ids,
            'amount_to_pay': pending_to_pay,
            'kind': kind,
        }

    def transition_pay_(self):
        pool = Pool()
        Booking = pool.get('hotel.booking')
        Folio = pool.get('hotel.folio')
        active_id = Transaction().context.get('active_id', False)

        form = self.start
        if form.kind == 'booking':
            record = Booking(active_id)
            booking = record
            # description = record.number
        else:
            record = Folio(form.folio.id)
            booking = record.booking
            # if record.booking.responsible_payment != 'holder':
            #     description = record.registration_card
            # else:
            #     description = booking.number

        args = {
            'statement': form.statement.id,
            'amount_to_pay': form.amount_to_pay,
            'kind': form.kind,
            'folio': form.folio.id if form.folio else None,
            'booking': booking.id,
            'number': form.voucher or '',
            'party': form.party.id if form.party else None,
        }
        print("args.....", args)
        Booking.add_payment(args)
        # if form.amount_to_pay:
        #     amount = form.amount_to_pay
        #     if abs(booking.pending_to_pay - amount) <= ADJUST_CURRENCY:
        #         amount = booking.pending_to_pay
        #     line = StatementLine(
        #         statement=form.statement.id,
        #         date=date.today(),
        #         amount=amount,
        #         party=form.party.id,
        #         account=account,
        #         number=number,
        #         description=description,
        #         voucher=self.start.voucher,
        #         source=str(record),
        #     )
        #     line.save()
        #     line.create_move()
        #
        #     # We keep to mark all concepts charges/folios as paid if
        #     # the customer to pay all pending values
        #     if form.kind == 'folio':
        #         if target_model == 'folio':
        #             folio = record
        #             if folio.booking.responsible_payment == 'guest':
        #                 pending = folio.pending_total
        #             elif folio.booking.responsible_payment == 'holder_guest':
        #                 pending = folio.pending_charges
        #             if form.amount_to_pay >= pending:
        #                 for charge in record.charges:
        #                     if charge.status == 'paid':
        #                         continue
        #                     charge.status = 'paid'
        #                     charge.save()
        #             folio.payment_status = 'paid'
        #
        #     if target_model == 'booking':
        #         if form.amount_to_pay >= record.pending_to_pay:
        #             for folio in record.lines:
        #                 for charge in folio.charges:
        #                     if charge.status == 'paid':
        #                         continue
        #                     charge.status = 'paid'
        #                     charge.save()
        #                 folio.payment_status = 'paid'
        #                 folio.save()
        #
        # record.save()
        return 'end'


class BillBookingStart(ModelView):
    'Bill Booking Form'
    __name__ = 'hotel.bill_booking.start'
    kind = fields.Selection([
        ('all', 'All'),
        ('only_accommodation', 'Only Accommodation'),
        ('only_products', 'Only Products'),
        ], 'Kind', required=True)
    party = fields.Many2One('party.party', 'Bill To', domain=[
        ('id', 'in', Eval('parties')),
    ])
    parties = fields.Many2Many('party.party', None, None, 'Parties')


class BillBooking(Wizard):
    'Bill Booking'
    __name__ = 'hotel.bill_booking'
    start = StateView(
        'hotel.bill_booking.start',
        'hotel.bill_booking_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Invoice', 'bill', 'tryton-ok', default=True),
        ])
    bill = StateTransition()

    def default_start(self, fields):
        pool = Pool()
        Booking = pool.get('hotel.booking')
        Folio = pool.get('hotel.folio')
        active_id = Transaction().context.get('active_id', False)
        active_model = Transaction().context.get('active_model')
        parties_ids = []
        if active_model == 'hotel.booking':
            bk = Booking(active_id)
            if bk.party:
                parties_ids = [bk.party.id]
            for fo in bk.lines:
                if fo.main_guest:
                    parties_ids.append(fo.main_guest.id)
        else:
            folio = Folio(active_id)
            parties_ids = [folio.id]
        return {
            'parties': parties_ids,
        }

    def transition_bill(self):
        pool = Pool()
        Booking = pool.get('hotel.booking')
        Folio = pool.get('hotel.folio')
        active_id = Transaction().context.get('active_id', False)
        active_model = Transaction().context.get('active_model')
        if active_model == 'hotel.booking':
            bk = Booking(active_id)
            folios_ids = bk.lines
        else:
            folio = Folio(active_id)
            folios_ids = [folio]
            bk = folio.booking

        kind = self.start.kind
        party_id = self.start.party.id if self.start.party else bk.party.id
        Booking.create_invoice(bk.id, folios_ids, party_id=party_id, kind=kind)
        # Moved to create invoice method
        # Booking.concile_charges([bk])
        # Booking.check_finished([bk])
        return 'end'


class OperationForecastStart(ModelView):
    'Operation Forecast Start'
    __name__ = 'hotel.print_operation_forecast.start'
    company = fields.Many2One('company.company', 'Company', required=True)
    fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear',
            required=True)
    period = fields.Many2One('account.period', 'Period', required=True,
            domain=[
                ('fiscalyear', '=', Eval('fiscalyear')),
            ])

    @staticmethod
    def default_fiscalyear():
        Fiscalyear = Pool().get('account.fiscalyear')
        fiscalyear, = Fiscalyear.search([], limit=1, order=[
            ('start_date', 'DESC')])
        return fiscalyear.id

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


class OperationForecast(Wizard):
    'Operation Forecast'
    __name__ = 'hotel.print_operation_forecast'
    start = StateView(
            'hotel.print_operation_forecast.start',
            'hotel.print_operation_forecast_start_view_form', [
                Button('Cancel', 'end', 'tryton-cancel'),
                Button('Print', 'print_', 'tryton-print', default=True),
            ])
    print_ = StateReport('hotel.operation_forecast.report')

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

    def transition_print_(self):
        return 'end'


class OperationForecastReport(Report):
    "Operation Forecast Report"
    __name__ = 'hotel.operation_forecast.report'

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        pool = Pool()
        Company = pool.get('company.company')
        Period = pool.get('account.period')
        Room = pool.get('hotel.room')
        Maintenance = pool.get('hotel.maintenance')
        Occupancy = pool.get('hotel.folio.occupancy')

        period = Period(data['period'])
        start_date = period.start_date
        end_date = period.end_date

        range_day = (end_date - start_date).days + 1
        default_data = {
            'available_rooms': 0,
            'occupied_rooms': 0,
            'pax': 0,
            'rooms_complementary': 0,
            'info_complementary': '',
            'rooms_maintenance': 0,
            'occupancy_rate': 0,
            'revenue': 0,
            'balance': 0,
            'average_price': 0,
        }

        records = []

        rooms = Room.search([])
        pax = []
        occupied_rooms = []
        revenue = []
        balance = 0

        totals = copy.deepcopy(default_data)
        today = date.today()
        for key, value in default_data.items():
            totals[key + '_effective'] = []
            totals[key + '_forecast'] = []

        available_rooms = 0
        for nday in range(range_day):
            available_rooms = len(rooms)
            rooms_complementary = 0
            rooms_maintenance = 0
            tdate = start_date + timedelta(nday)
            data_day = copy.deepcopy(default_data)
            data_day['date'] = tdate
            is_sunday = 'no'
            if tdate.weekday() == 6:
                is_sunday = 'yes'
            data_day['sunday'] = is_sunday
            occupancies = Occupancy.search([
                ('occupancy_date', '=', str(tdate))
            ])
            occ_rooms = len(occupancies)

            mnts_rooms = Maintenance.search_read([
                ('start_date', '<=', tdate),
                ('end_date', '>=', tdate),
            ])
            rooms_maintenance = len(mnts_rooms)
            data_day['rooms_maintenance'] = rooms_maintenance
            if mnts_rooms:
                available_rooms -= rooms_maintenance

            data_day['available_rooms'] = available_rooms
            data_day['occupied_rooms'] = occ_rooms
            occupied_rooms.append(occ_rooms)

            sum_pax = sum([len(occ.folio.guests) for occ in occupancies])
            data_day['pax'] = sum_pax
            pax.append(sum_pax)
            amounts = []
            for occ in occupancies:
                if occ.charge:
                    amount = occ.charge.unit_price
                else:
                    amount = occ.unit_price
                if occ.folio.booking.complementary:
                    rooms_complementary += 1
                amounts.append(amount)

            data_day['rooms_complementary'] = rooms_complementary
            total_amounts = sum(amounts)
            data_day['revenue'] = total_amounts
            revenue.append(total_amounts)

            balance += total_amounts
            data_day['balance'] = balance
            data_day['occupancy_rate'] = occ_rooms / available_rooms

            average_price = None
            if occ_rooms:
                average_price = round(total_amounts / occ_rooms, 2)
            data_day['average_price'] = average_price
            records.append(data_day)

            if tdate < today:
                row = '_effective'
            else:
                row = '_forecast'

            totals['available_rooms' + row].append(available_rooms)
            totals['occupied_rooms' + row].append(occ_rooms)
            totals['pax' + row].append(sum_pax)
            totals['rooms_complementary' + row].append(rooms_complementary)
            totals['rooms_maintenance' + row].append(rooms_maintenance)
            totals['revenue' + row].append(total_amounts)

        for field in (
            'available_rooms', 'occupied_rooms', 'pax',
                'rooms_complementary', 'revenue', 'rooms_maintenance'):
            _effective = field + '_effective'
            _forecast = field + '_forecast'
            totals[_effective] = sum(totals[_effective])
            totals[_forecast] = sum(totals[_forecast])

        average_price_effective = ''
        average_price_forecast = ''
        occupancy_rate_effective = ''
        occupancy_rate_forecast = ''
        if totals['occupied_rooms_effective'] > 0:
            average_price_effective = totals['revenue_effective'] / totals['occupied_rooms_effective']
        if totals['occupied_rooms_forecast'] > 0:
            average_price_forecast = totals['revenue_forecast'] / totals['occupied_rooms_forecast']
        if totals['available_rooms_effective'] > 0:
            occupancy_rate_effective = totals['occupied_rooms_effective'] / totals['available_rooms_effective']
        if totals['available_rooms_forecast'] > 0:
            occupancy_rate_forecast = totals['occupied_rooms_forecast'] / totals['available_rooms_forecast']

        totals['occupancy_rate_effective'] = occupancy_rate_effective
        totals['occupancy_rate_forecast'] = occupancy_rate_forecast
        totals['average_price_effective'] = average_price_effective
        totals['average_price_forecast'] = average_price_forecast
        data.update(totals)
        total_available_rooms = len(rooms) * range_day
        total_occupied_rooms = sum(occupied_rooms)
        data['available_rooms'] = total_available_rooms
        data['occupied_rooms'] = total_occupied_rooms
        data['pax'] = sum(pax)
        data['revenue'] = sum(revenue)
        data['balance'] = balance
        _average_price = None
        _occupancy_rate = None
        if total_occupied_rooms > 0:
            _average_price = sum(revenue) / sum(occupied_rooms)
        if total_available_rooms > 0:
            _occupancy_rate = round(
                total_occupied_rooms / total_available_rooms, 2)
        data['average_price'] = _average_price
        data['occupancy_rate'] = _occupancy_rate

        report_context['records'] = records
        report_context['period'] = period.name
        report_context['company'] = Company(data['company'])
        return report_context


class RevenueSegmentationStart(ModelView):
    'Revenue Segmentation Start'
    __name__ = 'hotel.revenue_segmentation.start'
    company = fields.Many2One('company.company', 'Company', required=True)
    fiscalyear = fields.Many2One('account.fiscalyear', 'Fiscalyear',
        required=True)
    kind = fields.Selection([
        ('by_day', 'By Day'),
        ('by_months', 'By Months'),
        ], 'Kind', required=True)
    period = fields.Many2One('account.period', 'Period', states={
            'invisible': Eval('kind') != 'by_day',
            'required': Eval('kind') == 'by_day'
        },
        domain=[
            ('fiscalyear', '=', Eval('fiscalyear')),
        ])
    periods = fields.Many2Many('account.period', None, None, 'Periods',
        states={
            'required': Eval('kind') == 'by_months',
            'invisible': Eval('kind') != 'by_months',
        }, domain=[
            ('fiscalyear', '=', Eval('fiscalyear')),
        ])

    @staticmethod
    def default_fiscalyear():
        Fiscalyear = Pool().get('account.fiscalyear')
        fiscalyear, = Fiscalyear.search([], limit=1, order=[
            ('start_date', 'DESC')])
        return fiscalyear.id

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


class RevenueSegmentation(Wizard):
    'Revenue Segmentation'
    __name__ = 'hotel.revenue_segmentation'
    start = StateView(
            'hotel.revenue_segmentation.start',
            'hotel.revenue_segmentation_start_view_form', [
                Button('Cancel', 'end', 'tryton-cancel'),
                Button('Print', 'print_', 'tryton-print', default=True),
            ])
    print_ = StateReport('hotel.revenue_segmentation.report')

    def do_print_(self, action):
        company = self.start.company
        data = {
            'company': company.id,
            'kind': self.start.kind,
            'period': self.start.period.id if self.start.period else None,
            'periods': [period.id for period in self.start.periods],
        }
        return action, data

    def transition_print_(self):
        return 'end'


class RevenueSegmentationReport(Report):
    "Revenue Segmentation Report"
    __name__ = 'hotel.revenue_segmentation.report'

    @classmethod
    def _set_indicators(cls, dict_data, record, price):
        record_id = record.id
        if record_id not in dict_data.keys():
            dict_data[record_id] = {
                'name': record.name,
                'nights': [],
                'amount': [],
                'average_price': 0,
            }
        dict_data[record_id]['amount'].append(price)
        dict_data[record_id]['nights'].append(1)

    @classmethod
    def _compute_segments(cls, data, segments):
        for segment in segments:
            _type, values = segment
            total_amount = []
            total_nights = []
            for _id, value in values.items():
                amount = sum(value['amount'])
                nights = sum(value['nights'])
                total_amount.append(amount)
                total_nights.append(nights)
                value['amount'] = amount
                value['nights'] = nights
                value['average_price'] = amount / nights

            data[_type + '_nights'] = sum(total_nights)
            data[_type + '_amount'] = sum(total_amount)
            average_price = ''
            if total_nights:
                average_price = sum(total_amount) / sum(total_nights)
            data[_type + '_average_price'] = average_price

    @classmethod
    def _get_days_data(cls, period, _data, default_data):
        pool = Pool()
        Occupancy = pool.get('hotel.folio.occupancy')
        channels = {}
        corporative = {}
        groups = {}
        records = []
        start_date = period.start_date
        end_date = period.end_date
        range_day = (end_date - start_date).days + 1
        for nday in range(range_day):
            tdate = start_date + timedelta(nday)
            data_day = copy.deepcopy(default_data)
            data_day['date'] = tdate
            occupancies = Occupancy.search([
                ('occupancy_date', '=', str(tdate))
            ])

            for occ in occupancies:
                price = occ.unit_price
                booking = occ.folio.booking
                field = 'direct'
                record = None
                if booking.channel:
                    channel = booking.channel
                    field = channel.code
                    seg_data = channels
                    if field not in ('booking', 'expedia'):
                        field = 'other'
                    record = channel
                elif booking.corporative:
                    field = 'corporative'
                    if booking.party:
                        record = booking.party
                        seg_data = corporative
                elif booking.group:
                    field = 'group'
                    if booking.party:
                        record = booking.party
                        seg_data = groups
                elif booking.media == 'web':
                    field = 'web'
                if record:
                    cls._set_indicators(seg_data, record, price)

                data_day[field].append(price)
                data_day['room_' + field].append(1)

                _data[field].append(price)
                _data['room_' + field].append(1)

            records.append(data_day)

        segments = [
            ('channels', channels),
            ('corporative', corporative),
            ('groups', groups),
        ]
        cls._compute_segments(_data, segments)
        return records, channels, corporative, groups

    @classmethod
    def _get_months_data(cls, periods, _data, default_data):
        Occupancy = Pool().get('hotel.folio.occupancy')
        channels = {}
        corporative = {}
        groups = {}
        records = []
        for period in periods:
            _month = {
                'date': period.name,
            }
            _month.update(copy.deepcopy(default_data))
            start = period.start_date
            end = period.end_date
            days = (end - start).days + 1

            _dates = [start + timedelta(nday) for nday in range(days)]
            occupancies = Occupancy.search([
                ('occupancy_date', 'in', _dates)
            ])

            for occ in occupancies:
                price = occ.unit_price
                booking = occ.folio.booking
                field = 'direct'
                record = None
                if booking.channel:
                    field = booking.channel.code
                    if field not in ('booking', 'expedia'):
                        field = 'other'
                    record = booking.channel
                    seg_data = channels
                elif booking.corporative:
                    field = 'corporative'
                    record = booking.party
                    seg_data = corporative
                elif booking.group:
                    field = 'group'
                    record = booking.party
                    seg_data = groups
                elif booking.media == 'web':
                    field = 'web'

                if record:
                    cls._set_indicators(seg_data, record, price)
                _month[field].append(price)
                _month['room_' + field].append(1)

                _data[field].append(price)
                _data['room_' + field].append(1)

            records.append(_month)

        segments = [
            ('channels', channels),
            ('corporative', corporative),
            ('groups', groups),
        ]
        cls._compute_segments(_data, segments)
        return records, channels, corporative, groups

    @classmethod
    def get_context(cls, records, header, data):
        report_context = super().get_context(records, header, data)
        pool = Pool()
        Company = pool.get('company.company')
        Period = pool.get('account.period')
        data.update({
            'channels_nights': 0,
            'channels_amount': 0,
            'channels_average_price': 0,
            'corporative_nights': 0,
            'corportative_amount': 0,
            'corportative_average_price': 0,
        })

        default_data = {
            'complementary': [],
            'room_complementary': [],
            'booking': [],
            'room_booking': [],
            'expedia': [],
            'room_expedia': [],
            'other': [],
            'room_other': [],
            'web': [],
            'room_web': [],
            'direct': [],
            'room_direct': [],
            'corporative': [],
            'room_corporative': [],
            'group': [],
            'room_group': [],
        }
        _data = copy.deepcopy(default_data)

        records = []
        if data['kind'] == 'by_day':
            period = Period(data['period'])
            records, channels, corporative, groups = cls._get_days_data(
                period, _data, default_data
            )
            desc_periods = period.name
        else:
            periods = Period.browse(data['periods'])
            records, channels, corporative, groups = cls._get_months_data(
                periods, _data, default_data)
            desc_periods = ' | '.join([pd.name for pd in periods])

        for rec in records:
            for key, values in rec.items():
                if key == 'date':
                    continue
                rec[key] = sum(values)

        for key, value in default_data.items():
            if key == 'date':
                continue
            _data[key] = sum(_data[key])

        data.update(_data)
        report_context['records'] = records
        report_context['channels'] = channels.values()
        report_context['corporative'] = corporative.values()
        report_context['groups'] = groups.values()
        report_context['periods'] = desc_periods
        report_context['company'] = Company(data['company'])
        return report_context


class UpdateTaxesStart(ModelView):
    'Update Taxes Start'
    __name__ = 'hotel.booking.update_taxes.start'
    DOMAIN = [
        ('parent', '=', None),
        ['OR',
            ('group', '=', None),
            ('group.kind', 'in', ['sale', 'both'])
        ],
    ]
    taxes_to_add = fields.Many2Many('account.tax', None, None,
        'Taxes to Add', domain=DOMAIN)
    taxes_to_remove = fields.Many2Many('account.tax', None, None,
        'Taxes to Remove', domain=DOMAIN)


class UpdateTaxes(Wizard):
    'Update Taxes'
    __name__ = 'hotel.booking.update_taxes'
    start = StateView(
        'hotel.booking.update_taxes.start',
        'hotel.booking_update_taxes_view_form', [
            Button('Cancel', 'end', 'tryton-cancel'),
            Button('Ok', 'accept', 'tryton-ok', default=True),
        ])
    accept = StateTransition()

    def transition_accept(self):
        pool = Pool()
        Booking = pool.get('hotel.booking')
        Charge = Pool().get('hotel.folio.charge')
        active_id = Transaction().context.get('active_id', False)
        booking = Booking(active_id)
        to_update = []
        if self.start.taxes_to_add:
            to_update.append(('add', self.start.taxes_to_add))
        if self.start.taxes_to_remove:
            to_update.append(('remove', self.start.taxes_to_remove))
        if to_update:
            for folio in booking.lines:
                Charge.write(list(folio.charges), {'taxes': to_update})
        return 'end'
