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

import com.gridnine.xtrip.common.midoffice.helper.ReservationGdsNameInfoHelper

// Отчет по продажам А/Б

import com.gridnine.xtrip.common.model.booking.*
import com.gridnine.xtrip.common.model.dict.AirlineReference
import com.gridnine.xtrip.common.model.dict.DictionaryReference
import com.gridnine.xtrip.common.model.dict.ProfileGroupReference
import com.gridnine.xtrip.common.model.profile.Person;
import com.gridnine.xtrip.common.model.profile.PersonMetadata;
import com.gridnine.xtrip.common.model.system.MetadataKey
import groovy.transform.Field

import org.apache.commons.lang.StringUtils

import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.common.l10n.model.L10nStringHelper
import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.booking.air.Product
import com.gridnine.xtrip.common.model.booking.air.SegmentTariff
import com.gridnine.xtrip.common.model.booking.archive.ArchivedBookingHelper;
import com.gridnine.xtrip.common.model.booking.archive.ArchivedProductIndex;
import com.gridnine.xtrip.common.model.dict.Airline
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.dict.DictionaryCache
import com.gridnine.xtrip.common.model.dict.GdsName;
import com.gridnine.xtrip.common.model.dict.MCOCategory
import com.gridnine.xtrip.common.model.dict.ProfileGroup;
import com.gridnine.xtrip.common.model.dict.TariffType;
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.helpers.*
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.model.system.PaymentType
import com.gridnine.xtrip.common.util.FormattedNumberUtil;
import com.gridnine.xtrip.common.util.MiscUtil;
import com.gridnine.xtrip.common.util.TextUtil
import com.gridnine.xtrip.common.vip.CommonVIPHelper;
import com.gridnine.xtrip.common.vip.HiddenTaxHelper
import com.gridnine.xtrip.common.vip.HiddenTaxHelper.WrapBookingCallback
import com.gridnine.xtrip.common.model.export.vip.VipBookingSofiCommonExportSettings

import java.text.SimpleDateFormat

class AeroTicket {
    String blankOwner
    String subAgent
    String group
    String agent
    String client
    ProductStatus productStatus
    Date issueDate
    String ticketNumber
    int segment
    BigDecimal ticketPrice
    BigDecimal portbiletFee
    BigDecimal portbiletTotalPrice
    BigDecimal subAgentFee
    BigDecimal totalPrice
    BigDecimal YVTaxAmount
    BigDecimal RVTaxAmount
    String carrier
    String pnr
    String passengerName
    String gdsName
    String paymentType
    String agency
    String supplier
    String exchangedTicketNumber
    String gdsAccount
    String transportationType
    BigDecimal supplierFee
    String tariffType
}

class TotalRow {
    BigDecimal ticketPrice = BigDecimal.ZERO
    BigDecimal supplierFee = BigDecimal.ZERO
    BigDecimal portbiletFee = BigDecimal.ZERO
    BigDecimal subAgentFee = BigDecimal.ZERO
    BigDecimal yvTaxAmount = BigDecimal.ZERO
    BigDecimal rvTaxAmount = BigDecimal.ZERO
    int ticketsNumber = 0
    int segmentsNumber = 0
    
    void append(AeroTicket ticket) {
        ticketPrice = MiscUtil.sum(ticketPrice, ticket.ticketPrice)
        supplierFee = MiscUtil.sum(supplierFee, ticket.supplierFee)
        portbiletFee = MiscUtil.sum(portbiletFee, ticket.portbiletFee)
        subAgentFee = MiscUtil.sum(subAgentFee, ticket.subAgentFee)
        yvTaxAmount = MiscUtil.sum(yvTaxAmount, ticket.getYVTaxAmount())
        rvTaxAmount = MiscUtil.sum(rvTaxAmount, ticket.getRVTaxAmount())
        segmentsNumber += ticket.segment
        ticketsNumber++
    }
}

class TotalRowByStatus {
    Map<ProductStatus, TotalRow> byStatus = [:]
    TotalRow total = new TotalRow()
    
    void append(AeroTicket ticket) {
        ProductStatus status = ticket.productStatus
        TotalRow totalRowByStatus
        if (byStatus.containsKey(status)) {
            totalRowByStatus = byStatus[status]
        } else {
            totalRowByStatus = new TotalRow()
            byStatus[status] = totalRowByStatus
        }
        totalRowByStatus.append(ticket)
        total.append(ticket)
    }
}

class TotalRowByBlankOwner {
    Map<String, TotalRowByStatus> byBlankOwner = [:]
    TotalRowByStatus total = new TotalRowByStatus()
    
    void append(AeroTicket ticket) {
        String blankOwner = ticket.blankOwner
        TotalRowByStatus byStatus
        if (byBlankOwner.containsKey(blankOwner)) {
            byStatus = byBlankOwner[blankOwner]
        } else {
            byStatus = new TotalRowByStatus()
            byBlankOwner[blankOwner] = byStatus
        }
        byStatus.append(ticket)
        total.append(ticket)
    }
}

enum TotalType{
    BLANK_OWNER, SUBAGENT, GROUP, AGENT
}

class SubagentInfo{
    String subagentName
    BigDecimal summ
    BigDecimal feePrtb
    BigDecimal finalSummAndFeePrtb
    BigDecimal feeSa
    BigDecimal finalAll
    BigDecimal finalYVTax
    BigDecimal finalRVTax

    SubagentInfo(String subagentName, BigDecimal summ, BigDecimal feePrtb, BigDecimal finalSummAndFeePrtb, BigDecimal feeSa, BigDecimal finalAll, BigDecimal finalYVTax, BigDecimal finalRVTax){
        this.subagentName = subagentName
        this.summ = summ
        this.feePrtb = feePrtb
        this.finalSummAndFeePrtb = finalSummAndFeePrtb
        this.feeSa = feeSa
        this.finalAll = finalAll
        this.finalYVTax = finalYVTax
        this.finalRVTax = finalRVTax
    }

    SubagentInfo(){
    }
}

class BlankOwnerDisplaySettings {
    Map<String,FormOwnerMapping> formOwnerMappingsByFormOwners = new HashMap<String,FormOwnerMapping>()
    boolean useRaw()
    {
        formOwnerMappingsByFormOwners.isEmpty()
    }
}
class FormOwnerMapping {
    Map<String,String> displayTextsBySupplier = new HashMap<String,String>()
}


@Field int numberOfSoldTickets = 0
@Field int numberOfSegmentsSoldTickets = 0
@Field int numberOfRefundedTickets = 0
@Field int numberOfSegmentsRefundedTickets = 0
@Field int numberOfVoidedTickets = 0
@Field int numberOfSegmentsVoidedTickets = 0
@Field int numberOfExchangedTickets = 0
@Field int numberOfSegmentsExchangedTickets = 0

@Field String specifiedBlankOwners = ''
@Field String specifiedAgencies = ''
@Field String specifiedSubAgents = ''
@Field String specifiedProfileGroup = ''
@Field String specifiedAgents = ''
@Field String specifiedCarrier = ''
@Field String specifiedPaymentType = ''
@Field String specifiedTariffType = ''
@Field String specifiedGds = ''
@Field boolean isGroupedByAgent = false
@Field boolean isGroupedByProfileGroup = false
@Field boolean isGroupedByBlankOwner = false
@Field boolean showClient = false

@Field List<String> specifiedPaymentTypeList = Collections.EMPTY_LIST;

@Field final static String FARE = 'fare'
@Field final static String PRTB_REWARD = 'prtb_reward'
@Field final static String SUB_AGENT_REWARD = 'subagent_reward'
@Field final static String YV_TAX = 'yv_tax'
@Field final static String RV_TAX = 'rv_tax'
@Field final static String BLANK_OWNER_FARE = 'owner_fare'
@Field final static String BLANK_OWNER_FEE_PRTB = 'owner_fee_prtb'
@Field final static String BLANK_OWNER_YV_TAX = 'owner_yv_tax'
@Field final static String BLANK_OWNER_RV_TAX = 'owner_rv_tax'
@Field final static String OPERATION_NUMBERS = 'operation_numbers'
@Field final static String SUPPLIER_FEE = 'supplierFee'

@Field final static int AGENT_FINAL_TYPE = 0
@Field final static int GROUP_FINAL_TYPE = 1
@Field final static int SUBAGENT_FINAL_TYPE = 2

processIncomingData()
createSpreadsheetStyles()
composeReport()

@Field private List<SubagentInfo> subagentInfoList = new ArrayList<SubagentInfo>()

@Field boolean showExchangeDelta;

private String getSpecifiedBlankOwners() {
    List objectList = parameters['BLANK_OWNER']
    String result = StringUtils.join(new ArrayList() { {
                    objectList.each {
                        add(it.toString())
                    }
                }
            }, ', ')
    return result
}

private String getSpecifiedAgencies() {
    List objectList = parameters['AGENCY']
    String result = StringUtils.join(objectList, ', ')
    return result
}

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

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

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

private String getSpecifiedCarriers() {
    def carrier = parameters['CARRIER']
    return carrier == null ? '' : carrier.toString()
}

private String getSpecifiedPaymentType() {
    def paymentType = parameters['PAYMENT_TYPE']
    return paymentType == null ? '' : paymentType.toString()
}

private List<String> getSpecifiedPaymentTypeList() {
    def paymentType = parameters['PAYMENT_TYPE']

    if(paymentType?.size() == 0) return Collections.EMPTY_LIST;
    List<String> list = new ArrayList<String>();
    paymentType.each { list.add(it.toString());}
    return list;
}

private String getSpecifiedTariffType() {
    def tariffType = parameters['TARIFF_TYPE']
    return tariffType == null ? '' : tariffType.toString()
}

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

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

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

private boolean isGroupedByBlankOwner() {
    return (parameters['GROUP_BY_BLANK_OWNER'] == Boolean.TRUE)
}

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

private boolean showClient() {
    return parameters['SHOW_CLIENT']
}

private boolean showSubagent() {
    return parameters['SHOW_BY_SUBAGENT']
}
private boolean showBlank() {
    return parameters['SHOW_BY_BLANK']
}

def repeateUntilSuccess(int repeateTimes, Closure closure) {
    while (true) {
        try {
            return closure()
        } catch (Throwable exception) {
            if (repeateTimes == 0) {
                throw exception
            }
            repeateTimes--
        }
    }
}

private void processIncomingData() {
    this.specifiedBlankOwners       = getSpecifiedBlankOwners();
    this.specifiedAgencies          = getSpecifiedAgencies();
    this.specifiedSubAgents         = getSpecifiedSubAgents();
    this.specifiedProfileGroup      = getSpecifiedProfileGroup();
    this.specifiedAgents            = getSpecifiedAgents();
    this.specifiedCarrier           = getSpecifiedCarriers();
    //    this.specifiedPaymentType       = getSpecifiedPaymentType();
    this.specifiedPaymentTypeList   = getSpecifiedPaymentTypeList();
    this.specifiedTariffType        = getSpecifiedTariffType();
    this.specifiedGds               = getSpecifiedGds();
    this.isGroupedByAgent           = isGroupedByAgent();
    this.isGroupedByProfileGroup    = isGroupedByProfileGroup();
    this.isGroupedByBlankOwner      = isGroupedByBlankOwner()
    this.showClient                 = showClient();
    this.showExchangeDelta = parameters['SHOW_EXCHANGE_DELTA'];
    if (this.isGroupedByBlankOwner) {
        this.isGroupedByAgent = false
        this.isGroupedByProfileGroup = false
    }
}

private void createSpreadsheetStyles() {
    createStyle(name: 'title', fontBold: true, h_span: getColumnShiftTotal(16), h_alignment: 'CENTER',
    v_alignment: 'CENTER', fontHeight: 20)
    createStyle(name: 'metadataTitle', fontBold: true, h_span: 2, h_alignment: 'RIGHT', v_alignment: 'CENTER',
    fontHeight: 8)
    createStyle(name: 'metadataValue', parent: 'metadataTitle', h_span: 10, fontBold: false, h_alignment: 'LEFT')
    createStyle(name: 'metadataDateValue', parent: 'metadataValue', format: 'm/d/yy')
    createStyle(name: 'header', fontBold: false, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight: 8,
    leftBorder: 'MEDIUM', rightBorder: 'MEDIUM', topBorder: 'MEDIUM', bottomBorder: 'MEDIUM',
    wrapText: true)
    createStyle(name: 'data', fontBold: false, h_alignment: 'CENTER', v_alignment: 'CENTER', fontHeight: 8,
    leftBorder: 'THIN', rightBorder: 'THIN', topBorder: 'THIN', bottomBorder: 'THIN')
    createStyle(name: 'textData', parent: 'data')
    createStyle(name: 'numberData', parent: 'data', h_alignment: 'RIGHT', format: '#,##0.00')
    createStyle(name: 'dateData', parent: 'data', h_alignment: 'RIGHT', format: 'm/d/yy')
    createStyle(name: 'preliminaryTotalText', parent: 'data', h_span: getColumnShiftTotal(6), fontBold: true,
    h_alignment: 'RIGHT')
    createStyle(name: 'finalTotalText', parent: 'data', h_span: 2, fontBold: true, h_alignment: 'RIGHT')
    createStyle(name: 'finalTotalHeaderText', parent: 'data', fontBold: true, h_alignment: 'RIGHT')
    createStyle(name: 'totalNumber', fontBold: true, parent: 'numberData')
    createStyle(name: 'totalIntegerNumber', fontBold: true, parent: 'numberData', format: '0')
}

String getTicketNumber(Product product, int index) {
    String ticketNumber = "";
    if(index == -1){
        ticketNumber = product.getSystemNumber();
    } else if(!TextUtil.isBlank(product.getSystemNumber()) && TextUtil.isContainsDigits(product.getSystemNumber())){
        ticketNumber = FormattedNumberUtil.add(product.getSystemNumber(), index + 1);
    }
    return ticketNumber;
}

BigDecimal getSum(BigDecimal a, BigDecimal b) {
    if (a == null) {
        return b;
    } else if (b == null) {
        return a;
    } else {
        return a.add(b);
    }
}

void addExchnagedTicket(AeroTicket ticket, AeroTicket exchangedProductTicket) {
    ticket.ticketPrice = getSum(ticket.ticketPrice, exchangedProductTicket.ticketPrice);
    ticket.portbiletFee = getSum(ticket.portbiletFee, exchangedProductTicket.portbiletFee);
    ticket.portbiletTotalPrice = getSum(ticket.portbiletTotalPrice, exchangedProductTicket.portbiletTotalPrice);
    ticket.subAgentFee = getSum(ticket.subAgentFee, exchangedProductTicket.subAgentFee);
    ticket.totalPrice = getSum(ticket.totalPrice, exchangedProductTicket.totalPrice);
    ticket.YVTaxAmount = getSum(ticket.YVTaxAmount, exchangedProductTicket.YVTaxAmount);
    ticket.RVTaxAmount = getSum(ticket.RVTaxAmount, exchangedProductTicket.RVTaxAmount);
}

String getTransportationType(Product product) {
    TransportationType type = AirProductHelper.getTransportationType(product);
    if (type == null) {
        return "?";
    }
    switch (type) {
        case TransportationType.DOMESTIC:
            return "ВВЛ";
        case TransportationType.INTERNATIONAL:
            return "МВЛ";
        case TransportationType.COMBINED:
            return "ВВЛ+МВЛ";
        default:
            return "?";
    }
}
    
BigDecimal getSupplierFee(Product product) {
    if (product.status == ProductStatus.VOID) {
        return BigDecimal.ZERO
    }
    return CommonVIPHelper.getSubagentPriceStructure(product).getVendorFeesSum()
}

private void composeReport() {
    String reportPeriodStr = parameters['REPORT_PERIOD']
    int reportDelimiterIndex = reportPeriodStr.indexOf('-')
    if (reportDelimiterIndex != -1) {
        String reportBeginStr = reportPeriodStr.substring(0, reportDelimiterIndex)
        String reportEndStr = reportPeriodStr.substring(reportDelimiterIndex + 1)
        SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yy")
        try {
            Date reportBegin = MiscUtil.clearTime(dateFormat.parse(reportBeginStr))
            Date reportEnd = MiscUtil.setDayEndTime(dateFormat.parse(reportEndStr))
            if (reportEnd.getTime() - reportBegin.getTime() > (92L * 24 * 60 * 60 * 1000)) {
                page {'Oшибка'} {
                    processNextCell(0, 0) {
                        rowHeight(30)
                        text('Период не можете превышать 92 дня', 'title')
                    }
                }
                return
            }
        } catch (java.text.ParseException ex) {
            warn ("parse exception: ${ex}")
        }
    }
    
    page { 'Авиа билеты'} {
        List<AeroTicket> ticketList = []
        EntityStorage entityStorage = Environment.getPublished(EntityStorage.class);

        //        warn 'allTickets.size()=' + allTickets.size()

        BlankOwnerDisplaySettings blankOwnerSettings = createBlankOwnerDisplaySettings(parameters, entityStorage)
                
        allTickets.each{ ArchivedProductIndex index ->
            def booking;

            ArchivedProductIndex indexCopy = index
            BookingFile originalBookingFile = repeateUntilSuccess(10, {
                return (booking = EntityStorage.get().resolve(indexCopy.source).entity).class.equals(BookingFile) ?
                    booking : ArchivedBookingHelper.getBookingContainer(booking).entity
            })

            HiddenTaxHelper.wrapAndExecute(originalBookingFile,
                    new WrapBookingCallback<Void>() {
                        @Override
                        public Void process(final BookingFile bookingFile) throws Exception {

                            BaseProduct baseProduct = BookingHelper.findProductByUid(index.getNavigationKey(), bookingFile);
                            if (baseProduct == null) return;
                            Product product = null;
                            if (baseProduct instanceof Product) {
                                product = (Product) baseProduct;
                            } else {
                                return;
                            }
                            
                            
                            if (showExchangeDelta && product.status == ProductStatus.EXCHANGE) {
                                return;
                            }

                            if(!isGoodAirProduct(product)){
                                return;
                            }

                            PaymentType paymentType = getPaymentType(product);

                            //                if ((!specifiedPaymentType.isEmpty()) &&
                            //                ((paymentType == null) || !specifiedPaymentType.contains(paymentType.toString()))) {
                            //                    return
                            //                }
                            if ((!specifiedPaymentTypeList.isEmpty()) && ((paymentType == null) || !containspecifiedPaymentType(paymentType))) {
                                return;
                            }

                            boolean negate = (index.getStatus() == ProductStatus.REFUND) || (index.getStatus() == ProductStatus.EXCHANGE);
                            
                            Product exchangedProduct = null;
                            boolean exchangedProductNegate;
                            if (showExchangeDelta && product.status == ProductStatus.SELL && product.previousProduct?.status == ProductStatus.EXCHANGE) {
                                exchangedProduct = product.previousProduct;
                                exchangedProductNegate = true;
                            }

                            boolean first = true;

                            for(int i = -1; i < product.getConjCount(); i++){
                                String ticketNumber = getTicketNumber(product, i);

                                AeroTicket ticket = new AeroTicket();
                                populateBlankOwner(index, product, entityStorage, ticket);
                                populateSubAgent(index, entityStorage, ticket);
                                populateAgencyName(index, entityStorage, ticket);
                                if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()) {
                                    populateProfileGroup(product, ticket)
                                }
                                if (isGroupedByAgent || !specifiedAgents.isEmpty()) {
                                    populateAgent(index,product, product.reservation, ticket)
                                }
                                if (showClient) {
                                    populateClient(index, ticket)
                                }
                                populateProductStatus(product, ticket)
                                populateIssueDate(index, ticket)
                                populateTicketNumber(ticketNumber, ticket)
                                populateSegment(product, ticket)
                                populateFare(product, negate, ticket, first)
                                ticket.supplierFee = getSupplierFee(product)
                                populateAgencyReward(product, ticket, negate, first)
                                populatePrtbTotalFare(ticket)
                                populateSubAgentReward(product, ticket, negate, first)
                                populateTotalFare(ticket)
                                populateYVTaxAmount(product, negate, ticket, first)
                                populateRVTaxAmount(product, negate, ticket, first)
                                populateCarrier(product, ticket)
                                populatePnr(product, ticket)
                                populatePassengerName(product, ticket)
                                populateGDS(product, ticket)
                                populatePaymentType(product, ticket)
                                populateSupplier(product, ticket)
                                
                                if (exchangedProduct != null) {
                                    AeroTicket exchangedProductTicket = new AeroTicket();
                                    populateFare(exchangedProduct, exchangedProductNegate, exchangedProductTicket, first)
                                    exchangedProductTicket.supplierFee = getSupplierFee(exchangedProduct)
                                    populateAgencyReward(exchangedProduct, exchangedProductTicket, exchangedProductNegate, first)
                                    populatePrtbTotalFare(exchangedProductTicket)
                                    populateSubAgentReward(exchangedProduct, exchangedProductTicket, exchangedProductNegate, first)
                                    populateTotalFare(exchangedProductTicket)
                                    populateYVTaxAmount(exchangedProduct, exchangedProductNegate, exchangedProductTicket, first)
                                    populateRVTaxAmount(exchangedProduct, exchangedProductNegate, exchangedProductTicket, first)
                                    addExchnagedTicket(ticket, exchangedProductTicket)
                                    ticket.exchangedTicketNumber = getTicketNumber(exchangedProduct, i);
                                }
                                
                                ticket.gdsAccount = product.reservation?.gdsNameInfo?.onlineGdsAccount?.caption
                                ticket.transportationType = getTransportationType(product)
                                ticket.tariffType = product?.tariffType?.toString()
                                
                                ticketList.add(ticket)
                                first = false;
                            }

                        }
                    });
        }

        fillSpreadSheetHeader();
        fillTableHeader();
        fillTable(ticketList, blankOwnerSettings);
        fillTableFooter();
    }

    if(showBlank() || showSubagent()){
        page { 'Упрощённая таблица'} {
            int colWidth = 11;
            processNextCell(0, 0) {columnWidth(colWidth)}
            15.times { processNextCell(0, 1) {columnWidth(colWidth)} }

            fillFinalTable();
        }
    }
}

private BlankOwnerDisplaySettings createBlankOwnerDisplaySettings(parameters, EntityStorage entityStorage) {
    def ruleRecordId = parameters["ruleRecordId"]
    BlankOwnerDisplaySettings settings = new BlankOwnerDisplaySettings()

    if (ruleRecordId?.trim()) {
        def settingsContainer = entityStorage.load(VipBookingSofiCommonExportSettings.class, ruleRecordId)
        if (settingsContainer)
        {
            VipBookingSofiCommonExportSettings ruleSettings = settingsContainer.getEntity()
            if (ruleSettings)
            {
                def rules = ruleSettings.getRules()

                if (rules) {
                    rules.each { rule ->
                        FormOwnerMapping formOwnerMapping = settings.formOwnerMappingsByFormOwners[rule.formOwner.caption]
                        if (!formOwnerMapping)
                        {
                            formOwnerMapping = new FormOwnerMapping()
                            settings.formOwnerMappingsByFormOwners[rule.formOwner.caption] = formOwnerMapping
                        }
                        formOwnerMapping.displayTextsBySupplier[rule.supplier.caption] = rule.sofiSupplier
                    }
                    settings.formOwnerMappingsByFormOwners.isEmpty()
                }
            }
        }
    }
    return settings
}

private boolean isGoodAirProduct(Product product){
    if(getGdsName() != null && !CommonReservationGdsNameInfoHelper.containsGds(product.getReservation(), getGdsName())) {
        return false;
    }

    if(getTariffType() != null && getTariffType() != product.getTariffType()){
        return false;
    }

    def airline = repeateUntilSuccess(10, {
        return Environment.getPublished(DictionaryCache.class).resolveReference(product.getCarrier())
    })
    if(getCarriers().size() != 0 && !isContaintCarrier(getCarriers(), airline)){
        return false;
    }

    def profileGroup = getProfileGroupAirProduct(product);
    if(getProfileGroups().size() != 0 && !isContaintProfileGroup(getProfileGroups(), profileGroup)){
        return false;
    }

    return true;
}

private ProfileGroup getProfileGroupAirProduct(Product product){
    ProfileGroup pg = null;

    EntityContainer<Person> agentContainer = repeateUntilSuccess(10, {
        return EntityStorage.get().resolve(product.getCashier())
    })

    if (agentContainer != null) {
        Person agent = agentContainer.getEntity();

        for (PersonMetadata meta : agent.getMetadata()) {

            if ((meta.getKey() != null) && (meta.getKey().getCode() != null) && MetadataKey.KEY_PERSON_PROFILE_GROUP.name().equals(meta.getKey().getCode())) {
                DictionaryReference<ProfileGroup> profileGroup = (DictionaryReference<ProfileGroup>) meta.getValue();
                if (profileGroup != null) {
                    pg = repeateUntilSuccess(10, {
                        return Environment.getPublished(DictionaryCache.class).resolveReference(profileGroup)
                    })
                }
            }
        }
    }

    return pg;
}

private boolean isContaintCarrier(List<Airline> carriers ,Airline airline){
    if(airline == null){
        return false;
    }
    for(Airline a : carriers){
        if(airline.getCode().equals(a.getCode())){
            return true;
        }
    }

    return false;
}

private boolean isContaintProfileGroup(List<ProfileGroup> profileGrous ,ProfileGroup profileGroup){
    if(profileGroup == null){
        return false;
    }
    for(ProfileGroup a : profileGrous){
        if(profileGroup.getCode().equals(a.getCode())){
            return true;
        }
    }

    return false;
}

private GdsName getGdsName() {
    return parameters['RESERVATION_SYSTEM'];
}

private TariffType getTariffType() {
    return parameters['TARIFF_TYPE'];
}

private List<Airline> getCarriers() {
    def carrier = parameters['CARRIER']
    List<Airline> list = new ArrayList<Airline>();

    if(carrier != null){
        if(carrier instanceof List){
            for(AirlineReference ref : carrier){
                list.add(
                    repeateUntilSuccess(10, {
                        return Environment.getPublished(DictionaryCache.class).resolveReference(ref)
                    })
                );
            }
        } else {
            list.add(repeateUntilSuccess(10, {
                return Environment.getPublished(DictionaryCache.class).resolveReference(carrier)
            }));
        }
    }

    return list;
}

private List<ProfileGroup> getProfileGroups() {
    def profileGroup = parameters['PROFILE_GROUP']
    List<ProfileGroup> list = new ArrayList<ProfileGroup>();

    if(profileGroup != null){
        if(profileGroup instanceof List){
            for(ProfileGroupReference ref : profileGroup){
                list.add(repeateUntilSuccess(10, {
                    return Environment.getPublished(DictionaryCache.class).resolveReference(ref)
                }))
            }
        } else {
            list.add(Environment.getPublished(DictionaryCache.class).resolveReference(profileGroup));
        }
    }

    return list;
}

private boolean containspecifiedPaymentType(PaymentType paymentType){
    boolean isContain = false;

    String paymentTypeString = paymentType.toString();
    this.specifiedPaymentTypeList.each{ String string ->
        if(isContain) return isContain;

        if(string.contains(paymentTypeString)){
            isContain = true;
        }
    }

    return isContain;
}

private void fillSpreadSheetHeader() {
    processNextCell(0, 0) {
        rowHeight(30)
        text('Отчет по авиа билетам ', 'title')
    }
    processNextCell(1, 0) {
        rowHeight(12)
        text('Дата составления отчета:', 'metadataTitle')
    }
    processNextCell(0, 1) {
        date(new Date(), 'metadataDateValue')
    }
    processNextCell(1, 0) { text('Период:', 'metadataTitle') }
    processNextCell(0, 1) {
        text(parameters.REPORT_PERIOD, 'metadataValue')
    }
    if (!specifiedBlankOwners.isEmpty()) {
        processNextCell(1, 0) { text('Владелец бланка:', 'metadataTitle') }
        processNextCell(0, 1) { text(specifiedBlankOwners, 'metadataValue') }
    }
    if (!specifiedAgencies.isEmpty()) {
        processNextCell(1, 0) { text('Агентство:', 'metadataTitle') }
        processNextCell(0, 1) { text(specifiedAgencies, 'metadataValue') }
    }
    if (!specifiedSubAgents.isEmpty()) {
        processNextCell(1, 0) { text('Субагент:', 'metadataTitle') }
        processNextCell(0, 1) { text(specifiedSubAgents, 'metadataValue') }
    }
    if (!specifiedProfileGroup.isEmpty()) {
        processNextCell(1, 0) { text('Группа:', 'metadataTitle') }
        processNextCell(0, 1) { text(specifiedProfileGroup, 'metadataValue') }
    }
    if (!specifiedAgents.isEmpty()) {
        processNextCell(1, 0) { text('Агенты:', 'metadataTitle') }
        processNextCell(0, 1) { text(specifiedAgents, 'metadataValue') }
    }
    if (!specifiedCarrier.isEmpty()) {
        processNextCell(1, 0) { text('Перевозчик:', 'metadataTitle') }
        processNextCell(0, 1) { text(specifiedCarrier, 'metadataValue') }
    }
    if (!specifiedGds.isEmpty()) {
        processNextCell(1, 0) { text('Система бронирования:', 'metadataTitle') }
        processNextCell(0, 1) { text(specifiedGds, 'metadataValue') }
    }
    //    if (specifiedPaymentType) {
    //        processNextCell(1, 0) { text('Форма оплаты:', 'metadataTitle') }
    //        processNextCell(0, 1) { text(this.specifiedPaymentType, 'metadataValue') }
    //    }
    if (!specifiedPaymentTypeList.isEmpty()) {
        processNextCell(1, 0) { text('Форма оплаты:', 'metadataTitle') }
        processNextCell(0, 1) { text(getSpecifiedPaymentTypeListAsString(), 'metadataValue') }
    }

    if (specifiedTariffType) {
        processNextCell(1, 0) { text('Тип тарифа:', 'metadataTitle') }
        processNextCell(0, 1) { text(specifiedTariffType, 'metadataValue') }
    }
}

private String getSpecifiedPaymentTypeListAsString(){
    StringBuilder sb = new StringBuilder('');

    this.specifiedPaymentTypeList.each {String string ->
        if(sb.length() != 0) sb.append(', ');
        sb.append(string);
    }

    return sb.toString();
}

private void fillTableHeader() {
    int colWidth = 11;
    processNextCell(2, 0) {
        setStyle('header')
        rowHeight(40)
    }
    processNextCell(0, 0) {
        columnWidth(colWidth)
        text('Владелец бланка')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Наименование субагента')
    }
    if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()) {
        processNextCell(0, 1) {
            columnWidth(colWidth)
            text('Группа')
        }
    }
    if (isGroupedByAgent || !specifiedAgents.isEmpty()) {
        processNextCell(0, 1) {
            columnWidth(colWidth)
            text('Выписывающий агент')
        }
    }
    if (showClient) {
        processNextCell(0, 1) {
            columnWidth(colWidth)
            text('Клиент')
        }
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Статус')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Дата выписки')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Номер билета')
    }
    if (this.showExchangeDelta) {
        processNextCell(0, 1) {
            columnWidth(colWidth)
            text("В обмен билета")
        }
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Сегменты')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Сумма')
    }
    
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Сбор Постащика')
    }
    
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Сбор (ПРТБ)')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Итого (сумма + Сбор Поставщика + сбор ПРТБ)')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Сбор (СА)')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Итого (сумма + Сбор Поставщика + сбор ПРТБ + сбор СА)')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('В том числе, такса YV')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('В том числе, такса RV')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Перевозчик')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('PNR')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Имя пассажира')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Система бронирования')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('ГДС Аккаунт')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Тип оплаты')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Агентство')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Тип перевозки')
    }
    processNextCell(0, 1) {
        columnWidth(colWidth)
        text('Тип тарифа')
    }
}

private void fillTable(List<AeroTicket> ticketList, BlankOwnerDisplaySettings blankOwnerSettings) {
    if ((isGroupedByAgent || !specifiedAgents.isEmpty()) &&
    (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty())) {
        ticketList = ticketList.sort { AeroTicket a, AeroTicket b ->
            a.blankOwner <=> b.blankOwner ?: a.subAgent <=> b.subAgent ?: a.group <=> b.group ?: a.agent <=> b.agent ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }
    else if (isGroupedByAgent || !specifiedAgents.isEmpty()) {
        ticketList = ticketList.sort { AeroTicket a, AeroTicket b ->
            a.blankOwner <=> b.blankOwner ?: a.subAgent <=> b.subAgent ?: a.agent <=> b.agent ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }
    else if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()) {
        ticketList = ticketList.sort { AeroTicket a, AeroTicket b ->
            a.blankOwner <=> b.blankOwner ?: a.subAgent <=> b.subAgent ?: a.group <=> b.group ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }
    else {
        ticketList = ticketList.sort { AeroTicket a, AeroTicket b ->
            a.blankOwner <=> b.blankOwner ?: a.subAgent <=> b.subAgent ?: a.productStatus <=> b.productStatus ?: a.issueDate <=> b.issueDate
        }
    }

    AeroTicket currentTicket
    AeroTicket nextTicket
    if (ticketList.size() > 0) {
        currentTicket = ticketList.first()
    }
    for (int i = 1; i <= ticketList.size(); i++) {
        if (i != ticketList.size()) {
            nextTicket = ticketList.get(i)
        }
        else {
            nextTicket = null
        }
        processNextCell(1, 0) {
            rowHeight(12)
            def blankOwnerToDisplay = getBlankOwnerToDisplay(blankOwnerSettings, currentTicket)
            text(blankOwnerToDisplay, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.subAgent, 'textData')
        }
        if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()) {
            processNextCell(0, 1) {
                text(currentTicket?.group, 'textData')
            }
        }
        if (isGroupedByAgent || !specifiedAgents.isEmpty()) {
            processNextCell(0, 1) {
                text(currentTicket?.agent, 'textData')
            }
        }
        if(showClient) {
            processNextCell(0, 1) {
                text(currentTicket?.client, 'textData')
            }
        }
        processNextCell(0, 1) {
            text(currentTicket.productStatus.toString(), 'textData')
        }
        processNextCell(0, 1) {
            date(currentTicket?.issueDate, 'dateData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.ticketNumber, 'textData')
        }
        if (this.showExchangeDelta) {
            processNextCell(0, 1) {
                text(currentTicket?.exchangedTicketNumber, 'textData')
            }
        }
        processNextCell(0, 1) {
            number(currentTicket?.segment, 'textData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.ticketPrice, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.supplierFee, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.portbiletFee, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.portbiletTotalPrice, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.subAgentFee, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.totalPrice, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.YVTaxAmount, 'numberData')
        }
        processNextCell(0, 1) {
            number(currentTicket?.RVTaxAmount, 'numberData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.carrier, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.pnr, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.passengerName, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.gdsName, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.gdsAccount, "textData")
        }
        processNextCell(0, 1) {
            text(currentTicket?.paymentType, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.agency, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.transportationType, 'textData')
        }
        processNextCell(0, 1) {
            text(currentTicket?.tariffType, 'textData')
        }

        fillTotalMapRow(currentTicket, TotalType.BLANK_OWNER);
        fillTotalMapRow(currentTicket, TotalType.SUBAGENT);
        if (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()) {
            fillTotalMapRow(currentTicket, TotalType.GROUP);
        }
        if (isGroupedByAgent || !specifiedAgents.isEmpty()) {
            fillTotalMapRow(currentTicket, TotalType.AGENT);
        }
        switch (currentTicket.productStatus) {
            case ProductStatus.SELL:
                numberOfSoldTickets++;
                numberOfSegmentsSoldTickets += currentTicket.segment;
                break;
            case ProductStatus.REFUND:
                numberOfRefundedTickets++;
                numberOfSegmentsRefundedTickets += currentTicket.segment;
                break;
            case ProductStatus.VOID:
                numberOfVoidedTickets++;
                numberOfSegmentsVoidedTickets += currentTicket.segment;
                break
            case ProductStatus.EXCHANGE:
                numberOfExchangedTickets++;
                numberOfSegmentsExchangedTickets += currentTicket.segment;
                break;
            default:
                break;
        }

        boolean isSubAgentChanged = !currentTicket?.getSubAgent()?.equals(nextTicket?.getSubAgent());
        boolean isBlankOwnerChanged = !currentTicket?.getBlankOwner()?.equals(nextTicket?.getBlankOwner());
        boolean isAgentChanged = !currentTicket?.agent?.equals(nextTicket?.agent)
        boolean isGroupChanged = !currentTicket?.group?.equals((nextTicket?.group))
        boolean isStatusChanged = !currentTicket?.productStatus?.equals(nextTicket?.productStatus)

        boolean needTotalForAgent = (isGroupedByAgent || !specifiedAgents.isEmpty()) && ((!isAgentChanged && isStatusChanged) || isAgentChanged)
        boolean needTotalForGroup = (isGroupedByProfileGroup || !specifiedProfileGroup.isEmpty()) && isGroupChanged

        if (needTotalForAgent && !isGroupedByBlankOwner) {
            if (isStatusChanged || isAgentChanged) {
                fillTotalRow(currentTicket,TotalType.AGENT,currentTicket.getProductStatus());
            }

            if (isAgentChanged) {
                fillTotalRow(currentTicket,TotalType.AGENT, null);
            }
        }

        if (needTotalForGroup && !isGroupedByBlankOwner) {
            fillTotalRow(currentTicket,TotalType.GROUP,ProductStatus.SELL);
            fillTotalRow(currentTicket,TotalType.GROUP,ProductStatus.REFUND);
            fillTotalRow(currentTicket,TotalType.GROUP,ProductStatus.EXCHANGE);
            fillTotalRow(currentTicket,TotalType.GROUP,ProductStatus.VOID);
            fillTotalRow(currentTicket,TotalType.GROUP,null);
        }


        if (isSubAgentChanged && !isGroupedByBlankOwner) {
            fillTotalRow(currentTicket,TotalType.SUBAGENT,ProductStatus.SELL);
            fillTotalRow(currentTicket,TotalType.SUBAGENT,ProductStatus.REFUND);
            fillTotalRow(currentTicket,TotalType.SUBAGENT,ProductStatus.EXCHANGE);
            fillTotalRow(currentTicket,TotalType.SUBAGENT,ProductStatus.VOID);
            fillTotalRow(currentTicket,TotalType.SUBAGENT,null);
        }


        if(isBlankOwnerChanged){
            fillTotalRow(currentTicket,TotalType.BLANK_OWNER,ProductStatus.SELL);
            fillTotalRow(currentTicket,TotalType.BLANK_OWNER,ProductStatus.REFUND);
            fillTotalRow(currentTicket,TotalType.BLANK_OWNER,ProductStatus.EXCHANGE);
            fillTotalRow(currentTicket,TotalType.BLANK_OWNER,ProductStatus.VOID);
            fillTotalRow(currentTicket,TotalType.BLANK_OWNER,null);
        }

        currentTicket = nextTicket;
        rowHeight(12);
    }
}

private String getBlankOwnerToDisplay(BlankOwnerDisplaySettings blankOwnerSettings, AeroTicket currentTicket) {
    def blankOwnerToDisplay = ""

    if (blankOwnerSettings.useRaw())
    {
        blankOwnerToDisplay = currentTicket?.blankOwner
    }
    else if (currentTicket && currentTicket.blankOwner && currentTicket.supplier)
    {
        blankOwnerToDisplay = currentTicket?.blankOwner
        FormOwnerMapping mapping = blankOwnerSettings.formOwnerMappingsByFormOwners[currentTicket?.blankOwner]
        if (mapping)
        {
            String textToDisplay = mapping.displayTextsBySupplier[currentTicket.supplier]

            if (textToDisplay)
            {
                blankOwnerToDisplay = textToDisplay
            }
        }
    }
    else
    {
        blankOwnerToDisplay = currentTicket?.blankOwner
    }
    return blankOwnerToDisplay
}


@Field TotalRowByBlankOwner totalsByBlankOwner = new TotalRowByBlankOwner()
@Field Map<String, TotalRowByBlankOwner> totalsBySubAgent = [:]
@Field Map<String, TotalRowByBlankOwner> totalsByGroup = [:]
@Field Map<String, TotalRowByBlankOwner> totalsByAgent = [:]

TotalRowByBlankOwner getTotalRowByBlankOwner(AeroTicket currentTicket, TotalType totalType) {
    Map<String, TotalRowByBlankOwner> map
    String key
    switch(totalType){
        case TotalType.SUBAGENT:
            map = totalsBySubAgent
            key = currentTicket.subAgent
            break;
        case TotalType.GROUP:
            map = totalsByGroup
            key = currentTicket.group
            break;
        case TotalType.AGENT:
            map = totalsByAgent
            key = currentTicket.agent
            break;
        default:
            throw new RuntimeException()
    }
    TotalRowByBlankOwner byBlankOwner
    if (map.containsKey(key)) {
        byBlankOwner = map[key]
    } else {
        byBlankOwner = new TotalRowByBlankOwner()
        map[key] = byBlankOwner
    }
    return byBlankOwner
}

private void fillTotalMapRow(AeroTicket currentTicket, TotalType totalType) {
    String blankOwner = currentTicket.getBlankOwner();
    TotalRowByBlankOwner byBlankOwner
    switch(totalType){
        case TotalType.BLANK_OWNER:
            byBlankOwner = totalsByBlankOwner
            break;
        default:
            byBlankOwner = getTotalRowByBlankOwner(currentTicket, totalType)
            break;
    }
    byBlankOwner.append(currentTicket)
}


private void fillTotalRow(AeroTicket currentTicket, TotalType totalType, ProductStatus status) {
    TotalRowByStatus byStatus
    switch(totalType){
        case TotalType.BLANK_OWNER:
            byStatus = totalsByBlankOwner.byBlankOwner[currentTicket.blankOwner]
            break;
        default:
            TotalRowByBlankOwner byBlankOwner = getTotalRowByBlankOwner(currentTicket, totalType)
            byStatus = byBlankOwner.byBlankOwner[currentTicket.blankOwner]
            break;
    }
    TotalRow row
    if(status == null){
        row = byStatus.total
    } else {
        row = byStatus.byStatus[status]
    }
    
    BigDecimal fare            = BigDecimal.ZERO;
    BigDecimal prtbReward      = BigDecimal.ZERO;
    BigDecimal YVTax           = BigDecimal.ZERO;
    BigDecimal RVTax           = BigDecimal.ZERO;
    BigDecimal supplierFee = BigDecimal.ZERO
    BigDecimal subAgentReward = BigDecimal.ZERO
    String total = '';
    
    if (row != null) {
        fare = row.ticketPrice
        supplierFee = row.supplierFee
        prtbReward = row.portbiletFee
        YVTax = row.yvTaxAmount
        RVTax = row.rvTaxAmount
        subAgentReward = row.subAgentFee
    }
    boolean finalResult = totalType != TotalType.BLANK_OWNER && status == null

    if(!showSubagent()){
        if (row != null) {
            processNextCell(1, 0) {
                text(status.equals('') ? String.format('Итого по %s:', total) :
                        String.format('Итого операции %s по %s:', status, total), 'preliminaryTotalText')
            }
            processNextCell(0, 1) { number(fare, 'totalNumber') }
            processNextCell(0, 1) { number(supplierFee, 'totalNumber') }
            processNextCell(0, 1) { number(prtbReward, 'totalNumber') }
            processNextCell(0, 1) { number(MiscUtil.sum(fare,prtbReward), 'totalNumber') }
            processNextCell(0, 1) { number(subAgentReward, 'totalNumber') }
            processNextCell(0, 1) { number(MiscUtil.sum(fare,prtbReward,subAgentReward), 'totalNumber') }
            processNextCell(0, 1) { number(YVTax, 'totalNumber') }
            processNextCell(0, 1) { number(RVTax, 'totalNumber') }
            5.times { processNextCell(0, 1) { text('', 'data') } }
        }
    }

    if(totalType == TotalType.SUBAGENT && showSubagent() && finalResult){
        boolean addToList = false;
        SubagentInfo subInfo = this.subagentInfoList.find {SubagentInfo subInfo ->
            subInfo.getSubagentName().equals(currentTicket.getSubAgent());
        }
        if(!subInfo){
            subInfo = new SubagentInfo();
            addToList = true;
        }
        subInfo.subagentName = currentTicket.getSubAgent()
        subInfo.summ = addToList ? fare : MiscUtil.sum(fare,subInfo.summ);
        subInfo.feePrtb = addToList ? prtbReward : MiscUtil.sum(prtbReward,subInfo.feePrtb);
        subInfo.finalSummAndFeePrtb = addToList ? MiscUtil.sum(fare,prtbReward) : MiscUtil.sum(fare,prtbReward,subInfo.finalSummAndFeePrtb);
        subInfo.feeSa = addToList ? subAgentReward : MiscUtil.sum(subInfo.feeSa,subAgentReward);
        subInfo.finalAll = addToList ? MiscUtil.sum(fare,prtbReward,subAgentReward) : MiscUtil.sum(fare,prtbReward,subAgentReward,subInfo.finalAll);
        subInfo.finalYVTax = addToList ? YVTax : MiscUtil.sum(YVTax,subInfo.finalYVTax);
        subInfo.finalRVTax = addToList ? RVTax : MiscUtil.sum(RVTax,subInfo.finalRVTax);

        if(addToList){
            subagentInfoList.add(subInfo);
        }
    }
}

private void fillTableFooter() {
    processNextCell(1, 0) {
    }
    processNextCell(1, getColumnShiftTotal(2)) { text('Количество билетов', 'finalTotalHeaderText') }
    processNextCell(0, 1) { text('Сегменты', 'finalTotalHeaderText') }
    fillTotalFooterRow(ProductStatus.SELL);
    fillTotalFooterRow(ProductStatus.REFUND);
    fillTotalFooterRow(ProductStatus.EXCHANGE);
    fillTotalFooterRow(ProductStatus.VOID);
    fillTotalFooterRow();
    //    fillFinalTable();
}

public void fillTotalFooterRow(ProductStatus totalByStatus = null) {
    String totalTicketCountString = '';
    int totalTicketCountValue = 0;
    int totalTicketSegmentValue = 0;
    String totalTicketSumString = '';
    BigDecimal totalTicketFareSumValue = BigDecimal.ZERO;
    BigDecimal totalTicketPrtbRewardSumValue = BigDecimal.ZERO;
    BigDecimal totalTicketSubAgentRewardSumValue = BigDecimal.ZERO;
    BigDecimal totalTicketYVTaxAmount = BigDecimal.ZERO;
    BigDecimal totalTicketRVTaxAmount = BigDecimal.ZERO;
    BigDecimal totalSupplierFee = BigDecimal.ZERO
    switch (totalByStatus) {
        case ProductStatus.SELL:
            totalTicketCountString = 'ПРОДАНО БИЛЕТОВ:'
            totalTicketCountValue = numberOfSoldTickets
            totalTicketSegmentValue = numberOfSegmentsSoldTickets
            totalTicketSumString = 'ИТОГО ПО ПРОДАЖАМ:'
            break
        case ProductStatus.REFUND:
            totalTicketCountString = 'ВОЗВРАЩЕНО БИЛЕТОВ:'
            totalTicketCountValue = numberOfRefundedTickets
            totalTicketSegmentValue = numberOfSegmentsRefundedTickets
            totalTicketSumString = 'ИТОГО ПО ВОЗВРАТАМ:'
            break
        case ProductStatus.EXCHANGE:
            totalTicketCountString = 'ОБМЕНЯНО БИЛЕТОВ:'
            totalTicketCountValue = numberOfExchangedTickets
            totalTicketSegmentValue = numberOfSegmentsExchangedTickets
            totalTicketSumString = 'ИТОГО ПО ОБМЕНАМ:'
            break
        case ProductStatus.VOID:
            totalTicketCountString = 'АННУЛИРОВАНО БИЛЕТОВ:'
            totalTicketCountValue = numberOfVoidedTickets
            totalTicketSegmentValue = numberOfSegmentsVoidedTickets
            totalTicketSumString = 'ИТОГО ПО АННУЛЯЦИЯМ:'
            break
        case null:
            totalTicketCountString = 'ИТОГО:'
            totalTicketCountValue = numberOfSoldTickets + numberOfRefundedTickets + numberOfExchangedTickets +
                    numberOfVoidedTickets
            totalTicketSegmentValue = numberOfSegmentsSoldTickets + numberOfSegmentsRefundedTickets +
                    numberOfSegmentsExchangedTickets + numberOfSegmentsVoidedTickets
            totalTicketSumString = 'ИТОГО:'
    }
    
    this.totalsBySubAgent.each {key, value ->
        TotalRow row
        if (totalByStatus == null) {
            row = value.total.total
        } else {
            row = value.total.byStatus[totalByStatus]
        }
        if (row != null) {
            totalTicketFareSumValue = totalTicketFareSumValue.add(row.ticketPrice)
            totalTicketPrtbRewardSumValue = totalTicketPrtbRewardSumValue.add(row.portbiletFee)
            totalTicketSubAgentRewardSumValue = totalTicketSubAgentRewardSumValue.add(row.subAgentFee)
            totalTicketYVTaxAmount = totalTicketYVTaxAmount.add(row.yvTaxAmount)
            totalTicketRVTaxAmount = totalTicketRVTaxAmount.add(row.rvTaxAmount)
            totalSupplierFee = totalSupplierFee.add(row.supplierFee)
        }
    }

    processNextCell(1, getColumnShiftTotal(0)) { text(totalTicketCountString, 'finalTotalText') }
    processNextCell(0, 1) { number(totalTicketCountValue, 'totalIntegerNumber') }
    processNextCell(0, 1) { number(totalTicketSegmentValue, 'totalIntegerNumber') }

    processNextCell(0, 1) { text(totalTicketSumString, 'finalTotalText') }
    processNextCell(0, 1) { number(totalTicketFareSumValue, 'totalNumber') }
    processNextCell(0, 1) { number(totalSupplierFee, 'totalNumber') }
    processNextCell(0, 1) { number(totalTicketPrtbRewardSumValue, 'totalNumber') }
    processNextCell(0, 1) {
        number(totalTicketFareSumValue.add(totalTicketPrtbRewardSumValue), 'totalNumber')
    }
    processNextCell(0, 1) { number(totalTicketSubAgentRewardSumValue, 'totalNumber') }
    processNextCell(0, 1) {
        number(totalTicketFareSumValue.add(totalTicketPrtbRewardSumValue).add(totalTicketSubAgentRewardSumValue),
                'totalNumber')
    }
    processNextCell(0, 1) { number(totalTicketYVTaxAmount, 'totalNumber') }
    processNextCell(0, 1) { number(totalTicketRVTaxAmount, 'totalNumber') }
}

private void fillFinalTable() {
    if(showBlank()){
        //        processNextCell(0, 0) {}
        processNextCell(1, 1) {text('Владелец бланка', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('Сумма', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('Количество операций', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('Сбор(ПРТБ)', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('В том числе, такса YV', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('В том числе, такса RV', 'finalTotalHeaderText') }
    }

    if(showSubagent()){
        int columnShift = showBlank() ? 3 : 1
        int rowShift = showBlank() ? 0 : 1
        processNextCell(rowShift, columnShift) { text('Субагент', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('Сумма', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('Сбор (ПРТБ)', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('Итого (сумма + сбор ПРТБ)', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('Сбор (СА)', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('Итого Общий', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('В том числе, такса YV', 'finalTotalHeaderText') }
        processNextCell(0, 1) { text('В том числе, такса RV', 'finalTotalHeaderText') }
    }
    int rowNum = 0;
    if(showBlank()){
        BigDecimal sum = BigDecimal.ZERO
        BigDecimal operationSum = BigDecimal.ZERO
        BigDecimal feePrtbSum = BigDecimal.ZERO
        BigDecimal YVTaxSum = BigDecimal.ZERO
        BigDecimal RVTaxSum = BigDecimal.ZERO
        totalsByBlankOwner.byBlankOwner.each { blankOwner, byStatus ->
            BigDecimal blankOwnerFare = byStatus.total.ticketPrice
            BigDecimal blankOwnerOperationNumber = byStatus.total.ticketsNumber
            BigDecimal blankOwnerFeePrtb = byStatus.total.portbiletFee
            BigDecimal blankOwnerYVTax =  byStatus.total.yvTaxAmount
            BigDecimal blankOwnerRVTax = byStatus.total.rvTaxAmount
            fillFinalFareRow(blankOwner, blankOwnerFare, blankOwnerOperationNumber, blankOwnerFeePrtb, blankOwnerYVTax, blankOwnerRVTax);
            sum = sum.add(blankOwnerFare);
            operationSum = operationSum.add(blankOwnerOperationNumber);
            feePrtbSum = feePrtbSum.add(blankOwnerFeePrtb);
            YVTaxSum = YVTaxSum.add(blankOwnerYVTax);
            RVTaxSum = RVTaxSum.add(blankOwnerRVTax);
            if(showSubagent() && rowNum < subagentInfoList.size()){
                fillSubInfo(subagentInfoList.get(rowNum),false)
            }
            rowNum++
        }
        fillFinalFareRow('ИТОГО', sum, operationSum, feePrtbSum, YVTaxSum, RVTaxSum);

        if(showSubagent() && subagentInfoList.size() >= rowNum){
            int i_proxy = (subagentInfoList.size() == rowNum) && (rowNum > 0) ? (rowNum - 1) : rowNum;
            if(subagentInfoList.size() > 0){
                fillSubInfo(subagentInfoList.get(i_proxy),false);
                rowNum++;
            }
        }
    }
    if(showSubagent() && subagentInfoList.size() >= rowNum){
        for(int i=rowNum; i < subagentInfoList.size(); i++){
            fillSubInfo(subagentInfoList.get(i),true)
        }
    }
}

private void fillFinalFareRow(String rowString, BigDecimal fare, BigDecimal operationNumber, BigDecimal feePrtb, BigDecimal YVTax, BigDecimal RVTax) {
    processNextCell(1, 0) { text(rowString, 'finalTotalText') }
    processNextCell(0, 1) {
        number(fare ?: BigDecimal.ZERO, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(operationNumber ?: BigDecimal.ZERO, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(feePrtb ?: BigDecimal.ZERO, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(YVTax ?: BigDecimal.ZERO, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(RVTax ?: BigDecimal.ZERO, 'totalNumber')
    }
}

private void fillSubInfo(SubagentInfo subInfo, boolean isNewString) {
    if(!isNewString){
        processNextCell(0, 2) { text(subInfo.subagentName, 'finalTotalText') }
    } else {
        int columShift = showBlank() ? 8 : 0;
        processNextCell(1, columShift) { text(subInfo.subagentName, 'finalTotalText') }
    }
    processNextCell(0, 1) {
        number(subInfo.summ ?: BigDecimal.ZERO, 'totalNumber')
    }
    processNextCell(0, 1) {
        number(subInfo.feePrtb ?: BigDecimal.ZERO, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(subInfo.finalSummAndFeePrtb ?: BigDecimal.ZERO, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(subInfo.feeSa ?: BigDecimal.ZERO, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(subInfo.finalAll ?: BigDecimal.ZERO, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(subInfo.finalYVTax ?: BigDecimal.ZERO, 'totalIntegerNumber')
    }
    processNextCell(0, 1) {
        number(subInfo.finalRVTax ?: BigDecimal.ZERO, 'totalIntegerNumber')
    }
}

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

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

private void populateSubAgent(ProductIndex index, EntityStorage entityStorage, AeroTicket ticket) {
    String subAgent = ''
    if (index.getSubagency() != null) {
        EntityContainer<Organization> cont = entityStorage.resolve(index.getSubagency());
        if (cont != null) {
            Organization org = cont.getEntity();
            if (org != null) {
                subAgent = L10nStringHelper.getValue(org.getShortName(), LocaleHelper.getCurrentLocale(), false);
            }
        }
    }
    ticket.setSubAgent(subAgent);
}
private void populateSubAgent(ArchivedProductIndex index, EntityStorage entityStorage, AeroTicket ticket) {
    String subAgent = ''
    if (index.getSubagency() != null) {
        EntityContainer<Organization> cont = entityStorage.resolve(index.getSubagency());
        if (cont != null) {
            Organization org = cont.getEntity();
            if (org != null) {
                subAgent = L10nStringHelper.getValue(org.getShortName(), LocaleHelper.getCurrentLocale(), false);
            }
        }
    }
    ticket.setSubAgent(subAgent);
}
private void populateAgencyName(ArchivedProductIndex index, EntityStorage entityStorage, AeroTicket ticket) {
    String agencyName = '';
    if (index.getAgency() != null) {
        EntityContainer<Organization> cont = entityStorage.resolve(index.getAgency());
        if (cont != null) {
            Organization org = cont.getEntity();
            if (org != null) {
                agencyName = L10nStringHelper.getValue(org.getShortName(), LocaleHelper.getCurrentLocale(), false);
            }
        }
    }
    ticket.setAgency(agencyName);
}

private void populateTotalFare(AeroTicket ticket) {
    ticket.setTotalPrice(MiscUtil.sum(ticket.ticketPrice, ticket.supplierFee, ticket.portbiletFee, ticket.subAgentFee))
}

private void populateYVTaxAmount(Product product, negate, AeroTicket ticket, boolean first) {
    BigDecimal price = AirProductTaxHelper.getEquivalentTaxesAmountByCodes(product, 'YV', 'YVI')
    if (price == null) {
        price = BigDecimal.ZERO;
    } else if (product.getStatus() == ProductStatus.VOID || !first) {
        price = BigDecimal.ZERO;
    } else {
        if (negate) {
            price = price.negate();
        }
    }
    ticket.YVTaxAmount = price;
}
private void populateRVTaxAmount(Product product, negate, AeroTicket ticket, boolean first) {
    BigDecimal price = AirProductTaxHelper.getEquivalentTaxesAmountByCodes(product, 'RV', 'RVI')
    if (price == null) {
        price = BigDecimal.ZERO;
    } else if (product.getStatus() == ProductStatus.VOID || !first) {
        price = BigDecimal.ZERO;
    } else {
        if (negate) {
            price = price.negate();
        }
    }
    ticket.RVTaxAmount = price;
}


private void populateFare(ProductIndex index, Product product, boolean negate, AeroTicket ticket, boolean first) {
    BigDecimal price = AirProductHelper.getEquivalentFare(product);
    if (price == null) {
        price = BigDecimal.ZERO;
    }
    if (index.getStatus() == ProductStatus.VOID || !first) {
        price = BigDecimal.ZERO;
    }
    else {
        price = price.add(AirProductTaxHelper.getEquivalentTaxesAmount(product));
        if (product.getPenalty() != null) {
            BigDecimal penalty = product.getPenalty();
            if (((index.getStatus() == ProductStatus.REFUND) || (index.getStatus() == ProductStatus.EXCHANGE)) &&
            (penalty != null) && (penalty.doubleValue() > 0)) {
                Product penaltyMco = null;
                for (Product prod : AirProductHelper.getMcoByRelatedProduct(product)) {
                    if ((prod.getMcoCategory() == MCOCategory.PENALTY) ||
                    (prod.getMcoCategory() == MCOCategory.REBOOKING)) {
                        penaltyMco = prod;
                        break;
                    }
                }
                if (penaltyMco == null) {
                    price = price.subtract(penalty);
                }
            }
            else if ((index.getStatus() == ProductStatus.SELL) && (product.getPreviousProduct() != null) &&
            (product.getPreviousProduct().getStatus() == ProductStatus.EXCHANGE) && (penalty != null) &&
            (penalty.doubleValue() > 0)) {
                price = price.add(penalty);
            }
            else if (product.getMcoCategory() == MCOCategory.REBOOKING) {
                price = price.add(penalty);
            }
        }
        if (negate) {
            price = price.negate();
        }
    }
    ticket.setTicketPrice(price)
}

private void populateFare(Product product, boolean negate, AeroTicket ticket, boolean first) {
    BigDecimal price = AirProductHelper.getEquivalentFare(product);
    if (price == null) {
        price = BigDecimal.ZERO;
    }
    if (product.getStatus() == ProductStatus.VOID || !first) {
        price = BigDecimal.ZERO;
    }
    else {
        price = price.add(AirProductTaxHelper.getEquivalentTaxesAmount(product));
        if (product.getPenalty() != null) {
            BigDecimal penalty = product.getPenalty();
            if (((product.getStatus() == ProductStatus.REFUND) || (product.getStatus() == ProductStatus.EXCHANGE)) &&
            (penalty != null) && (penalty.doubleValue() > 0)) {
                Product penaltyMco = null;
                for (Product prod : AirProductHelper.getMcoByRelatedProduct(product)) {
                    if ((prod.getMcoCategory() == MCOCategory.PENALTY) ||
                    (prod.getMcoCategory() == MCOCategory.REBOOKING)) {
                        penaltyMco = prod;
                        break;
                    }
                }
                if (penaltyMco == null) {
                    price = price.subtract(penalty);
                }
            }
            else if ((product.getStatus() == ProductStatus.SELL) && (product.getPreviousProduct() != null) &&
            (product.getPreviousProduct().getStatus() == ProductStatus.EXCHANGE) && (penalty != null) &&
            (penalty.doubleValue() > 0)) {
                price = price.add(penalty);
            }
            else if (product.getMcoCategory() == MCOCategory.REBOOKING) {
                price = price.add(penalty);
            }
        }
        if (negate) {
            price = price.negate();
        }
    }
    ticket.setTicketPrice(price)
}

private void populateIssueDate(ProductIndex index, AeroTicket ticket) {
    if (index.getIssueDate() != null) {
        ticket.setIssueDate(index.getIssueDate())
    }
}

private void populateIssueDate(ArchivedProductIndex index, AeroTicket ticket) {
    if (index.getIssueDate() != null) {
        ticket.setIssueDate(index.getIssueDate())
    }
}

private void populateProductStatus(Product product, AeroTicket ticket) {
    if (product.getStatus() != null) {
        ticket.setProductStatus(product.getStatus())
    }
}

private void populateAgent(ArchivedProductIndex index,Product product, Reservation reservation, AeroTicket ticket) {
    String agent = '';
    if (index.agent != null) {
        agent = index.getAgent().toString()
    }
    else if (product?.getPreviousProduct()?.getCashier() != null) {
        agent = product.getPreviousProduct().getCashier().toString()
    }
    else if (reservation?.getBookingAgent() != null) {
        agent = reservation?.getBookingAgent()?.toString()
    }
    ticket.setAgent(agent)
}

private void populateBlankOwner(Object index, EntityStorage entityStorage, AeroTicket ticket) {
    String blankOwnerStr = index.getBlankOwnerNumber();
    if (index.getBlankOwner() != null) {
        EntityContainer<Organization> cont = entityStorage.resolve(index.getBlankOwner());
        if (cont != null) {
            Organization org = cont.getEntity();
            blankOwnerStr = L10nStringHelper.getValue(org.getShortName(), LocaleHelper.getCurrentLocale(), false);
        }
    }
    ticket.setBlankOwner(blankOwnerStr);
}

private void populateBlankOwner(ProductIndex index, EntityStorage entityStorage, AeroTicket ticket) {
    String blankOwnerStr = index.getBlankOwnerNumber();
    if (index.getBlankOwner() != null) {
        EntityContainer<Organization> cont = entityStorage.resolve(index.getBlankOwner());
        if (cont != null) {
            Organization org = cont.getEntity();
            blankOwnerStr = L10nStringHelper.getValue(org.getShortName(), LocaleHelper.getCurrentLocale(), false);
        }
    }
    ticket.setBlankOwner(blankOwnerStr);
}

private void populateSupplier(Product product, AeroTicket ticket) {
    def supplier = AirProductHelper.getSupplier(product)
    if (supplier)
    {
        ticket.setSupplier(supplier.getCaption())
    }
}

private void populateBlankOwner(Product product, EntityStorage entityStorage, AeroTicket ticket) {
    String blankOwnerStr = product.getBlankOwnerNumber();
    if (product.getBlankOwner() != null) {
        EntityContainer<Organization> cont = entityStorage.resolve(product.getBlankOwner());
        if (cont != null) {
            Organization org = cont.getEntity();
            blankOwnerStr = L10nStringHelper.getValue(org.getShortName(), LocaleHelper.getCurrentLocale(), false);
        }
    }
    ticket.setBlankOwner(blankOwnerStr);
}

private void populateBlankOwner(ArchivedProductIndex index,Product product, EntityStorage entityStorage, AeroTicket ticket) {
    String blankOwnerStr = product.getBlankOwnerNumber();
    if (index.getBlankOwner() != null) {
        EntityContainer<Organization> cont = entityStorage.resolve(index.getBlankOwner());
        if (cont != null) {
            Organization org = cont.getEntity();
            blankOwnerStr = L10nStringHelper.getValue(org.getShortName(), LocaleHelper.getCurrentLocale(), false);
            if ((blankOwnerStr == null || blankOwnerStr.empty) && org.fullName != null) {
                blankOwnerStr = L10nStringHelper.getValue(org.fullName, LocaleHelper.getCurrentLocale(), false);
            }
        }
    }
    ticket.setBlankOwner(blankOwnerStr);
}

private void populateTicketNumber(String ticketNumber, AeroTicket ticket) {
    if (ticketNumber != null) {
        ticket.setTicketNumber(ticketNumber)
    }
    else {
        ticket.setTicketNumber("")
    }
}

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

private void populateGDS(Product product, AeroTicket ticket) {
    GdsName gdsName = CommonReservationGdsNameInfoHelper.getDisplayedGdsName(product.getReservation())
    if (gdsName != null) {
        ticket.setGdsName(gdsName.toString())
    }
    else {
        ticket.setGdsName("")
    }
}

private void populatePassengerName(Product product, AeroTicket ticket) {
    ticket.setPassengerName(product.getTraveller() != null ? product.getTraveller().getName() : '')
}

private void populateAgencyReward(ProductIndex index, Product product, AeroTicket ticket, boolean negate, boolean first) {
    boolean substractSupplierFee = true
    BigDecimal agencyReward = BigDecimal.ZERO
    if (product.getStatus() == ProductStatus.VOID) {
        agencyReward = BigDecimal.ZERO;
        substractSupplierFee = false
    }
    else {
        agencyReward = GeneralProductHelper.calculateCommissions(product, ContractType.SUBAGENCY,
                GeneralProductHelper.feePropertyTypes);
        if (negate && (agencyReward != null)) {
            agencyReward = agencyReward.negate();
        }
        if ((agencyReward == null) || (index.getStatus() == ProductStatus.VOID) || !first) {
            agencyReward = BigDecimal.ZERO;
            substractSupplierFee = false
        }
    }

    if (substractSupplierFee) {
        ticket.setPortbiletFee(agencyReward - ticket.supplierFee)
    } else {
        ticket.setPortbiletFee(agencyReward)
    }
}

private void populateAgencyReward(Product product, AeroTicket ticket, boolean negate, boolean first) {
    boolean substractSupplierFee = true
    BigDecimal agencyReward = BigDecimal.ZERO
    if (product.getStatus() == ProductStatus.VOID) {
        agencyReward = BigDecimal.ZERO;
        substractSupplierFee = false
    }
    else {
        agencyReward = GeneralProductHelper.calculateCommissions(product, ContractType.SUBAGENCY,
                GeneralProductHelper.feePropertyTypes);
        if (negate && (agencyReward != null)) {
            agencyReward = agencyReward.negate();
        }
        if ((agencyReward == null) || (product.getStatus() == ProductStatus.VOID) || !first) {
            agencyReward = BigDecimal.ZERO;
            substractSupplierFee = false
        }
    }

    if (substractSupplierFee) {
        agencyReward = agencyReward - ticket.supplierFee
    }
    ticket.setPortbiletFee(agencyReward)
}

private void populateSubAgentReward(Product product, AeroTicket ticket, boolean negate, boolean first) {
    BigDecimal subAgentFee = BigDecimal.ZERO
    if (product.getStatus() == ProductStatus.VOID || !first) {
        agencyReward = BigDecimal.ZERO;
    }
    else {
        subAgentFee = GeneralProductHelper.calculateCommissions(product, ContractType.CLIENT,
                GeneralProductHelper.feePropertyTypes);
        if ((subAgentFee != null) && negate) {
            subAgentFee = subAgentFee.negate();
        }
        if ((subAgentFee != null) && (ticket.getPortbiletFee() != null)) {
            subAgentFee = subAgentFee.subtract(ticket.getPortbiletFee());
        }
        if (subAgentFee != null) {
            subAgentFee = subAgentFee.subtract(ticket.supplierFee)
        }
    }
    ticket.setSubAgentFee(subAgentFee);
}

private void populateCarrier(ProductIndex index, AeroTicket ticket) {
    String carrierStr = "";
    Locale ruLocale = new Locale("RU");
    DictionaryCache dictCache = Environment.getPublished(DictionaryCache.class);
    if (index.getCarrier() != null) {
        Airline carrier = dictCache.resolveReference(index.getCarrier());
        if (carrier != null) {
            carrierStr = !TextUtil.isBlank(carrier.getTranslations().get(ruLocale)) ?
                    carrier.getTranslations().get(ruLocale) : carrier.getTranslations().get(Locale.ENGLISH);
            if (TextUtil.isBlank(carrierStr)) {
                Iterator<Map.Entry<Locale, String>> iter = carrier.getTranslations().entrySet().iterator();
                if (iter.hasNext()) {
                    carrierStr = iter.next().getValue();
                }
            }
        }
    }
    ticket.setCarrier(carrierStr);
}

private void populateCarrier(Product product, AeroTicket ticket) {
    String carrierStr = "";
    Locale ruLocale = new Locale("RU");
    DictionaryCache dictCache = Environment.getPublished(DictionaryCache.class);
    if (product.getCarrier() != null) {
        Airline carrier = dictCache.resolveReference(product.getCarrier());
        if (carrier != null) {
            carrierStr = !TextUtil.isBlank(carrier.getTranslations().get(ruLocale)) ?
                    carrier.getTranslations().get(ruLocale) : carrier.getTranslations().get(Locale.ENGLISH);
            if (TextUtil.isBlank(carrierStr)) {
                Iterator<Map.Entry<Locale, String>> iter = carrier.getTranslations().entrySet().iterator();
                if (iter.hasNext()) {
                    carrierStr = iter.next().getValue();
                }
            }
        }
    }
    ticket.setCarrier(carrierStr);
}

private void populateProfileGroup(Product product, AeroTicket ticket) {
    def pg = getProfileGroupAirProduct(product);
    ticket.setGroup(pg == null ? '' : pg.toString());
}

private void populatePaymentType(Product product, AeroTicket ticket) {
    ticket.setPaymentType(getPaymentType(product)?.toString())
}

private void populatePnr(ProductIndex index, AeroTicket ticket) {
    if (TextUtil.nonBlank(index.getDisplayedRecordLocator())) {
        ticket.setPnr(index.getDisplayedRecordLocator())
    } else {
        ticket.setPnr(index.getRecordLocator())
    }
}
private void populatePnr(Product product, AeroTicket ticket) {
    ticket.setPnr(CommonReservationGdsNameInfoHelper
        .getDisplayedRecordLocator(product.getReservation()));
}

private void populateSegment(Product product, AeroTicket ticket) {
    int segCount = 0;
    for (SegmentTariff t : product.getSegmentTariffs()) {
        segCount += t.getSegments().size();
    }
    ticket.setSegment(segCount);
}

private void populatePrtbTotalFare(AeroTicket ticket) {
    ticket.setPortbiletTotalPrice(MiscUtil.sum(ticket.ticketPrice, ticket.portbiletFee, ticket.supplierFee))
}

private PaymentType getPaymentType(Product product) {
    return GeneralProductHelper.findFop(product)?.type
}

private void populateClient(ProductIndex index, AeroTicket ticket) {
    String client = ''
    if (index.client != null) {
        client = index.client.toString()
    }
    ticket.setClient(client)
}

private void populateClient(ArchivedProductIndex index, AeroTicket ticket) {
    String client = ''
    if (index.client != null) {
        client = index.client.toString()
    }
    ticket.setClient(client)
}

warn 'reportVersion=' + '0.2.35'
