package com.gridnine.xtrip.server.reports.templates

import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.common.l10n.model.L10nStringHelper
import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.booking.*
import com.gridnine.xtrip.common.model.booking.xtriphotels.HotelProduct
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.dict.DictionaryCache
import com.gridnine.xtrip.common.model.dict.GeoLocation
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.helpers.*
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.model.profile.Person
import groovy.transform.Field
import org.apache.commons.lang.StringUtils
import java.text.SimpleDateFormat

processIncomingData()
createSpreadSheetStyle()
composeReport()

class Hotel
{
    String subagentName
    String agent
    String group
    ProductStatus productStatus
    String issueDate
    String checkIn
    String checkOut
    String hotelCountry
    String hotelCity
    String hotelName
    String travellersName
    String currencyCode
    String reservationNumber
    String blankOwner
    String bookingNumberPortbilet
    BigDecimal supplierFee
    String formTax
    BigDecimal agencyCommision
    BigDecimal vipCommision
    String supplier
    BigDecimal vipFee
    BigDecimal totalFee
}

class TotalKey
{
    FareType fareType
    String object
    ProductStatus productStatus

    TotalKey(FareType fareType, String object, ProductStatus productStatus)
    {
        this.fareType = fareType
        this.object = object
        this.productStatus = productStatus
    }

    @Override
    public String toString()
    {
        return "TotalKey{" + "fareType=" + fareType + ", object='" + object +
                '\'' + ", productStatus=" + productStatus + '}';
    }

    boolean equals(o)
    {
        if (this.is(o))
        {
            return true
        }
        if (getClass() != o.class)
        {
            return false
        }

        TotalKey totalKey = (TotalKey) o

        if (fareType != totalKey.fareType)
        {
            return false
        }
        if (object != totalKey.object)
        {
            return false
        }
        if (productStatus != totalKey.productStatus)
        {
            return false
        }

        return true
    }

    int hashCode()
    {
        int result
        result = (fareType != null ? fareType.hashCode() : 0)
        result = 31 * result + (object != null ? object.hashCode() : 0)
        result = 31 * result +
                (productStatus != null ? productStatus.hashCode() : 0)
        return result
    }
}

enum FareType
{
    SUPPLIER_FEE, VIP_FEE, TOTAL_FEE, SUB_AGENCY_COMMISSION, VENDOR_COMMISSION
}

@Field private String specifiedProfileGroup = ''
@Field private String specifiedSubAgents = ''
@Field private String specifiedAgents = ''
@Field private String specifiedGds = ''
@Field private boolean isGroupedByAgent = false
@Field private boolean isGroupedByProfileGroup = false
@Field private boolean isByDepartureDate = false

@Field private Map<TotalKey, BigDecimal> totalMap = new HashMap<TotalKey, BigDecimal>()

@Field SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy")

private void createSpreadSheetStyle()
{
    createStyle(name: 'title', fontBold: true, h_span: getColumnShiftTotal(20),
                h_alignment: 'CENTER',
                v_alignment: 'CENTER', fontHeight: 20)
    createStyle(name: 'metadataTitle', fontBold: true, h_span: 2,
                h_alignment: 'RIGHT', v_alignment: 'CENTER',
                fontHeight: 10)
    createStyle(name: 'metadataValue', parent: 'metadataTitle', h_span: 10,
                fontBold: false, h_alignment: 'LEFT')
    createStyle(name: 'metadataDateValue', parent: 'metadataValue',
                format: 'm/d/yy')
    createStyle(name: 'header', fontBold: false, h_alignment: 'CENTER',
                v_alignment: 'CENTER', fontHeight: 10,
                leftBorder: 'MEDIUM', rightBorder: 'MEDIUM',
                topBorder: 'MEDIUM', bottomBorder: 'MEDIUM',
                wrapText: true)
    createStyle(name: 'data', fontBold: false, h_alignment: 'CENTER',
                v_alignment: 'CENTER', fontHeight: 10,
                leftBorder: 'THIN', rightBorder: 'THIN', topBorder: 'THIN',
                bottomBorder: 'THIN')
    createStyle(name: 'textData', parent: 'data')
    createStyle(name: 'numberData', parent: 'data', h_alignment: 'RIGHT',
                format: '#,##0.00')
    createStyle(name: 'preliminaryTotalText', parent: 'data',
                h_span: getColumnShiftTotal(15), fontBold: true,
                h_alignment: 'RIGHT')
    createStyle(name: 'finalTotalText', parent: 'data', h_span: 2,
                fontBold: true, h_alignment: 'RIGHT')
    createStyle(name: 'totalNumber', fontBold: true, parent: 'numberData')
}

private String getSpecifiedSubAgents()
{
    List objectList = parameters['SUBAGENT']
    String result = StringUtils.join(new ArrayList() {
        {
            objectList.each {
                def object = EntityStorage.get().resolve(it)?.getEntity()
                add(ProfileHelper.getFullName(object,
                                              LocaleHelper.getCurrentLocale(),
                                              false))
            }
        }
    }, ', ')
    return result
}

private String getSpecifiedProfileGroup()
{
    def group = parameters['PROFILE_GROUP']
    return group == null ? '' : group.toString()
}

private String getSpecifiedAgents()
{
    List objectList = parameters['AGENT']
    String result = StringUtils.join(new ArrayList() {
        {
            objectList.each {
                def object = EntityStorage.get().resolve(it)?.getEntity()
                add(ProfileHelper.getFullName(object,
                                              LocaleHelper.getCurrentLocale(),
                                              false))
            }
        }
    }, ', ')
    return result
}

private String getSpecifiedGds()
{
    def gds = parameters['RESERVATION_SYSTEM']
    return gds == null ? '' : gds.toString()
}

private boolean isGroupedByAgent()
{
    return parameters['GROUP_BY_AGENT']
}

private boolean isGroupedByProfileGroup()
{
    return parameters['GROUP_BY_PROFILE_GROUP']
}

private boolean isByDepartureDate()
{
    return parameters['BY_DEPARTURE_DATE']
}

private void processIncomingData()
{
    this.specifiedSubAgents = getSpecifiedSubAgents()
    this.specifiedProfileGroup = getSpecifiedProfileGroup()
    this.specifiedAgents = getSpecifiedAgents()
    this.specifiedGds = getSpecifiedGds()
    this.isGroupedByAgent = isGroupedByAgent()
    this.isGroupedByProfileGroup = isGroupedByProfileGroup()
    this.isByDepartureDate = isByDepartureDate()
}

private void composeReport()
{
    page { 'Отели' } {
        def hotelList = []
        allTickets.each { CommonProductIndex index ->
            Hotel hotel = new Hotel()

            def bookingCtr = EntityStorage.get().resolve(index.source)
            if (!bookingCtr)
            {
                return
            }
            BookingFile bookingFile = bookingCtr.entity

            def prod = BookingHelper.findProductByUid(index.navigationKey,
                                                      bookingCtr.entity)
            if (!prod)
            {
                return
            }
            if (prod != null)
            {
                BaseProduct baseProduct = prod
                if (prod instanceof HotelProduct)
                {
                    HotelProduct product = (HotelProduct) baseProduct
                    EntityReference<Organization> subagencyRef = HotelProductHelper.getSubagency(
                            product)
                    Reservation reservation = product.getReservation()
                    boolean negate = false
                    if ((product.getStatus() == ProductStatus.REFUND) ||
                            (product.getStatus() == ProductStatus.EXCHANGE))
                    {
                        negate = true
                    }
                    populateTaxForm(subagencyRef, hotel)
                    populateSubAgent(hotel, subagencyRef)
                    if (isGroupedByAgent || !specifiedAgents.isEmpty())
                    {
                        populateAgent(reservation, hotel)
                    }
                    if (isGroupedByProfileGroup ||
                            !specifiedProfileGroup.isEmpty())
                    {
                        populateProfileGroup(index, hotel)
                    }
                    populateProductStatus(product, hotel)
                    populateIssueDate(product, hotel)
                    populateCheckInDate(product, hotel)
                    populateCheckOutDate(product, hotel)
                    populateHotelCountry(product, hotel)
                    populateHotelCity(product, hotel)
                    populateHotelName(hotel, product)
                    populateTravellersNames(product, hotel)
                    populateCurrencyCode(hotel)
                    populateReservationNumber(reservation, hotel)
                    populatePrtbBookingNumber(bookingFile, hotel)
                    populateSupplierFee(product, negate, hotel)
                    populateSubAgencyComission(product, negate, hotel)
                    populateVendorCommission(product, negate, hotel)
                    populateVipFee(product, hotel)
                    populateTotalFee(product, hotel)
                    populateBlankOwner(product, hotel)
                    populateSupplier(product, hotel)
                    if (isByDepartureDate)
                    {
                        if (HotelProductHelper.getLastCheckoutDate(product) >
                                new Date())
                        {
                            return
                        }
                    }
                    hotelList.add(hotel)
                }
            }
        }

        fillSpreadSheetHeader()
        fillTableHeader()
        fillTable(hotelList)
        fillTableFooter()
    }
}

private void fillSpreadSheetHeader()
{
    processNextCell(0, 0) {
        rowHeight(30)
        text('Отчет по продажам отелей ', 'title')
    }
    processNextCell(1, 0) {
        rowHeight(13)
        text('Дата составления отчета:', 'metadataTitle')
    }
    processNextCell(0, 1) {
        date(new Date(), 'metadataDateValue')
    }
    processNextCell(1, 0) {
        text('Период:', 'metadataTitle')
    }
    processNextCell(0, 1) {
        text(parameters.REPORT_PERIOD, 'metadataValue')
    }
    if (!specifiedSubAgents.isEmpty())
    {
        processNextCell(1, 0) {
            text('Субагент:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedSubAgents, 'metadataValue')
        }
    }
    if (!specifiedAgents.isEmpty())
    {
        processNextCell(1, 0) {
            text('Агент:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedAgents, 'metadataValue')
        }
    }
    if (!specifiedProfileGroup.isEmpty())
    {
        processNextCell(1, 0) {
            text('Группа:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedProfileGroup, 'metadataValue')
        }
    }
    if (!specifiedGds.isEmpty())
    {
        processNextCell(1, 0) {
            text('Система бронирования:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedGds, 'metadataValue')
        }
    }
}

private void fillTableHeader()
{
    int scw = 10
    processNextCell(2, 0) {
        setStyle('header')
        rowHeight(40)
        columnWidth(2 * scw)
        text('Наименование субагента')
    }
    if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty())
    {
        processNextCell(0, 1) {
            columnWidth(2 * scw)
            text('Группа')
        }
    }
    if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        processNextCell(0, 1) {
            columnWidth(2 * scw)
            text('Выписывающий агент')
        }
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Номер заказа поставщика')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Номер заказа Портбилет')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Статус')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Дата оформления')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Дата начала оказания услуги')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Дата окончания оказания услуги (выезд)')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Страна оказания услуги')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Город')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Название отеля')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Имя путешественника')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Валюта взаиморасчетов')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Название поставщика')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Форма налогообложения')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Владелец бланков')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Стоимость поставщика')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Сбор ВИП-Сервис')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Общая стоимость проживания')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Агентское вознаграждение субагента')
    }
    processNextCell(0, 1) {
        columnWidth(2 * scw)
        text('Агентское вознаграждение ВИП-СЕРВИС')
    }
}

private void fillTable(ArrayList hotelList)
{
    if ((isGroupedByAgent || !specifiedAgents.isEmpty()) &&
            (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()))
    {
        hotelList = hotelList.sort { Hotel a, Hotel b ->
            a.subagentName <=> b.subagentName ?: a.group <=> b.group ?:
                    a.agent <=> b.agent ?:
                            a.productStatus <=> b.productStatus ?:
                                    a.issueDate <=> b.issueDate
        }
    }
    else if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        hotelList = hotelList.sort { Hotel a, Hotel b ->
            a.subagentName <=> b.subagentName ?: a.agent <=> b.agent ?:
                    a.productStatus <=> b.productStatus ?:
                            a.issueDate <=> b.issueDate
        }
    }
    else if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty())
    {
        hotelList = hotelList.sort { Hotel a, Hotel b ->
            a.subagentName <=> b.subagentName ?: a.group <=> b.group ?:
                    a.productStatus <=> b.productStatus ?:
                            a.issueDate <=> b.issueDate
        }
    }
    else
    {
        hotelList = hotelList.sort { Hotel a, Hotel b ->
            a.subagentName <=> b.subagentName ?:
                    a.productStatus <=> b.productStatus ?:
                            a.issueDate <=> b.issueDate
        }
    }

    Hotel currentHotel
    Hotel nextHotel
    if (hotelList.size() > 0)
    {
        currentHotel = hotelList.first()
    }
    for (int i = 1; i <= hotelList.size(); i++)
    {
        if (i != hotelList.size())
        {
            nextHotel = hotelList.get(i)
        }
        else
        {
            nextHotel = null
        }
        processNextCell(1, 0) {
            rowHeight(12)
            text(currentHotel?.subagentName, 'textData')
        }
        if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty())
        {
            processNextCell(0, 1) {
                text(currentHotel?.group, 'textData')
            }
        }
        if (isGroupedByAgent || !specifiedAgents.isEmpty())
        {
            processNextCell(0, 1) {
                text(currentHotel?.agent, 'textData')
            }
        }
        processNextCell(0, 1) {
            text(currentHotel?.reservationNumber, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.bookingNumberPortbilet, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.productStatus?.toString(), 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.issueDate, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.checkIn, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.checkOut, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.hotelCountry, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.hotelCity, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.hotelName, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.travellersName, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.currencyCode, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.supplier, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.formTax, 'textData')
        }
        processNextCell(0, 1) {
            text(currentHotel?.blankOwner, 'textData')
        }
        processNextCell(0, 1) {
            number(currentHotel?.supplierFee, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentHotel?.vipFee, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentHotel?.totalFee, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentHotel?.agencyCommision, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentHotel?.vipCommision, 'numberData')
        }

        fillTotalMap(currentHotel, currentHotel.productStatus)

        boolean isAgentChanged = !currentHotel?.agent?.equals(nextHotel?.agent)
        boolean isGroupChanged = !currentHotel?.group?.equals(
                (nextHotel?.group))
        boolean isSubAgentChanged = !currentHotel?.subagentName?.equals(
                nextHotel?.subagentName)
        boolean isStatusChanged = !currentHotel?.productStatus?.equals(
                nextHotel?.productStatus)
        boolean needTotalForAgent = (isGroupedByAgent ||
                !specifiedAgents.isEmpty()) &&
                ((!isAgentChanged && isStatusChanged) || isAgentChanged)
        boolean needTotalForSubAgent = isSubAgentChanged
        boolean needTotalForGroup = (isGroupedByProfileGroup ||
                !specifiedProfileGroup.isEmpty()) && isGroupChanged

        if (needTotalForAgent)
        {
            if (isStatusChanged || isAgentChanged)
            {
                fillPreliminaryTotalRow(currentHotel.agent,
                                        currentHotel.productStatus)
            }

            if (isAgentChanged)
            {
                fillPreliminaryTotalRow(currentHotel.agent, null)
            }
        }

        if (needTotalForGroup)
        {
            fillPreliminaryTotalRow(currentHotel.group, ProductStatus.SELL)
            fillPreliminaryTotalRow(currentHotel.group, ProductStatus.REFUND)
            fillPreliminaryTotalRow(currentHotel.group, ProductStatus.EXCHANGE)
            fillPreliminaryTotalRow(currentHotel.group, ProductStatus.VOID)
            fillPreliminaryTotalRow(currentHotel.group, null)
        }

        if (needTotalForSubAgent)
        {
            fillPreliminaryTotalRow(currentHotel.subagentName,
                                    ProductStatus.SELL)
            fillPreliminaryTotalRow(currentHotel.subagentName,
                                    ProductStatus.REFUND)
            fillPreliminaryTotalRow(currentHotel.subagentName,
                                    ProductStatus.EXCHANGE)
            fillPreliminaryTotalRow(currentHotel.subagentName,
                                    ProductStatus.VOID)
            fillPreliminaryTotalRow(currentHotel.subagentName, null)
        }
        currentHotel = nextHotel
        rowHeight(12)
    }
}

private void fillPreliminaryTotalRow(String targetObject,
        ProductStatus status = null)
{
    BigDecimal supplierFee = BigDecimal.ZERO
    BigDecimal vipFee = BigDecimal.ZERO
    BigDecimal totalFee = BigDecimal.ZERO
    BigDecimal subAgencyCommission = BigDecimal.ZERO
    BigDecimal vendorCommission = BigDecimal.ZERO
    if (!status.equals(null))
    {
        TotalKey supplierFeeKey = new TotalKey(FareType.SUPPLIER_FEE,
                                               targetObject, status)
        supplierFee = totalMap.get(supplierFeeKey) ?: BigDecimal.ZERO
        TotalKey vipFeeKey = new TotalKey(FareType.VIP_FEE, targetObject,
                                          status)
        vipFee = totalMap.get(vipFeeKey) ?: BigDecimal.ZERO
        TotalKey totalFeeKey = new TotalKey(FareType.TOTAL_FEE, targetObject,
                                            status)
        totalFee = totalMap.get(totalFeeKey) ?: BigDecimal.ZERO
        TotalKey subAgencyCommissionKey = new TotalKey(
                FareType.SUB_AGENCY_COMMISSION, targetObject, status)
        subAgencyCommission = totalMap.get(subAgencyCommissionKey) ?:
                BigDecimal.ZERO
        TotalKey vendorCommissionKey = new TotalKey(FareType.VENDOR_COMMISSION,
                                                    targetObject, status)
        vendorCommission = totalMap.get(vendorCommissionKey) ?: BigDecimal.ZERO
    }
    else
    {
        TotalKey sellSupplierFeeKey = new TotalKey(FareType.SUPPLIER_FEE,
                                                   targetObject,
                                                   ProductStatus.SELL)
        TotalKey sellVipFeeKey = new TotalKey(FareType.VIP_FEE, targetObject,
                                              ProductStatus.SELL)
        TotalKey sellTotalFeeKey = new TotalKey(FareType.TOTAL_FEE,
                                                targetObject,
                                                ProductStatus.SELL)
        TotalKey sellSubAgencyCommissionKey = new TotalKey(
                FareType.SUB_AGENCY_COMMISSION, targetObject,
                ProductStatus.SELL)
        TotalKey sellVendorCommissionKey = new TotalKey(
                FareType.VENDOR_COMMISSION, targetObject, ProductStatus.SELL)

        TotalKey refundSupplierFeeKey = new TotalKey(FareType.SUPPLIER_FEE,
                                                     targetObject,
                                                     ProductStatus.REFUND)
        TotalKey refundVipFeeKey = new TotalKey(FareType.VIP_FEE, targetObject,
                                                ProductStatus.REFUND)
        TotalKey refundTotalFeeKey = new TotalKey(FareType.TOTAL_FEE,
                                                  targetObject,
                                                  ProductStatus.REFUND)
        TotalKey refundSubAgencyCommissionKey = new TotalKey(
                FareType.SUB_AGENCY_COMMISSION, targetObject,
                ProductStatus.REFUND)
        TotalKey refundVendorCommissionKey = new TotalKey(
                FareType.VENDOR_COMMISSION, targetObject,
                ProductStatus.REFUND)

        TotalKey exchangeSupplierFeeKey = new TotalKey(FareType.SUPPLIER_FEE,
                                                       targetObject,
                                                       ProductStatus.EXCHANGE)
        TotalKey exchangeVipFeeKey = new TotalKey(FareType.VIP_FEE,
                                                  targetObject,
                                                  ProductStatus.EXCHANGE)
        TotalKey exchangeTotalFeeKey = new TotalKey(FareType.TOTAL_FEE,
                                                    targetObject,
                                                    ProductStatus.EXCHANGE)
        TotalKey exchangeSubAgencyCommissionKey = new TotalKey(
                FareType.SUB_AGENCY_COMMISSION, targetObject,
                ProductStatus.EXCHANGE)
        TotalKey exchangeVendorCommissionKey = new TotalKey(
                FareType.VENDOR_COMMISSION, targetObject,
                ProductStatus.EXCHANGE)

        TotalKey voidSupplierFeeKey = new TotalKey(FareType.SUPPLIER_FEE,
                                                   targetObject,
                                                   ProductStatus.VOID)
        TotalKey voidVipFeeKey = new TotalKey(FareType.VIP_FEE, targetObject,
                                              ProductStatus.VOID)
        TotalKey voidTotalFeeKey = new TotalKey(FareType.TOTAL_FEE,
                                                targetObject,
                                                ProductStatus.VOID)
        TotalKey voidSubAgencyCommissionKey = new TotalKey(
                FareType.SUB_AGENCY_COMMISSION, targetObject,
                ProductStatus.VOID)
        TotalKey voidVendorCommissionKey = new TotalKey(
                FareType.VENDOR_COMMISSION, targetObject, ProductStatus.VOID)

        supplierFee = (totalMap.get(sellSupplierFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(refundSupplierFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(exchangeSupplierFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(voidSupplierFeeKey) ?: BigDecimal.ZERO)
        vipFee = (totalMap.get(sellVipFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(refundVipFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(exchangeVipFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(voidVipFeeKey) ?: BigDecimal.ZERO)
        totalFee = (totalMap.get(sellTotalFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(refundTotalFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(exchangeTotalFeeKey) ?: BigDecimal.ZERO).add(
                totalMap.get(voidTotalFeeKey) ?: BigDecimal.ZERO)
        subAgencyCommission = (totalMap.get(sellSubAgencyCommissionKey) ?:
                BigDecimal.ZERO).add(
                totalMap.get(refundSubAgencyCommissionKey) ?:
                        BigDecimal.ZERO).add(
                totalMap.get(exchangeSubAgencyCommissionKey) ?:
                        BigDecimal.ZERO).add(
                totalMap.get(voidSubAgencyCommissionKey) ?: BigDecimal.ZERO)
        vendorCommission = (totalMap.get(sellVendorCommissionKey) ?:
                BigDecimal.ZERO).add(
                totalMap.get(refundVendorCommissionKey) ?: BigDecimal.ZERO).add(
                totalMap.get(exchangeVendorCommissionKey) ?:
                        BigDecimal.ZERO).add(
                totalMap.get(voidVendorCommissionKey) ?: BigDecimal.ZERO)
    }

    if (!supplierFee.equals(BigDecimal.ZERO) ||
            !vipFee.equals(BigDecimal.ZERO) ||
            !totalFee.equals(BigDecimal.ZERO) ||
            !subAgencyCommission.equals(BigDecimal.ZERO) ||
            !vendorCommission.equals(BigDecimal.ZERO))
    {
        processNextCell(1, 0) {
            text(status.equals(null) ?
                         String.format('Итого по %s:', targetObject) :
                         String.format('Итого операции %s по %s:', status,
                                       targetObject), 'preliminaryTotalText')
        }
        processNextCell(0, 1) {
            number(supplierFee, 'totalNumber')
        }
        processNextCell(0, 1) {
            number(vipFee, 'totalNumber')
        }
        processNextCell(0, 1) {
            number(totalFee, 'totalNumber')
        }
        processNextCell(0, 1) {
            number(subAgencyCommission, 'totalNumber')
        }
        processNextCell(0, 1) {
            number(vendorCommission, 'totalNumber')
        }
    }
}

private void fillTotalMap(Hotel hotel, ProductStatus status)
{
    String subAgent = hotel.subagentName
    TotalKey supplierFeeSubAgentKey = new TotalKey(FareType.SUPPLIER_FEE, subAgent,
                                           status)
    calculateTotalValueForObject(totalMap, supplierFeeSubAgentKey, hotel.supplierFee)
    TotalKey vipFeeSubAgentKey = new TotalKey(FareType.VIP_FEE, subAgent,
                                           status)
    calculateTotalValueForObject(totalMap, vipFeeSubAgentKey, hotel.vipFee)
    TotalKey totalFeeSubAgentKey = new TotalKey(FareType.TOTAL_FEE, subAgent,
                                           status)
    calculateTotalValueForObject(totalMap, totalFeeSubAgentKey, hotel.totalFee)
    TotalKey subAgencyCommissionSubAgentKey = new TotalKey(
            FareType.SUB_AGENCY_COMMISSION, subAgent, status)
    calculateTotalValueForObject(totalMap, subAgencyCommissionSubAgentKey,
                                 hotel.agencyCommision)
    TotalKey vendorCommissionSubAgentKey = new TotalKey(
            FareType.VENDOR_COMMISSION, subAgent, status)
    calculateTotalValueForObject(totalMap, vendorCommissionSubAgentKey,
                                 hotel.vipCommision)
    if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        String agent = hotel.agent
        TotalKey supplierFeeAgentKey = new TotalKey(FareType.SUPPLIER_FEE, agent,
                                            status)
        calculateTotalValueForObject(totalMap, supplierFeeAgentKey, hotel.supplierFee)
        TotalKey vipFeeAgentKey = new TotalKey(FareType.VIP_FEE, agent,
                                                  status)
        calculateTotalValueForObject(totalMap, vipFeeAgentKey, hotel.vipFee)
        TotalKey totalFeeAgentKey = new TotalKey(FareType.TOTAL_FEE, agent,
                                                    status)
        calculateTotalValueForObject(totalMap, totalFeeAgentKey, hotel.totalFee)
        TotalKey subAgencyCommissionAgentKey = new TotalKey(
                FareType.SUB_AGENCY_COMMISSION, agent, status)
        calculateTotalValueForObject(totalMap, subAgencyCommissionAgentKey,
                                     hotel.agencyCommision)
        TotalKey vendorCommissionAgentKey = new TotalKey(
                FareType.VENDOR_COMMISSION, agent, status)
        calculateTotalValueForObject(totalMap, vendorCommissionAgentKey,
                                     hotel.vipCommision)
    }
    if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty())
    {
        String group = hotel.group
        TotalKey supplierFeeGroupKey = new TotalKey(FareType.SUPPLIER_FEE, group,
                                            status)
        calculateTotalValueForObject(totalMap, supplierFeeGroupKey, hotel.supplierFee)
        TotalKey vipFeeGroupKey = new TotalKey(FareType.VIP_FEE, group,
                                                  status)
        calculateTotalValueForObject(totalMap, vipFeeGroupKey, hotel.vipFee)
        TotalKey totalFeeGroupKey = new TotalKey(FareType.TOTAL_FEE, group,
                                                    status)
        calculateTotalValueForObject(totalMap, totalFeeGroupKey, hotel.totalFee)
        TotalKey subAgencyCommissionGroupKey = new TotalKey(
                FareType.SUB_AGENCY_COMMISSION, group, status)
        calculateTotalValueForObject(totalMap, subAgencyCommissionGroupKey,
                                     hotel.agencyCommision)
        TotalKey vendorCommissionGroupKey = new TotalKey(
                FareType.VENDOR_COMMISSION, group, status)
        calculateTotalValueForObject(totalMap, vendorCommissionGroupKey,
                                     hotel.vipCommision)
    }
}

private void calculateTotalValueForObject(Map<TotalKey, BigDecimal> map,
        TotalKey objectKey, BigDecimal valueToProcess)
{
    BigDecimal value = map.get(objectKey)
    if (!value)
    {
        map.put(objectKey, valueToProcess)
    }
    else
    {
        map.put(objectKey, value.add(valueToProcess))
    }
}

private void fillTableFooter()
{
    fillTableFooterRow(ProductStatus.SELL)
    fillTableFooterRow(ProductStatus.REFUND)
//    fillTableFooterRow(ProductStatus.EXCHANGE)
    fillTableFooterRow(ProductStatus.VOID)
    fillTableFooterRow(null)
}

private void fillTableFooterRow(ProductStatus status)
{
    BigDecimal supplierFee = BigDecimal.ZERO
    BigDecimal vipFee = BigDecimal.ZERO
    BigDecimal totalFee = BigDecimal.ZERO
    BigDecimal agencyCommission = BigDecimal.ZERO
    BigDecimal vendorCommission = BigDecimal.ZERO
    totalMap.each { key, value ->
        if (key.productStatus.equals(status) || status == null)
        {
            if (key.fareType.equals(FareType.SUPPLIER_FEE))
            {
                supplierFee = supplierFee.add(value)
            }
            else if (key.fareType.equals(FareType.VIP_FEE))
            {
                vipFee = vipFee.add(value)
            }
            else if(key.fareType.equals(FareType.TOTAL_FEE))
            {
                totalFee = totalFee.add(value)
            }
            else if (key.fareType.equals(FareType.SUB_AGENCY_COMMISSION))
            {
                agencyCommission = agencyCommission.add(value)
            }
            else if (key.fareType.equals(FareType.VENDOR_COMMISSION))
            {
                vendorCommission = vendorCommission.add(value)
            }
        }
    }
    processNextCell(1, getColumnShiftTotal(13)) {
        if (status.equals(null))
        {
            text('ИТОГО:', 'finalTotalText')
        }
        else
        {
            text(status.toString(), 'finalTotalText')
        }
    }
    processNextCell(0, 1) {
        number(supplierFee, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(vipFee, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(totalFee, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(agencyCommission, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(vendorCommission, 'totalNumber')
    }
}

private int getColumnShiftTotal(int baseShift)
{
    if ((isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()))
    {
        baseShift++
    }
    if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        baseShift++
    }
    return baseShift
}

private void processNextCell(int numberOfRowShifts, int numberOfColumnShifts,
        Closure action)
{
    numberOfRowShifts.times { nextRow() }
    numberOfColumnShifts.times { nextColumn() }
    action()
}

private void populateBlankOwner(HotelProduct hotelProduct, Hotel hotel)
{
    hotel.setBlankOwner(hotelProduct?.getBlankOwnerRef()?.caption?.toString())
}

private void populateTaxForm(EntityReference<Organization> subagencyRef,
        Hotel hotel)
{
    if (subagencyRef != null)
    {
        Organization organization = EntityStorage.get().resolve(
                subagencyRef).getEntity()
        if (organization.simpleTaxed)
        {
            hotel.setFormTax("УСН")
        }
        else
        {
            hotel.setFormTax("ОСН")
        }
    }
}

private void populateSubAgent(Hotel hotel,
        EntityReference<Organization> subagencyRef)
{
    hotel.setSubagentName(subagencyRef.toString())
}

private void populateAgent(Reservation reservation, Hotel hotel)
{
    EntityReference<Person> bookingAgent = reservation.getBookingAgent()
    if (bookingAgent != null)
    {
        EntityContainer<Person> bookingAgentCont =
                EntityStorage.get().resolve(bookingAgent)
        if (bookingAgentCont != null)
        {
            Person person = bookingAgentCont.getEntity()
            Locale locale = person.getPreferredLocale()
            String fullName = ""
            String lastName = L10nStringHelper.getValue(person.getLastName(),
                                                        locale, false)

            String firstName =
                    L10nStringHelper.getValue(person.getFirstName(), locale,
                                              false)

            if (lastName != null)
            {
                fullName = fullName + lastName
            }
            if (firstName != null)
            {
                fullName = fullName + firstName
            }
            if (!fullName.isEmpty())
            {
                hotel.setAgent(fullName)
            }
        }
    }
}

private void populateVendorCommission(HotelProduct hotelProduct, boolean negate,
        Hotel hotel)
{
    BigDecimal vendorCommission = HotelProductHelper.calculateCommissionsEquivalentValue(
            HotelProductHelper.getUnmodifiableCommissions(hotelProduct,
                                                          ContractType.VENDOR),
            (Set<ContractType>) Arrays.asList(ContractType.VENDOR),
            HotelProductHelper.commissionPropertyTypes)
    if (vendorCommission == null)
    {
        vendorCommission = BigDecimal.ZERO
    }
    if (negate)
    {
        vendorCommission = vendorCommission.negate()
    }
    hotel.setVipCommision(
            vendorCommission == null ? BigDecimal.ZERO : vendorCommission)
}

private void populateSubAgencyComission(HotelProduct hotelProduct,
        boolean negate, Hotel hotel)
{
    BigDecimal subagencyCommission = HotelProductHelper.calculateCommissionsEquivalentValue(
            HotelProductHelper.getUnmodifiableCommissions(hotelProduct,
                                                          ContractType.SUBAGENCY),
            (Set<ContractType>) Arrays.asList(ContractType.SUBAGENCY),
            HotelProductHelper.commissionPropertyTypes)
    if (subagencyCommission == null)
    {
        subagencyCommission = BigDecimal.ZERO
    }
    if (negate)
    {
        subagencyCommission = subagencyCommission.negate()
    }
    hotel.setAgencyCommision(subagencyCommission)
}

private void populateSupplierFee(HotelProduct hotelProduct, boolean negate,
        Hotel hotel)
{
    BigDecimal totalFare = HotelProductHelper.calculateTotalEquivalentFare(
            hotelProduct)

    if (totalFare == null)
    {
        totalFare = BigDecimal.ZERO
    }
    if (negate)
    {
        totalFare = totalFare.negate()
    }
    hotel.setSupplierFee(totalFare)
}

private void populatePrtbBookingNumber(BookingFile bookingFile, Hotel hotel)
{
    String bookingNumber = bookingFile.getNumber()
    hotel.setBookingNumberPortbilet(bookingNumber)
}

private void populateReservationNumber(Reservation reservation, Hotel hotel)
{
    String recordLocator = CommonReservationGdsNameInfoHelper
        .getDisplayedRecordLocator(reservation)
    hotel.setReservationNumber(recordLocator)
}

private void populateCurrencyCode(Hotel hotel)
{
    hotel.setCurrencyCode(DictHelper.getLocalCurrencyCode())
}

private void populateTravellersNames(HotelProduct hotelProduct, Hotel hotel)
{
    List<Traveller> travellerList = hotelProduct.getTravellers()
    if (travellerList != null && !travellerList.isEmpty())
    {
        Traveller traveller = travellerList.get(0)
        String name = traveller.getName()
        hotel.setTravellersName(name)
    }
}

private void populateHotelName(Hotel hotel, HotelProduct hotelProduct)
{
    hotel.setHotelName(hotelProduct.getHotelName())
}

private void populateHotelCity(HotelProduct hotelProduct, Hotel hotel)
{
    if (hotelProduct.getHotelLocation() != null)
    {
        GeoLocation location = DictHelper.findCity(
                hotelProduct.getHotelLocation())
        if (location != null)
        {
            hotel.setHotelCity(location.toString())
        }
    }
}

private void populateHotelCountry(HotelProduct hotelProduct, Hotel hotel)
{
    if (hotelProduct.getHotelLocation() != null)
    {
        GeoLocation result =
                Environment.getPublished(
                        DictionaryCache.class).resolveReference(
                        hotelProduct.getHotelLocation())
        if (result.getCountry() != null)
        {
            hotel.setHotelCountry(result.getCountry().toString())
        }
    }
}

private void populateProductStatus(HotelProduct hotelProduct, Hotel hotel)
{
    ProductStatus status = hotelProduct.getStatus()
    hotel.setProductStatus(status)
}

private void populateIssueDate(HotelProduct hotelProduct, Hotel hotel)
{
    String issueDate = dateFormat.format(hotelProduct.getIssueDate())
    hotel.setIssueDate(issueDate)
}

private void populateCheckInDate(HotelProduct hotelProduct, Hotel hotel)
{
    String checkIn = dateFormat.format(
            HotelProductHelper.getFirstCheckinDate(hotelProduct))
    hotel.setCheckIn(checkIn)
}

private void populateCheckOutDate(HotelProduct hotelProduct, Hotel hotel)
{
    String checkOut = dateFormat.format(
            HotelProductHelper.getLastCheckoutDate(hotelProduct))
    hotel.setCheckOut(checkOut)
}

private void populateSupplier(HotelProduct product, Hotel hotel)
{
    EntityReference<Organization> supplier = HotelProductHelper.getSupplier(
            product)
    if (supplier != null)
    {
        hotel.setSupplier(supplier?.caption)
    }
}

private void populateProfileGroup(CommonProductIndex index, Hotel ticket)
{
    ticket.setGroup(index.getProfileGroup() == null ? '' :
                            index.getProfileGroup().toString())
}

private void populateVipFee(HotelProduct product, Hotel ticket)
{
    BigDecimal totalFee = HotelProductHelper.calculateTotalEquivalentFop(
            HotelProductHelper.getSubagentFops(product, false),
            DictHelper.getLocalCurrencyCode(),
            FinanceHelper.getCurrencyRateType(
                    DictHelper.getLocalCurrencyCode()),
            product.getIssueDate())
    ticket.setVipFee(totalFee.subtract(ticket.supplierFee))
}

private void populateTotalFee(HotelProduct product, Hotel ticket)
{
    BigDecimal totalFee = HotelProductHelper.calculateTotalEquivalentFop(
            HotelProductHelper.getSubagentFops(product, false),
            DictHelper.getLocalCurrencyCode(),
            FinanceHelper.getCurrencyRateType(
                    DictHelper.getLocalCurrencyCode()),
            product.getIssueDate())
    ticket.setTotalFee(totalFee)
}