// 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.Environment
import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.l10n.model.Number2WordsConverterRegistry
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.AccountingEntry
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.MiscUtil.Pair;
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 remainsCashAmount = parameters['key-remains-cash-amount']
def collectionsDebitData = parameters['key-collections-debit-data']
def collectionsCreditData = parameters['key-collections-credit-data']
def collectionsDebitAmount = parameters['key-collections-debit-amount']
def collectionsCreditAmount = parameters['key-collections-credit-amount']
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 number2Words = { number ->

    def converter = Environment.getPublished(Number2WordsConverterRegistry.class)
            .findConverter(LocaleHelper.getLocale('ru', 'RU'), DictHelper.getLocalCurrencyCode())
    return converter ? (number != null ? TextUtil.capitalize(converter.toCurrencyWords(number,
            DictHelper.getLocalCurrencyCode(), true, false, false)) : '') : ''
}

page{'Касса'}{

    // 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 amount = MiscUtil.sum(remainsCashAmount, collectionsDebitAmount, collectionsCreditAmount)

    def paidPaymentTypesSet = [] as Set
    def refusedPaymentTypesSet = [] as Set
    def unpaidPaymentTypesSet = [] as Set

    def productAgentDebitPenalty = null
    def productAgentCreditPenalty = null
    def feesAgentDebitPenalty = null
    def feesAgentCreditPenalty = null

    def collection = null

    def hasVoids = false

    // Initialization
    tickets{

        amount = MiscUtil.sum(amount, it.productPaidBeforeDebitAmounts.get(PaymentType.CASH))
        amount = MiscUtil.sum(amount, it.productPaidBeforeCreditAmounts.get(PaymentType.CASH))
        amount = MiscUtil.sum(amount, it.productPaidWithinDebitAmounts.get(PaymentType.CASH))
        amount = MiscUtil.sum(amount, it.productPaidWithinCreditAmounts.get(PaymentType.CASH))
        amount = MiscUtil.sum(amount, it.feesPaidBeforeDebitAmounts.get(PaymentType.CASH))
        amount = MiscUtil.sum(amount, it.feesPaidBeforeCreditAmounts.get(PaymentType.CASH))
        amount = MiscUtil.sum(amount, it.feesPaidWithinDebitAmounts.get(PaymentType.CASH))
        amount = MiscUtil.sum(amount, it.feesPaidWithinCreditAmounts.get(PaymentType.CASH))

        if(includeDiscounts) {

            amount = MiscUtil.sum(amount, MiscUtil.negate((BigDecimal)it.discountsPaidBeforeDebitAmounts.get(PaymentType.CASH)))
            amount = MiscUtil.sum(amount, MiscUtil.negate((BigDecimal)it.discountsPaidBeforeCreditAmounts.get(PaymentType.CASH)))
            amount = MiscUtil.sum(amount, MiscUtil.negate((BigDecimal)it.discountsPaidWithinDebitAmounts.get(PaymentType.CASH)))
            amount = MiscUtil.sum(amount, MiscUtil.negate((BigDecimal)it.discountsPaidWithinCreditAmounts.get(PaymentType.CASH)))
        }

        paidPaymentTypesSet.addAll(it.productPaidBeforeDebitAmounts.keySet())
        paidPaymentTypesSet.addAll(it.productPaidBeforeCreditAmounts.keySet())
        paidPaymentTypesSet.addAll(it.productPaidWithinDebitAmounts.keySet())
        paidPaymentTypesSet.addAll(it.productPaidWithinCreditAmounts.keySet())
        paidPaymentTypesSet.addAll(it.feesPaidBeforeDebitAmounts.keySet())
        paidPaymentTypesSet.addAll(it.feesPaidBeforeCreditAmounts.keySet())
        paidPaymentTypesSet.addAll(it.feesPaidWithinDebitAmounts.keySet())
        paidPaymentTypesSet.addAll(it.feesPaidWithinCreditAmounts.keySet())

        if(includeDiscounts) {

            paidPaymentTypesSet.addAll(it.discountsPaidBeforeDebitAmounts.keySet())
            paidPaymentTypesSet.addAll(it.discountsPaidBeforeCreditAmounts.keySet())
            paidPaymentTypesSet.addAll(it.discountsPaidWithinDebitAmounts.keySet())
            paidPaymentTypesSet.addAll(it.discountsPaidWithinCreditAmounts.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())
        }

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

        if(includeDiscounts) {

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

        productAgentDebitPenalty = MiscUtil.sum(productAgentDebitPenalty, it.productAgentDebitPenaltyEquivalentAmount)
        productAgentCreditPenalty = MiscUtil.sum(productAgentCreditPenalty, it.productAgentCreditPenaltyEquivalentAmount)
        feesAgentDebitPenalty = MiscUtil.sum(feesAgentDebitPenalty, it.feesAgentDebitPenaltyEquivalentAmount)
        feesAgentCreditPenalty = MiscUtil.sum(feesAgentCreditPenalty, it.feesAgentCreditPenaltyEquivalentAmount)

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

    def paymentTypesSet = paidPaymentTypesSet + refusedPaymentTypesSet + unpaidPaymentTypesSet

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

    def paymentTypesColumnsCount = paidPaymentTypesSet.size() * fopTypesColumnsCount + unpaidPaymentTypesSet.size() * fopTypesColumnsCount

    text(agency(), 'titleH1|ahl|bold', paymentTypesColumnsCount + 3, 1)

    // Column widths
    (paymentTypesColumnsCount + 3).times{

        columnWidth(9)
        nextColumn()
    }

    nextRow()

    text('Отчет по продаже перевозок и услуг', 'titleH2|ahl|bold', paymentTypesColumnsCount + 3, 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()}

    text("Остаток денежных средств на начало смены", 'rowHeader|ba', 3, 2)
    3.times{nextColumn()}
    number(remainsCashAmount, 'dataNumber|ba', 1, 2)
    nextColumn()
    text(currency, 'dataText|ba', 1, 2)
    3.times{nextRow()}

    def paymentTypes = paymentTypesSet.asList()

    Collections.sort(paymentTypes, AgentReportHelper.paymentTypesComparator)

    // Table header (first row)
    text(null, 'columnHeader|bold|ba', 3, 4)
    3.times{nextColumn()}

    if(paymentTypes.size() > 0) {

        rowHeight(24, false)
        text("Сумма по формам оплаты (${currency})", 'columnHeader|bold|ba', paymentTypesColumnsCount, 1)
    }

    nextRow()

    // Table header (second row)
    rowHeight(24, false)
    3.times{nextColumn()}

    for(PaymentType paymentType : paymentTypes) {

        def paymentTypeColumnsCount = 0

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

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

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

    nextRow()

    // Table header (third row)
    3.times{nextColumn()}

    for(PaymentType paymentType : paymentTypes) {

        if(paidPaymentTypesSet.contains(paymentType)) {

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

        if(unpaidPaymentTypesSet.contains(paymentType)) {

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

    nextRow()

    // Table header (fourth row)
    3.times{nextColumn()}

    for(PaymentType paymentType : paymentTypes) {

        if(paidPaymentTypesSet.contains(paymentType)) {

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

            if(includeDiscounts) {

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

        if(unpaidPaymentTypesSet.contains(paymentType)) {

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

            if(includeDiscounts) {

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

    nextRow()

    def generalGroupsCount = 0

    // General 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.productPaidWithinDebitAmounts.keySet())
            ticketPaymentTypesSet.addAll(it.productPaidWithinCreditAmounts.keySet())
            ticketPaymentTypesSet.addAll(it.feesPaidBeforeDebitAmounts.keySet())
            ticketPaymentTypesSet.addAll(it.feesPaidBeforeCreditAmounts.keySet())
            ticketPaymentTypesSet.addAll(it.feesPaidWithinDebitAmounts.keySet())
            ticketPaymentTypesSet.addAll(it.feesPaidWithinCreditAmounts.keySet())

            if(includeDiscounts) {

                ticketPaymentTypesSet.addAll(it.discountsPaidBeforeDebitAmounts.keySet())
                ticketPaymentTypesSet.addAll(it.discountsPaidBeforeCreditAmounts.keySet())
                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(it.groupName != null && ticketPaymentTypesSet.size() > 0) {
                groupName = it.groupName
            }
        }

        if(groupName == null) {
            return
        }

        // General table (group title row)
        text(groupName, 'columnHeader|bold|ba', paymentTypesColumnsCount + 3, 1)
        nextRow()

        // General table (group first row)
        text('Оформлено билетов', 'rowHeader|ba', 3, 1)
        3.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            def amountProductPaid = null
            def amountFeesPaid = null
            def amountDiscountsPaid = null
            def amountProductUnpaid = null
            def amountFeesUnpaid = null
            def amountDiscountsUnpaid = null

            tickets{

                if(paidPaymentTypesSet.contains(paymentType)) {

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

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

                    amountDiscountsPaid = MiscUtil.sum(amountDiscountsPaid, it.discountsPaidWithinDebitAmounts[paymentType])
                }

                if(unpaidPaymentTypesSet.contains(paymentType)) {

                    amountProductUnpaid = MiscUtil.sum(amountProductUnpaid, it.productRefusedWithinDebitAmounts[paymentType])
                    amountProductUnpaid = MiscUtil.sum(amountProductUnpaid, it.productUnpaidWithinDebitAmounts[paymentType])

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

                    amountDiscountsUnpaid = MiscUtil.sum(amountDiscountsUnpaid, it.discountsRefusedWithinDebitAmounts[paymentType])
                    amountDiscountsUnpaid = MiscUtil.sum(amountDiscountsUnpaid, it.discountsUnpaidWithinDebitAmounts[paymentType])
                }
            }

            if(paidPaymentTypesSet.contains(paymentType)) {

                number(amountProductPaid, 'dataNumber|ba')
                nextColumn()
                number(amountFeesPaid, 'dataNumber|ba')
                nextColumn()

                if(includeDiscounts) {

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

            if(unpaidPaymentTypesSet.contains(paymentType)) {

                number(amountProductUnpaid, 'dataNumber|ba')
                nextColumn()
                number(amountFeesUnpaid, 'dataNumber|ba')
                nextColumn()

                if(includeDiscounts) {

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

        nextRow()

        // General table (group second row)
        text('Оплата за ранее оформл.', 'rowHeader|ba', 3, 1)
        3.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            def amountProductPaid = null
            def amountFeesPaid = null
            def amountDiscountsPaid = null

            tickets{

                if(paidPaymentTypesSet.contains(paymentType)) {

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

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

                    amountDiscountsPaid = MiscUtil.sum(amountDiscountsPaid, it.discountsPaidBeforeDebitAmounts[paymentType])
                }
            }

            if(paidPaymentTypesSet.contains(paymentType)) {

                number(amountProductPaid, 'dataNumber|ba')
                nextColumn()
                number(amountFeesPaid, 'dataNumber|ba')
                nextColumn()

                if(includeDiscounts) {

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

            if(unpaidPaymentTypesSet.contains(paymentType)) {

                fopTypesColumnsCount.times{

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

        nextRow()

        // General table (group third row)
        text('Итого', 'rowHeader|ba', 3, 1)
        3.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            if(paidPaymentTypesSet.contains(paymentType)) {

                fopTypesColumnsCount.times{

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

            if(unpaidPaymentTypesSet.contains(paymentType)) {

                fopTypesColumnsCount.times{

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

        nextRow()

        // General table (group fourth row)
        text('Возврат билетов', 'rowHeader|ba', 3, 1)
        3.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            def amountProductPaid = null
            def amountFeesPaid = null
            def amountDiscountsPaid = null
            def amountProductUnpaid = null
            def amountFeesUnpaid = null
            def amountDiscountsUnpaid = null

            tickets{

                if(paidPaymentTypesSet.contains(paymentType)) {

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

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

                    amountDiscountsPaid = MiscUtil.sum(amountDiscountsPaid, it.discountsPaidWithinCreditAmounts[paymentType])
                }

                if(unpaidPaymentTypesSet.contains(paymentType)) {

                    amountProductUnpaid = MiscUtil.sum(amountProductUnpaid, it.productRefusedWithinCreditAmounts[paymentType])
                    amountProductUnpaid = MiscUtil.sum(amountProductUnpaid, it.productUnpaidWithinCreditAmounts[paymentType])

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

                    amountDiscountsUnpaid = MiscUtil.sum(amountDiscountsUnpaid, it.discountsRefusedWithinCreditAmounts[paymentType])
                    amountDiscountsUnpaid = MiscUtil.sum(amountDiscountsUnpaid, it.discountsUnpaidWithinCreditAmounts[paymentType])
                }
            }

            if(paidPaymentTypesSet.contains(paymentType)) {

                number(amountProductPaid, 'dataNumber|ba')
                nextColumn()
                number(amountFeesPaid, 'dataNumber|ba')
                nextColumn()

                if(includeDiscounts) {

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

            if(unpaidPaymentTypesSet.contains(paymentType)) {

                number(amountProductUnpaid, 'dataNumber|ba')
                nextColumn()
                number(amountFeesUnpaid, 'dataNumber|ba')
                nextColumn()

                if(includeDiscounts) {

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

        nextRow()

        // General table (group fifth row)
        text('Возврат за ранее оформл.', 'rowHeader|ba', 3, 1)
        3.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            def amountProductPaid = null
            def amountFeesPaid = null
            def amountDiscountsPaid = null

            tickets{

                if(paidPaymentTypesSet.contains(paymentType)) {

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

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

                    amountDiscountsPaid = MiscUtil.sum(amountDiscountsPaid, it.discountsPaidBeforeCreditAmounts[paymentType])
                }
            }

            if(paidPaymentTypesSet.contains(paymentType)) {

                number(amountProductPaid, 'dataNumber|ba')
                nextColumn()
                number(amountFeesPaid, 'dataNumber|ba')
                nextColumn()

                if(includeDiscounts) {

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

            if(unpaidPaymentTypesSet.contains(paymentType)) {

                fopTypesColumnsCount.times{

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

        nextRow()

        // General table (group sixth row)
        text('Итого', 'rowHeader|ba', 3, 1)
        3.times{nextColumn()}

        for(PaymentType paymentType : paymentTypes) {

            if(paidPaymentTypesSet.contains(paymentType)) {

                fopTypesColumnsCount.times{

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

            if(unpaidPaymentTypesSet.contains(paymentType)) {

                fopTypesColumnsCount.times{

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

        nextRow()

        generalGroupsCount++
    }

    nextRow()

    def refusedGroupsCount = 0

    if(refusedPaymentTypesSet.size() > 0) {

        // Refused table (title row)
        text('Отказ от оформленных билетов', 'columnHeader|bold|ba', paymentTypesColumnsCount + 3, 1)
        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', paymentTypesColumnsCount + 3, 1)
            nextRow()

            // Refused table (group first row)
            text('Отказ от оформленных билетов', 'rowHeader|ba', 3, 1)
            3.times{nextColumn()}

            for(PaymentType paymentType : paymentTypes) {

                def amountProductRefused = null
                def amountFeesRefused = null
                def amountDiscountsRefused = null

                tickets{

                    if(refusedPaymentTypesSet.contains(paymentType)) {

                        amountProductRefused = MiscUtil.sum(amountProductRefused, it.productRefusedBeforeDebitAmounts[paymentType])
                        amountProductRefused = MiscUtil.sum(amountProductRefused, it.productRefusedWithinDebitAmounts[paymentType])

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

                        amountDiscountsRefused = MiscUtil.sum(amountDiscountsRefused, it.discountsRefusedBeforeDebitAmounts[paymentType])
                        amountDiscountsRefused = MiscUtil.sum(amountDiscountsRefused, it.discountsRefusedWithinDebitAmounts[paymentType])
                    }
                }

                if(paidPaymentTypesSet.contains(paymentType)) {

                    fopTypesColumnsCount.times{

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

                if(unpaidPaymentTypesSet.contains(paymentType)) {

                    number(amountProductRefused, 'dataNumber|ba')
                    nextColumn()
                    number(amountFeesRefused, 'dataNumber|ba')
                    nextColumn()

                    if(includeDiscounts) {

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

            nextRow()

            // Refused table (group second row)
            text('Возврат в связи с отказом', 'rowHeader|ba', 3, 1)
            3.times{nextColumn()}

            for(PaymentType paymentType : paymentTypes) {

                def amountProductRefused = null
                def amountFeesRefused = null
                def amountDiscountsRefused = null

                tickets{

                    if(refusedPaymentTypesSet.contains(paymentType)) {

                        amountProductRefused = MiscUtil.sum(amountProductRefused, it.productRefusedBeforeCreditAmounts[paymentType])
                        amountProductRefused = MiscUtil.sum(amountProductRefused, it.productRefusedWithinCreditAmounts[paymentType])

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

                        amountDiscountsRefused = MiscUtil.sum(amountDiscountsRefused, it.discountsRefusedBeforeCreditAmounts[paymentType])
                        amountDiscountsRefused = MiscUtil.sum(amountDiscountsRefused, it.discountsRefusedWithinCreditAmounts[paymentType])
                    }
                }

                if(paidPaymentTypesSet.contains(paymentType)) {

                    fopTypesColumnsCount.times{

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

                if(unpaidPaymentTypesSet.contains(paymentType)) {

                    number(amountProductRefused, 'dataNumber|ba')
                    nextColumn()
                    number(amountFeesRefused, 'dataNumber|ba')
                    nextColumn()

                    if(includeDiscounts) {

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

            nextRow()

            // Refused table (group third row)
            text('Итого', 'rowHeader|ba', 3, 1)
            3.times{nextColumn()}

            for(PaymentType paymentType : paymentTypes) {

                if(paidPaymentTypesSet.contains(paymentType)) {

                    fopTypesColumnsCount.times{

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

                if(unpaidPaymentTypesSet.contains(paymentType)) {

                    fopTypesColumnsCount.times{

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

            nextRow()

            refusedGroupsCount++
        }

        nextRow()
    }

    // Table footer (first row)
    text('Всего', 'columnHeader|bold|ba', 3, 1)
    3.times{nextColumn()}

    if(paymentTypes.size() > 0) {
        text("в том числе (${currency})", 'columnHeader|bold|ba', paymentTypesColumnsCount, 1)
    }

    nextRow()

    def refusedTableRowCount = refusedPaymentTypesSet.size() > 0 ? 2 + refusedGroupsCount * 4 : 0

    // Table footer (second row)
    text('Приход', 'rowHeader|ba', 3, 1)
    3.times{nextColumn()}

    for(PaymentType paymentType : paymentTypes) {

        def paymentTypeColumnsCount = 0

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

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

        paymentTypeColumnsCount.times{

            def totalFormula = null

            for(int i = 0; i < generalGroupsCount; i++) {
                totalFormula = totalFormula != null ? totalFormula + "+${cellIndex(1 - refusedTableRowCount - (generalGroupsCount - i) * 7, 0)}" : "${cellIndex(1 - refusedTableRowCount - (generalGroupsCount - i) * 7, 0)}"
            }

            formula(totalFormula, 'dataNumber|bold|ba')
            nextColumn()
        }
    }

    nextRow()

    // Table footer (third row)
    text('Расход', 'rowHeader|ba', 3, 1)
    3.times{nextColumn()}

    for(PaymentType paymentType : paymentTypes) {

        def paymentTypeColumnsCount = 0

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

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

        paymentTypeColumnsCount.times{

            def totalFormula = null

            for(int i = 0; i < generalGroupsCount; i++) {
                totalFormula = totalFormula != null ? totalFormula + "+${cellIndex(3 - refusedTableRowCount - (generalGroupsCount - i) * 7, 0)}" : "${cellIndex(3 - refusedTableRowCount - (generalGroupsCount - i) * 7, 0)}"
            }

            formula(totalFormula, 'dataNumber|bold|ba')
            nextColumn()
        }
    }

    2.times{nextRow()}

    // Summary table
    def cashDebitFormula = null
    def cashCreditFormula = null
    def creditCardDebitFormula = null
    def creditCardCreditFormula = null
    def columnCount = 0

    for(PaymentType paymentType : paymentTypes) {

        if(paidPaymentTypesSet.contains(paymentType)) {

            if(paymentType == PaymentType.CASH) {
                
                
                cashDebitFormula = "${cellIndex(-3, 3 + columnCount)}+${cellIndex(-3, 4 + columnCount)}"
                
                if(includeDiscounts) {
                    cashDebitFormula += "-${cellIndex(-3, 5 + columnCount)}"
                }
                
                cashCreditFormula = "${cellIndex(-2, 3 + columnCount)}+${cellIndex(-2, 4 + columnCount)}"
                
                if(includeDiscounts) {
                    cashCreditFormula += "-${cellIndex(-2, 5 + columnCount)}"
                }
            }

            if(paymentType == PaymentType.CREDIT_CARD_AGENCY) {
                
                
                creditCardDebitFormula = "${cellIndex(-3, 3 + columnCount)}+${cellIndex(-3, 4 + columnCount)}"
                
                if(includeDiscounts) {
                    creditCardDebitFormula += "-${cellIndex(-3, 5 + columnCount)}"
                }
                
                creditCardCreditFormula = "${cellIndex(-2, 3 + columnCount)}+${cellIndex(-2, 4 + columnCount)}"
                
                if(includeDiscounts) {
                    creditCardCreditFormula += "-${cellIndex(-2, 5 + columnCount)}"
                }
            }

            columnCount += fopTypesColumnsCount
        }

        if(unpaidPaymentTypesSet.contains(paymentType)) {
            columnCount += fopTypesColumnsCount
        }
    }

    text('Оборот за смену (наличные)', 'rowHeader|bold|ba', 3, 2)
    3.times{nextColumn()}
    formula("SUM(${cellIndex(0, 5)}:${cellIndex(1, 5)})", 'dataNumber|bold|ba', 1, 2)
    nextColumn()
    text(currency, 'dataText|bold|ba', 1, 2)
    nextColumn()
    text('Приход за смену (наличные)', 'rowHeader|bold|ba', 3, 1)
    3.times{nextColumn()}
    formula(cashDebitFormula, 'dataNumber|bold|ba')
    nextColumn()
    text(currency, 'dataText|bold|ba')
    nextRow()

    5.times{nextColumn()}
    text('Расход за смену (наличные)', 'rowHeader|bold|ba', 3, 1)
    3.times{nextColumn()}
    formula(cashCreditFormula, 'dataNumber|bold|ba')
    nextColumn()
    text(currency, 'dataText|bold|ba')
    nextRow()

    text('Оборот за смену (POS-терминал)', 'rowHeader|bold|ba', 3, 2)
    3.times{nextColumn()}
    formula("SUM(${cellIndex(0, 5)}:${cellIndex(1, 5)})", 'dataNumber|bold|ba', 1, 2)
    nextColumn()
    text(currency, 'dataText|bold|ba', 1, 2)
    nextColumn()
    text('Приход за смену (POS-терминал)', 'rowHeader|bold|ba', 3, 1)
    3.times{nextColumn()}
    formula(creditCardDebitFormula, 'dataNumber|bold|ba')
    nextColumn()
    text(currency, 'dataText|bold|ba')
    nextRow()

    5.times{nextColumn()}
    text('Расход за смену (POS-терминал)', 'rowHeader|bold|ba', 3, 1)
    3.times{nextColumn()}
    formula(creditCardCreditFormula, 'dataNumber|bold|ba')
    nextColumn()
    text(currency, 'dataText|bold|ba')
    2.times{nextRow()}

    def collectionsTableRowCount = 0

    if(collectionsDebitData.size() > 0 || collectionsCreditData.size() > 0) {

        collectionsTableRowCount = 1 + collectionsDebitData.size() + collectionsCreditData.size()

        // Collections table (debit)
        if(collectionsDebitData.size() > 0) {

            for(Pair<BigDecimal, String> collectionData : collectionsDebitData) {

                text("Инкассация (приход)", 'rowHeader|ba', 3, 1)
                3.times{nextColumn()}
                number(collectionData.getFirst(), 'dataNumber|ba', 2, 1)
                2.times{nextColumn()}
                text(collectionData.getSecond(), 'dataText|ahl|ba', 5, 1)
                nextRow()
            }
        }

        // Collections table (credit)
        if(collectionsCreditData.size() > 0) {

            for(Pair<BigDecimal, String> collectionData : collectionsCreditData) {

                text("Инкассация (расход)", 'rowHeader|ba', 3, 1)
                3.times{nextColumn()}
                number(collectionData.getFirst(), 'dataNumber|ba', 2, 1)
                2.times{nextColumn()}
                text(collectionData.getSecond(), 'dataText|ahl|ba', 5, 1)
                nextRow()
            }
        }

        nextRow()

    } else {

        collectionsTableRowCount = 3

        text("Инкассация (приход)", 'rowHeader|ba', 3, 1)
        3.times{nextColumn()}
        number(collectionsDebitAmount, 'dataNumber|ba', 2, 1)
        nextRow()
        text("Инкассация (расход)", 'rowHeader|ba', 3, 1)
        3.times{nextColumn()}
        number(collectionsCreditAmount, 'dataNumber|ba', 2, 1)
        2.times{nextRow()}
    }

    text("Остаток денежных средств на конец смены", 'rowHeader|ba', 3, 2)
    3.times{nextColumn()}
    formula("${cellIndex(-17 - generalGroupsCount * 7 - refusedTableRowCount - collectionsTableRowCount, 0)}+${cellIndex(-5 - collectionsTableRowCount, 0)}" + (collectionsTableRowCount > 0 ? "+SUM(${cellIndex(-collectionsTableRowCount, 0)}:${cellIndex(-2, 0)})" : ""), 'dataNumber|ba', 1, 2)
    nextColumn()
    text(currency, 'dataText|ba', 1, 2)
    3.times{nextRow()}

    text("Ошибка кассира", 'columnHeader|ba', 5, 1)
    nextRow()
    text("Излишек", 'rowHeader|ba', 3, 1)
    3.times{nextColumn()}
    number(productAgentDebitPenalty, 'dataNumber|ba')
    nextColumn()
    number(feesAgentDebitPenalty, 'dataNumber|ba')
    nextRow()
    text("Недобор", 'rowHeader|ba', 3, 1)
    3.times{nextColumn()}
    number(productAgentCreditPenalty, 'dataNumber|ba')
    nextColumn()
    number(feesAgentCreditPenalty, 'dataNumber|ba')
    2.times{nextRow()}

    text('Подпись', 'titleH2|ahl')
    nextColumn()
    text(null, 'titleH2|bb', 2, 1)
    3.times{nextColumn()}
    text(agent(), 'titleH2|bb', 3, 1)
    nextRow()
    nextColumn()
    text('подпись', 'titleH3', 2, 1)
    3.times{nextColumn()}
    text('фамилия', 'titleH3', 3, 1)
    2.times{nextRow()}

    text("Денежные средства в сумме:", 'titleH2|ahl')
    nextRow()
    rowHeight(24, false)
    text(number2Words(amount), 'titleH2|ahl|avt')
    2.times{nextRow()}

    text('Принял', 'titleH2|ahl')
    nextColumn()
    text(null, 'titleH2|bb', 2, 1)
    3.times{nextColumn()}
    text(null, 'titleH2|bb', 3, 1)
    nextRow()
    nextColumn()
    text('подпись', 'titleH3', 2, 1)
    3.times{nextColumn()}
    text('фамилия', 'titleH3', 3, 1)
}
