// 2017-09-04
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.air.Tax;
import com.gridnine.xtrip.common.model.booking.commission.ProductType;
import com.gridnine.xtrip.common.model.booking.xtriphotels.HotelProduct
import com.gridnine.xtrip.common.model.booking.xtriphotels.HotelProductContractRelationData;
import com.gridnine.xtrip.common.model.booking.xtriphotels.HotelProductFop;
import com.gridnine.xtrip.common.model.booking.xtriphotels.HotelProductTax;
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 com.gridnine.xtrip.common.search.SearchCriteria;
import com.gridnine.xtrip.common.search.SearchCriterion;
import com.gridnine.xtrip.common.search.SearchQuery;
import com.gridnine.xtrip.common.util.MiscUtil;
import com.gridnine.xtrip.common.l10n.model.L10nString
import com.gridnine.xtrip.common.model.booking.commission.BaseCommissionProperties
import com.gridnine.xtrip.common.model.booking.commission.FeeProperties
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.profile.Person
import com.google.common.base.Joiner
import com.gridnine.xtrip.common.model.booking.GeneralProductCommission
import com.gridnine.xtrip.common.vip.CommonVIPHelper
import com.gridnine.xtrip.common.model.booking.vip.VipSubagentSitePriceStructure
//import com.gridnine.xtrip.server.vip.hotel.svc.ServiceUtil
//import com.gridnine.xtrip.server.vip.hotel.svc.out.PriceDetailizationSubagent
import com.gridnine.xtrip.common.vip.CommonVIPHelper
import com.gridnine.xtrip.common.model.helpers.GeneralProductHelper;
import groovy.transform.Field
import com.gridnine.xtrip.common.model.booking.commission.CommissionProperties
import com.gridnine.xtrip.common.model.booking.commission.DiscountProperties
import com.gridnine.xtrip.common.model.helpers.HotelProductHelper

import org.apache.commons.lang.StringUtils

import java.text.SimpleDateFormat
import java.util.Date;
import java.util.List;

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
    BigDecimal toPaid
    String onHold
    BigDecimal vat = BigDecimal.ZERO;

    String clientName
    String agencyName
    BigDecimal hiddenFee
    BigDecimal clientServiceFee
    String additionalServices
}

class Aggregator {
    BigDecimal supplierFee = BigDecimal.ZERO
    BigDecimal hiddenFee = BigDecimal.ZERO
    BigDecimal totalFee = BigDecimal.ZERO
    BigDecimal clientServiceFee = BigDecimal.ZERO
    BigDecimal toPaid = BigDecimal.ZERO

    public def append(Hotel hotel) {
        if (hotel.supplierFee != null) {
            supplierFee = supplierFee.add(hotel.supplierFee)
        }
        if (hotel.hiddenFee != null) {
            hiddenFee = hiddenFee.add(hotel.hiddenFee)
        }
        if (hotel.totalFee != null) {
            totalFee = totalFee.add(hotel.totalFee)
        }
        if (hotel.clientServiceFee != null) {
            clientServiceFee = clientServiceFee.add(hotel.clientServiceFee)
        }
        if (hotel.toPaid != null) {
            toPaid = toPaid.add(hotel.toPaid)
        }
    }
}

class ProductStruct {
    HotelProduct product
    VipSubagentSitePriceStructure subagentPriceStructure
}

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

@Field SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy")
@Field int totalColumnCount
@Field int headerColumnCount
@Field List<EntityReference<Person>> filterClients

processIncomingData()
processTableHeader(false)
createSpreadSheetStyle()
composeReport()

ProductStruct getProductStruct(HotelProduct product) {
    ProductStruct result = new ProductStruct()
    result.product = product
    result.subagentPriceStructure = CommonVIPHelper.getSubagentPriceStructure(product)
    return result
}

private void createSpreadSheetStyle() {
    createStyle(name: 'title', fontBold: true, h_span: headerColumnCount,
            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: headerColumnCount - 2,
            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: totalColumnCount, 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()
            if (Objects.nonNull(object)) {
                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()
            if (Objects.nonNull(object)) {
                add(ProfileHelper.getFullName(object,
                        LocaleHelper.getCurrentLocale(),
                        false))
            }
        }
    }
    }, ', ')
    return result
}

private List<String> getAgencyList() {
    List<String> result = new ArrayList<String>()

    parameters['SUBAGENT'].each {
        result.add(it.uid)
    }

    return result
}

private String[] getClientUidArray() {
    String[] result = new String[filterClients.size()]
    filterClients.eachWithIndex { entity, index ->
        result[index] = entity.uid
    }
    return result;
}

private List<String> getAgentList() {
    List<String> result = new ArrayList<String>()

    parameters['AGENT'].each {
        result.add(it.uid)
    }

    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 String getRuString(L10nString string) {
    if (string == null) {
        return null
    }
    String ruValue = null
    String enValue = null
    String anyValue = null
    string.getValues().each { Locale loc, String value  ->
        if (loc.language == "ru") {
            ruValue = value
        } else if (loc.language == "en") {
            enValue = value
        }
        anyValue = value
    }
    if (ruValue != null) {
        return ruValue
    } else if (enValue != null) {
        return enValue
    } else {
        return anyValue
    }
}

private String getOrganizationName(Organization organization) {
    if (organization == null) {
        return null
    }
    if (organization.fullName != null) {
        return getRuString(organization.fullName);
    } else if (organization.shortName != null) {
        return getRuString(organization.shortName)
    } else {
        return null
    }
}

private BigDecimal getHiddenFee(ProductStruct product) {
    VipSubagentSitePriceStructure priceDetailization = product.subagentPriceStructure
    return priceDetailization.hiddenFeesSum
}

private BigDecimal getClientServiceFee(ProductStruct product) {
    VipSubagentSitePriceStructure priceDetailization = product.subagentPriceStructure
    return priceDetailization.subagentFeesSum
}

private void composeReport() {
    def hotelList = []
    if(isByDepartureDate){
        try{
            allTickets.clear();
            SearchQuery searchQuery = new SearchQuery()

            searchQuery.getCriteria().getCriterions().add(SearchCriterion.eq(CommonProductIndex.Property.productType.name(),ProductType.HOTEL_RESERVATION))

            searchQuery.getCriteria().getCriterions().add(SearchCriterion.or(
                    SearchCriterion.eq(CommonProductIndex.Property.status.name(), ProductStatus.SELL),
                    SearchCriterion.eq(CommonProductIndex.Property.status.name(), ProductStatus.REFUND)
            ))

            if (!filterClients.empty) {
                searchQuery.getCriteria().getCriterions().add(SearchCriterion.in(CommonProductIndex.Property.client.name(), getClientUidArray()))
            }

            if(parameters['SUBAGENT'].size() > 0){
                searchQuery.getCriteria().getCriterions().add(SearchCriterion.in(CommonProductIndex.Property.subagency.name(), getAgencyList().toArray()))
            }
            if(parameters['AGENT'].size() > 0){
                searchQuery.getCriteria().getCriterions().add(SearchCriterion.in(CommonProductIndex.Property.agent.name(), getAgentList().toArray()))
            }

            List<CommonProductIndex> list = EntityStorage.get().search(CommonProductIndex.class, searchQuery).getData()
            int added = 0
            int finded = 0

            list.each { CommonProductIndex index ->
                Date checkOutDate = index.hotelCheckOutDate
                if(checkOutDate != null && (checkOutDate.compareTo(parameters['params'].periodBegin) >= 0 && checkOutDate.compareTo(parameters['params'].periodEnd) <= 0)){
                    finded++
                    if(allTickets.size() == 0) {
                        added++
                        allTickets.add(index)
                    } else {
                        def findCPI = allTickets.find { CommonProductIndex cpi ->
                            cpi.uid.equals(index.uid)
                        }
                        if(findCPI == null){
                            added++
                            allTickets.add(index)
                        }
                    }
                }

            }
        }catch(Exception e){
            warn 'error=' + e.message

            StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw);
            e.printStackTrace(pw);

            warn sw.toString();
        }
    }

    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
                ProductStruct productStruct = getProductStruct(product)
                if(product.status == ProductStatus.BOOKING){
                    return;
                }

                if(isByDepartureDate){
                    if(product.status == ProductStatus.SELL || product.status == ProductStatus.REFUND ? product.onHold : true) {return;}
                }

                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,index)
                populateTotalFee(product, hotel);
                populateBlankOwner(product, hotel)
                populateSupplier(product, hotel)
                populateToPaid(product, hotel)
                populateOnHold(product, hotel)
                populateVat(product, hotel)
                populateAdditionalServices(product, hotel)

                hotel.clientName = getOrganizationName(EntityStorage.get().resolve(index.client)?.entity)
                hotel.agencyName = getOrganizationName(EntityStorage.get().resolve(index.agency)?.entity)
                hotel.hiddenFee = getHiddenFee(productStruct)
                hotel.clientServiceFee = getClientServiceFee(productStruct)
                hotel.totalFee = hotel.supplierFee.add(hotel.hiddenFee)
                hotel.toPaid = hotel.totalFee.add(hotel.clientServiceFee)

                hotelList.add(hotel)
            }
        }
    }

    fillSpreadSheetHeader()
    processTableHeader(true)
    fillTable(hotelList)
}


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

private void processTableHeader(boolean doPrint) {
    int scw = 10
    int skipRow = doPrint ? 2 : 0
    int skipColumn = doPrint ? 1 : 0
    int totalColumnCount = 0

    processNextCell(skipRow, 0) {
        setStyle('header')
        rowHeight(40)
        columnWidth(2 * scw)
        if (doPrint) text('Наименование субагента')
        totalColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text("Наименование клиента")
        totalColumnCount++
    }

    if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()) {
        processNextCell(0, skipColumn) {
            columnWidth(2 * scw)
            if (doPrint) text('Группа')
            totalColumnCount++
        }
    }

    if (isGroupedByAgent || !specifiedAgents.isEmpty()) {
        processNextCell(0, skipColumn) {
            columnWidth(2 * scw)
            if (doPrint) text('Выписывающий агент')
            totalColumnCount++
        }
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Номер заказа поставщика')
        totalColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Номер заказа Портбилет')
        totalColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Статус')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Дата оформления')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Дата начала оказания услуги')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Дата окончания оказания услуги (выезд)')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Страна оказания услуги')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Город')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Название отеля')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Имя путешественника')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Валюта взаиморасчетов')
        totalColumnCount++
    }
    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Название поставщика')
        totalColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text("Агентство")
        totalColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Форма налогообложения')
        totalColumnCount++
    }

    int headerColumnCount = totalColumnCount

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Стоимость поставщика')
        headerColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Скрытый сбор')
        headerColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Общая стоимость проживания')
        headerColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Сервисный сбор с клиента')
        headerColumnCount++
    }


    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('К оплате')
        headerColumnCount++
    }

    processNextCell(0, skipColumn) {
        columnWidth(2 * scw)
        if (doPrint) text('Допуслуги')
        headerColumnCount++
    }
    this.totalColumnCount = totalColumnCount;
    this.headerColumnCount = headerColumnCount;

}

@Field boolean isGroupedByClient
@Field boolean isGroupedByGroup
@Field boolean isGroupedByAgent

private void processIncomingData() {
    this.specifiedSubAgents = getSpecifiedSubAgents()
    this.specifiedProfileGroup = getSpecifiedProfileGroup()
    this.specifiedAgents = getSpecifiedAgents()
    this.specifiedGds = getSpecifiedGds()
    this.isGroupedByAgent = isGroupedByAgent()
    this.isGroupedByProfileGroup = isGroupedByProfileGroup()
    this.isByDepartureDate = isByDepartureDate()
    isGroupedByClient = !parameters['GROUP_BY_PROFILE_GROUP'] && !parameters['GROUP_BY_AGENT']
    isGroupedByGroup = parameters['GROUP_BY_PROFILE_GROUP']
    isGroupedByAgent = !parameters['GROUP_BY_PROFILE_GROUP'] && parameters['GROUP_BY_AGENT']
    filterClients = parameters['CLIENT']
}

private void fillTable(ArrayList hotelList) {
    Closure groupSortClosure

    if (isGroupedByClient) {
        groupSortClosure = { Hotel a, Hotel b ->
            a.clientName <=> b.clientName
        }
    } else if (isGroupedByGroup) {
        groupSortClosure = { Hotel a, Hotel b ->
            a.group <=> b.group
        }
    } else if (isGroupedByAgent) {
        groupSortClosure = { Hotel a, Hotel b ->
            a.agent <=> b.agent
        }
    }

    hotelList.sort { Hotel a, Hotel b ->
        groupSortClosure(a, b) ?:
                a.productStatus <=> b.productStatus ?:
                        a.issueDate <=> b.issueDate
    }

    Hotel currentHotel
    Hotel nextHotel
    if (hotelList.size() > 0) {
        currentHotel = hotelList.first()
    }

    Map<String, Aggregator> groupStatusAggregators = [:]
    Aggregator groupAggregator = new Aggregator()

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

        processNextCell(0, 1) {
            text(currentHotel?.clientName, "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?.agencyName, "textData");
        }

        processNextCell(0, 1) {
            text(currentHotel?.formTax, 'textData')
        }

        processNextCell(0, 1) {
            number(currentHotel?.supplierFee, 'numberData')
        }

        processNextCell(0, 1) {
            number(currentHotel?.hiddenFee, "numberData")
        }

        processNextCell(0, 1) {
            number(currentHotel?.totalFee, 'numberData')
        }

        processNextCell(0, 1) {
            number(currentHotel?.clientServiceFee, "numberData")
        }

        processNextCell(0, 1) {
            number(currentHotel?.toPaid, 'numberData')
        }

        processNextCell(0, 1) {
            text(currentHotel?.additionalServices, "textData")
        }

        groupAggregator.append(currentHotel)

        Aggregator groupStatusAggregator = groupStatusAggregators[currentHotel?.productStatus]
        if (groupStatusAggregator == null) {
            groupStatusAggregator = new Aggregator()
            groupStatusAggregators.put(currentHotel?.productStatus, groupStatusAggregator)
        }
        groupStatusAggregator.append(currentHotel)

        boolean isClientChanged = currentHotel?.clientName != nextHotel?.clientName
        boolean isAgentChanged = !currentHotel?.agent?.equals(nextHotel?.agent)
        boolean isGroupChanged = !currentHotel?.group?.equals((nextHotel?.group))
        boolean isStatusChanged = currentHotel?.productStatus != nextHotel?.productStatus

        if (isStatusChanged) {
            printAggregator("Итого '${currentHotel?.productStatus}':", groupStatusAggregator)
        }

        boolean groupFinished = false;

        if (isGroupedByClient && isClientChanged) {
            printAggregator("Итого по клиенту '${currentHotel?.clientName}':", groupAggregator)
            groupFinished = true
        } else if (isGroupedByGroup && isGroupChanged) {
            printAggregator("Итого по группе '${currentHotel?.group}':", groupAggregator)
            groupFinished = true
        } else if (isGroupedByAgent && isAgentChanged) {
            printAggregator("Итого по агенту '${currentHotel?.agent}':", groupAggregator)
            groupFinished = true
        }

        if (groupFinished) {
            groupAggregator = new Aggregator()
            groupStatusAggregators.each { ProductStatus status, Aggregator aggregator ->
                printAggregator("${status}:", aggregator)
            }
            groupStatusAggregators = [:]
        }

        currentHotel = nextHotel
    }
}

private void printAggregator(String name, Aggregator aggregator) {
    processNextCell(1, 0) {
        text(name, "preliminaryTotalText")
    }
    processNextCell(0, 1) {
        number(aggregator.supplierFee, "totalNumber")
    }
    processNextCell(0, 1) {
        number(aggregator.hiddenFee, "totalNumber")
    }
    processNextCell(0, 1) {
        number(aggregator.totalFee, "totalNumber")
    }
    processNextCell(0, 1) {
        number(aggregator.clientServiceFee, "totalNumber")
    }
    processNextCell(0, 1) {
        number(aggregator.toPaid, "totalNumber")
    }
}

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

private void populateBlankOwner(HotelProduct hotelProduct, Hotel hotel) {
    String blankOwner = hotelProduct?.getBlankOwnerRef()?.caption?.toString() ?: 'отсутствует';
    hotel.setBlankOwner(blankOwner);
}

private void populateTaxForm(EntityReference<Organization> subagencyRef,
                             Hotel hotel) {
    if (subagencyRef != null) {
        Organization organization = EntityStorage.get().resolve(
                subagencyRef)?.getEntity()
        if (organization != null) {
            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) {
    Collection commissions = HotelProductHelper.getUnmodifiableCommissions(hotelProduct, ContractType.VENDOR)
    commissions = filterCommissionsByContractTypes(commissions, vendorContractTypes)
    commissions = GeneralProductHelper.filterCommissionsByCommissionProperties(commissions, commissionPropertyTypes)
    BigDecimal vendorCommission = GeneralProductHelper.calculateCommissionsEquivalentValue(commissions)
    if (vendorCommission == null) {
        vendorCommission = BigDecimal.ZERO
    }
    if (negate) {
        vendorCommission = vendorCommission.negate()
    }
    hotel.setVipCommision(
            vendorCommission == null ? BigDecimal.ZERO : vendorCommission)
}

Collection filterCommissionsByContractTypes(
        Collection<GeneralProductCommission> commissions,
        Set<ContractType> contractTypes) {

    if (commissions == null) {
        return Collections.emptyList();
    }

    if (contractTypes == null) {
        return new ArrayList<GeneralProductCommission>(commissions);
    }

    Collection<GeneralProductCommission> filteredCommissions =
            new ArrayList<GeneralProductCommission>();

    for (GeneralProductCommission commission : commissions) {

        if (contractTypes.contains(commission.getContractType())) {
            filteredCommissions.add(commission);
        }
    }

    return filteredCommissions;
}

@Field Set subagencyContractTypes = [ContractType.SUBAGENCY] as Set
@Field Set vendorContractTypes = [ContractType.VENDOR] as Set
@Field Set commissionPropertyTypes = [CommissionProperties.class, DiscountProperties.class] as Set

private void populateSubAgencyComission(HotelProduct hotelProduct,
                                        boolean negate, Hotel hotel) {
    Collection commissions = HotelProductHelper.getUnmodifiableCommissions(hotelProduct, ContractType.SUBAGENCY)
    commissions = filterCommissionsByContractTypes(commissions, subagencyContractTypes)
    commissions = GeneralProductHelper.filterCommissionsByCommissionProperties(commissions, commissionPropertyTypes)
    BigDecimal subagencyCommission = GeneralProductHelper.calculateCommissionsEquivalentValue(commissions)
    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!=null&&result.getCountry() != null) {
            hotel.setHotelCountry(result.getCountry().toString())
        } else {
            hotel.setHotelCountry('');
        }
    }
}

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

private void populateIssueDate(HotelProduct hotelProduct, Hotel hotel) {
    String issueDate = hotelProduct.getIssueDate()? 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) {
    String supplierName = 'отсутствует';
    EntityReference<Organization> supplier = HotelProductHelper.getSupplier(product);

    if (supplier != null) {
        supplierName = supplier?.caption.toString() ?: 'отсутствует';
    }
    hotel.setSupplier(supplierName);
}

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

private void populateVipFee(HotelProduct product, Hotel ticket, CommonProductIndex index) {
    ticket.setVipFee(index.subagencyFeeValue);
}

private void populateTotalFee(HotelProduct product, Hotel ticket) {
    BigDecimal totalFee = HotelProductHelper.calculateTotalEquivalentFop(
            HotelProductHelper.getSubagentFops(product, false),
            DictHelper.getLocalCurrencyCode(),
            FinanceHelper.getCurrencyRateType(
                    DictHelper.getLocalCurrencyCode()),
            product.getIssueDate());
    if ((product.getStatus() == ProductStatus.REFUND) || (product.getStatus() == ProductStatus.EXCHANGE)) {
        totalFee = totalFee == null ? totalFee : totalFee.negate();
    }
    ticket.setTotalFee(totalFee);
}

private void populateToPaid(HotelProduct product, Hotel ticket) {
    BigDecimal sf = ticket?.supplierFee     ?: BigDecimal.ZERO;
    BigDecimal vc = ticket?.vipCommision    ?: BigDecimal.ZERO;
    BigDecimal totalFee = sf.subtract(vc);

    ticket.setToPaid(totalFee);
}

private void populateOnHold(HotelProduct product, Hotel ticket) {
    ticket.setOnHold(product.onHold? '+' : '-');
}

private void populateVat(HotelProduct product, Hotel ticket) {
    ticket.setVat(MiscUtil.sum(HotelProductHelper.calculateTotalVatAmount(product), ticket.getVat()));
}

private void populateAdditionalServices(HotelProduct product, Hotel ticket) {
    boolean additionalServices = false
    if (product.additionalServices?.size() > 0) {
        additionalServices = true
    }
    if (additionalServices) {
        ticket.setAdditionalServices('Да')
    } else {
        ticket.setAdditionalServices('Нет')
    }
}

warn 'reportVersion=' + '0.0.1'