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

import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.booking.BaseProduct
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.booking.railway.RailwayProduct
import com.gridnine.xtrip.common.model.booking.railway.RailwayProductIndex
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.dict.ProfileGroupReference
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.helpers.GeneralProductHelper
import com.gridnine.xtrip.common.model.helpers.ProfileHelper
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.util.stream.Collectors

class RailwayTicket
{
    String blankOwner
    String subAgent
    String agent
    ProductStatus productStatus
    Date issueDate
    String systemNumber
    String endorsement
    BigDecimal ticketPrice
    BigDecimal commission
    BigDecimal totalPrice
    String passengerName
    String gdsName
    String groupName
    String agency
    String bookingNumber;
}

@Field int numberOfSoldTickets = 0
@Field BigDecimal generalSellFare = BigDecimal.ZERO
@Field BigDecimal generalSellReward = BigDecimal.ZERO
@Field int numberOfRefundedTickets = 0
@Field BigDecimal generalRefundFare = BigDecimal.ZERO
@Field BigDecimal generalRefundReward = BigDecimal.ZERO

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

processIncomingData()
createSpreedsheetStyles()
composeReport()

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

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

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

private String getSpecifiedAgencies()
{
    List objectList = parameters['AGENCY']
    StringBuilder builder = new StringBuilder()
    objectList.each {
        if (it)
        {
            builder.append(it)
            builder.append(', ')
        }
    }
    if (builder.length() > 0)
    {
        builder.delete(builder.lastIndexOf(","), builder.length())
    }
    return builder.toString()
}

private String getSpecifiedSubAgents()
{
    List objectList = parameters['SUBAGENT']
    StringBuilder builder = new StringBuilder()
    objectList.each {
        Organization object = EntityStorage.get().resolve(it)?.getEntity()
        if (object)
        {
            builder.append(ProfileHelper.getFullName(object, LocaleHelper.getCurrentLocale(), false))
            builder.append(', ')
        }
    }
    if (builder.length() > 0)
    {
        builder.delete(builder.lastIndexOf(","), builder.length())
    }
    return builder.toString()
}

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

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

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

private void createSpreedsheetStyles()
{
    createStyle(name: 'title', fontBold: true, h_span: getColumnShiftTotal(12), 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: 'dateData', parent: 'data', h_alignment: 'RIGHT', format: 'm/d/yy')
    createStyle(name: 'preliminaryTotalText', parent: 'data', h_span: getColumnShiftTotal(6), 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')
    createStyle(name: 'totalIntegerNumber', fontBold: true, parent: 'numberData', format: '0')
}

private void composeReport()
{
    page { 'ЖД билеты' } {
        List<RailwayTicket> ticketList = []

        allTickets.each { RailwayProductIndex index ->
            if (index.status == ProductStatus.SELL || index.status == ProductStatus.REFUND || index.status ==
                    ProductStatus.VOID || index.status == ProductStatus.EXCHANGE)
            {
                RailwayTicket railwayTicket = new RailwayTicket()
                def container = EntityStorage.get().resolve(index.source)
                if (!container)
                {
                    return
                }
                BaseProduct prod = BookingHelper.findProductByUid(index.navigationKey, container.entity)
                if (!prod)
                {
                    return
                }

                BaseProduct baseProduct = prod
                RailwayProduct railwayProduct = (RailwayProduct) baseProduct

                boolean negate = (index.getStatus() == ProductStatus.REFUND) ||
                        (index.getStatus() == ProductStatus.EXCHANGE)

                populateSupplier(index, railwayTicket)
                populateSubAgent(index, railwayTicket)
                if (isGroupedByAgent || !specifiedAgents.isEmpty())
                {
                    populateAgent(index, container, railwayTicket)
                }
                if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty())
                {
                    populateGroup(index, railwayTicket)
                }
                populateProductStatus(railwayProduct, railwayTicket)
                populateIssueDate(index, railwayTicket)
                populateSystemNumber(index, railwayTicket)
                populateEndorsement(railwayProduct, railwayTicket)
                populateFare(index, railwayTicket)
                populateAgencyReward(railwayProduct, railwayTicket, negate)
                populateTotalFare(railwayTicket)
                populatePassengerName(index, railwayTicket)
                populateGDS(index, railwayTicket)
                populateAgency(index, railwayTicket)
                railwayTicket.bookingNumber = prod.reservation.bookingFile.number;
                ticketList.add(railwayTicket)
            }
        }

        fillSpreadSheetHeader()
        fillTableHeader()
        fillTable(ticketList)
        fillTableFooter(this.numberOfSoldTickets, this.generalSellFare, this.generalSellReward,
                        this.numberOfRefundedTickets, this.generalRefundFare, this.generalRefundReward)
    }
}

private void fillSpreadSheetHeader()
{
    processNextCell(0, 0) {
        rowHeight(30)
        text('Отчет по ЖД билетам ', 'title')
    }
    processNextCell(1, 0) {
        rowHeight(12)
        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 (!specifiedProfileGroup.isEmpty())
    {
        processNextCell(1, 0) {
            text('Группа:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedProfileGroup, 'metadataValue')
        }
    }
    if (!specifiedAgents.isEmpty())
    {
        processNextCell(1, 0) {
            text('Агенты:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedAgents, 'metadataValue')
        }
    }
    if (!specifiedGds.isEmpty())
    {
        processNextCell(1, 0) {
            text('Система бронирования:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedGds, 'metadataValue')
        }
    }
    if (!specifiedAgencies.isEmpty())
    {
        processNextCell(1, 0) {
            text('Агентство:', 'metadataTitle')
        }
        processNextCell(0, 1) {
            text(specifiedAgencies, 'metadataValue')
        }
    }
}

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

private void fillTable(List<RailwayTicket> ticketList)
{
    BigDecimal sellFareByAgent = BigDecimal.ZERO
    BigDecimal refundFareByAgent = BigDecimal.ZERO
    BigDecimal sellFareByGroup = BigDecimal.ZERO
    BigDecimal refundFareByGroup = BigDecimal.ZERO
    BigDecimal sellFareBySubAgent = BigDecimal.ZERO
    BigDecimal refundFareBySubAgent = BigDecimal.ZERO
    BigDecimal sellRewardByAgent = BigDecimal.ZERO
    BigDecimal refundRewardByAgent = BigDecimal.ZERO
    BigDecimal sellRewardByGroup = BigDecimal.ZERO
    BigDecimal refundRewardByGroup = BigDecimal.ZERO
    BigDecimal sellRewardBySubAgent = BigDecimal.ZERO
    BigDecimal refundRewardBySubAgent = BigDecimal.ZERO

    generalRefundFare = BigDecimal.ZERO
    generalRefundReward = BigDecimal.ZERO
    generalSellFare = BigDecimal.ZERO
    generalSellReward = BigDecimal.ZERO

    numberOfSoldTickets = 0
    numberOfRefundedTickets = 0

    if ((isGroupedByAgent || !specifiedAgents.isEmpty()) &&
            (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()))
    {
        ticketList = ticketList.sort { RailwayTicket a, RailwayTicket b ->
            a.subAgent <=> b.subAgent ?: a.groupName <=> b.groupName ?:
                    a.agent <=> b.agent ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }
    else if (isGroupedByAgent || !specifiedAgents.isEmpty())
    {
        ticketList = ticketList.sort { RailwayTicket a, RailwayTicket b ->
            a.subAgent <=> b.subAgent ?:
                    a.agent <=> b.agent ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }
    else if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty())
    {
        ticketList = ticketList.sort { RailwayTicket a, RailwayTicket b ->
            a.subAgent <=> b.subAgent ?:
                    a.groupName <=> b.groupName ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }
    else
    {
        ticketList = ticketList.sort { RailwayTicket a, RailwayTicket b ->
            a.subAgent <=> b.subAgent ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }

    RailwayTicket currentTicket
    RailwayTicket nextTicket
    if (ticketList.size() > 0)
    {
        currentTicket = ticketList.first()
    }
    for (int i = 1; i <= ticketList.size(); i++)
    {
        if (i != ticketList.size())
        {
            nextTicket = ticketList.get(i)
        }
        else
        {
            nextTicket = null
        }
        processNextCell(1, 0) {
            text(currentTicket?.blankOwner, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.subAgent, 'textData')
        }
        if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty())
        {
            processNextCell(0, 1) {
                text(currentTicket?.groupName, 'textData')
            }
        }
        if (isGroupedByAgent || !specifiedAgents.isEmpty())
        {
            processNextCell(0, 1) {
                text(currentTicket?.agent, 'textData')
            }
        }
        processNextCell(0, 1) {
            text(getProductStatusString(currentTicket), 'textData')
        }
        processNextCell(0, 1) {
            date(currentTicket?.issueDate, 'dateData')
        }
        processNextCell(0, 1) {
            text(currentTicket.bookingNumber, 'textData');
        }
        processNextCell(0, 1) {
            text(currentTicket?.systemNumber, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.endorsement, 'textData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.ticketPrice, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.commission, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.totalPrice, 'numberData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.passengerName, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.gdsName, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.agency, 'textData')
        }

        if (currentTicket.productStatus == ProductStatus.SELL)
        {
            sellFareByAgent = sellFareByAgent.add(currentTicket.ticketPrice)
            sellRewardByAgent = sellRewardByAgent.add(currentTicket.commission)
            sellFareBySubAgent = sellFareBySubAgent.add(currentTicket.ticketPrice)
            sellRewardBySubAgent = sellRewardBySubAgent.add(currentTicket.commission)
            sellFareByGroup = sellFareByGroup.add(currentTicket.ticketPrice)
            sellRewardByGroup = sellRewardByGroup.add(currentTicket.commission)
            this.generalSellFare = this.generalSellFare.add(currentTicket.ticketPrice)
            this.generalSellReward = this.generalSellReward.add(currentTicket.commission)
            this.numberOfSoldTickets++
        }
        else
        {
            refundFareByAgent = refundFareByAgent.add(currentTicket.ticketPrice)
            refundRewardByAgent = refundRewardByAgent.add(currentTicket.commission)
            refundFareBySubAgent = refundFareBySubAgent.add(currentTicket.ticketPrice)
            refundRewardBySubAgent = refundRewardBySubAgent.add(currentTicket.commission)
            refundFareByGroup = refundFareByGroup.add(currentTicket.ticketPrice)
            refundRewardByGroup = refundRewardByGroup.add(currentTicket.commission)
            this.generalRefundFare = this.generalRefundFare.add(currentTicket.ticketPrice)
            this.generalRefundReward = this.generalRefundReward.add(currentTicket.commission)
            this.numberOfRefundedTickets++
        }

        boolean isAgentChanged = !currentTicket?.agent?.equals(nextTicket?.agent)
        boolean isGroupChanged = !currentTicket?.groupName?.equals((nextTicket?.groupName))
        boolean isSubAgentChanged = !currentTicket?.subAgent?.equals(nextTicket?.subAgent)
        boolean isStatusChanged = !currentTicket?.productStatus?.equals(nextTicket?.productStatus)
        boolean needTotalForAgent = (isGroupedByAgent || !specifiedAgents.isEmpty()) && ((!isAgentChanged && isStatusChanged) || isAgentChanged)
        boolean needTotalForSubAgent = isSubAgentChanged
        boolean needTotalForGroup = (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()) && isGroupChanged

        if (needTotalForAgent)
        {
            if (isStatusChanged || isAgentChanged)
            {
                if (currentTicket.productStatus == ProductStatus.SELL)
                {
                    processNextCell(1, 0) {
                        text(String.format('Итого операции Продажа по агенту %s:', currentTicket.agent),
                             'preliminaryTotalText')
                    }
                    processNextCell(0, 1) {
                        number(sellFareByAgent, 'totalNumber')
                    }
                    processNextCell(0, 1) {
                        number(sellRewardByAgent, 'totalNumber')
                    }
                    processNextCell(0, 1) {
                        number(sellFareByAgent.add(sellRewardByAgent), 'totalNumber')
                    }
                    processNextCell(0, 1) {
                        text('', 'data')
                    }
                    processNextCell(0, 1) {
                        text('', 'data')
                    }
                    processNextCell(0, 1) {
                        text('', 'data')
                    }
                }
                else
                {
                    processNextCell(1, 0) {
                        text(String.format('Итого операции Возврат по агенту %s:', currentTicket.agent),
                             'preliminaryTotalText')
                    }
                    processNextCell(0, 1) {
                        number(refundFareByAgent, 'totalNumber')
                    }
                    processNextCell(0, 1) {
                        number(refundRewardByAgent, 'totalNumber')
                    }
                    processNextCell(0, 1) {
                        number(refundFareByAgent.add(refundRewardByAgent), 'totalNumber')
                    }
                    processNextCell(0, 1) {
                        text('', 'data')
                    }
                    processNextCell(0, 1) {
                        text('', 'data')
                    }
                    processNextCell(0, 1) {
                        text('', 'data')
                    }
                }
            }

            if (isAgentChanged)
            {
                processNextCell(1, 0) {
                    text(String.format('Итого по агенту %s:', currentTicket.agent),
                         'preliminaryTotalText')
                }
                processNextCell(0, 1) {
                    number(sellFareByAgent.add(refundFareByAgent), 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(sellRewardByAgent.add(refundRewardByAgent), 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(sellFareByAgent.add(refundFareByAgent).add(sellRewardByAgent).add(refundRewardByAgent),
                           'totalNumber')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                sellFareByAgent = BigDecimal.ZERO
                sellRewardByAgent = BigDecimal.ZERO
                refundFareByAgent = BigDecimal.ZERO
                refundRewardByAgent = BigDecimal.ZERO
            }
        }

        if (needTotalForGroup)
        {
            if (!sellFareByGroup.equals(BigDecimal.ZERO))
            {
                processNextCell(1, 0) {
                    text(String.format('Итого операции Продажа по группе %s:', currentTicket.groupName),
                         'preliminaryTotalText')
                }
                processNextCell(0, 1) {
                    number(sellFareByGroup, 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(sellRewardByGroup, 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(sellFareByGroup.add(sellRewardByGroup), 'totalNumber')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
            }

            if (!refundFareByGroup.equals(BigDecimal.ZERO))
            {
                processNextCell(1, 0) {
                    text(String.format('Итого операции Возврат по группе %s:', currentTicket.groupName),
                         'preliminaryTotalText')
                }
                processNextCell(0, 1) {
                    number(refundFareByGroup, 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(refundRewardByGroup, 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(refundFareByGroup.add(refundRewardByGroup), 'totalNumber')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
            }

            processNextCell(1, 0) {
                text(String.format('Итого по группе %s:', currentTicket.groupName), 'preliminaryTotalText')
            }
            processNextCell(0, 1) {
                number(sellFareByGroup.add(refundFareByGroup), 'totalNumber')
            }
            processNextCell(0, 1) {
                number(sellRewardByGroup.add(refundRewardByGroup), 'totalNumber')
            }
            processNextCell(0, 1) {
                number(sellFareByGroup.add(sellRewardByGroup).add(refundFareByGroup).add(refundRewardByGroup),
                       'totalNumber')
            }
            processNextCell(0, 1) {
                text('', 'data')
            }
            processNextCell(0, 1) {
                text('', 'data')
            }
            processNextCell(0, 1) {
                text('', 'data')
            }
            sellFareByGroup = BigDecimal.ZERO
            sellRewardByGroup = BigDecimal.ZERO
            refundFareByGroup = BigDecimal.ZERO
            refundRewardByGroup = BigDecimal.ZERO
        }

        if (needTotalForSubAgent)
        {
            if (!sellFareBySubAgent.equals(BigDecimal.ZERO))
            {
                processNextCell(1, 0) {
                    text(String.format('Итого операции Продажа по субагенту %s:', currentTicket.subAgent),
                         'preliminaryTotalText')
                }
                processNextCell(0, 1) {
                    number(sellFareBySubAgent, 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(sellRewardBySubAgent, 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(sellFareBySubAgent.add(sellRewardBySubAgent), 'totalNumber')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
            }

            if (!refundFareBySubAgent.equals(BigDecimal.ZERO))
            {
                processNextCell(1, 0) {
                    text(String.format('Итого операции Возврат по субагенту %s:', currentTicket.subAgent),
                         'preliminaryTotalText')
                }
                processNextCell(0, 1) {
                    number(refundFareBySubAgent, 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(refundRewardBySubAgent, 'totalNumber')
                }
                processNextCell(0, 1) {
                    number(refundFareBySubAgent.add(refundRewardBySubAgent), 'totalNumber')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
                processNextCell(0, 1) {
                    text('', 'data')
                }
            }

            processNextCell(1, 0) {
                text(String.format('Итого по субагенту %s:', currentTicket.subAgent), 'preliminaryTotalText')
            }
            processNextCell(0, 1) {
                number(sellFareBySubAgent.add(refundFareBySubAgent), 'totalNumber')
            }
            processNextCell(0, 1) {
                number(sellRewardBySubAgent.add(refundRewardBySubAgent), 'totalNumber')
            }
            processNextCell(0, 1) {
                number(sellFareBySubAgent.add(refundFareBySubAgent).add(sellRewardBySubAgent).add(
                        refundRewardBySubAgent), 'totalNumber')
            }
            processNextCell(0, 1) {
                text('', 'data')
            }
            processNextCell(0, 1) {
                text('', 'data')
            }
            processNextCell(0, 1) {
                text('', 'data')
            }
            sellFareBySubAgent = BigDecimal.ZERO
            sellRewardBySubAgent = BigDecimal.ZERO
            refundFareBySubAgent = BigDecimal.ZERO
            refundRewardBySubAgent = BigDecimal.ZERO
        }
        currentTicket = nextTicket
        rowHeight(12)
    }
}

private void fillTableFooter(int numberOfSoldTickets, BigDecimal generalSellFare, BigDecimal generalSellReward,
        int numberOfRefundedTickets, BigDecimal generalRefundFare, BigDecimal generalRefundReward)
{
    processNextCell(2, getColumnShiftTotal(1)) {
        text('ПРОДАНО БИЛЕТОВ:', 'finalTotalText')
    }
    processNextCell(0, 1) {
        number(numberOfSoldTickets, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        text('ИТОГО ПО ПРОДАЖАМ:', 'finalTotalText')
    }
    processNextCell(0, 1) {
        number(generalSellFare, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(generalSellReward, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(generalSellFare.add(generalSellReward), 'totalNumber')
    }
    processNextCell(1, getColumnShiftTotal(1)) {
        text('ВОЗВРАЩЕНО БИЛЕТОВ:', 'finalTotalText')
    }
    processNextCell(0, 1) {
        number(numberOfRefundedTickets, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        text('ИТОГО ПО ВОЗВРАТАМ:', 'finalTotalText')
    }
    processNextCell(0, 1) {
        number(generalRefundFare, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(generalRefundReward, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(generalRefundFare.add(generalRefundReward), 'totalNumber')
    }
    processNextCell(1, getColumnShiftTotal(1)) {
        text('ИТОГО БИЛЕТОВ:', 'finalTotalText')
    }
    processNextCell(0, 1) {
        number(numberOfSoldTickets - numberOfRefundedTickets, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        text('ИТОГО:', 'finalTotalText')
    }
    processNextCell(0, 1) {
        number(generalSellFare.add(generalRefundFare), 'totalNumber')
    }
    processNextCell(0, 1) {
        number(generalSellReward.add(generalRefundReward), 'totalNumber')
    }
    processNextCell(0, 1) {
        number(generalSellFare.add(generalSellReward).add(generalRefundFare).add(generalRefundReward), 'totalNumber')
    }
}

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

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

private void populateSubAgent(RailwayProductIndex index, RailwayTicket railwayTicket)
{
    if (index.getSubagency() != null)
    {
        railwayTicket.setSubAgent(index.getSubagency().getCaption())
    }
    else
    {
        railwayTicket.setSubAgent("")
    }
}

private void populateTotalFare(RailwayTicket railwayTicket)
{
    railwayTicket.setTotalPrice(railwayTicket.getTicketPrice() + railwayTicket.getCommission())
}

private void populateFare(RailwayProductIndex index, RailwayTicket railwayTicket)
{
    BigDecimal fare = BigDecimal.ZERO
    if (index.getEquivalentTotalFare() != null)
    {
        fare = index.getEquivalentTotalFare()
    }
    railwayTicket.setTicketPrice(fare)
}

private void populateIssueDate(RailwayProductIndex index, RailwayTicket railwayTicket)
{
    if (index.getSystemNumber() != null)
    {
        railwayTicket.setIssueDate(index.getIssueDate())
    }
}

private void populateProductStatus(RailwayProduct product, RailwayTicket railwayTicket)
{
    if (product.getStatus() != null)
    {
        railwayTicket.setProductStatus(product.getStatus())
    }
}

private String getProductStatusString(RailwayTicket ticket)
{
    ProductStatus productStatus = ticket.productStatus
    String statusString
    switch (productStatus)
    {
        case ProductStatus.EXCHANGE:
            statusString = "Обмен"
            break
        case ProductStatus.REFUND:
            statusString = "Возврат"
            break
        case ProductStatus.SELL:
            statusString = "Продажа"
            break
        case ProductStatus.VOID:
            statusString = "Аннулирован"
            break
        default:
            statusString = "Не указано"
            break
    }
    return statusString
}

private void populateAgent(RailwayProductIndex index, EntityContainer<BookingFile> container,
        RailwayTicket railwayTicket)
{
    String issAgent = ""
    if (container.getEntity().getCustomerProfile() != null)
    {
        if (container.getEntity().getCustomerProfile().equals(ProfileHelper.getRetailProfileContainer().toReference()))
        {
            if (index.getAgent() != null)
            {
                issAgent = index.getAgent().getCaption()
            }
        }
        else
        {
            issAgent = container.getEntity().getCustomerProfile().getCaption()
        }
    }
    railwayTicket.setAgent(issAgent)
}

private void populateSupplier(RailwayProductIndex index, RailwayTicket railwayTicket)
{
    if (index.getBlankOwner() != null)
    {
        railwayTicket.setBlankOwner(index.getBlankOwner().getCaption())
    }
    else
    {
        railwayTicket.setBlankOwner("")
    }
}

private void populateSystemNumber(RailwayProductIndex index, RailwayTicket ticket)
{
    if (index.getSystemNumber() != null)
    {
        ticket.setSystemNumber(index.getSystemNumber())
    }
    else
    {
        ticket.setSystemNumber("")
    }
}

private void populateGDS(RailwayProductIndex index, RailwayTicket ticket)
{
    if (index.getDisplayedGdsName() != null) {
        ticket.setGdsName(index.getDisplayedGdsName().toString())
    } else if (index.getGdsName() != null) {
        ticket.setGdsName(index.getGdsName().toString())
    } else {
        ticket.setGdsName("")
    }
}

private void populatePassengerName(RailwayProductIndex index, RailwayTicket ticket)
{
    ticket.setPassengerName(buildNameString(index.getTravellers()))
}

private void populateAgencyReward(RailwayProduct product, RailwayTicket ticket, boolean isRefund)
{
    BigDecimal agencyReward = BookingHelper.getCommissionValue(
            GeneralProductHelper.calculateCommissions(product, ContractType.SUBAGENCY,
                                                      GeneralProductHelper.feePropertyTypes), isRefund)

    if (agencyReward == null)
    {
        agencyReward = BigDecimal.ZERO
    }

    ticket.setCommission(agencyReward)
}

private void populateEndorsement(RailwayProduct product, RailwayTicket ticket)
{
    ticket.setEndorsement(product.getEndorsement() == null ? '' : product.getEndorsement())
}

private void populateGroup(RailwayProductIndex index, RailwayTicket ticket)
{
    ticket.setGroupName(index.getProfileGroup() == null ? '' : index.getProfileGroup().toString())
}

private void populateAgency(RailwayProductIndex index, RailwayTicket ticket)
{
    ticket.setAgency(index.getAgency() == null ? '' : index.getAgency().toString())
}

private static String buildNameString(final Collection<String> travellersNames)
{
    StringBuilder sb = new StringBuilder()

    for (String name : travellersNames)
    {
        if (name == null)
        {
            continue
        }
        sb.append(name)
        sb.append(', ')
    }

    if (sb.length() > 0)
    {
        sb.deleteCharAt(sb.lastIndexOf(","))
    }
    return sb.toString()
}
