// Imports
import com.gridnine.xtrip.common.model.dict.CurrencyInfo
import com.gridnine.xtrip.common.model.helpers.DictHelper
import java.text.SimpleDateFormat
import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.helpers.PersonalLocalizableNameFormatter
import com.gridnine.xtrip.common.model.helpers.ProfileHelper
import com.gridnine.xtrip.common.model.system.PaymentType
import com.gridnine.xtrip.common.reports.AgentReportHelper
import com.gridnine.xtrip.common.util.MiscUtil
import com.gridnine.xtrip.common.util.TextUtil

// Styles
createStyle(name: 'title', h_alignment: 'CENTER', v_alignment: 'CENTER')
createStyle(name: 'titleH1', fontHeight: 14, parent: 'title')
createStyle(name: 'titleH2', fontHeight: 9, parent: 'title')
createStyle(name: 'titleH3', fontHeight: 7, parent: 'title')
createStyle(name: 'header', h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight: 7)
createStyle(name: 'columnHeader', wrapText: true, parent: 'header')
createStyle(name: 'rowHeader', wrapText: true, parent: 'header')
createStyle(name: 'data', h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight: 7)
createStyle(name: 'dataText', parent: 'data')
createStyle(name: 'dataDate', format: 'm/d/yy', parent: 'data')
createStyle(name: 'dataNumber', h_alignment: 'RIGHT', format: '#,##0.00', parent: 'data')
createStyle(name: 'ahl', h_alignment: 'LEFT')
createStyle(name: 'ahc', h_alignment: 'CENTER')
createStyle(name: 'ahr', h_alignment: 'RIGHT')
createStyle(name: 'avt', v_alignment: 'TOP')
createStyle(name: 'avc', v_alignment: 'CENTER')
createStyle(name: 'avb', v_alignment: 'BOTTOM')
createStyle(name: 'aac', h_alignment: 'CENTER', v_alignment: 'CENTER')
createStyle(name: 'bold', fontBold: true)
createStyle(name: 'italic', fontItalic: true)
createStyle(name: 'bt', topBorder: 'THIN')
createStyle(name: 'bl', leftBorder: 'THIN')
createStyle(name: 'bb', bottomBorder: 'THIN')
createStyle(name: 'br', rightBorder: 'THIN')
createStyle(name: 'ba', topBorder: 'THIN', leftBorder: 'THIN', bottomBorder: 'THIN', rightBorder: 'THIN')
createStyle(name: 'grey25', foreground: 'GREY_25_PERCENT')
createStyle(name: 'locked', locked: true)

// Properties
def periodBeginParameter = parameters['key-report-params']?.periodBegin
def periodEndParameter = parameters['key-report-params']?.periodEnd
def subagencyParameter = parameters['key-report-params']?.subagency
def salesPointParameter = parameters['key-report-params']?.salesPoint
def cashRegisterParameter = parameters['key-report-params']?.cashRegister
def cashRegisterWorkplaceParameter = parameters['key-report-params']?.cashRegisterWorkplace
def agentParameter = parameters['key-report-params']?.agent
def includeDiscounts = parameters['includeDiscounts']

// Closures
def agency = {

    def subagency = EntityStorage.get().resolve(subagencyParameter)?.entity
    def salesPoint = EntityStorage.get().resolve(salesPointParameter)?.entity
    def agency = subagency ? subagency : EntityStorage.get().resolve(salesPoint?.agency)?.entity
    return agency ? ProfileHelper.getFullName(agency, LocaleHelper.getLocale('ru', 'RU'), false) : 'Не указано'
}

def salesPoint = {

    def salesPoint = EntityStorage.get().resolve(salesPointParameter)?.entity
    return salesPoint ? ProfileHelper.getFullName(salesPoint) : 'Не указано'
}

def agent = {

    def agent = EntityStorage.get().resolve(agentParameter)?.entity
    return agent ? new PersonalLocalizableNameFormatter('L f m').format(agent, LocaleHelper.getLocale('ru', 'RU'), false) : 'Не указано'
}

def period = {

    def format = new SimpleDateFormat('dd.MM.yy HH:mm')
    return String.format('с %s по %s', periodBeginParameter != null ? format.format(periodBeginParameter) : '?', periodEndParameter != null ? format.format(periodEndParameter) : '?')
}

def systemNumbers = { it ->
    return it.systemNumber ? (it.conjunction > 0 ? String.format('%s/%s', it.systemNumber, it.conjunction) : it.systemNumber) : null
}

page{'Возврат'}{

    // Set landcape mode
    landscape(true)

    // Set narrow margins
    margin(0.25, 0.25, 0.75, 0.75)

    CurrencyInfo currencyInfo = DictHelper.getCurrencyInfoByAnyCode(DictHelper.getLocalCurrencyCode())
    def currency = currencyInfo?.toString(LocaleHelper.RU_LOCALE)

    def paidWithinPaymentTypesSet = [] as Set
    def unpaidWithinPaymentTypesSet = [] as Set
    def paidBeforePaymentTypesSet = [] as Set
    def refusedPaymentTypesSet = [] as Set

    def hasEmptyPaymentTypes = false
    def hasVoids = false

    // Initialization
    tickets{

        paidWithinPaymentTypesSet.addAll(it.productPaidWithinDebitAmounts.keySet())
        paidWithinPaymentTypesSet.addAll(it.productPaidWithinCreditAmounts.keySet())
        paidWithinPaymentTypesSet.addAll(it.feesPaidWithinDebitAmounts.keySet())
        paidWithinPaymentTypesSet.addAll(it.feesPaidWithinCreditAmounts.keySet())

        if(includeDiscounts) {

            paidWithinPaymentTypesSet.addAll(it.discountsPaidWithinDebitAmounts.keySet())
            paidWithinPaymentTypesSet.addAll(it.discountsPaidWithinCreditAmounts.keySet())
        }

        unpaidWithinPaymentTypesSet.addAll(it.productRefusedWithinDebitAmounts.keySet())
        unpaidWithinPaymentTypesSet.addAll(it.productRefusedWithinCreditAmounts.keySet())
        unpaidWithinPaymentTypesSet.addAll(it.productUnpaidWithinDebitAmounts.keySet())
        unpaidWithinPaymentTypesSet.addAll(it.productUnpaidWithinCreditAmounts.keySet())
        unpaidWithinPaymentTypesSet.addAll(it.feesRefusedWithinDebitAmounts.keySet())
        unpaidWithinPaymentTypesSet.addAll(it.feesRefusedWithinCreditAmounts.keySet())
        unpaidWithinPaymentTypesSet.addAll(it.feesUnpaidWithinDebitAmounts.keySet())
        unpaidWithinPaymentTypesSet.addAll(it.feesUnpaidWithinCreditAmounts.keySet())

        if(includeDiscounts) {

            unpaidWithinPaymentTypesSet.addAll(it.discountsRefusedWithinDebitAmounts.keySet())
            unpaidWithinPaymentTypesSet.addAll(it.discountsRefusedWithinCreditAmounts.keySet())
            unpaidWithinPaymentTypesSet.addAll(it.discountsUnpaidWithinDebitAmounts.keySet())
            unpaidWithinPaymentTypesSet.addAll(it.discountsUnpaidWithinCreditAmounts.keySet())
        }

        paidBeforePaymentTypesSet.addAll(it.productPaidBeforeDebitAmounts.keySet())
        paidBeforePaymentTypesSet.addAll(it.productPaidBeforeCreditAmounts.keySet())
        paidBeforePaymentTypesSet.addAll(it.feesPaidBeforeDebitAmounts.keySet())
        paidBeforePaymentTypesSet.addAll(it.feesPaidBeforeCreditAmounts.keySet())

        if(includeDiscounts) {

            paidBeforePaymentTypesSet.addAll(it.discountsPaidBeforeDebitAmounts.keySet())
            paidBeforePaymentTypesSet.addAll(it.discountsPaidBeforeCreditAmounts.keySet())
        }

        refusedPaymentTypesSet.addAll(it.productRefusedBeforeDebitAmounts.keySet())
        refusedPaymentTypesSet.addAll(it.productRefusedBeforeCreditAmounts.keySet())
        refusedPaymentTypesSet.addAll(it.productRefusedWithinDebitAmounts.keySet())
        refusedPaymentTypesSet.addAll(it.productRefusedWithinCreditAmounts.keySet())
        refusedPaymentTypesSet.addAll(it.feesRefusedBeforeDebitAmounts.keySet())
        refusedPaymentTypesSet.addAll(it.feesRefusedBeforeCreditAmounts.keySet())
        refusedPaymentTypesSet.addAll(it.feesRefusedWithinDebitAmounts.keySet())
        refusedPaymentTypesSet.addAll(it.feesRefusedWithinCreditAmounts.keySet())

        if(includeDiscounts) {

            refusedPaymentTypesSet.addAll(it.discountsRefusedBeforeDebitAmounts.keySet())
            refusedPaymentTypesSet.addAll(it.discountsRefusedBeforeCreditAmounts.keySet())
            refusedPaymentTypesSet.addAll(it.discountsRefusedWithinDebitAmounts.keySet())
            refusedPaymentTypesSet.addAll(it.discountsRefusedWithinCreditAmounts.keySet())
        }

        if (it.productPaidWithinDebitAmounts.isEmpty()
        && it.productPaidWithinCreditAmounts.isEmpty()
        && it.feesPaidWithinDebitAmounts.isEmpty()
        && it.feesPaidWithinCreditAmounts.isEmpty()
        && it.productRefusedWithinDebitAmounts.isEmpty()
        && it.productRefusedWithinCreditAmounts.isEmpty()
        && it.feesRefusedWithinDebitAmounts.isEmpty()
        && it.feesRefusedWithinCreditAmounts.isEmpty()
        && it.productUnpaidWithinDebitAmounts.isEmpty()
        && it.productUnpaidWithinCreditAmounts.isEmpty()
        && it.feesUnpaidWithinDebitAmounts.isEmpty()
        && it.feesUnpaidWithinCreditAmounts.isEmpty()
        && it.productPaidBeforeDebitAmounts.isEmpty()
        && it.productPaidBeforeCreditAmounts.isEmpty()
        && it.feesPaidBeforeDebitAmounts.isEmpty()
        && it.feesPaidBeforeCreditAmounts.isEmpty()
        && it.productRefusedBeforeDebitAmounts.isEmpty()
        && it.productRefusedBeforeCreditAmounts.isEmpty()
        && it.feesRefusedBeforeDebitAmounts.isEmpty()
        && it.feesRefusedBeforeCreditAmounts.isEmpty()
        && (!includeDiscounts || (it.discountsPaidWithinDebitAmounts.isEmpty()
        && it.discountsPaidWithinCreditAmounts.isEmpty()
        && it.discountsRefusedWithinDebitAmounts.isEmpty()
        && it.discountsRefusedWithinCreditAmounts.isEmpty()
        && it.discountsUnpaidWithinDebitAmounts.isEmpty()
        && it.discountsUnpaidWithinCreditAmounts.isEmpty()
        && it.discountsPaidBeforeDebitAmounts.isEmpty()
        && it.discountsPaidBeforeCreditAmounts.isEmpty()
        && it.discountsRefusedBeforeDebitAmounts.isEmpty()
        && it.discountsRefusedBeforeCreditAmounts.isEmpty()))) {
            hasEmptyPaymentTypes = true
        }

        if(it.status == ProductStatus.VOID) {
            hasVoids = true
        }
    }

    def withinPaymentTypesSet = paidWithinPaymentTypesSet + unpaidWithinPaymentTypesSet
    def beforePaymentTypesSet = paidBeforePaymentTypesSet

    def fopTypesColumnsCount = 3 + (includeDiscounts ? 1 : 0)

    def withinPaymentTypesColumnsCount = paidWithinPaymentTypesSet.size() * fopTypesColumnsCount + unpaidWithinPaymentTypesSet.size() * fopTypesColumnsCount
    def beforePaymentTypesColumnsCount = paidBeforePaymentTypesSet.size() * fopTypesColumnsCount
    def refusedPaymentTypesColumnsCount = refusedPaymentTypesSet.size() * fopTypesColumnsCount

    def columnsCount = Math.max(Math.max(14 + withinPaymentTypesColumnsCount, 15 + beforePaymentTypesColumnsCount), 15 + refusedPaymentTypesColumnsCount)

    // Report header
    text(agency(), 'titleH1|ahl|bold', columnsCount, 1)

    columnWidth(4)
    nextColumn()
    columnWidth(5)
    nextColumn()
    columnWidth(12)
    nextColumn()
    columnWidth(17)

    nextRow()
    text('Реестр возврата билетов к отчету', 'titleH2|ahl|bold', columnsCount, 1)
    2.times{nextRow()}
    2.times{nextColumn()}
    text('Точка продажи', 'titleH2|ahr|bold')
    nextColumn()
    text(salesPoint(), 'titleH2|ahl|bold')
    nextRow()
    2.times{nextColumn()}
    text('Агент', 'titleH2|ahr|bold')
    nextColumn()
    text(agent(), 'titleH2|ahl|bold')
    nextRow()
    2.times{nextColumn()}
    text('Период', 'titleH2|ahr|bold')
    nextColumn()
    text(period(), 'titleH2|ahl|bold')
    2.times{nextRow()}

    if(withinPaymentTypesSet.size() > 0 || hasEmptyPaymentTypes || hasVoids) {

        def paymentTypes = withinPaymentTypesSet.asList()

        Collections.sort(paymentTypes, AgentReportHelper.paymentTypesComparator)

        text('Оформленные билеты', 'columnHeader|bold|ba', withinPaymentTypesColumnsCount + 14, 1)
        nextRow()

        // Within table header (first row)
        text('№', 'columnHeader|ba', 1, 4)
        nextColumn()
        text('№ билета', 'columnHeader|ba', 2, 4)
        2.times{nextColumn()}
        text('Маршрут', 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Тариф,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Таксы,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Такса RU/ZZ,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Штраф,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Сборы,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Доп. услуги,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()

        if(withinPaymentTypesSet.size() > 0) {

            text('Итого по формам оплаты', 'columnHeader|ba', withinPaymentTypesColumnsCount, 1)
            withinPaymentTypesColumnsCount.times{nextColumn()}
        }

        text('Путешественники', 'columnHeader|ba', 2, 4)
        2.times{nextColumn()}
        text('Примечания', 'columnHeader|ba', 2, 4)
        nextRow()

        // Within table header (second row)
        rowHeight(24, false)
        10.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            def paymentTypeColumnsCount = 0

            if(paidWithinPaymentTypesSet.contains(paymentType)) {
                paymentTypeColumnsCount += fopTypesColumnsCount
            }

            if(unpaidWithinPaymentTypesSet.contains(paymentType)) {
                paymentTypeColumnsCount += fopTypesColumnsCount
            }

            text(paymentType.toString(), 'columnHeader|ba', paymentTypeColumnsCount, 1)
            paymentTypeColumnsCount.times{nextColumn()}
        }

        nextRow()

        // Within table header (third row)
        10.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            if(paidWithinPaymentTypesSet.contains(paymentType)) {

                text('Оплачено', 'columnHeader|ba', fopTypesColumnsCount, 1)
                fopTypesColumnsCount.times{nextColumn()}
            }

            if(unpaidWithinPaymentTypesSet.contains(paymentType)) {

                text('Неоплачено', 'columnHeader|ba', fopTypesColumnsCount, 1)
                fopTypesColumnsCount.times{nextColumn()}
            }
        }

        nextRow()

        if(withinPaymentTypesSet.size() > 0) {
            rowHeight(22, false)
        }

        // Within table header (fourth row)
        10.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            if(paidWithinPaymentTypesSet.contains(paymentType)) {

                text('По билету', 'rowHeader|ba')
                nextColumn()
                text('Возврат сборов', 'rowHeader|ba')
                nextColumn()
                text('Сборы за возврат', 'rowHeader|ba')
                nextColumn()

                if(includeDiscounts) {

                    text('Скидки', 'rowHeader|ba')
                    nextColumn()
                }
            }

            if(unpaidWithinPaymentTypesSet.contains(paymentType)) {

                text('По билету', 'rowHeader|ba')
                nextColumn()
                text('Возврат сборов', 'rowHeader|ba')
                nextColumn()
                text('Сборы за возврат', 'rowHeader|ba')
                nextColumn()

                if(includeDiscounts) {

                    text('Скидки', 'rowHeader|ba')
                    nextColumn()
                }
            }
        }

        nextRow()

        // Within table
        groups{it.groupName}{

            def groupName = null

            tickets{

                def ticketPaymentTypesSet = [] as Set

                ticketPaymentTypesSet.addAll(it.productPaidWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productPaidWithinCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesPaidWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesPaidWithinCreditAmounts.keySet())

                if(includeDiscounts) {

                    ticketPaymentTypesSet.addAll(it.discountsPaidWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsPaidWithinCreditAmounts.keySet())
                }

                ticketPaymentTypesSet.addAll(it.productRefusedWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productRefusedWithinCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productUnpaidWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productUnpaidWithinCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedWithinCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesUnpaidWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesUnpaidWithinCreditAmounts.keySet())

                if(includeDiscounts) {

                    ticketPaymentTypesSet.addAll(it.discountsRefusedWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsRefusedWithinCreditAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsUnpaidWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsUnpaidWithinCreditAmounts.keySet())
                }

                boolean emptyPaymentTypes = false

                if (it.productPaidWithinDebitAmounts.isEmpty()
                && it.productPaidWithinCreditAmounts.isEmpty()
                && it.feesPaidWithinDebitAmounts.isEmpty()
                && it.feesPaidWithinCreditAmounts.isEmpty()
                && it.productRefusedWithinDebitAmounts.isEmpty()
                && it.productRefusedWithinCreditAmounts.isEmpty()
                && it.feesRefusedWithinDebitAmounts.isEmpty()
                && it.feesRefusedWithinCreditAmounts.isEmpty()
                && it.productUnpaidWithinDebitAmounts.isEmpty()
                && it.productUnpaidWithinCreditAmounts.isEmpty()
                && it.feesUnpaidWithinDebitAmounts.isEmpty()
                && it.feesUnpaidWithinCreditAmounts.isEmpty()
                && it.productPaidBeforeDebitAmounts.isEmpty()
                && it.productPaidBeforeCreditAmounts.isEmpty()
                && it.feesPaidBeforeDebitAmounts.isEmpty()
                && it.feesPaidBeforeCreditAmounts.isEmpty()
                && it.productRefusedBeforeDebitAmounts.isEmpty()
                && it.productRefusedBeforeCreditAmounts.isEmpty()
                && it.feesRefusedBeforeDebitAmounts.isEmpty()
                && it.feesRefusedBeforeCreditAmounts.isEmpty()
                && (!includeDiscounts || (it.discountsPaidWithinDebitAmounts.isEmpty()
                && it.discountsPaidWithinCreditAmounts.isEmpty()
                && it.discountsRefusedWithinDebitAmounts.isEmpty()
                && it.discountsRefusedWithinCreditAmounts.isEmpty()
                && it.discountsUnpaidWithinDebitAmounts.isEmpty()
                && it.discountsUnpaidWithinCreditAmounts.isEmpty()
                && it.discountsPaidBeforeDebitAmounts.isEmpty()
                && it.discountsPaidBeforeCreditAmounts.isEmpty()
                && it.discountsRefusedBeforeDebitAmounts.isEmpty()
                && it.discountsRefusedBeforeCreditAmounts.isEmpty()))) {
                    emptyPaymentTypes = true
                }

                if(it.groupName != null && (ticketPaymentTypesSet.size() > 0 || emptyPaymentTypes || it.status == ProductStatus.VOID)) {
                    groupName = it.groupName
                }
            }

            if(groupName == null) {
                return
            }

            // Within table (group title row)
            text(groupName, 'columnHeader|bold|ba', withinPaymentTypesColumnsCount + 14, 1)
            nextRow()

            def ticketsCount = 0

            // Within table (ticket rows)
            tickets{

                boolean emptyPaymentTypes = false

                if (it.productPaidWithinDebitAmounts.isEmpty()
                && it.productPaidWithinCreditAmounts.isEmpty()
                && it.feesPaidWithinDebitAmounts.isEmpty()
                && it.feesPaidWithinCreditAmounts.isEmpty()
                && it.productRefusedWithinDebitAmounts.isEmpty()
                && it.productRefusedWithinCreditAmounts.isEmpty()
                && it.feesRefusedWithinDebitAmounts.isEmpty()
                && it.feesRefusedWithinCreditAmounts.isEmpty()
                && it.productUnpaidWithinDebitAmounts.isEmpty()
                && it.productUnpaidWithinCreditAmounts.isEmpty()
                && it.feesUnpaidWithinDebitAmounts.isEmpty()
                && it.feesUnpaidWithinCreditAmounts.isEmpty()
                && it.productPaidBeforeDebitAmounts.isEmpty()
                && it.productPaidBeforeCreditAmounts.isEmpty()
                && it.feesPaidBeforeDebitAmounts.isEmpty()
                && it.feesPaidBeforeCreditAmounts.isEmpty()
                && it.productRefusedBeforeDebitAmounts.isEmpty()
                && it.productRefusedBeforeCreditAmounts.isEmpty()
                && it.feesRefusedBeforeDebitAmounts.isEmpty()
                && it.feesRefusedBeforeCreditAmounts.isEmpty()
                && (!includeDiscounts || (it.discountsPaidWithinDebitAmounts.isEmpty()
                && it.discountsPaidWithinCreditAmounts.isEmpty()
                && it.discountsRefusedWithinDebitAmounts.isEmpty()
                && it.discountsRefusedWithinCreditAmounts.isEmpty()
                && it.discountsUnpaidWithinDebitAmounts.isEmpty()
                && it.discountsUnpaidWithinCreditAmounts.isEmpty()
                && it.discountsPaidBeforeDebitAmounts.isEmpty()
                && it.discountsPaidBeforeCreditAmounts.isEmpty()
                && it.discountsRefusedBeforeDebitAmounts.isEmpty()
                && it.discountsRefusedBeforeCreditAmounts.isEmpty()))) {
                    emptyPaymentTypes = true
                }

                if(!emptyPaymentTypes && it.status != ProductStatus.VOID) {

                    def ticketPaymentTypesSet = [] as Set

                    ticketPaymentTypesSet.addAll(it.productPaidWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.productPaidWithinCreditAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.feesPaidWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.feesPaidWithinCreditAmounts.keySet())

                    if(includeDiscounts) {

                        ticketPaymentTypesSet.addAll(it.discountsPaidWithinDebitAmounts.keySet())
                        ticketPaymentTypesSet.addAll(it.discountsPaidWithinCreditAmounts.keySet())
                    }

                    ticketPaymentTypesSet.addAll(it.productRefusedWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.productRefusedWithinCreditAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.productUnpaidWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.productUnpaidWithinCreditAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.feesRefusedWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.feesRefusedWithinCreditAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.feesUnpaidWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.feesUnpaidWithinCreditAmounts.keySet())

                    if(includeDiscounts) {

                        ticketPaymentTypesSet.addAll(it.discountsRefusedWithinDebitAmounts.keySet())
                        ticketPaymentTypesSet.addAll(it.discountsRefusedWithinCreditAmounts.keySet())
                        ticketPaymentTypesSet.addAll(it.discountsUnpaidWithinDebitAmounts.keySet())
                        ticketPaymentTypesSet.addAll(it.discountsUnpaidWithinCreditAmounts.keySet())
                    }

                    if(ticketPaymentTypesSet.size() == 0) {
                        return
                    }
                }

                text(String.valueOf(ticketsCount + 1), 'dataText|ba')
                nextColumn()
                text(it.ticketSeries, 'dataText|ba')
                nextColumn()
                text(systemNumbers(it), 'dataText|ba')
                nextColumn()
                text(it.status != ProductStatus.VOID ? it.routeLine : 'Аннулирован', 'dataText|ba')
                nextColumn()
                number(it.tariffEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(MiscUtil.sum(it.taxesFuelEquivalentAmount, it.taxesStateEquivalentAmount, it.taxesOthersEquivalentAmount), 'dataNumber|ba')
                nextColumn()
                number(it.taxesCarrierEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.penaltyEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.feesEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.additionalServicesEquivalentAmount, 'dataNumber|ba')
                nextColumn()

                for(PaymentType paymentType : paymentTypes) {

                    if(paidWithinPaymentTypesSet.contains(paymentType)) {

                        def amountProduct = null
                        def amountFeesDebit = null
                        def amountFeesCredit = null
                        def amountDiscounts = null

                        amountProduct = MiscUtil.sum(amountProduct, it.productPaidWithinDebitAmounts[paymentType])
                        amountProduct = MiscUtil.sum(amountProduct, it.productPaidWithinCreditAmounts[paymentType])

                        amountFeesDebit = MiscUtil.sum(amountFeesDebit, it.feesPaidWithinDebitAmounts[paymentType])

                        amountFeesCredit = MiscUtil.sum(amountFeesCredit, it.feesPaidWithinCreditAmounts[paymentType])

                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsPaidWithinDebitAmounts[paymentType])
                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsPaidWithinCreditAmounts[paymentType])

                        number(amountProduct, 'dataNumber|ba')
                        nextColumn()
                        number(amountFeesCredit, 'dataNumber|ba')
                        nextColumn()
                        number(amountFeesDebit, 'dataNumber|ba')
                        nextColumn()

                        if(includeDiscounts) {

                            number(amountDiscounts, 'dataNumber|ba')
                            nextColumn()
                        }
                    }

                    if(unpaidWithinPaymentTypesSet.contains(paymentType)) {

                        def amountProduct = null
                        def amountFeesDebit = null
                        def amountFeesCredit = null
                        def amountDiscounts = null

                        amountProduct = MiscUtil.sum(amountProduct, it.productRefusedWithinDebitAmounts[paymentType])
                        amountProduct = MiscUtil.sum(amountProduct, it.productRefusedWithinCreditAmounts[paymentType])
                        amountProduct = MiscUtil.sum(amountProduct, it.productUnpaidWithinDebitAmounts[paymentType])
                        amountProduct = MiscUtil.sum(amountProduct, it.productUnpaidWithinCreditAmounts[paymentType])

                        amountFeesDebit = MiscUtil.sum(amountFeesDebit, it.feesRefusedWithinDebitAmounts[paymentType])
                        amountFeesDebit= MiscUtil.sum(amountFeesDebit, it.feesUnpaidWithinDebitAmounts[paymentType])

                        amountFeesCredit = MiscUtil.sum(amountFeesCredit, it.feesRefusedWithinCreditAmounts[paymentType])
                        amountFeesCredit = MiscUtil.sum(amountFeesCredit, it.feesUnpaidWithinCreditAmounts[paymentType])

                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsRefusedWithinDebitAmounts[paymentType])
                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsRefusedWithinCreditAmounts[paymentType])
                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsUnpaidWithinDebitAmounts[paymentType])
                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsUnpaidWithinCreditAmounts[paymentType])

                        number(amountProduct, 'dataNumber|ba')
                        nextColumn()
                        number(amountFeesCredit, 'dataNumber|ba')
                        nextColumn()
                        number(amountFeesDebit, 'dataNumber|ba')
                        nextColumn()

                        if(includeDiscounts) {

                            number(amountDiscounts, 'dataNumber|ba')
                            nextColumn()
                        }
                    }
                }

                text(TextUtil.join('/', it.travellerNames, true), 'dataText|ahl|ba', 2, 1)
                2.times{nextColumn()}
                text(it.comments, 'dataText|ba', 2, 1)
                nextRow()

                ticketsCount++
            }

            text('Итого:', 'dataText|ahr|bold|italic|ba', 4, 1)
            4.times{nextColumn()}

            6.times{

                formula("SUM(${cellIndex(-ticketsCount, 0)}:${cellIndex(-1, 0)})", 'dataNumber|bold|italic|ba')
                nextColumn()
            }

            if(withinPaymentTypesSet.size() > 0) {

                withinPaymentTypesColumnsCount.times{

                    formula("SUM(${cellIndex(-ticketsCount, 0)}:${cellIndex(-1, 0)})", 'dataNumber|bold|italic|ba')
                    nextColumn()
                }
            }

            text(null, 'dataText|italic|ba', 2, 1)
            2.times{nextColumn()}
            text(null, 'dataText|italic|ba', 2, 1)
            nextRow()
        }

        nextRow()
    }

    if(beforePaymentTypesSet.size() > 0) {

        def paymentTypes = beforePaymentTypesSet.asList()

        Collections.sort(paymentTypes, AgentReportHelper.paymentTypesComparator)

        text('Возврат по билетам, выписанным ранее', 'columnHeader|bold|ba', beforePaymentTypesColumnsCount + 15, 1)
        nextRow()

        // Before table header (first row)
        text('№', 'columnHeader|ba', 1, 4)
        nextColumn()
        text('№ билета', 'columnHeader|ba', 2, 4)
        2.times{nextColumn()}
        text('Маршрут', 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Тариф,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Таксы,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Такса RU/ZZ,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Штраф,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Сборы,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Доп. услуги,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text('Дата выписки', 'columnHeader|ba', 1, 4)
        nextColumn()
        text('Итого по формам оплаты', 'columnHeader|ba', beforePaymentTypesColumnsCount, 1)
        beforePaymentTypesColumnsCount.times{nextColumn()}
        text('Путешественники', 'columnHeader|ba', 2, 4)
        2.times{nextColumn()}
        text('Примечания', 'columnHeader|ba', 2, 4)
        nextRow()

        // Before table header (second row)
        rowHeight(24, false)
        11.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            def paymentTypeColumnsCount = 0

            if(paidBeforePaymentTypesSet.contains(paymentType)) {
                paymentTypeColumnsCount += fopTypesColumnsCount
            }

            text(paymentType.toString(), 'columnHeader|ba', paymentTypeColumnsCount, 1)
            paymentTypeColumnsCount.times{nextColumn()}
        }

        nextRow()

        // Before table header (third row)
        11.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            if(paidBeforePaymentTypesSet.contains(paymentType)) {

                text('Оплачено', 'columnHeader|ba', fopTypesColumnsCount, 1)
                fopTypesColumnsCount.times{nextColumn()}
            }
        }

        nextRow()

        rowHeight(22, false)

        // Before table header (fourth row)
        11.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            if(paidBeforePaymentTypesSet.contains(paymentType)) {

                text('По билету', 'rowHeader|ba')
                nextColumn()
                text('Возврат сборов', 'rowHeader|ba')
                nextColumn()
                text('Сборы за возврат', 'rowHeader|ba')
                nextColumn()

                if(includeDiscounts) {

                    text('Скидки', 'rowHeader|ba')
                    nextColumn()
                }
            }
        }

        nextRow()

        // Before table
        groups{it.groupName}{

            def groupName = null

            tickets{

                def ticketPaymentTypesSet = [] as Set

                ticketPaymentTypesSet.addAll(it.productPaidBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productPaidBeforeCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesPaidBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesPaidBeforeCreditAmounts.keySet())

                if(includeDiscounts) {

                    ticketPaymentTypesSet.addAll(it.discountsPaidBeforeDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsPaidBeforeCreditAmounts.keySet())
                }

                if(it.groupName != null && ticketPaymentTypesSet.size() > 0) {
                    groupName = it.groupName
                }
            }

            if(groupName == null) {
                return
            }

            // Before table (group title row)
            text(groupName, 'columnHeader|bold|ba', beforePaymentTypesColumnsCount + 15, 1)
            nextRow()

            def ticketsCount = 0

            // Before table (ticket rows)
            tickets{

                def ticketPaymentTypesSet = [] as Set

                ticketPaymentTypesSet.addAll(it.productPaidBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productPaidBeforeCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesPaidBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesPaidBeforeCreditAmounts.keySet())

                if(includeDiscounts) {

                    ticketPaymentTypesSet.addAll(it.discountsPaidBeforeDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsPaidBeforeCreditAmounts.keySet())
                }

                if(ticketPaymentTypesSet.size() == 0) {
                    return
                }

                text(String.valueOf(ticketsCount + 1), 'dataText|ba')
                nextColumn()
                text(it.ticketSeries, 'dataText|ba')
                nextColumn()
                text(systemNumbers(it), 'dataText|ba')
                nextColumn()
                text(it.routeLine, 'dataText|ba')
                nextColumn()
                number(it.tariffEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(MiscUtil.sum(it.taxesFuelEquivalentAmount, it.taxesStateEquivalentAmount, it.taxesOthersEquivalentAmount), 'dataNumber|ba')
                nextColumn()
                number(it.taxesCarrierEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.penaltyEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.feesEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.additionalServicesEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                date(it.issueDate, 'dataDate|ba')
                nextColumn()

                for(PaymentType paymentType : paymentTypes) {

                    if(paidBeforePaymentTypesSet.contains(paymentType)) {

                        def amountProduct = null
                        def amountFeesDebit = null
                        def amountFeesCredit = null
                        def amountDiscounts = null

                        amountProduct = MiscUtil.sum(amountProduct, it.productPaidBeforeDebitAmounts[paymentType])
                        amountProduct = MiscUtil.sum(amountProduct, it.productPaidBeforeCreditAmounts[paymentType])

                        amountFeesDebit = MiscUtil.sum(amountFeesDebit, it.feesPaidBeforeDebitAmounts[paymentType])

                        amountFeesCredit= MiscUtil.sum(amountFeesCredit, it.feesPaidBeforeCreditAmounts[paymentType])

                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsPaidBeforeDebitAmounts[paymentType])
                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsPaidBeforeCreditAmounts[paymentType])

                        number(amountProduct, 'dataNumber|ba')
                        nextColumn()
                        number(amountFeesCredit, 'dataNumber|ba')
                        nextColumn()
                        number(amountFeesDebit, 'dataNumber|ba')
                        nextColumn()

                        if(includeDiscounts) {

                            number(amountDiscounts, 'dataNumber|ba')
                            nextColumn()
                        }
                    }
                }

                text(TextUtil.join('/', it.travellerNames, true), 'dataText|ahl|ba', 2, 1)
                2.times{nextColumn()}
                text(it.comments, 'dataText|ba', 2, 1)
                nextRow()

                ticketsCount++
            }

            text('Итого:', 'dataText|ahr|bold|italic|ba', 4, 1)
            4.times{nextColumn()}

            6.times{

                formula("SUM(${cellIndex(-ticketsCount, 0)}:${cellIndex(-1, 0)})", 'dataNumber|bold|italic|ba')
                nextColumn()
            }

            text(null, 'dataText|bold|italic|ba')
            nextColumn()

            beforePaymentTypesColumnsCount.times{

                formula("SUM(${cellIndex(-ticketsCount, 0)}:${cellIndex(-1, 0)})", 'dataNumber|bold|italic|ba')
                nextColumn()
            }

            text(null, 'dataText|italic|ba', 2, 1)
            2.times{nextColumn()}
            text(null, 'dataText|italic|ba', 2, 1)
            nextRow()
        }

        nextRow()
    }

    if(refusedPaymentTypesSet.size() > 0) {

        def paymentTypes = refusedPaymentTypesSet.asList()

        Collections.sort(paymentTypes, AgentReportHelper.paymentTypesComparator)

        text('Возврат по билетам в связи с отказом', 'columnHeader|bold|ba', refusedPaymentTypesColumnsCount + 15, 1)
        nextRow()

        // Refused table header (first row)
        text('№', 'columnHeader|ba', 1, 4)
        nextColumn()
        text('№ билета', 'columnHeader|ba', 2, 4)
        2.times{nextColumn()}
        text('Маршрут', 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Тариф,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Таксы,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Такса RU/ZZ,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Штраф,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Сборы,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text("Доп. услуги,\n${currency}", 'columnHeader|ba', 1, 4)
        nextColumn()
        text('Дата выписки', 'columnHeader|ba', 1, 4)
        nextColumn()
        text('Итого по формам оплаты', 'columnHeader|ba', refusedPaymentTypesColumnsCount, 1)
        refusedPaymentTypesColumnsCount.times{nextColumn()}
        text('Путешественники', 'columnHeader|ba', 2, 4)
        2.times{nextColumn()}
        text('Примечания', 'columnHeader|ba', 2, 4)
        nextRow()

        // Refused table header (second row)
        rowHeight(24, false)
        11.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            def paymentTypeColumnsCount = 0

            if(refusedPaymentTypesSet.contains(paymentType)) {
                paymentTypeColumnsCount += fopTypesColumnsCount
            }

            text(paymentType.toString(), 'columnHeader|ba', paymentTypeColumnsCount, 1)
            paymentTypeColumnsCount.times{nextColumn()}
        }

        nextRow()

        // Refused table header (third row)
        11.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            if(refusedPaymentTypesSet.contains(paymentType)) {

                text('Неоплачено', 'columnHeader|ba', fopTypesColumnsCount, 1)
                fopTypesColumnsCount.times{nextColumn()}
            }
        }

        nextRow()

        rowHeight(22, false)

        // Refused table header (fourth row)
        11.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            if(refusedPaymentTypesSet.contains(paymentType)) {

                text('По билету', 'rowHeader|ba')
                nextColumn()
                text('Возврат сборов', 'rowHeader|ba')
                nextColumn()
                text('Сборы за возврат', 'rowHeader|ba')
                nextColumn()

                if(includeDiscounts) {

                    text('Скидки', 'rowHeader|ba')
                    nextColumn()
                }
            }
        }

        nextRow()

        // Refused table
        groups{it.groupName}{

            def groupName = null

            tickets{

                def ticketPaymentTypesSet = [] as Set

                ticketPaymentTypesSet.addAll(it.productRefusedBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productRefusedBeforeCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productRefusedWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productRefusedWithinCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedBeforeCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedWithinCreditAmounts.keySet())

                if(includeDiscounts) {

                    ticketPaymentTypesSet.addAll(it.discountsRefusedBeforeDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsRefusedBeforeCreditAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsRefusedWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsRefusedWithinCreditAmounts.keySet())
                }

                if(it.groupName != null && ticketPaymentTypesSet.size() > 0) {
                    groupName = it.groupName
                }
            }

            if(groupName == null) {
                return
            }

            // Refused table (group title row)
            text(groupName, 'columnHeader|bold|ba', refusedPaymentTypesColumnsCount + 15, 1)
            nextRow()

            def ticketsCount = 0

            // Before table (ticket rows)
            tickets{

                def ticketPaymentTypesSet = [] as Set

                ticketPaymentTypesSet.addAll(it.productRefusedBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productRefusedBeforeCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productRefusedWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.productRefusedWithinCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedBeforeCreditAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedWithinDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.feesRefusedWithinCreditAmounts.keySet())

                if(includeDiscounts) {

                    ticketPaymentTypesSet.addAll(it.discountsRefusedBeforeDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsRefusedBeforeCreditAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsRefusedWithinDebitAmounts.keySet())
                    ticketPaymentTypesSet.addAll(it.discountsRefusedWithinCreditAmounts.keySet())
                }

                if(ticketPaymentTypesSet.size() == 0) {
                    return
                }

                text(String.valueOf(ticketsCount + 1), 'dataText|ba')
                nextColumn()
                text(it.ticketSeries, 'dataText|ba')
                nextColumn()
                text(systemNumbers(it), 'dataText|ba')
                nextColumn()
                text(it.routeLine, 'dataText|ba')
                nextColumn()
                number(it.tariffEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(MiscUtil.sum(it.taxesFuelEquivalentAmount, it.taxesStateEquivalentAmount, it.taxesOthersEquivalentAmount), 'dataNumber|ba')
                nextColumn()
                number(it.taxesCarrierEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.penaltyEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.feesEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                number(it.additionalServicesEquivalentAmount, 'dataNumber|ba')
                nextColumn()
                date(it.issueDate, 'dataDate|ba')
                nextColumn()

                for(PaymentType paymentType : paymentTypes) {

                    if(refusedPaymentTypesSet.contains(paymentType)) {

                        def amountProduct = null
                        def amountFeesDebit = null
                        def amountFeesCredit = null
                        def amountDiscounts = null

                        amountProduct = MiscUtil.sum(amountProduct, it.productRefusedBeforeDebitAmounts[paymentType])
                        amountProduct = MiscUtil.sum(amountProduct, it.productRefusedBeforeCreditAmounts[paymentType])
                        amountProduct = MiscUtil.sum(amountProduct, it.productRefusedWithinDebitAmounts[paymentType])
                        amountProduct = MiscUtil.sum(amountProduct, it.productRefusedWithinCreditAmounts[paymentType])

                        amountFeesDebit = MiscUtil.sum(amountFeesDebit, it.feesRefusedBeforeDebitAmounts[paymentType])
                        amountFeesDebit = MiscUtil.sum(amountFeesDebit, it.feesRefusedWithinDebitAmounts[paymentType])

                        amountFeesCredit = MiscUtil.sum(amountFeesCredit, it.feesRefusedBeforeCreditAmounts[paymentType])
                        amountFeesCredit = MiscUtil.sum(amountFeesCredit, it.feesRefusedWithinCreditAmounts[paymentType])

                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsRefusedBeforeDebitAmounts[paymentType])
                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsRefusedBeforeCreditAmounts[paymentType])
                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsRefusedWithinDebitAmounts[paymentType])
                        amountDiscounts = MiscUtil.sum(amountDiscounts, it.discountsRefusedWithinCreditAmounts[paymentType])

                        number(amountProduct, 'dataNumber|ba')
                        nextColumn()
                        number(amountFeesCredit, 'dataNumber|ba')
                        nextColumn()
                        number(amountFeesDebit, 'dataNumber|ba')
                        nextColumn()

                        if(includeDiscounts) {

                            number(amountDiscounts, 'dataNumber|ba')
                            nextColumn()
                        }
                    }
                }

                text(TextUtil.join('/', it.travellerNames, true), 'dataText|ahl|ba', 2, 1)
                2.times{nextColumn()}
                text(it.comments, 'dataText|ba', 2, 1)
                nextRow()

                ticketsCount++
            }

            text('Итого:', 'dataText|ahr|bold|italic|ba', 4, 1)
            4.times{nextColumn()}

            6.times{

                formula("SUM(${cellIndex(-ticketsCount, 0)}:${cellIndex(-1, 0)})", 'dataNumber|bold|italic|ba')
                nextColumn()
            }

            text(null, 'dataText|bold|italic|ba')
            nextColumn()

            refusedPaymentTypesColumnsCount.times{

                formula("SUM(${cellIndex(-ticketsCount, 0)}:${cellIndex(-1, 0)})", 'dataNumber|bold|italic|ba')
                nextColumn()
            }

            text(null, 'dataText|italic|ba', 2, 1)
            2.times{nextColumn()}
            text(null, 'dataText|italic|ba', 2, 1)
            nextRow()
        }

        nextRow()
    }

    nextRow()

    // Report footer
    2.times{nextColumn()}
    text('Подпись', 'titleH2|ahr')
    nextColumn()
    text(null, 'titleH2|bb', 2, 1)
    2.times{nextColumn()}
    text("${agent()}", 'titleH2|ahl')
}
