import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.midoffice.helper.CodeTranslator
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.booking.AeroexpressTicket
import com.gridnine.xtrip.common.model.booking.AeroexpressTicketIndex
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.model.booking.ProductStatus
import com.gridnine.xtrip.common.model.dict.Airline
import com.gridnine.xtrip.common.model.dict.CodeSystem
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.dict.DictionaryReference
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.helpers.GeneralProductHelper
import com.gridnine.xtrip.common.model.helpers.SystemHelper
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.model.profile.OrganizationIndex
import com.gridnine.xtrip.common.model.profile.OrganizationType
import com.gridnine.xtrip.common.model.system.MetadataKey
import com.gridnine.xtrip.common.model.system.PaymentType
import com.gridnine.xtrip.common.parsers.model.ProcessingStatus
import com.gridnine.xtrip.common.parsers.model.S7ExcelExchangeDocumentIndex
import com.gridnine.xtrip.common.parsers.model.TransactionType
import com.gridnine.xtrip.common.reports.model.AirTicketsTemplateReportTicket
import com.gridnine.xtrip.common.search.SearchCriterion
import com.gridnine.xtrip.common.search.SearchQuery
import com.gridnine.xtrip.common.util.BooleanUtil
import com.gridnine.xtrip.common.util.MiscUtil
import com.gridnine.xtrip.common.util.TextUtil

import java.util.stream.Collectors

//CREATING STYLES
createStyle(name: 'title', fontBold: true, h_span: 12, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight: 20)
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')
createStyle(name: 'dateData', parent: 'data', h_alignment: 'RIGHT', format: 'm/d/yy')
createStyle(name: 'subtotalTitle', parent: 'header', h_span: 6, h_alignment: 'RIGHT', fontBold: true)
createStyle(name: 'subtotalNumber', parent: 'subtotalTitle', h_span: 1)
createStyle(name: 'totalTitle', parent: 'subtotalTitle', h_span: 3)
createStyle(name: 'totalNumber', parent: 'totalTitle')

//supplementary functions
def notNullNumber = {
    return it ? it : BigDecimal.ZERO
}

def dateToLong = {
    if (!it) {
        return 0
    }
    return MiscUtil.clearTime(it).getTime()
}

def join = { List<PaymentType> paymentTypes ->
    StringBuilder sb = new StringBuilder()
    paymentTypes.each {
        if (sb.length() != 0) {
            sb = sb.append(", ")
        }
        sb = sb.append(it.toString(LocaleHelper.RU_LOCALE))
    }
    return sb.toString()
}

def equalsByTariff = { S7Wrapper t1, S7Wrapper t2 ->
    BigDecimal diff = notNullNumber(t1.equivalentFare)
    BigDecimal diffTax = notNullNumber(t1.taxesSum)
    BigDecimal diffPenalty = notNullNumber(t1.penalty)
    BigDecimal diffCommissionValue = notNullNumber(t1.commissionValue)

    BigDecimal diff2 = notNullNumber(t2.equivalentFare)
    BigDecimal diffTax2 = notNullNumber(t2.taxesSum)
    BigDecimal diffPenalty2 = notNullNumber(t2.penalty)
    BigDecimal diffCommissionValue2 = notNullNumber(t2.commissionValue)

    if (diff2 != BigDecimal.ZERO) {
        if (diff2 == diff) {
            return true
        }
        if (diff2 == diffTax) {
            t1.taxesSum = BigDecimal.ZERO
            return true
        }
        if (diff2 == diffCommissionValue) {
            t1.commissionValue = BigDecimal.ZERO
            return true
        }
        if (diff2 == diffPenalty) {
            t1.penalty = BigDecimal.ZERO
            return true
        }
    } else if (diff != BigDecimal.ZERO) {
        if (diff == diff2) {
            return true
        }
        if (diff == diffTax2) {
            t2.taxesSum = BigDecimal.ZERO
            return true
        }
        if (diff == diffCommissionValue2) {
            t2.commissionValue = BigDecimal.ZERO
            return true
        }
        if (diff == diffPenalty2) {
            t2.penalty = BigDecimal.ZERO
            return true
        }
    } else if (diffTax2 != BigDecimal.ZERO) {
        if (diffTax2 == diffPenalty) {
            t2.taxesSum = BigDecimal.ZERO
            t1.penalty = BigDecimal.ZERO
            return true
        }
        if (diffTax2 == diffCommissionValue) {
            t2.taxesSum = BigDecimal.ZERO
            t1.commissionValue = BigDecimal.ZERO
            return true
        }
    } else if (diffTax != BigDecimal.ZERO) {
        if (diffTax == diffPenalty2) {
            t1.taxesSum = BigDecimal.ZERO
            t2.penalty = BigDecimal.ZERO
            return true
        }
        if (diffTax == diffCommissionValue2) {
            t1.taxesSum = BigDecimal.ZERO
            t2.commissionValue = BigDecimal.ZERO
            return true
        }
    } else if (diffPenalty2 != BigDecimal.ZERO) {
        if (diffPenalty2 == diffCommissionValue) {
            t2.penalty = BigDecimal.ZERO
            t1.commissionValue = BigDecimal.ZERO
            return true
        }
    } else if (diffPenalty != BigDecimal.ZERO) {
        if (diffPenalty == diffCommissionValue2) {
            t1.penalty = BigDecimal.ZERO
            t2.commissionValue = BigDecimal.ZERO
            return true
        }
    }
    return diff == diff2
}


def equalsByCommissionRate = { S7Wrapper t1, S7Wrapper t2 ->
    return notNullNumber(t1.commissionRate) == notNullNumber(t2.commissionRate)
}

def equalsByCommissionValue = { S7Wrapper t1, S7Wrapper t2 ->
    return notNullNumber(t1.commissionValue) == notNullNumber(t2.commissionValue)
}

def equalsByTaxesSum = { S7Wrapper t1, S7Wrapper t2 ->
    return notNullNumber(t1.taxesSum) == notNullNumber(t2.taxesSum)
}

def equalsByPenalty = { S7Wrapper t1, S7Wrapper t2 ->
    return notNullNumber(t1.penalty) == notNullNumber(t2.penalty)
}

def equalsByPaymentTypes = { S7Wrapper t1, S7Wrapper t2 ->
    def isMoneyTransferFromAgencyToSupplier = { PaymentType paymentType ->
        return paymentType != PaymentType.MTD &&
                paymentType != PaymentType.CREDIT &&
                paymentType != PaymentType.CREDIT_CARD &&
                paymentType != PaymentType.CREDIT_CARD_TCH
    }
    def hasMoneyTransferFromAgencyToSupplierPaymentType = { List<PaymentType> paymentTypes ->
        paymentTypes.any({
            isMoneyTransferFromAgencyToSupplier(it)
        })
    }
    def hasNotMoneyTransferFromAgencyToSupplierPaymentType = { List<PaymentType> paymentTypes ->
        paymentTypes.any({
            !isMoneyTransferFromAgencyToSupplier(it)
        })
    }

    return (hasMoneyTransferFromAgencyToSupplierPaymentType(t1.paymentTypes) ==
            hasMoneyTransferFromAgencyToSupplierPaymentType(t2.paymentTypes)) &&
            (hasNotMoneyTransferFromAgencyToSupplierPaymentType(t1.paymentTypes) ==
                    hasNotMoneyTransferFromAgencyToSupplierPaymentType(t2.paymentTypes))
}

def equalsByValidator = { S7Wrapper t1, S7Wrapper t2 ->
    if (t1.status == ProductStatus.VOID || t2.status == ProductStatus.VOID) {
        return true
    } else {
        return TextUtil.isSame(t1.validator, t2.validator)
    }
}

def equalsByDate = { S7Wrapper t1, S7Wrapper t2 ->
    return dateToLong(t1.operationDate) == dateToLong(t2.operationDate)
}

def transactionTypeToStatus = { TransactionType type ->
    if (type == TransactionType.REFUND) {
        return ProductStatus.REFUND
    }
    if (type == TransactionType.CANCEL) {
        return ProductStatus.VOID
    }
    return ProductStatus.SELL

}

def convertToAirTicketsTemplateReportTicket = { AeroexpressTicket product ->
    AirTicketsTemplateReportTicket result = new AirTicketsTemplateReportTicket()
    result.setTicketNumber(GeneralProductHelper.getSystemNumber(product))
    result.setConjCount(0)
    result.setIssueDate(product.getIssueDate())
    result.status = product.getStatus()
    result.setEquivalentFare(GeneralProductHelper.getEquivalentFare(product))
    result.setVendorCommissionRate(GeneralProductHelper.calculateCommissionsRate(
            GeneralProductHelper.getUnmodifiableCommissions(product, ContractType.CLIENT), true))
    result.setVendorCommissionValue(GeneralProductHelper.calculateCommissionsEquivalentValue(
            GeneralProductHelper.getUnmodifiableCommissions(product, ContractType.CLIENT)))
    result.setTaxesSum(GeneralProductHelper.getTaxes(product).stream()
            .map({ taxes -> taxes.getAmount().getValue() })
            .filter({ amount -> Objects.nonNull(amount) })
            .reduce({ a, b -> a.add(b) }).orElse(BigDecimal.ZERO))
    result.setPenalty(product.getPenalty())
    result.validator = product.validator
    result.getPaymentTypes().addAll(GeneralProductHelper.getFops(product, ContractType.CLIENT).stream()
            .map({ fop -> fop.getType() })
            .filter({ paymentType -> Objects.nonNull(paymentType) })
            .distinct()
            .collect(Collectors.toList()))
    result.setValidator(product.getValidator())
    return result
}

def createWrapperFromProduct = { AirTicketsTemplateReportTicket prod ->
    S7Wrapper result = new S7Wrapper()
    result.source = "AGENCY"
    result.ticketNumber = prod.ticketNumber
    result.conjCount = prod.conjCount ? prod.conjCount : 0
    result.operationDate = prod.issueDate
    result.status = prod.status
    result.equivalentFare = prod.equivalentFare
    result.commissionRate = prod.vendorCommissionRate
    result.commissionValue = prod.vendorCommissionValue
    result.taxesSum = prod.taxesSum
    result.penalty = prod.penalty
    result.paymentTypes = prod.paymentTypes
    result.validator = prod.validator
    return result
}
def createWrapperFromDoc = { S7ExcelExchangeDocumentIndex prod ->
    S7Wrapper result = new S7Wrapper()
    result.source = "S7"
    result.conjCount = prod.conjCount ?: 0
    if (prod.conjCount > 0) {
        int count = prod.conjCount
        Long nextTicketLong = Long.parseLong(prod.ticketNumber)
        result.conjTicketNumber = prod.ticketNumber
        while (count > 0) {
            String nextTicketNumber = (++nextTicketLong).toString()
            result.conjTicketNumber = result.conjTicketNumber + '/' + nextTicketNumber.substring(nextTicketNumber.length() - 1, nextTicketNumber.length())
            count--
        }
    }
    result.ticketNumber = prod.ticketNumber
    result.operationDate = prod.operationDate
    result.status = transactionTypeToStatus(prod.transactionType)
    result.equivalentFare = prod.equivalentFare
    result.commissionRate = prod.commissionRate
    result.commissionValue = prod.commissionValue
    result.taxesSum = prod.taxesSum
    result.penalty = prod.penalty
    result.paymentTypes = prod.paymentTypes
    result.validator = prod.validator
    return result
}

boolean isParamTrue(String paramName) {
    Boolean param = parameters[paramName]
    return BooleanUtil.nullAsFalse(param)
}

static def fetchS7Organization() {
    SearchQuery query = new SearchQuery()
    DictionaryReference<Airline> s7AirlineRef = new CodeTranslator(CodeSystem.IATA.name())
            .resolveNativeCode(Airline.class, "S7")
    query.getCriteria().getCriterions().add(
            SearchCriterion.and(
                    SearchCriterion.eq(OrganizationIndex.Property.airline.name(), s7AirlineRef),
                    SearchCriterion.contains(OrganizationIndex.Property.types.name(), OrganizationType.BLANK_OWNER)
            ))
    List<OrganizationIndex> organizationIndices = EntityStorage.get()
            .search(OrganizationIndex.class, query).getData()

    if (organizationIndices.size() == 1) {
        return organizationIndices.get(0).getSource()
    }
    return null
}

boolean notCheckValidators = isParamTrue("not_check_validators")
boolean notCheckCommissions = isParamTrue("not_check_commissions")
boolean notCheckIssueDates = isParamTrue("not_check_issue_dates")
boolean checkPaymentTypes = isParamTrue("check_payment_types")
boolean excludeZeroPriceTickets = isParamTrue("exclude_zero_price_tickets")

//creating wrappers from products
def productWrappers = []

SearchQuery aeroexpressTicketsQuery = new SearchQuery()
aeroexpressTicketsQuery.getCriteria().getCriterions().add(
        SearchCriterion.and(
                SearchCriterion.between(AeroexpressTicketIndex.Property.issueDate.name(),
                        parameters['params'].periodBegin, parameters['params'].periodEnd),
                SearchCriterion.eq(AeroexpressTicketIndex.Property.blankOwner.name(), fetchS7Organization())
        ))

def aeroexpressBookings =
        EntityStorage.get().search(AeroexpressTicketIndex.class, aeroexpressTicketsQuery).getData()
                .groupBy({ idx ->
                    idx.getSource()
                })

aeroexpressBookings.entrySet().stream()
        .forEach(({ aeroexpressBooking ->
            EntityContainer<BookingFile> ctr = EntityStorage.get().resolve(aeroexpressBooking.getKey())
            BookingFile bookingFile = ctr.getEntity()
            bookingFile.getReservations().each { reservation ->
                List<AirTicketsTemplateReportTicket> tickets = new ArrayList<>()
                reservation.getProducts().each { product ->
                    if (product instanceof AeroexpressTicket) {
                        tickets.add(convertToAirTicketsTemplateReportTicket(product))
                    }
                }
                allTickets.addAll(tickets)
            }
        }))

allTickets.each { AirTicketsTemplateReportTicket ticket ->
    productWrappers << createWrapperFromProduct(ticket)
}

//creating wrappers from s7documents
SearchQuery query = new SearchQuery()
query.getCriteria().getCriterions().add(SearchCriterion.ge(S7ExcelExchangeDocumentIndex.Property.operationDate.name(), parameters['params'].periodBegin))
query.getCriteria().getCriterions().add(SearchCriterion.le(S7ExcelExchangeDocumentIndex.Property.operationDate.name(), parameters['params'].periodEnd))
query.getCriteria().getCriterions().add(SearchCriterion.ne(S7ExcelExchangeDocumentIndex.Property.status.name(), ProcessingStatus.ERROR))
def documentWrappers = []
def documentIndices = EntityStorage.get().search(S7ExcelExchangeDocumentIndex.class, query).getData()
EntityReference<Organization> agencyRef = parameters['params'].agency
EntityContainer<Organization> agencyCtr = EntityStorage.get().resolve(agencyRef)
def containsCode = agencyCtr != null
        ? TextUtil.nonBlank(SystemHelper.findMetadataAsString(
        agencyCtr.getEntity().getMetadata(), MetadataKey.KEY_S7_AGENCY_CODE))
        : false
documentIndices
        .findAll { idx -> containsCode ? idx.agency == agencyRef : true }
        .each { S7ExcelExchangeDocumentIndex idx ->
            documentWrappers << createWrapperFromDoc(idx)
        }

def addConjData = { S7Wrapper airProd, S7Wrapper conjFound ->
    airProd.commissionValue = notNullNumber(airProd.commissionValue).add(notNullNumber(conjFound.commissionValue))
    airProd.equivalentFare = notNullNumber(airProd.equivalentFare).add(notNullNumber(conjFound.equivalentFare))
    airProd.penalty = notNullNumber(airProd.penalty).add(notNullNumber(conjFound.penalty))
    airProd.taxesSum = notNullNumber(airProd.taxesSum).add(notNullNumber(conjFound.taxesSum))
}

def conjTickets = { S7Wrapper airDoc, S7Wrapper airProd ->
    int count = airDoc.conjCount
    long nextTicket = Long.parseLong(airProd.ticketNumber)
    while (count > 0) {
        nextTicket += 1
        S7Wrapper conjfound = productWrappers.find { S7Wrapper doc ->
            if (TextUtil.isSame(doc.ticketNumber, (nextTicket.toString()))) {
                return true
            }
            return false
        } as S7Wrapper
        if (conjfound != null) {
            addConjData(airProd, conjfound)
            String nextTicketStr = nextTicket.toString()
            airProd.ticketNumber = airProd.ticketNumber + '/' + nextTicketStr.substring(nextTicketStr.length() - 1, nextTicketStr.length())
        }
        count--
    }

    return airProd
}

def resultWrappers = []
documentWrappers.each { S7Wrapper doc ->
    S7Wrapper conjTicket = null
    S7Wrapper found = productWrappers.find { S7Wrapper prod ->
        if (doc.status == prod.status) {
            if (TextUtil.isSame(doc.ticketNumber, prod.ticketNumber) && doc.conjCount == 0
                    || TextUtil.isSame(doc.conjTicketNumber, prod.ticketNumber)) {
                return true
            } else if (doc.conjCount > 0 && doc.conjTicketNumber != null && doc.conjTicketNumber.contains("/") && !doc.conjTicketNumber.startsWith("/")) {
                if (TextUtil.isSame(prod.ticketNumber, doc.ticketNumber)) {
                    conjTicket = conjTickets(doc, prod)
                    return true
                }
            }
        }
        return false
    } as S7Wrapper

    if (found == null) {
        if (excludeZeroPriceTickets) {
            if (MiscUtil.isZero(doc.equivalentFare, true) &&
                    MiscUtil.isZero(doc.taxesSum, true)) {
                return
            }
        }
        //info("${prod.ticketNumber} ${prod.status} not found in S7 data")
        resultWrappers << doc
        return
    }

    if (excludeZeroPriceTickets) {
        if (MiscUtil.isZero(doc.equivalentFare, true) &&
                MiscUtil.isZero(doc.taxesSum, true) &&
                MiscUtil.isZero(found.equivalentFare, true) &&
                MiscUtil.isZero(found.taxesSum, true)) {
            return
        }
    }

    productWrappers.remove(found)
    if (conjTicket) {
        found = conjTicket
    }

    S7Wrapper item1 = new S7Wrapper()
    item1.source = doc.source
    item1.status = doc.status
    if (doc.conjCount > 0) {
        item1.ticketNumber = doc.conjTicketNumber
    } else {
        item1.ticketNumber = doc.ticketNumber
    }
    S7Wrapper item2 = new S7Wrapper()
    item2.source = found.source
    item2.status = found.status
    item2.ticketNumber = found.ticketNumber
    boolean hasDiff = false
    if (!notCheckCommissions) {
        if (!equalsByCommissionRate(doc, found)) {
            //info(doc.ticketNumber + " diff by commission rate")
            hasDiff = true
            item1.commissionRate = doc.commissionRate
            item2.commissionRate = found.commissionRate
        }
        if (!equalsByCommissionValue(doc, found)) {
            //info(doc.ticketNumber + " diff by commission value")
            hasDiff = true
            item1.commissionValue = doc.commissionValue
            item2.commissionValue = found.commissionValue
        }
    }
    if (!notCheckIssueDates) {
        if (!equalsByDate(doc, found)) {
            //info(doc.ticketNumber + " diff by date")
            hasDiff = true
            item1.operationDate = doc.operationDate
            item2.operationDate = found.operationDate
        }
    }
    if (checkPaymentTypes) {
        if (!MiscUtil.isZero(doc.equivalentFare, true) ||
                !MiscUtil.isZero(doc.taxesSum, true) ||
                !MiscUtil.isZero(doc.penalty, true) ||
                !MiscUtil.isZero(found.equivalentFare, true) ||
                !MiscUtil.isZero(found.taxesSum, true) ||
                !MiscUtil.isZero(found.penalty, true)) {
            if (!equalsByPaymentTypes(doc, found)) {
                //info(doc.ticketNumber + " diff by pyamenttype")
                hasDiff = true
                item1.paymentTypes = doc.paymentTypes
                item2.paymentTypes = found.paymentTypes
            }
        }
    }
    if (!equalsByTariff(doc, found)) {
        //info(doc.ticketNumber + " diff by tariff")
        hasDiff = true
        item1.equivalentFare = doc.equivalentFare
        item2.equivalentFare = found.equivalentFare
    }
    if (!equalsByPenalty(doc, found)) {
        //info(doc.ticketNumber + " diff by penalty")
        hasDiff = true
        item1.penalty = doc.penalty
        item2.penalty = found.penalty
    }
    if (!equalsByTaxesSum(doc, found)) {
        //info(doc.ticketNumber + " diff by taxes")
        hasDiff = true
        item1.taxesSum = doc.taxesSum
        item2.taxesSum = found.taxesSum
    }
    if (!notCheckValidators) {
        if (!equalsByValidator(doc, found)) {
            hasDiff = true
            //info(doc.ticketNumber + " diff by validator")
            item1.validator = doc.validator
            item2.validator = found.validator
        }
    }
    if (hasDiff) {
        resultWrappers << item1
        resultWrappers << item2
    }

}

productWrappers.each { S7Wrapper doc ->
    if (excludeZeroPriceTickets) {
        if (MiscUtil.isZero(doc.equivalentFare, true) &&
                MiscUtil.isZero(doc.taxesSum, true)) {
            return
        }
    }

    resultWrappers << doc
}

resultWrappers.sort { S7Wrapper it -> it.ticketNumber + it.source + it.status }
allTickets.clear()
allTickets.addAll(resultWrappers)


//REPORT
page { "Сравнение" } {
    //TITLE
    rowHeight(30)
    setStyle('title')
    text("Сравнение данных о продажах с базой S7 за период ${parameters.REPORT_PERIOD}")
    //HEADER
    setStyle('normal')
    nextRow()
    rowHeight(15)
    nextRow()
    rowHeight(40)
    setStyle('header')
    text('Источник')
    nextColumn()
    columnWidth(12)
    text('Номер билета')
    nextColumn()
    columnWidth(10)
    text('Дата')
    nextColumn()
    columnWidth(10)
    text('Статус')
    nextColumn()
    columnWidth(12)
    text('Тариф')
    nextColumn()
    columnWidth(10)
    text('% комиссии')
    nextColumn()
    columnWidth(10)
    text('Сумма комиссии')
    nextColumn()
    columnWidth(10)
    text('Таксы')
    nextColumn()
    columnWidth(10)
    text('Штрафы')
    nextColumn()
    columnWidth(10)
    text('ФОП')
    nextColumn()
    columnWidth(10)
    text('Валидатор')
    nextRow()
    tickets { S7Wrapper wrapper ->
        rowHeight(12)
        text(wrapper.source, 'textData')
        nextColumn()
        text(wrapper.ticketNumber, 'textData')
        nextColumn()
        date(wrapper.operationDate, 'dateData')
        nextColumn()
        text(wrapper.status?.toString(), 'textData')
        nextColumn()
        number(wrapper.status != ProductStatus.REFUND ? wrapper.equivalentFare : MiscUtil.negate(wrapper.equivalentFare), 'numberData')
        nextColumn()
        number(wrapper.commissionRate, 'numberData')
        nextColumn()
        number(wrapper.status != ProductStatus.REFUND ? wrapper.commissionValue : MiscUtil.negate(wrapper.commissionValue), 'numberData')
        nextColumn()
        number(wrapper.status != ProductStatus.REFUND ? wrapper.taxesSum : MiscUtil.negate(wrapper.taxesSum), 'numberData')
        nextColumn()
        number(wrapper.penalty, 'numberData')
        nextColumn()
        text(join(wrapper.paymentTypes ?: new ArrayList<>()), 'textData')
        nextColumn()
        text(wrapper.validator, 'textData')
        nextRow()
    }

    9.times {
        nextColumn()
    }
    columnAutoWidth()
}

class S7Wrapper {

    String ticketNumber = ""

    Date operationDate

    ProductStatus status

    BigDecimal equivalentFare

    BigDecimal commissionRate

    BigDecimal commissionValue

    BigDecimal taxesSum

    BigDecimal penalty

    List<PaymentType> paymentTypes

    String validator

    String source

    int conjCount = 0

    String conjTicketNumber = ""
}