<?php

namespace App\Http\Controllers\Sales;

use App\Helpers\Helper;
use App\Models\Base\Pac;
use App\Helpers\PacHelper;
use App\Helpers\BaseHelper;
use Illuminate\Support\Str;
use App\Models\Base\Company;
use App\Models\Catalogs\Tax;
use Illuminate\Http\Request;
use App\Helpers\Cfdi33Helper;
use App\Models\Sales\Customer;
use App\Models\Catalogs\Project;
use App\Mail\SendCustomerPayment;
use App\Models\Base\BranchOffice;
use App\Models\Base\CfdiDownload;
use App\Models\Catalogs\Currency;
use App\Models\Catalogs\PaymentWay;
use App\Models\Catalogs\TaxRegimen;
use App\Http\Controllers\Controller;
use Maatwebsite\Excel\Facades\Excel;
use App\Models\Catalogs\CfdiRelation;
use App\Models\Sales\CustomerInvoice;
use App\Models\Sales\CustomerPayment;
use Illuminate\Support\Facades\Crypt;
use App\Models\Catalogs\PaymentMethod;
use App\Exports\CustomerPaymentsExport;
use App\Models\Base\CompanyBankAccount;
use App\Models\Sales\CustomerBankAccount;
use App\Models\Sales\CustomerPaymentCfdi;
use App\Models\Catalogs\ReasonCancellation;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
use App\Models\Sales\CustomerPaymentRelation;
use Illuminate\Validation\ValidationException;
use App\Models\Sales\CustomerPaymentReconciled;
use App\Exports\CustomerPaymentTemplateImportExport;
use App\Imports\CustomerPaymentTemplateImportImport;
use App\Imports\CustomerPaymentTemplateBeforeImportImport;

class CustomerPaymentController extends Controller
{
    private $list_status = [];
    private $filter_date_types = [];
    private $tipo_cadena_pagos = [];
    private $document_type_code = 'customer.payment';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->list_status = [
            CustomerPayment::OPEN => __('sales/customer_payment.text_status_open'),
            CustomerPayment::PER_RECONCILED => __('sales/customer_payment.text_status_per_reconciled'),
            CustomerPayment::RECONCILED => __('sales/customer_payment.text_status_reconciled'),
            CustomerPayment::CANCEL => __('sales/customer_payment.text_status_cancel'),
        ];
        $this->filter_date_types = [
            1 => __('sales/customer_payment.text_filter_date_type_1'),
            2 => __('sales/customer_payment.text_filter_date_type_2'),
        ];
        $this->tipo_cadena_pagos = ['01'=>'SPEI'];
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        //Variables
        $limit = ($request->has('limit') ? $request->get('limit') : 100);
        if(!empty($request->download_xmlpdf)){
            $request->request->add([
                'page' => 1
            ]);
            $limit = 1000000;
        }
        $branch_offices = BranchOffice::populateSelect()->pluck('name', 'id');
        $payment_ways = PaymentWay::populateSelect()->get()->pluck('name_sat', 'id');
        $list_status = $this->list_status;
        $filter_date_types = $this->filter_date_types;
        if (empty($request->filter_date_from)) {
            $request->request->add([
                'filter_date_from' => Helper::date(\Date::parse('first day of this month'))
            ]);
        }
        if (empty($request->filter_date_to)) {
            $request->request->add([
                'filter_date_to' => Helper::date(\Date::parse('last day of this month'))
            ]);
        }
        $request->request->add(['filter_document_type_code' => $this->document_type_code]); //Filtra tipo de documento

        //Consulta
        $results = CustomerPayment::filter($request->all())
            ->with('customerPaymentCfdi')
            ->with('customer')
            ->with('currency')
            ->with('paymentWay')
            ->sortable(['date' => 'desc'])->paginate($limit);

        //Descar zip de XML´s y PDF´s
        if(!empty($request->download_xmlpdf)){
            if($results->isNotEmpty()) {
                $company = Helper::defaultCompany();
                $file_zip_name = __('sales/customer_payment.document_title_download_xmlpdf').'_' . $company->taxid .'_'. str_replace(['-','/'],['',''],$request->filter_date_from).'_'. str_replace(['-','/'],['',''],$request->filter_date_to) .'_'.random_int(10,99).'.zip';
                if(\Storage::exists('temp/' . $file_zip_name)) {
                    @\Storage::delete('temp/' . $file_zip_name);
                }
                $zip =\Zipper::make(\Storage::path('temp/' . $file_zip_name));

                foreach ($results as $result) {
                    if (!empty($result->customerPaymentCfdi->cfdi_version) && !empty($result->customerPaymentCfdi->uuid)) {

                        //Ruta y validacion del XML
                        $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $result->company_id) . '/';
                        $file_xml_pac = $path_xml . $result->customerPaymentCfdi->file_xml_pac;
                        if (!empty($result->customerPaymentCfdi->file_xml_pac) && \Storage::exists($file_xml_pac)) {
                            $zip->add(\Storage::path($file_xml_pac), str_replace('/','',$result->name) . '.xml');
                        }

                        //Ruta y validacion del pdf
                        $pdf = $this->print($result, false, true);
                        $file_pdf_name = Str::random().'.pdf';
                        //Guardamos en directorio temporal
                        \Storage::put('temp/' . $file_pdf_name, $pdf);
                        $zip->add(\Storage::path('temp/' . $file_pdf_name), str_replace('/','',$result->name) . '.pdf');

                    }
                }
                $zip->close();
                if(\Storage::exists('temp/' . $file_zip_name)) {
                    while (ob_get_level()) ob_end_clean();
                    ob_start();

                    return response()->download(\Storage::path('temp/' . $file_zip_name));
                }else{
                    flash(__('base/customer_payment.error_download_xmlpdf'))->warning();
                }
            }
        }

        //Vista
        return view('sales.customer_payments.index',
            compact('results', 'branch_offices','payment_ways', 'list_status', 'filter_date_types'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create(Request $request)
    {
        $company_bank_accounts = CompanyBankAccount::populateSelect()->pluck('name', 'id');
        $branch_offices = BranchOffice::populateSelect()->pluck('name', 'id');
        $currencies = Currency::populateSelect()->get()->pluck('name_sat', 'id');
        $currency_codes = Currency::populateSelect()->get()->pluck('name_sat', 'code');
        $payment_ways = PaymentWay::populateSelect()->get()->pluck('name_sat', 'id');
        $cfdi_relations = CfdiRelation::populateSelect()->get()->pluck('name_sat', 'id');
        $payment_method_codes = PaymentMethod::populateSelect()->get()->pluck('name_sat', 'code');
        $tipo_cadena_pagos = $this->tipo_cadena_pagos;
        $projects = Project::populateSelect()->pluck('name', 'id');
        $company = Helper::defaultCompany(); //Empresa
        $taxes = Tax::populateSelect()->pluck('name', 'id');
        if(!empty($company->tax_regimen_id2)){
            $tax_regimens = TaxRegimen::populateSelect()->whereIn('id',[$company->tax_regimen_id, $company->tax_regimen_id2])->get()->pluck('name_sat', 'id');
        }else{
            $tax_regimens = collect([]);
        }
        $tax_regimen_customers = TaxRegimen::populateSelect()->get()->pluck('name_sat', 'id');

        $duplicate_ci = null;
        if($request->duplicate_id){
            $duplicate_ci = CustomerPayment::findOrFail($request->duplicate_id);
        }

        return view('sales.customer_payments.create',
            compact( 'branch_offices','company_bank_accounts', 'currencies', 'payment_ways', 'cfdi_relations','tipo_cadena_pagos','currency_codes','payment_method_codes','duplicate_ci','projects','tax_regimens','tax_regimen_customers', 'taxes'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //Validacion
        $this->validation($request);

        //Almacenamiento de archivos
        $customer_payment = null;

        \DB::connection('tenant')->beginTransaction();
        try {
            //Logica
            $company = Helper::defaultCompany(); //Empresa
            $request->merge(['created_uid' => \Auth::user()->id]);
            $request->merge(['updated_uid' => \Auth::user()->id]);
            $request->merge(['status' => isset($request->pre_payment) ? CustomerPayment::DRAFT : CustomerPayment::OPEN]);
            $request->merge(['cfdi' => !empty($request->cfdi) ? 1 : 0]);
            $request->merge(['company_id' => $company->id]);
            //Ajusta fecha y genera fecha de vencimiento
            $date = Helper::createDateTime($request->date);
            $request->merge(['date' => Helper::dateTimeToSql($date)]);
            if (!empty($request->date_payment)) {
                $date_payment = Helper::createDateTime($request->date_payment);
                $request->merge(['date_payment' => Helper::dateTimeToSql($date_payment)]);
            }

            //Obtiene folio
            $document_type = Helper::getNextDocumentTypeByCode($this->document_type_code,$company->id,isset($request->pre_payment) ? true : false, $request->branch_office_id);
            $request->merge(['document_type_id' => $document_type['id']]);
            $request->merge(['name' => $document_type['name']]);
            $request->merge(['serie' => $document_type['serie']]);
            $request->merge(['folio' => $document_type['folio']]);

            //Valida que tenga folios disponibles
            if(!empty($request->cfdi) && !isset($request->pre_payment)) {
                if (BaseHelper::getAvailableFolios() <= 0) {
                    throw new \Exception(__('general.error_available_folios'));
                }
            }

            //Guardar
            //Registro principal
            $customer_payment = CustomerPayment::create($request->input());

            //Facturas conciliadas
            $amount = (double)$request->amount;
            $amount_reconciled = 0;
            $count = 1; //Contador de lineas
            //Lineas
            if (!empty($request->item_reconciled)) {
                foreach ($request->item_reconciled as $key => $item_reconciled) {
                    if(!empty($item_reconciled['amount_reconciled'])) {
                        //Datos de factura
                        $customer_invoice = CustomerInvoice::findOrFail($item_reconciled['reconciled_id']);

                        //Logica
                        $item_reconciled_amount_reconciled = round((double)$item_reconciled['amount_reconciled'],2);
                        $amount_reconciled += $item_reconciled_amount_reconciled;
                        $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                        //Convertimos el monto aplicado si la moneda del documento es diferente a la de pago
                        $item_reconciled_amount_reconciled = round(Helper::invertBalanceCurrency($customer_payment->currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);

                        //fix error 0.01
                        if($customer_invoice->balance - $item_reconciled_amount_reconciled < 0.03){
                            $item_reconciled_amount_reconciled = $customer_invoice->balance;
                        }

                        //Guardar linea
                        $customer_payment_reconciled = CustomerPaymentReconciled::create([
                            'created_uid' => \Auth::user()->id,
                            'updated_uid' => \Auth::user()->id,
                            'customer_payment_id' => $customer_payment->id,
                            'name' => $customer_invoice->name,
                            'reconciled_id' => $customer_invoice->id,
                            'currency_value' => $item_reconciled_currency_value,
                            'amount_reconciled' => $item_reconciled_amount_reconciled,
                            'last_balance' => $customer_invoice->balance,
                            'sort_order' => $count,
                            'status' => 1,
                            'uuid_related' => $customer_invoice->customerInvoiceCfdi->uuid,
                            'serie_related' => $customer_invoice->serie,
                            'folio_related' => $customer_invoice->folio,
                            'currency_code_related' => $customer_invoice->currency->code,
                            'payment_method_code_related' => $customer_invoice->paymentMethod->code,
                            'current_balance' => $customer_invoice->balance - $item_reconciled_amount_reconciled,
                        ]);

                        //Numero de pagos realizados que no esten cancelados
                        if(!isset($request->pre_payment)) { //Solo descuenta del pago cuando no es borrador
                            $tmp_customer_payment_reconciled = CustomerPaymentReconciled::where('status', '=', '1')
                                ->where('reconciled_id', '=', $customer_invoice->id)
                                ->where(function ($query) {
                                    $query->WhereHas('customerPayment', function ($q) {
                                        $q->whereIn('customer_payments.status',
                                            [CustomerPayment::OPEN, CustomerPayment::RECONCILED]);
                                    });
                                });
                            $customer_payment_reconciled->number_of_payment = $tmp_customer_payment_reconciled->count(); //Guarda el numero de parcialidad
                            $customer_payment_reconciled->save();

                            //Actualiza el saldo de la factura relacionada
                            $customer_invoice->balance -= $item_reconciled_amount_reconciled;
                            if ($customer_invoice->balance < 0) {
                                throw new \Exception(sprintf(__('sales/customer_payment.error_amount_balance_reconciled'), $item_reconciled_amount_reconciled, $customer_invoice->name));
                            }
                            if ($customer_invoice->balance <= 0.1) {
                                $customer_invoice->status = CustomerInvoice::PAID;
                            }
                            $customer_invoice->save();
                        }
                        $count++;
                    }
                }
            }

            //Facturas manuales
            //Lineas
            if (!empty($request->item_manual_reconciled)) {
                foreach ($request->item_manual_reconciled as $key => $item_reconciled) {
                    if(!empty($item_reconciled['amount_reconciled'])) {
                        //Logica
                        $item_reconciled_amount_reconciled = round((double)$item_reconciled['amount_reconciled'],2);
                        $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                        if(!empty($item_reconciled_currency_value)){
                            $amount_reconciled += $item_reconciled_amount_reconciled * $item_reconciled_currency_value;
                        }else{
                            $amount_reconciled += $item_reconciled_amount_reconciled;
                        }

                        //Guardar linea
                        $customer_payment_reconciled = CustomerPaymentReconciled::create([
                            'created_uid' => \Auth::user()->id,
                            'updated_uid' => \Auth::user()->id,
                            'customer_payment_id' => $customer_payment->id,
                            'name' => $item_reconciled['serie_related'].$item_reconciled['folio_related'],
                            'reconciled_id' => null,
                            'currency_value' => $item_reconciled_currency_value,
                            'amount_reconciled' => $item_reconciled_amount_reconciled,
                            'number_of_payment' => $item_reconciled['number_of_payment'],
                            'last_balance' => $item_reconciled['last_balance'],
                            'sort_order' => $count,
                            'status' => 1,
                            'uuid_related' => $item_reconciled['uuid_related'],
                            'serie_related' => $item_reconciled['serie_related'],
                            'folio_related' => $item_reconciled['folio_related'],
                            'currency_code_related' => $item_reconciled['currency_code_related'],
                            'payment_method_code_related' => $item_reconciled['payment_method_code_related'],
                            'current_balance' => $item_reconciled['current_balance'],
                            'tax_id_1' => $item_reconciled['tax_id_1'] ?? null,
                            'amount_base_1' => (double)($item_reconciled['amount_base_1'] ?? 0),
                            'tax_id_2' => $item_reconciled['tax_id_2'] ?? null,
                            'amount_base_2' => (double)($item_reconciled['amount_base_2'] ?? 0),
                            'tax_id_3' => $item_reconciled['tax_id_3'] ?? null,
                            'amount_base_3' => (double)($item_reconciled['amount_base_3'] ?? 0),
                            'tax_id_4' => $item_reconciled['tax_id_4'] ?? null,
                            'amount_base_4' => (double)($item_reconciled['amount_base_4'] ?? 0),
                            'tax_id_5' => $item_reconciled['tax_id_5'] ?? null,
                            'amount_base_5' => (double)($item_reconciled['amount_base_5'] ?? 0),
                            'amount_total' => (double)($item_reconciled['amount_total'] ?? 0),
                        ]);

                        $count++;
                    }
                }
            }

            //Cfdi relacionados
            if (!empty($request->item_relation)) {
                foreach ($request->item_relation as $key => $result) {
                    $customer_payment_cfdi_relation = CustomerPaymentCfdi::where('customer_payment_id','=',$result['relation_id'])->first();

                    //Guardar
                    $customer_payment_relation = CustomerPaymentRelation::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_payment_id' => $customer_payment->id,
                        'relation_id' => !empty($result['relation_id']) ? $result['relation_id'] : null,
                        'sort_order' => $key,
                        'status' => 1,
                        'uuid_related' => $result['uuid_related'],
                    ]);
                }
            }

            //Valida que tenga exista la clase de facturacion
            if (!empty($request->cfdi)) {
                $class_cfdi = setting('cfdi_version');
                if (empty($class_cfdi)) {
                    throw new \Exception(__('general.error_cfdi_version'));
                }
                if (!method_exists($this, $class_cfdi)) {
                    throw new \Exception(__('general.error_cfdi_class_exists'));
                }
            }

            //Registros de cfdi
            if (!empty($request->cfdi)) {
                $customer_payment_cfdi = CustomerPaymentCfdi::create([
                    'created_uid' => \Auth::user()->id,
                    'updated_uid' => \Auth::user()->id,
                    'customer_payment_id' => $customer_payment->id,
                    'name' => $customer_payment->name,
                    'cfdi_version' => $class_cfdi,
                    'status' => 1,
                ]);
            }

            //Actualiza estatus de acuerdo al monto conciliado
            $customer_payment->balance = $amount - $amount_reconciled;
            if(($customer_payment->balance <= 0.1 || !empty($request->cfdi)) && !isset($request->pre_payment)){ //Si es un CFDI lo marca como conciliado
                $customer_payment->status = CustomerPayment::RECONCILED;
            }
            $customer_payment->update();


            //Solo guarda los datos sin timbrar en caso de pre-factura
            if(isset($request->pre_payment)) {
                //Mensaje
                flash(__('general.text_success_pre_customer_payment'))->success();

            }else { //Crear CFDI

                //Crear el CFDI si marcaron la opcion
                if (!empty($request->cfdi)) {

                    //Valida Empresa y PAC para timbrado
                    PacHelper::validateSatActions($company);

                    //Crear XML y timbra
                    $tmp = $this->$class_cfdi($customer_payment);

                    //Guardar registros de CFDI
                    $customer_payment_cfdi->fill(array_only($tmp, [
                        'pac_id',
                        'cfdi_version',
                        'uuid',
                        'date',
                        'file_xml',
                        'file_xml_pac',
                    ]));
                    $customer_payment_cfdi->save();

                    //Disminuye folios
                    BaseHelper::decrementFolios();

                    //Mensaje
                    flash(__('general.text_success_customer_payment_cfdi'))->success();
                } else {
                    //Mensaje
                    flash(__('general.text_form_success_add'))->success();
                }
            }

            \DB::connection('tenant')->commit();

            if(!isset($request->pre_payment)) {
                $this->saveCfdiDownloads($customer_payment, $customer_payment_cfdi);
            }



        } catch (\Exception $e) {
            //Fix fechas
            $request->merge([
                'date' => Helper::convertSqlToDateTime($request->date),
            ]);
            if (!empty($request->date_payment)) {
                $request->merge([
                    'date_payment' => Helper::convertSqlToDateTime($request->date_payment),
                ]);
            }

            \DB::connection('tenant')->rollback();
            $company = Helper::defaultCompany(); //Empresa
            \Log::error('(' . $company->taxid . ') ' . $e->getMessage());
            flash($e->getMessage())->error();
            return back()->withInput();
        }

        //Almacenamiento dropbox
        self::dropboxBackup($customer_payment);

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Display the specified resource.
     *
     * @param  \App\Models\Sales\CustomerPayment  $customer_payment
     * @return \Illuminate\Http\Response
     */
    public function show(CustomerPayment $customer_payment)
    {
        //Datos
        $data = [];

        //Si tiene CFDI obtiene la informacion de los nodos
        if(!empty($customer_payment->customerPaymentCfdi->file_xml_pac) && !empty($customer_payment->customerPaymentCfdi->uuid)){

            $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
            $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;

            //Valida que el archivo exista
            if(!empty($customer_payment->customerPaymentCfdi->file_xml_pac) && \Storage::exists($file_xml_pac)) {
                $cfdi = \CfdiUtils\Cfdi::newFromString(\Storage::get($file_xml_pac));
                $data = Cfdi33Helper::getQuickArrayCfdi($cfdi);

                //Genera codigo QR
                $image = QrCode::format('png')->size(150)->margin(0)->generate($data['qr_cadena']);
                $data['qr'] = 'data:image/png;base64,' . base64_encode($image);

                //Regimen fiscal
                $data['tax_regimen'] = TaxRegimen::where('code', $data['cfdi33']->emisor['RegimenFiscal'])->first()->name_sat;
                $data['tax_regimen_customer'] = !empty($data['cfdi33']->receptor['RegimenFiscalReceptor']) ? TaxRegimen::where('code', $data['cfdi33']->receptor['RegimenFiscalReceptor'])->first()->name_sat : '';
            }
        }

        return view('sales.customer_payments.show', compact('customer_payment','data'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Models\Sales\CustomerPayment  $customer_payment
     * @return \Illuminate\Http\Response
     */
    public function edit(CustomerPayment $customer_payment)
    {
        $company_bank_accounts = CompanyBankAccount::populateSelect()->pluck('name', 'id');
        $branch_offices = BranchOffice::populateSelect()->pluck('name', 'id');
        $currencies = Currency::populateSelect()->get()->pluck('name_sat', 'id');
        $currency_codes = Currency::populateSelect()->get()->pluck('name_sat', 'code');
        $payment_ways = PaymentWay::populateSelect()->get()->pluck('name_sat', 'id');
        $cfdi_relations = CfdiRelation::populateSelect()->get()->pluck('name_sat', 'id');
        $payment_method_codes = PaymentMethod::populateSelect()->get()->pluck('name_sat', 'code');
        $tipo_cadena_pagos = $this->tipo_cadena_pagos;
        $projects = Project::populateSelect()->pluck('name', 'id');
        $company = $customer_payment->company; //Empresa
        $taxes = Tax::populateSelect()->pluck('name', 'id');
        if(!empty($company->tax_regimen_id2)){
            $tax_regimens = TaxRegimen::populateSelect()->whereIn('id',[$company->tax_regimen_id, $company->tax_regimen_id2])->get()->pluck('name_sat', 'id');
        }else{
            $tax_regimens = collect([]);
        }
        $tax_regimen_customers = TaxRegimen::populateSelect()->get()->pluck('name_sat', 'id');

        return view('sales.customer_payments.edit',
            compact( 'customer_payment','branch_offices','company_bank_accounts', 'currencies', 'payment_ways', 'cfdi_relations','tipo_cadena_pagos','currency_codes','payment_method_codes','projects','tax_regimens','tax_regimen_customers', 'taxes'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Sales\CustomerPayment  $customer_payment
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, CustomerPayment $customer_payment)
    {
        //Validacion
        $this->validation($request);

        \DB::connection('tenant')->beginTransaction();
        try {
            //Logica
            $request->merge(['created_uid' => \Auth::user()->id]);
            $request->merge(['updated_uid' => \Auth::user()->id]);
            $request->merge(['status' => isset($request->pre_payment) ? CustomerPayment::DRAFT : CustomerPayment::OPEN]);
            $request->merge(['cfdi' => !empty($request->cfdi) ? 1 : 0]);

            //Ajusta fecha y genera fecha de vencimiento
            $date = Helper::createDateTime($request->date);
            $request->merge(['date' => Helper::dateTimeToSql($date)]);
            if (!empty($request->date_payment)) {
                $date_payment = Helper::createDateTime($request->date_payment);
                $request->merge(['date_payment' => Helper::dateTimeToSql($date_payment)]);
            }
            $customer_payment->fill($request->only([
                'updated_uid',
                'date',
                'date_payment',
                'reference',
                'company_bank_account_id',
                'customer_id',
                'customer_bank_account_id',
                'branch_office_id',
                'payment_way_id',
                'currency_id',
                'currency_value',
                'amount',
                'cfdi_relation_id',
                'cfdi',
                'comment',
                'mail_sent',
                'sort_order',
                'status',
                'confirmacion',
                'tipo_cadena_pago',
                'certificado_pago',
                'cadena_pago',
                'sello_pago',
                'project_id',
                'tax_regimen_id',
                'tax_regimen_customer_id'
            ]));

            //Guardar
            //Registro principal
            $customer_payment->save();

            //Actualiza folios
            if(!isset($request->pre_payment)) {
                //Valida que tenga folios disponibles
                if(BaseHelper::getAvailableFolios()<=0){
                    throw new \Exception(__('general.error_available_folios'));
                }

                //Obtiene folio
                $document_type = Helper::getNextDocumentTypeByCode($this->document_type_code, $customer_payment->company->id, false, $customer_payment->branch_office_id);
                $customer_payment->draft = $customer_payment->name;
                $customer_payment->name = $document_type['name'];
                $customer_payment->serie = $document_type['serie'];
                $customer_payment->folio = $document_type['folio'];
                $customer_payment->save();
            }

            //Facturas conciliadas
            $amount = (double)$request->amount;
            $amount_reconciled = 0;
            $count = 1; //Contador de lineas
            //Lineas
            CustomerPaymentReconciled::where('customer_payment_id','=',$customer_payment->id)->whereNotNull('reconciled_id')->delete(); //Borra todo e inserta nuevamente
            if (!empty($request->item_reconciled)) {
                foreach ($request->item_reconciled as $key => $item_reconciled) {
                    if(!empty($item_reconciled['amount_reconciled'])) {
                        //Datos de factura
                        $customer_invoice = CustomerInvoice::findOrFail($item_reconciled['reconciled_id']);

                        //Logica
                        $item_reconciled_amount_reconciled = round((double)$item_reconciled['amount_reconciled'],2);
                        $amount_reconciled += $item_reconciled_amount_reconciled;
                        $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                        //Convertimos el monto aplicado si la moneda del documento es diferente a la de pago
                        $item_reconciled_amount_reconciled = round(Helper::invertBalanceCurrency($customer_payment->currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);

                        //fix error 0.01
                        if($customer_invoice->balance - $item_reconciled_amount_reconciled < 0.03){
                            $item_reconciled_amount_reconciled = $customer_invoice->balance;
                        }

                        //Guardar linea
                        $customer_payment_reconciled = CustomerPaymentReconciled::create([
                            'created_uid' => \Auth::user()->id,
                            'updated_uid' => \Auth::user()->id,
                            'customer_payment_id' => $customer_payment->id,
                            'name' => $customer_invoice->name,
                            'reconciled_id' => $customer_invoice->id,
                            'currency_value' => $item_reconciled_currency_value,
                            'amount_reconciled' => $item_reconciled_amount_reconciled,
                            'last_balance' => $customer_invoice->balance,
                            'sort_order' => $count,
                            'status' => 1,
                            'uuid_related' => $customer_invoice->customerInvoiceCfdi->uuid,
                            'serie_related' => $customer_invoice->serie,
                            'folio_related' => $customer_invoice->folio,
                            'currency_code_related' => $customer_invoice->currency->code,
                            'payment_method_code_related' => $customer_invoice->paymentMethod->code,
                            'current_balance' => $customer_invoice->balance - $item_reconciled_amount_reconciled,
                        ]);

                        //Numero de pagos realizados que no esten cancelados
                        if(!isset($request->pre_payment)) { //Solo descuenta del pago cuando no es borrador
                            $tmp_customer_payment_reconciled = CustomerPaymentReconciled::where('status', '=', '1')
                                ->where('reconciled_id', '=', $customer_invoice->id)
                                ->where(function ($query) {
                                    $query->WhereHas('customerPayment', function ($q) {
                                        $q->whereIn('customer_payments.status',
                                            [CustomerPayment::OPEN, CustomerPayment::RECONCILED]);
                                    });
                                });
                            $customer_payment_reconciled->number_of_payment = $tmp_customer_payment_reconciled->count(); //Guarda el numero de parcialidad
                            $customer_payment_reconciled->save();

                            //Actualiza el saldo de la factura relacionada
                            $customer_invoice->balance -= $item_reconciled_amount_reconciled;
                            if ($customer_invoice->balance < 0) {
                                throw new \Exception(sprintf(__('sales/customer_payment.error_amount_balance_reconciled'), $item_reconciled_amount_reconciled, $customer_invoice->name));
                            }
                            if ($customer_invoice->balance <= 0.1) {
                                $customer_invoice->status = CustomerInvoice::PAID;
                            }
                            $customer_invoice->save();
                        }
                        $count++;
                    }
                }
            }

            //Facturas manuales
            //Lineas
            CustomerPaymentReconciled::where('customer_payment_id','=',$customer_payment->id)->whereNull('reconciled_id')->delete(); //Borra todo e inserta nuevamente
            if (!empty($request->item_manual_reconciled)) {
                foreach ($request->item_manual_reconciled as $key => $item_reconciled) {
                    if(!empty($item_reconciled['amount_reconciled'])) {
                        //Logica
                        $item_reconciled_amount_reconciled = round((double)$item_reconciled['amount_reconciled'],2);
                        $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                        if(!empty($item_reconciled_currency_value)){
                            $amount_reconciled += $item_reconciled_amount_reconciled * $item_reconciled_currency_value;
                        }else{
                            $amount_reconciled += $item_reconciled_amount_reconciled;
                        }

                        //Guardar linea
                        $customer_payment_reconciled = CustomerPaymentReconciled::create([
                            'created_uid' => \Auth::user()->id,
                            'updated_uid' => \Auth::user()->id,
                            'customer_payment_id' => $customer_payment->id,
                            'name' => $item_reconciled['serie_related'].$item_reconciled['folio_related'],
                            'reconciled_id' => null,
                            'currency_value' => $item_reconciled_currency_value,
                            'amount_reconciled' => $item_reconciled_amount_reconciled,
                            'number_of_payment' => $item_reconciled['number_of_payment'],
                            'last_balance' => $item_reconciled['last_balance'],
                            'sort_order' => $count,
                            'status' => 1,
                            'uuid_related' => $item_reconciled['uuid_related'],
                            'serie_related' => $item_reconciled['serie_related'],
                            'folio_related' => $item_reconciled['folio_related'],
                            'currency_code_related' => $item_reconciled['currency_code_related'],
                            'payment_method_code_related' => $item_reconciled['payment_method_code_related'],
                            'current_balance' => $item_reconciled['current_balance'],
                            'tax_id_1' => $item_reconciled['tax_id_1'] ?? null,
                            'amount_base_1' => (double)($item_reconciled['amount_base_1'] ?? 0),
                            'tax_id_2' => $item_reconciled['tax_id_2'] ?? null,
                            'amount_base_2' => (double)($item_reconciled['amount_base_2'] ?? 0),
                            'tax_id_3' => $item_reconciled['tax_id_3'] ?? null,
                            'amount_base_3' => (double)($item_reconciled['amount_base_3'] ?? 0),
                            'tax_id_4' => $item_reconciled['tax_id_4'] ?? null,
                            'amount_base_4' => (double)($item_reconciled['amount_base_4'] ?? 0),
                            'tax_id_5' => $item_reconciled['tax_id_5'] ?? null,
                            'amount_base_5' => (double)($item_reconciled['amount_base_5'] ?? 0),
                            'amount_total' => (double)($item_reconciled['amount_total'] ?? 0),
                        ]);

                        $count++;
                    }
                }
            }

            //Cfdi relacionados
            CustomerPaymentRelation::where('customer_payment_id','=',$customer_payment->id)->delete(); //Borra todo e inserta nuevamente
            if (!empty($request->item_relation)) {
                foreach ($request->item_relation as $key => $result) {
                    $customer_payment_cfdi_relation = CustomerPaymentCfdi::where('customer_payment_id','=',$result['relation_id'])->first();

                    //Guardar
                    $customer_payment_relation = CustomerPaymentRelation::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_payment_id' => $customer_payment->id,
                        'relation_id' => !empty($result['relation_id']) ? $result['relation_id'] : null,
                        'sort_order' => $key,
                        'status' => 1,
                        'uuid_related' => $result['uuid_related'],
                    ]);
                }
            }

            //Valida que tenga exista la clase de facturacion
            if (!empty($request->cfdi)) {
                $class_cfdi = setting('cfdi_version');
                if (empty($class_cfdi)) {
                    throw new \Exception(__('general.error_cfdi_version'));
                }
                if (!method_exists($this, $class_cfdi)) {
                    throw new \Exception(__('general.error_cfdi_class_exists'));
                }
            }

            //Registros de cfdi
            CustomerPaymentCfdi::where('customer_payment_id','=',$customer_payment->id)->delete(); //Borra todo e inserta nuevamente

            if (!empty($request->cfdi)) {
                $customer_payment_cfdi = CustomerPaymentCfdi::create([
                    'created_uid' => \Auth::user()->id,
                    'updated_uid' => \Auth::user()->id,
                    'customer_payment_id' => $customer_payment->id,
                    'name' => $customer_payment->name,
                    'cfdi_version' => $class_cfdi,
                    'status' => 1,
                ]);
            }

            //Actualiza estatus de acuerdo al monto conciliado
            $customer_payment->balance = $amount - $amount_reconciled;
            if(($customer_payment->balance <= 0.1 || !empty($request->cfdi)) && !isset($request->pre_payment)){ //Si es un CFDI lo marca como conciliado
                $customer_payment->status = CustomerPayment::RECONCILED;
            }
            $customer_payment->update();


            //Solo guarda los datos sin timbrar en caso de pre-factura
            if(isset($request->pre_payment)) {
                //Mensaje
                flash(__('general.text_success_pre_customer_payment'))->success();

            }else { //Crear CFDI

                //Crear el CFDI si marcaron la opcion
                if (!empty($request->cfdi)) {

                    //Valida Empresa y PAC para timbrado
                    PacHelper::validateSatActions($customer_payment->company);

                    //Crear XML y timbra
                    $tmp = $this->$class_cfdi($customer_payment);

                    //Guardar registros de CFDI
                    $customer_payment_cfdi->fill(array_only($tmp, [
                        'pac_id',
                        'cfdi_version',
                        'uuid',
                        'date',
                        'file_xml',
                        'file_xml_pac',
                    ]));
                    $customer_payment_cfdi->save();

                    //Disminuye folios
                    BaseHelper::decrementFolios();

                    //Mensaje
                    flash(__('general.text_success_customer_payment_cfdi'))->success();
                } else {
                    //Mensaje
                    flash(__('general.text_form_success_add'))->success();
                }
            }

            \DB::connection('tenant')->commit();

            if(!isset($request->pre_payment) && !empty($request->cfdi)) {
                $this->saveCfdiDownloads($customer_payment, $customer_payment_cfdi);
            }



        } catch (\Exception $e) {
            //Fix fechas
            $request->merge([
                'date' => Helper::convertSqlToDateTime($request->date),
            ]);
            if (!empty($request->date_payment)) {
                $request->merge([
                    'date_payment' => Helper::convertSqlToDateTime($request->date_payment),
                ]);
            }

            \DB::connection('tenant')->rollback();
            $company = Helper::defaultCompany(); //Empresa
            \Log::error('(' . $company->taxid . ') ' . $e->getMessage());
            flash($e->getMessage())->error();
            return back()->withInput();
        }

        //Almacenamiento dropbox
        self::dropboxBackup($customer_payment);

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Sales\CustomerPayment  $customer_payment
     * @return \Illuminate\Http\Response
     */
    public function destroy(Request $request, CustomerPayment $customer_payment)
    {
        \DB::connection('tenant')->beginTransaction();
        try {
            //Logica
            if ((int)$customer_payment->status != CustomerPayment::CANCEL) {
                //Actualiza status
                $status_old = $customer_payment->status;
                $customer_payment->updated_uid = \Auth::user()->id;
                $customer_payment->status = CustomerPayment::CANCEL;
                $customer_payment->save();

                //Actualiza saldos de facturas
                if($customer_payment->customerPaymentReconcileds->isNotEmpty()){
                    foreach($customer_payment->customerPaymentReconcileds as $result){

                        if(!empty($result->reconciled_id) && $status_old != CustomerPayment::DRAFT) {
                            //Datos de factura
                            $customer_invoice = CustomerInvoice::findOrFail($result->reconciled_id);

                            //Actualiza el saldo de la factura relacionada
                            $customer_invoice->balance += $result->amount_reconciled;
                            if (($customer_invoice->balance - $customer_invoice->amount_total) > 0.01) {
                                throw new \Exception(sprintf(__('sales/customer_payment.error_amount_balance_reconciled'), $result->amount_reconciled, $customer_invoice->name));
                            }
                            if ($customer_invoice->balance > 0) {
                                $customer_invoice->status = CustomerInvoice::OPEN;
                            }
                            $customer_invoice->save();
                        }
                    }
                }

                if(!is_null($customer_payment->customerPaymentCfdi)) {
                    //Actualiza status CFDI
                    $customer_payment->customerPaymentCfdi->status = 0;
                    $customer_payment->customerPaymentCfdi->reason_cancellation_id = $request->reason_cancellation_id;
                    $customer_payment->customerPaymentCfdi->reason_cancellation_uuid = $request->reason_cancellation_uuid;
                    $customer_payment->customerPaymentCfdi->save();

                    //Cancelacion del timbre fiscal
                    if (!empty($customer_payment->customerPaymentCfdi->cfdi_version) && !empty($customer_payment->customerPaymentCfdi->uuid)) {
                        //Valida Empresa y PAC para cancelar timbrado
                        PacHelper::validateSatCancelActions($customer_payment->company,
                            $customer_payment->customerPaymentCfdi->pac);

                        //Obtener el sellos del CFDI
                        $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES,
                                $customer_payment->company_id) . '/';
                        $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;
                        $cfdi = \CfdiUtils\Cfdi::newFromString(\Storage::get($file_xml_pac));
                        $data = Cfdi33Helper::getQuickArrayCfdi($cfdi);

                        //Arreglo temporal para actualizar Customer Invoice CFDI
                        $tmp = [
                            'cancel_date' => Helper::dateTimeToSql(\Date::now()),
                            'cancel_response' => '',
                            'cancel_state' => '',
                            'rfcR' => $data['cfdi33']->Receptor['Rfc'] ?? $customer_payment->customer->taxid,
                            'uuid' => $customer_payment->customerPaymentCfdi->uuid,
                            'total' => Helper::numberFormat($customer_payment->amount,
                                $customer_payment->currency->decimal_place, false),
                            'cfdi_type' => $customer_payment->documentType->cfdiType->code,
                            'cfdi_version' => 'cfdi3.3',
                            'fe' => substr($data['cfdi33']->complemento->timbreFiscalDigital['SelloCFD'], -8),
                            //Los últimos 8 caracteres del Atributo Sello del CFDI
                            'file_xml_pac' => $file_xml_pac,
                            'reason_cancellation_code' => $customer_payment->customerPaymentCfdi->reasonCancellation->code ?? '',
                            'reason_cancellation_uuid' => $customer_payment->customerPaymentCfdi->reason_cancellation_uuid ?? '',
                        ];

                        //Cancelar Timbrado de XML
                        $class_pac = $customer_payment->customerPaymentCfdi->pac->code . 'Cancel';
                        $tmp = PacHelper::$class_pac($tmp, $customer_payment->company,
                            $customer_payment->customerPaymentCfdi->pac);

                        //Guardar registros de CFDI
                        $customer_payment->customerPaymentCfdi->fill(array_only($tmp, [
                            'cancel_date',
                            'cancel_response',
                            'cancel_state',
                        ]));
                        $customer_payment->customerPaymentCfdi->save();

                        $cfdi_download = CfdiDownload::where('uuid', $customer_payment->customerPaymentCfdi->uuid)->where('type', 2)->first();
                        if(!empty($cfdi_download)){
                            $cfdi_download->status = 'Cancelado';
                            $cfdi_download->save();
                        }

                        //Disminuye folios
                        BaseHelper::decrementFolios();

                    }
                }
            }
            \DB::connection('tenant')->commit();

            //Mensaje
            flash(__('general.text_form_success_cancel'))->success();


        } catch (\Exception $e) {
            \DB::connection('tenant')->rollback();
            \Log::error('(' . $customer_payment->company->taxid . ') ' . $e->getMessage());
            flash($e->getMessage())->error();
            return redirect('/sales/customer-payments');
        }

        //Almacenamiento dropbox
        if ((int)$customer_payment->status == CustomerPayment::CANCEL) {
            self::dropboxBackup($customer_payment,false,true);
        }

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Validacion de formulario
     *
     * @param Request $request
     * @throws ValidationException
     */
    public function validation(Request $request)
    {
        $this->validate($request, [
            'date' => 'required|date|date_format:"'.setting('datetime_format', 'd-m-Y H:i:s').'"',
            'date_payment' => 'required|date|date_format:"'.setting('datetime_format', 'd-m-Y H:i:s').'"',
            'customer_id' => 'required|integer',
            'payment_way_id' => 'required|integer',
            'branch_office_id' => 'required|integer',
            'currency_id' => 'required|integer',
            'currency_value' => 'required|numeric|min:0.1',
            'amount' => 'required|numeric|min:0.1',
            'certificado_pago' => 'nullable|required_with:tipo_cadena_pago',
            'cadena_pago' => 'nullable|required_with:tipo_cadena_pago',
            'sello_pago' => 'nullable|required_with:tipo_cadena_pago',
            'cfdi_relation_id' => 'nullable|integer|required_with:item_relation',
            'item_reconciled.*.currency_value' => 'nullable|numeric|min:0.00001',
            'item_reconciled.*.amount_reconciled' => 'nullable|numeric|min:0',
            'item_relation.*.relation_id' => 'required',
            'item_manual_reconciled.*.uuid_related' => 'required',
            'item_manual_reconciled.*.currency_code_related' => 'required',
            'item_manual_reconciled.*.currency_value' => 'nullable|numeric|min:0.00001',
            'item_manual_reconciled.*.payment_method_code_related' => 'required',
            'item_manual_reconciled.*.amount_total' => 'required|numeric|min:0.00001',
            'item_manual_reconciled.*.number_of_payment' => 'required|numeric|min:1',
            'item_manual_reconciled.*.last_balance' => 'required|numeric|min:0.00001',
            'item_manual_reconciled.*.amount_reconciled' => 'required|numeric|min:0.00001',
            'item_manual_reconciled.*.current_balance' => 'required|numeric|min:0',

        ], [
            'date.required' => __('sales/customer_payment.error_date'),
            'date.date' => __('sales/customer_payment.error_date_format'),
            'date.date_format' => __('sales/customer_payment.error_date_format'),
            'date_payment.required' => __('sales/customer_payment.error_date_payment'),
            'date_payment.date' => __('sales/customer_payment.error_date_payment_format'),
            'date_payment.date_format' => __('sales/customer_payment.error_date_payment_format'),
            'customer_id.*' => __('sales/customer_payment.error_customer_id'),
            'payment_way_id.*' => __('sales/customer_payment.error_payment_way_id'),
            'branch_office_id.*' => __('sales/customer_payment.error_branch_office_id'),
            'currency_id.*' => __('sales/customer_payment.error_currency_id'),
            'currency_value.*' => __('sales/customer_payment.error_currency_value'),
            'amount.*' => __('sales/customer_payment.error_amount'),
            'certificado_pago.*' => __('sales/customer_payment.error_certificado_pago'),
            'cadena_pago.*' => __('sales/customer_payment.error_cadena_pago'),
            'sello_pago.*' => __('sales/customer_payment.error_sello_pago'),
            'cfdi_relation_id.*' => __('sales/customer_payment.error_cfdi_relation_id'),
            'item_reconciled.*.currency_value.*' => __('sales/customer_payment.error_reconciled_currency_value'),
            'item_reconciled.*.amount_reconciled.*' => __('sales/customer_payment.error_reconciled_amount_reconciled'),
            'item_relation.*.relation_id.*' => __('sales/customer_payment.error_relation_relation_id'),
            'item_manual_reconciled.*.uuid_related.*' => __('sales/customer_payment.error_manual_reconciled_uuid_related'),
            'item_manual_reconciled.*.currency_code_related.*' => __('sales/customer_payment.error_currency_id'),
            'item_manual_reconciled.*.currency_value.*' => __('sales/customer_payment.error_reconciled_currency_value'),
            'item_manual_reconciled.*.payment_method_code_related.*' => __('sales/customer_payment.error_payment_method_id'),
            'item_manual_reconciled.*.amount_total.*' => __('sales/customer_payment.error_manual_reconciled_amount_total'),
            'item_manual_reconciled.*.number_of_payment.*' => __('sales/customer_payment.error_manual_reconciled_number_of_payment'),
            'item_manual_reconciled.*.last_balance.*' => __('sales/customer_payment.error_manual_reconciled_last_balance'),
            'item_manual_reconciled.*.amount_reconciled.*' => __('sales/customer_payment.error_manual_reconciled_amount_reconciled'),
            'item_manual_reconciled.*.current_balance.*' => __('sales/customer_payment.error_manual_reconciled_current_balance'),
        ]);
        //Validaciones manuales
        $validator = \Validator::make([], []);

        $currency = Currency::findOrFail($request->currency_id);
        //Valida que la moneda de la cuenta bancaria sea igual a la de pago
        if(!empty($request->company_bank_account_id)) {
            $company_bank_account = CompanyBankAccount::findOrFail($request->company_bank_account_id);
            if ($company_bank_account->currency->code != $currency->code) {
                $validator->after(function ($validator) {
                    $validator->errors()->add('company_bank_account_id', __('sales/customer_payment.error_currency_id_company_bank_account_id'));
                });
            }
            //Valida que el patron de la cuenta beneficiaria coincida con el patron de la forma de pago solo para CFDI
            if(!empty($request->cfdi)){
                $payment_way = PaymentWay::findOrFail($request->payment_way_id);
                if(!empty($payment_way->patron_cuenta_beneficiaria)){
                    if(!preg_match('/^('.$payment_way->patron_cuenta_beneficiaria.')$/',$company_bank_account->account_number)){
                        $validator->after(function ($validator) {
                            $validator->errors()->add('company_bank_account_id',
                                __('sales/customer_payment.error_pattern_account_company_bank_account_id'));
                        });
                    }
                }
            }
        }
        //Valida que la moneda de la cuenta bancaria sea igual a la de pago
        if(!empty($request->customer_bank_account_id)) {
            $customer_bank_account = CustomerBankAccount::findOrFail($request->customer_bank_account_id);
            if ($customer_bank_account->currency->code != $currency->code) {
                $validator->after(function ($validator) {
                    $validator->errors()->add('customer_bank_account_id', __('sales/customer_payment.error_currency_id_customer_bank_account_id'));
                });
            }
            //Valida que el patron de la cuenta beneficiaria coincida con el patron de la forma de pago solo para CFDI
            if(!empty($request->cfdi)){
                $payment_way = PaymentWay::findOrFail($request->payment_way_id);
                if(!empty($payment_way->patron_cuenta_ordenante)){
                    if(!preg_match('/^('.$payment_way->patron_cuenta_ordenante.')$/',$customer_bank_account->account_number)){
                        $validator->after(function ($validator) {
                            $validator->errors()->add('customer_bank_account_id',
                                __('sales/customer_payment.error_pattern_account_customer_bank_account_id'));
                        });
                    }
                }
            }
        }

        //Valida que el monto total de las lineas no supere al pago
        $amount = (double)$request->amount;
        $amount_reconciled = 0;
        if (!empty($request->item_reconciled)) {
            foreach ($request->item_reconciled as $key => $item_reconciled) {
                if (!empty($item_reconciled['amount_reconciled'])) {
                    $item_reconciled_amount_reconciled = round((double)$item_reconciled['amount_reconciled'],2);
                    $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                    $amount_reconciled += $item_reconciled_amount_reconciled;

                    //Valida que el monto conciliado no supere el saldo de la factura
                    $currency = Currency::findOrFail($request->currency_id);
                    $customer_invoice = CustomerInvoice::findOrFail($item_reconciled['reconciled_id']);
                    $tmp = round(Helper::invertBalanceCurrency($currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);
                    if(($tmp - $customer_invoice->balance) > 0.01){
                        $validator->after(function ($validator) use($key) {
                            $validator->errors()->add('item_reconciled.'.$key.'.amount_reconciled', __('sales/customer_payment.error_reconciled_amount_reconciled_customer_invoice_balance'));
                        });
                    }
                }
            }
        }

        //Valida que el monto conciliado no supere el monto del pago
        if(($amount_reconciled  - $amount) > 0.01){
            $validator->after(function ($validator) use($amount_reconciled, $amount) {
                $validator->errors()->add('amount', sprintf(__('sales/customer_payment.error_amount_amount_reconciled'),$amount_reconciled, $amount));
            });
        }

        //Validaciones manuales
        if(setting('cfdi_version') == 'cfdi40'){
            if (empty($request->tax_regimen_customer_id)) {
                $validator->after(function ($validator) {
                    $validator->errors()->add('tax_regimen_customer_id', __('sales/customer_payment.error_tax_regimen_customer_id'));
                });
            }
            $customer = Customer::find($request->customer_id);
            if (empty($customer->postcode) && !in_array($customer->taxid, ['XAXX010101000', 'XEXX010101000'])) {
                $validator->after(function ($validator) {
                    $validator->errors()->add('customer_id', __('sales/customer_payment.error_postcode_customer'));
                });
            }
        }

        if ($validator->fails()) {
            throw new ValidationException($validator);
        }

    }

    /**
     * Crear XML y enviar a timbrar CFDI 3.3
     *
     * @param CustomerPayment $customer_payment
     * @return array|\CfdiUtils\Elements\Cfdi33\Concepto|float|int
     * @throws \Exception
     */
    private function cfdi33(CustomerPayment $customer_payment)
    {
        $current_entity_loader = @libxml_disable_entity_loader(false);
        try {
            //Logica
            $pac = Pac::findOrFail(setting('default_pac_id')); //PAC

            //Arreglo CFDI 3.3
            $cfdi33 = [];
            if (!empty($customer_payment->serie)) {
                $cfdi33['Serie'] = $customer_payment->serie;
            }
            $cfdi33['Folio'] = $customer_payment->folio;
            $cfdi33['Fecha'] = \Date::parse($customer_payment->date)->format('Y-m-d\TH:i:s');
            //$cfdi33['Sello']
            //$cfdi33['FormaPago'] No debe existir
            $cfdi33['NoCertificado'] = $customer_payment->company->certificate_number;
            //$cfdi33['Certificado']
            //$cfdi33['CondicionesDePago'] No debe existir
            $cfdi33['SubTotal'] = Helper::numberFormat(0, 0, false);
            //$cfdi33['Descuento'] No debe existir
            $cfdi33['Moneda'] = 'XXX';
            //$cfdi33['TipoCambio'] No debe existir
            $cfdi33['Total'] = Helper::numberFormat(0, 0, false);
            $cfdi33['TipoDeComprobante'] = $customer_payment->documentType->cfdiType->code;
            //$cfdi33['MetodoPago'] No debe existir
            $cfdi33['LugarExpedicion'] = $customer_payment->branchOffice->postcode;
            if (!empty($customer_payment->confirmacion)) {
                $cfdi33['Confirmacion'] = $customer_payment->confirmacion;
            }
            //---Cfdi Relacionados
            $cfdi33_relacionados = [];
            $cfdi33_relacionado = [];
            if (!empty($customer_payment->cfdi_relation_id)) {
                $cfdi33_relacionados['TipoRelacion'] = $customer_payment->cfdiRelation->code;
                if ($customer_payment->customerPaymentRelations->isNotEmpty()) {
                    foreach ($customer_payment->customerPaymentRelations as $key => $result) {
                        $cfdi33_relacionado[$key] = [];
                        $cfdi33_relacionado[$key]['UUID'] = $result->uuid_related;
                    }
                }
            }
            //---Emisor
            $cfdi33_emisor = [];
            $cfdi33_emisor['Rfc'] = $customer_payment->company->taxid;
            $cfdi33_emisor['Nombre'] = $customer_payment->company->name;
            $cfdi33_emisor['RegimenFiscal'] = !empty($customer_payment->tax_regimen_id) ? $customer_payment->taxRegimen->code : $customer_payment->company->taxRegimen->code;
            //---Receptor
            $cfdi33_receptor = [];
            $cfdi33_receptor['Rfc'] = $customer_payment->customer->taxid;
            $cfdi33_receptor['Nombre'] = $customer_payment->customer->name;
            if ($customer_payment->customer->taxid == 'XEXX010101000') {
                $cfdi33_receptor['ResidenciaFiscal'] = $customer_payment->customer->country->code;
                $cfdi33_receptor['NumRegIdTrib'] = $customer_payment->customer->numid;
            }
            $cfdi33_receptor['UsoCFDI'] = 'P01';
            //---Conceptos
            $cfdi33_conceptos = [];
            $cfdi33_conceptos_traslados = [];
            $cfdi33_conceptos_retenciones = [];
            $key = 0;
            $cfdi33_conceptos [$key]['ClaveProdServ'] = '84111506';
            //$cfdi33_conceptos[$key]['NoIdentificacion'] No debe existir
            $cfdi33_conceptos [$key]['Cantidad'] = Helper::numberFormat(1, 0, false);
            $cfdi33_conceptos [$key]['ClaveUnidad'] = 'ACT';
            //$cfdi33_conceptos[$key]['Unidad'] No debe existir
            $cfdi33_conceptos [$key]['Descripcion'] = 'Pago';
            $cfdi33_conceptos [$key]['ValorUnitario'] = Helper::numberFormat(0, 0, false);
            $cfdi33_conceptos [$key]['Importe'] = Helper::numberFormat(0, 0, false);
            //$cfdi33_conceptos[$key]['Descuento'] No debe existir
            //['InformacionAduanera']
            //['CuentaPredial']
            //['ComplementoConcepto']
            //['Parte']

            //Impuestos por concepto
            //No debe existir

            //Impuestos
            //No debe existir

            //Genera XML
            $certificado = new \CfdiUtils\Certificado\Certificado(\Storage::path($customer_payment->company->pathFileCer()));
            $creator = new \CfdiUtils\CfdiCreator33($cfdi33, $certificado);
            $creator->setXmlResolver(PacHelper::resourcePathCfdiUtils()); //Almacenamiento local
            $comprobante = $creator->comprobante();
            $comprobante->addAttributes([
                'xsi:schemaLocation' => 'http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd',
                'xmlns:pago10' => 'http://www.sat.gob.mx/Pagos'
            ]);
            if (!empty($cfdi33_relacionados)) {
                $comprobante->addCfdiRelacionados($cfdi33_relacionados);
            }
            if (!empty($cfdi33_relacionado)) {
                foreach ($cfdi33_relacionado as $key => $result) {
                    $comprobante->addCfdiRelacionado($result);
                }
            }
            $comprobante->addEmisor($cfdi33_emisor);
            $comprobante->addReceptor($cfdi33_receptor);
            //Conceptos
            foreach ($cfdi33_conceptos as $key => $result) {
                $concepto = $comprobante->addConcepto($result);
                //Impuestos
                //No debe existir
            }
            //Impuestos
            //No debe existir

            //Complemento de pago
            $pagos10 = [];
            $pagos10['FechaPago'] = \Date::parse($customer_payment->date_payment)->format('Y-m-d\TH:i:s');
            $pagos10['FormaDePagoP'] = $customer_payment->paymentWay->code;
            $pagos10['MonedaP'] = $customer_payment->currency->code;
            if ($customer_payment->currency->code != 'MXN') {
                $pagos10['TipoCambioP'] = Helper::numberFormat($customer_payment->currency_value, 4, false);
            }
            $pagos10['Monto'] = Helper::numberFormat($customer_payment->amount,
                $customer_payment->currency->decimal_place, false);
            if (!empty($customer_payment->reference)) {
                $pagos10['NumOperacion'] = $customer_payment->reference;
            }
            if (!empty($customer_payment->customerBankAccount)) {
                if(!empty($customer_payment->customerBankAccount->bank->taxid)){
                    $pagos10['RfcEmisorCtaOrd'] = $customer_payment->customerBankAccount->bank->taxid;
                    //Solo extranjeros
                    if($customer_payment->customerBankAccount->bank->taxid == 'XEXX010101000'){
                        $pagos10['NomBancoOrdExt'] = $customer_payment->customerBankAccount->bank->name;
                    }
                }
                $pagos10['CtaOrdenante'] = $customer_payment->customerBankAccount->account_number;
            }
            if (!empty($customer_payment->companyBankAccount)) {
                if(!empty($customer_payment->companyBankAccount->bank->taxid)){
                    $pagos10['RfcEmisorCtaBen'] = $customer_payment->companyBankAccount->bank->taxid;
                }
                $pagos10['CtaBeneficiario'] = $customer_payment->companyBankAccount->account_number;
            }
            if (!empty($customer_payment->tipo_cadena_pago)) {
                $pagos10['TipoCadPago'] = $customer_payment->tipo_cadena_pago;
                $pagos10['CertPago'] = $customer_payment->certificado_pago;
                $pagos10['CadPago'] = $customer_payment->cadena_pago;
                $pagos10['SelloPago'] = $customer_payment->sello_pago;
            }
            $pagos10_doc_relacionados = [];
            if ($customer_payment->customerPaymentReconcileds->isNotEmpty()) {
                foreach ($customer_payment->customerPaymentReconcileds as $key => $result) {
                    /*$customer_invoice = $result->reconciled;
                    $tmp_id = $customer_invoice->id;
                    //Cuando se hace un pago en MXN para facturas en USD
                    $tmp = Helper::invertBalanceCurrency($customer_payment->currency,$result->amount_reconciled,$customer_invoice->currency->code,$result->currency_value);
                    $saldo_insoluto = $result->last_balance - $tmp;
                    //Numero de pagos realizados que no esten cancelados
                    $tmp_customer_payment_reconciled = CustomerPaymentReconciled::where('status','=','1')
                        ->where('reconciled_id', '=', $tmp_id)
                        ->where(function ($query) {
                            $query->WhereHas('customerPayment', function ($q) {
                                $q->whereIn('customer_payments.status',[CustomerPayment::OPEN,CustomerPayment::RECONCILED]);
                            });
                        });
                    $result->number_of_payment = $tmp_customer_payment_reconciled->count(); //Guarda el numero de parcialidad
                    $result->save();*/
                    $tcDr = 0;
                    if($customer_payment->currency->code != $result->currency_code_related){
                        $tcDr = Helper::numberFormat($customer_payment->currency_value/$result->currency_value, 6, false);
                        $amountReconciledReal = round($result->amount_reconciled / $customer_payment->currency_value/$result->currency_value, 2);
                        $amountReconciledXml = round($result->amount_reconciled / $tcDr, 2);
                        if(($amountReconciledXml - $amountReconciledReal) > 0){
                            $tcDr += 0.000001;
                        }
                    }

                    //
                    $pagos10_doc_relacionados [$key]['IdDocumento'] = $result->uuid_related;
                    $pagos10_doc_relacionados [$key]['Serie'] = $result->serie_related;
                    $pagos10_doc_relacionados [$key]['Folio'] = $result->folio_related;
                    $pagos10_doc_relacionados [$key]['MonedaDR'] = $result->currency_code_related;
                    if($customer_payment->currency->code != $result->currency_code_related) {
                        $pagos10_doc_relacionados [$key]['TipoCambioDR'] = $tcDr;
                    }
                    $pagos10_doc_relacionados [$key]['MetodoDePagoDR'] = $result->payment_method_code_related;
                    $pagos10_doc_relacionados [$key]['NumParcialidad'] = $result->number_of_payment;
                    $pagos10_doc_relacionados [$key]['ImpSaldoAnt'] = Helper::numberFormat($result->last_balance,$customer_payment->currency->decimal_place, false);
                    $pagos10_doc_relacionados [$key]['ImpPagado'] = Helper::numberFormat($result->amount_reconciled,$customer_payment->currency->decimal_place, false);
                    $pagos10_doc_relacionados [$key]['ImpSaldoInsoluto'] = Helper::numberFormat($result->current_balance,$customer_payment->currency->decimal_place, false);
                }
            }

            $pagos = new \CfdiUtils\Elements\Pagos10\Pagos();
            $pago = $pagos->addPago($pagos10);
            if(!empty($pagos10_doc_relacionados)) {
                foreach($pagos10_doc_relacionados as $key => $result) {
                    $tmp = $pago->addDoctoRelacionado($result);
                }
            }
            $comprobante->addComplemento($pagos);

            //Método de ayuda para establecer las sumas del comprobante e impuestos con base en la suma de los conceptos y la agrupación de sus impuestos
            //$creator->addSumasConceptos(null, 2);
            //Método de ayuda para generar el sello (obtener la cadena de origen y firmar con la llave privada)
            $creator->addSello('file://' . \Storage::path($customer_payment->company->pathFileKeyPassPem()), Crypt::decryptString($customer_payment->company->password_key));
            //Valida la estructura
            //$creator->validate();

            //Guarda XML
            //dd($creator->asXml());
            $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
            $file_xml = Helper::makeDirectoryCfdi($path_xml) . '/' . Str::random(40) . '.xml';
            $creator->saveXml(\Storage::path($path_xml . $file_xml));

            //Arreglo temporal para actualizar Customer Invoice CFDI
            $tmp = [
                'pac_id' => $pac->id,
                'cfdi_version' => setting('cfdi_version'),
                'uuid' => '',
                'date' => '',
                'path_xml' => $path_xml,
                'file_xml' => $file_xml,
                'file_xml_pac' => '',
                'pac' => $pac,
            ];

            //Timbrado de XML
            $class_pac = $pac->code;
            $tmp = PacHelper::$class_pac($tmp, $creator);

            return $tmp;
        } catch (\Exception $e) {
            throw $e;
        }
        @libxml_disable_entity_loader($current_entity_loader);
    }

    /**
     * Crear XML y enviar a timbrar CFDI 4.0
     *
     * @param CustomerPayment $customer_payment
     * @return array|\CfdiUtils\Elements\Cfdi33\Concepto|float|int
     * @throws \Exception
     */
    private function cfdi40(CustomerPayment $customer_payment)
    {
        $current_entity_loader = @libxml_disable_entity_loader(false);
        try {
            //Logica
            $pac = Pac::findOrFail(setting('default_pac_id')); //PAC

            //Arreglo CFDI 3.3
            $cfdi33 = [];
            if (!empty($customer_payment->serie)) {
                $cfdi33['Serie'] = $customer_payment->serie;
            }
            $cfdi33['Folio'] = $customer_payment->folio;
            $cfdi33['Fecha'] = \Date::parse($customer_payment->date)->format('Y-m-d\TH:i:s');
            //$cfdi33['Sello']
            //$cfdi33['FormaPago'] No debe existir
            $cfdi33['NoCertificado'] = $customer_payment->company->certificate_number;
            //$cfdi33['Certificado']
            //$cfdi33['CondicionesDePago'] No debe existir
            $cfdi33['SubTotal'] = Helper::numberFormat(0, 0, false);
            //$cfdi33['Descuento'] No debe existir
            $cfdi33['Moneda'] = 'XXX';
            //$cfdi33['TipoCambio'] No debe existir
            $cfdi33['Total'] = Helper::numberFormat(0, 0, false);
            $cfdi33['TipoDeComprobante'] = $customer_payment->documentType->cfdiType->code;
            $cfdi33['Exportacion'] = '01';
            //$cfdi33['MetodoPago'] No debe existir
            $cfdi33['LugarExpedicion'] = $customer_payment->branchOffice->postcode;
            if (!empty($customer_payment->confirmacion)) {
                $cfdi33['Confirmacion'] = $customer_payment->confirmacion;
            }
            //---Informacion global
            //---Cfdi Relacionados
            $cfdi33_relacionados = [];
            $cfdi33_relacionado = [];
            if (!empty($customer_payment->cfdi_relation_id)) {
                $cfdi33_relacionados['TipoRelacion'] = $customer_payment->cfdiRelation->code;
                if ($customer_payment->customerPaymentRelations->isNotEmpty()) {
                    foreach ($customer_payment->customerPaymentRelations as $key => $result) {
                        $cfdi33_relacionado[$key] = [];
                        $cfdi33_relacionado[$key]['UUID'] = $result->uuid_related;
                    }
                }
            }
            //---Emisor
            $cfdi33_emisor = [];
            $cfdi33_emisor['Rfc'] = $customer_payment->company->taxid;
            $cfdi33_emisor['Nombre'] = $customer_payment->company->name;
            $cfdi33_emisor['RegimenFiscal'] = !empty($customer_payment->tax_regimen_id) ? $customer_payment->taxRegimen->code : $customer_payment->company->taxRegimen->code;
            $cfdi33_emisor['FacAtrAdquirente'] = null;
            //---Receptor
            $cfdi33_receptor = [];
            $cfdi33_receptor['Rfc'] = $customer_payment->customer->taxid;
            $cfdi33_receptor['Nombre'] = trim($customer_payment->customer->name);
            if (!in_array($customer_payment->customer->taxid, ['XEXX010101000'])) {
                $cfdi33_receptor['DomicilioFiscalReceptor'] = $customer_payment->customer->postcode;
            }else{
                $cfdi33_receptor['DomicilioFiscalReceptor'] = $customer_payment->branchOffice->postcode;
            }
            if ($customer_payment->customer->taxid == 'XEXX010101000') {
                $cfdi33_receptor['ResidenciaFiscal'] = $customer_payment->customer->country->code;
                $cfdi33_receptor['NumRegIdTrib'] = $customer_payment->customer->numid;
            }
            $cfdi33_receptor['RegimenFiscalReceptor'] = !empty($customer_payment->tax_regimen_customer_id) ? $customer_payment->taxRegimenCustomer->code : $customer_payment->customer->taxRegimen->code;
            $cfdi33_receptor['UsoCFDI'] = 'CP01';
            //---Conceptos
            $cfdi33_conceptos = [];
            $cfdi33_conceptos_traslados = [];
            $cfdi33_conceptos_retenciones = [];
            $key = 0;
            $cfdi33_conceptos [$key]['ClaveProdServ'] = '84111506';
            //$cfdi33_conceptos[$key]['NoIdentificacion'] No debe existir
            $cfdi33_conceptos [$key]['Cantidad'] = Helper::numberFormat(1, 0, false);
            $cfdi33_conceptos [$key]['ClaveUnidad'] = 'ACT';
            //$cfdi33_conceptos[$key]['Unidad'] No debe existir
            $cfdi33_conceptos [$key]['Descripcion'] = 'Pago';
            $cfdi33_conceptos [$key]['ValorUnitario'] = Helper::numberFormat(0, 0, false);
            $cfdi33_conceptos [$key]['Importe'] = Helper::numberFormat(0, 0, false);
            //$cfdi33_conceptos[$key]['Descuento'] No debe existir
            //['InformacionAduanera']
            //['CuentaPredial']
            //['ComplementoConcepto']
            //['Parte']

            //Impuestos por concepto
            //No debe existir

            //Impuestos
            //No debe existir

            $cfdi33_conceptos [$key]['ObjetoImp'] = '01';

            //Genera XML
            $certificado = new \CfdiUtils\Certificado\Certificado(\Storage::path($customer_payment->company->pathFileCer()));
            $creator = new \CfdiUtils\CfdiCreator40($cfdi33, $certificado);
            $creator->setXmlResolver(PacHelper::resourcePathCfdiUtils()); //Almacenamiento local
            $comprobante = $creator->comprobante();
            $comprobante->addAttributes([
                'xsi:schemaLocation' => 'http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd',
                'xmlns:pago20' => 'http://www.sat.gob.mx/Pagos20'
            ]);
            if (!empty($cfdi33_relacionados)) {
                $relacionados = $comprobante->addCfdiRelacionados($cfdi33_relacionados);
                if (!empty($cfdi33_relacionado)) {
                    foreach ($cfdi33_relacionado as $key => $result) {
                        $relacionados->addCfdiRelacionado($result);
                    }
                }
            }
            $comprobante->addEmisor($cfdi33_emisor);
            $comprobante->addReceptor($cfdi33_receptor);
            //Conceptos
            foreach ($cfdi33_conceptos as $key => $result) {
                $concepto = $comprobante->addConcepto($result);
                //Impuestos
                //No debe existir
            }
            //Impuestos
            //No debe existir

            //Complemento de pago
            $pagos20 = [];
            $pagos20['FechaPago'] = \Date::parse($customer_payment->date_payment)->format('Y-m-d\TH:i:s');
            $pagos20['FormaDePagoP'] = $customer_payment->paymentWay->code;
            $pagos20['MonedaP'] = $customer_payment->currency->code;
            $tipo_cambio_p = 1;
            if ($customer_payment->currency->code != 'MXN') {
                $pagos20['TipoCambioP'] = Helper::numberFormat($customer_payment->currency_value, 4, false);
                $tipo_cambio_p = Helper::numberFormat($customer_payment->currency_value, 4, false);
            }else{
                $pagos20['TipoCambioP'] = Helper::numberFormat(1, 0, false);
            }
            $pagos20['Monto'] = Helper::numberFormat($customer_payment->amount,$customer_payment->currency->decimal_place, false);
            if (!empty($customer_payment->reference)) {
                $pagos20['NumOperacion'] = $customer_payment->reference;
            }
            if (!empty($customer_payment->customerBankAccount)) {
                if(!empty($customer_payment->customerBankAccount->bank->taxid)){
                    $pagos20['RfcEmisorCtaOrd'] = $customer_payment->customerBankAccount->bank->taxid;
                    //Solo extranjeros
                    if($customer_payment->customerBankAccount->bank->taxid == 'XEXX010101000'){
                        $pagos20['NomBancoOrdExt'] = $customer_payment->customerBankAccount->bank->name;
                    }
                }
                $pagos20['CtaOrdenante'] = $customer_payment->customerBankAccount->account_number;
            }
            if (!empty($customer_payment->companyBankAccount)) {
                if(!empty($customer_payment->companyBankAccount->bank->taxid)){
                    $pagos20['RfcEmisorCtaBen'] = $customer_payment->companyBankAccount->bank->taxid;
                }
                $pagos20['CtaBeneficiario'] = $customer_payment->companyBankAccount->account_number;
            }
            if (!empty($customer_payment->tipo_cadena_pago)) {
                $pagos20['TipoCadPago'] = $customer_payment->tipo_cadena_pago;
                $pagos20['CertPago'] = $customer_payment->certificado_pago;
                $pagos20['CadPago'] = $customer_payment->cadena_pago;
                $pagos20['SelloPago'] = $customer_payment->sello_pago;
            }

            $impuestos_retenciones_p = [];
            $impuestos_traslados_p = [];
            $pagos20_doc_relacionados = [];
            $same_currency = true;
            if ($customer_payment->customerPaymentReconcileds->isNotEmpty()) {
                foreach ($customer_payment->customerPaymentReconcileds as $key => $result) {
                    $equivalencia_dr = 1;
                    if($customer_payment->currency->code != $result->currency_code_related) {
                        $equivalencia_dr = Helper::numberFormat($result->amount_reconciled / round($result->amount_reconciled * $result->currency_value, 2), 10, false);
                    }

                    if($customer_payment->currency->code != $result->currency_code_related) {
                        $same_currency = false;
                        $amountReconciledReal = round($result->amount_reconciled / $customer_payment->currency_value/$result->currency_value, 2);
                        $amountReconciledXml = round($result->amount_reconciled / $equivalencia_dr, 2);
                        if(($amountReconciledXml - $amountReconciledReal) > 0){
                            //$equivalencia_dr += 0.000001;
                        }
                    }

                    $tax_object = '01';
                    $impuestos_retenciones_dr = [];
                    $impuestos_traslados_dr = [];
                    $base_amount = $result->amount_reconciled;
                    if(!empty($result->reconciled_id)){
                        $customer_invoice = $result->reconciled;
                        if($customer_invoice->customerInvoiceTaxes->isNotEmpty()){
                            foreach($customer_invoice->customerInvoiceTaxes as $reconciled){
                                $base_amount = round($result->amount_reconciled / $customer_invoice->amount_total * $reconciled->amount_base, 2);
                                $tax = $reconciled->tax;
                                $tax_amount = 0;
                                if ($tax->factor == 'Tasa') {
                                    $tax_amount = $base_amount * $tax->rate / 100;
                                } elseif ($tax->factor == 'Cuota') {
                                    $tax_amount = $tax->rate;
                                }
                                $tax_amount = round($tax_amount, 2);

                                $rate = $reconciled->tax->rate;
                                if ($reconciled->tax->factor == 'Tasa') {
                                    $rate /= 100;
                                }
                                if($reconciled->tax->local_taxes) {
                                    continue;
                                }
                                if($reconciled->tax->type == 'R') { //Retenciones
                                    $taxTmp = abs($tax_amount / $equivalencia_dr);
                                    if(isset($impuestos_retenciones_p[$reconciled->tax->code])){
                                        $impuestos_retenciones_p[$reconciled->tax->code]['ImporteP'] += $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($taxTmp, 6, false) : Helper::roundDown($taxTmp, 2, false);
                                    }else{
                                        $impuestos_retenciones_p[$reconciled->tax->code] = [
                                            'ImpuestoP' => $reconciled->tax->code,
                                            'ImporteP' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($taxTmp, 6, false) : Helper::roundDown($taxTmp, 2, false),
                                        ];
                                    }
                                    $impuestos_retenciones_dr[] = array(
                                        'BaseDR' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($base_amount, 2, false) : Helper::roundDown($base_amount, 2, false),
                                        'ImpuestoDR' => $reconciled->tax->code,
                                        'TipoFactorDR' => $reconciled->tax->factor,
                                        'TasaOCuotaDR' => Helper::numberFormat(abs($rate), 6, false),
                                        'ImporteDR' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat(abs($tax_amount), 2, false) : Helper::roundDown(abs($tax_amount), 2, false),
                                    );
                                } else {
                                    $baseTmp = $base_amount / $equivalencia_dr;
                                    $taxTmp = $tax_amount / $equivalencia_dr;
                                    $keyTax = $reconciled->tax->code . abs($rate) . $reconciled->tax->factor;
                                    if(isset($impuestos_traslados_p[$keyTax])){
                                        $impuestos_traslados_p[$keyTax]['BaseP'] += $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($baseTmp, 6, false) : Helper::roundDown($baseTmp, 2, false);
                                        $impuestos_traslados_p[$keyTax]['ImporteP'] += $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($taxTmp, 6, false) : Helper::roundDown($taxTmp, 2, false);
                                    }else{
                                        $impuestos_traslados_p[$keyTax] = array(
                                            'BaseP' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($baseTmp, 6, false) : Helper::roundDown($baseTmp, 2, false),
                                            'ImpuestoP' => $reconciled->tax->code,
                                            'TipoFactorP' => $reconciled->tax->factor,
                                            'TasaOCuotaP' => Helper::numberFormat(abs($rate), 6, false),
                                            'ImporteP' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($taxTmp, 6, false) : Helper::roundDown($taxTmp, 2, false),
                                        );
                                    }
                                    $impuestos_traslados_dr[] = array(
                                        'BaseDR' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($base_amount, 2, false) : Helper::roundDown($base_amount, 2, false),
                                        'ImpuestoDR' => $reconciled->tax->code,
                                        'TipoFactorDR' => $reconciled->tax->factor,
                                        'TasaOCuotaDR' => $reconciled->tax->factor == 'Exento' ? null : Helper::numberFormat(abs($rate), 6,false),
                                        'ImporteDR' => $reconciled->tax->factor == 'Exento' ? null : ($customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat(abs($tax_amount), 2, false) : Helper::roundDown(abs($tax_amount), 2, false)),
                                    );
                                }
                            }
                        }
                    }else{
                        $taxes = [];
                        if(!empty($result->tax_id_1)){
                            $taxes[] = (object)[
                                'tax_id' => $result->tax_id_1,
                                'amount_base' => $result->amount_base_1,
                            ];
                        }
                        if(!empty($result->tax_id_2)){
                            $taxes[] = (object)[
                                'tax_id' => $result->tax_id_2,
                                'amount_base' => $result->amount_base_2,
                            ];
                        }
                        if(!empty($result->tax_id_3)){
                            $taxes[] = (object)[
                                'tax_id' => $result->tax_id_3,
                                'amount_base' => $result->amount_base_3,
                            ];
                        }
                        if(!empty($result->tax_id_4)){
                            $taxes[] = (object)[
                                'tax_id' => $result->tax_id_4,
                                'amount_base' => $result->amount_base_4,
                            ];
                        }
                        if(!empty($result->tax_id_5)){
                            $taxes[] = (object)[
                                'tax_id' => $result->tax_id_5,
                                'amount_base' => $result->amount_base_5,
                            ];
                        }

                        if(!empty($taxes) && $result->amount_total > 0 ){
                            foreach($taxes as $taxTmp){
                                $base_amount = round($result->amount_reconciled / $result->amount_total * $taxTmp->amount_base, 2);
                                $tax = Tax::find($taxTmp->tax_id);
                                $tax_amount = 0;
                                if ($tax->factor == 'Tasa') {
                                    $tax_amount = $base_amount * $tax->rate / 100;
                                } elseif ($tax->factor == 'Cuota') {
                                    $tax_amount = $tax->rate;
                                }
                                $tax_amount = round($tax_amount, 2);

                                $rate = $tax->rate;
                                if ($tax->factor == 'Tasa') {
                                    $rate /= 100;
                                }
                                if($tax->local_taxes) {
                                    continue;
                                }
                                if($tax->type == 'R') { //Retenciones
                                    $taxTmp = abs($tax_amount / $equivalencia_dr);
                                    if(isset($impuestos_retenciones_p[$tax->code])){
                                        $impuestos_retenciones_p[$tax->code]['ImporteP'] += $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($taxTmp, 6, false) : Helper::roundDown($taxTmp, 2, false);
                                    }else{
                                        $impuestos_retenciones_p[$tax->code] = [
                                            'ImpuestoP' => $tax->code,
                                            'ImporteP' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($taxTmp, 6, false) : Helper::roundDown($taxTmp, 2, false),
                                        ];
                                    }
                                    $impuestos_retenciones_dr[] = array(
                                        'BaseDR' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($base_amount, 2, false) : Helper::roundDown($base_amount, 2, false),
                                        'ImpuestoDR' => $tax->code,
                                        'TipoFactorDR' => $tax->factor,
                                        'TasaOCuotaDR' => Helper::numberFormat(abs($rate), 6, false),
                                        'ImporteDR' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat(abs($tax_amount), 2, false) : Helper::roundDown(abs($tax_amount), 2, false),
                                    );
                                } else {
                                    $baseTmp = $base_amount / $equivalencia_dr;
                                    $taxTmp = $tax_amount / $equivalencia_dr;
                                    $keyTax = $tax->code . abs($rate) . $tax->factor;
                                    if(isset($impuestos_traslados_p[$keyTax])){
                                        $impuestos_traslados_p[$keyTax]['BaseP'] += $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($baseTmp, 6, false) : Helper::roundDown($baseTmp, 2, false);
                                        $impuestos_traslados_p[$keyTax]['ImporteP'] += $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($taxTmp, 6, false) : Helper::roundDown($taxTmp, 2, false);
                                    }else{
                                        $impuestos_traslados_p[$keyTax] = array(
                                            'BaseP' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($baseTmp, 6, false) : Helper::roundDown($baseTmp, 2, false),
                                            'ImpuestoP' => $tax->code,
                                            'TipoFactorP' => $tax->factor,
                                            'TasaOCuotaP' => Helper::numberFormat(abs($rate), 6, false),
                                            'ImporteP' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($taxTmp, 6, false) : Helper::roundDown($taxTmp, 2, false),
                                        );
                                    }
                                    $impuestos_traslados_dr[] = array(
                                        'BaseDR' => $customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat($base_amount, 2, false) : Helper::roundDown($base_amount, 2, false),
                                        'ImpuestoDR' => $tax->code,
                                        'TipoFactorDR' => $tax->factor,
                                        'TasaOCuotaDR' => $tax->factor == 'Exento' ? null : Helper::numberFormat(abs($rate), 6,false),
                                        'ImporteDR' => $tax->factor == 'Exento' ? null : ($customer_payment->currency->code != $result->currency_code_related ? Helper::numberFormat(abs($tax_amount), 2, false) : Helper::roundDown(abs($tax_amount), 2, false)),
                                    );
                                }
                            }
                        }
                    }

                    /*$customer_invoice = $result->reconciled;
                    $tmp_id = $customer_invoice->id;
                    //Cuando se hace un pago en MXN para facturas en USD
                    $tmp = Helper::invertBalanceCurrency($customer_payment->currency,$result->amount_reconciled,$customer_invoice->currency->code,$result->currency_value);
                    $saldo_insoluto = $result->last_balance - $tmp;
                    //Numero de pagos realizados que no esten cancelados
                    $tmp_customer_payment_reconciled = CustomerPaymentReconciled::where('status','=','1')
                        ->where('reconciled_id', '=', $tmp_id)
                        ->where(function ($query) {
                            $query->WhereHas('customerPayment', function ($q) {
                                $q->whereIn('customer_payments.status',[CustomerPayment::OPEN,CustomerPayment::RECONCILED]);
                            });
                        });
                    $result->number_of_payment = $tmp_customer_payment_reconciled->count(); //Guarda el numero de parcialidad
                    $result->save();*/
                    //
                    $pagos20_doc_relacionados [$key]['IdDocumento'] = $result->uuid_related;
                    $pagos20_doc_relacionados [$key]['Serie'] = $result->serie_related;
                    $pagos20_doc_relacionados [$key]['Folio'] = $result->folio_related;
                    $pagos20_doc_relacionados [$key]['MonedaDR'] = $result->currency_code_related;
                    $pagos20_doc_relacionados [$key]['EquivalenciaDR'] = $equivalencia_dr;
                    //$pagos20_doc_relacionados [$key]['MetodoDePagoDR'] = $result->payment_method_code_related;
                    $pagos20_doc_relacionados [$key]['NumParcialidad'] = $result->number_of_payment;
                    $pagos20_doc_relacionados [$key]['ImpSaldoAnt'] = Helper::numberFormat($result->last_balance,$customer_payment->currency->decimal_place, false);
                    $pagos20_doc_relacionados [$key]['ImpPagado'] = Helper::numberFormat($result->amount_reconciled,$customer_payment->currency->decimal_place, false);
                    $pagos20_doc_relacionados [$key]['ImpSaldoInsoluto'] = Helper::numberFormat($result->current_balance,$customer_payment->currency->decimal_place, false);
                    $pagos20_doc_relacionados [$key]['ObjetoImpDR'] = (empty($impuestos_traslados_dr) && empty($impuestos_retenciones_dr)) ? '01' : '02';
                    $pagos20_doc_relacionados [$key]['ImpuestosDR'] = [
                        'TrasladosDR' => $impuestos_traslados_dr,
                        'RetencionesDR' => $impuestos_retenciones_dr
                    ];
                }
            }

            $total_retenciones_iva = null;
            $total_retenciones_isr = null;
            $total_retenciones_ieps = null;
            $total_traslados_base_iva_16 = null;
            $total_traslados_iva_16 = null;
            $total_traslados_base_iva_8 = null;
            $total_traslados_iva_8 = null;
            $total_traslados_base_iva_0 = null;
            $total_traslados_iva_0 = null;
            $total_traslados_base_iva_excento = null;

            $impuestos_traslados_p = array_map(function ($element) use(&$total_traslados_base_iva_16, &$total_traslados_iva_16, &$total_traslados_base_iva_8, &$total_traslados_iva_8, &$total_traslados_base_iva_0, &$total_traslados_iva_0, &$total_traslados_base_iva_excento) {
                $baseTmp = Helper::roundDown($element['BaseP'], 2);
                $taxTmp = Helper::roundDown($element['ImporteP'], 2);
                if($element['ImpuestoP'] == '002' && $element['TipoFactorP'] == 'Tasa' && $element['TasaOCuotaP'] == 0.16){
                    $total_traslados_base_iva_16 += $baseTmp;
                    $total_traslados_iva_16 += $taxTmp;
                }
                if($element['ImpuestoP'] == '002' && $element['TipoFactorP'] == 'Tasa' && $element['TasaOCuotaP'] == 0.08){
                    $total_traslados_base_iva_8 += $baseTmp;
                    $total_traslados_iva_8 += $taxTmp;
                }
                if($element['ImpuestoP'] == '002' && $element['TipoFactorP'] == 'Tasa' && $element['TasaOCuotaP'] == 0){
                    $total_traslados_base_iva_0 += $baseTmp;
                    $total_traslados_iva_0 += $taxTmp;
                }
                if($element['ImpuestoP'] == '002' && $element['TipoFactorP'] == 'Exento' && $element['TasaOCuotaP'] == 0){
                    $total_traslados_base_iva_excento += $baseTmp;
                }

                $element['BaseP'] = Helper::numberFormat($baseTmp, 2, false);
                $element['ImporteP'] = Helper::numberFormat($taxTmp, 2, false);
                if($element['ImpuestoP'] == '002' && $element['TipoFactorP'] == 'Exento' && $element['TasaOCuotaP'] == 0){
                    $element['TasaOCuotaP'] = null;
                    $element['ImporteP'] = null;
                }
                return $element;
            },$impuestos_traslados_p);
            $impuestos_retenciones_p = array_map(function ($element)  use(&$total_retenciones_isr, &$total_retenciones_iva, &$total_retenciones_ieps) {
                $taxTmp = Helper::roundDown($element['ImporteP'], 2);
                if($element['ImpuestoP'] == '001'){
                    $total_retenciones_isr += $taxTmp;
                }
                if($element['ImpuestoP'] == '002'){
                    $total_retenciones_iva += $taxTmp;
                }
                if($element['ImpuestoP'] == '003'){
                    $total_retenciones_ieps += $taxTmp;
                }
                $element['ImporteP'] = Helper::numberFormat($taxTmp, 2, false);
                return $element;
            },$impuestos_retenciones_p);

            $totales20 = [];
            $totales20['TotalRetencionesIVA'] = !is_null($total_retenciones_iva) ? Helper::numberFormat($total_retenciones_iva * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalRetencionesISR'] = !is_null($total_retenciones_isr) ? Helper::numberFormat($total_retenciones_isr * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalRetencionesIEPS'] = !is_null($total_retenciones_ieps) ? Helper::numberFormat($total_retenciones_ieps * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalTrasladosBaseIVA16'] = !is_null($total_traslados_base_iva_16) ? Helper::numberFormat($total_traslados_base_iva_16 * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalTrasladosImpuestoIVA16'] = !is_null($total_traslados_iva_16) ? Helper::numberFormat($total_traslados_iva_16 * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalTrasladosBaseIVA8'] = !is_null($total_traslados_base_iva_8) ? Helper::numberFormat($total_traslados_base_iva_8 * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalTrasladosImpuestoIVA8'] = !is_null($total_traslados_iva_8) ? Helper::numberFormat($total_traslados_iva_8 * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalTrasladosBaseIVA0'] = !is_null($total_traslados_base_iva_0) ? Helper::numberFormat($total_traslados_base_iva_0 * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalTrasladosImpuestoIVA0'] = !is_null($total_traslados_iva_0) ? Helper::numberFormat($total_traslados_iva_0 * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['TotalTrasladosBaseIVAExento'] = !is_null($total_traslados_base_iva_excento) ? Helper::numberFormat($total_traslados_base_iva_excento * $tipo_cambio_p,$customer_payment->currency->decimal_place, false) : null;
            $totales20['MontoTotalPagos'] = Helper::numberFormat($customer_payment->amount * $tipo_cambio_p,$customer_payment->currency->decimal_place, false);

            $pagos = new \CfdiUtils\Elements\Pagos20\Pagos();
            $totales = $pagos->addTotales($totales20);
            $pago = $pagos->addPago($pagos20);
            if(!empty($pagos20_doc_relacionados)) {
                foreach($pagos20_doc_relacionados as $key => $result) {
                    $traslados_dr = $result['ImpuestosDR']['TrasladosDR'];
                    $retenciones_dr = $result['ImpuestosDR']['RetencionesDR'];
                    unset($result['ImpuestosDR']);
                    $tmp_rel = $pago->addDoctoRelacionado($result);
                    $tmp_impuestos_dr = null;
                    if(!empty($traslados_dr) || !empty($retenciones_dr)){
                        $tmp_impuestos_dr = $tmp_rel->addImpuestosDR();
                    }
                    if(!empty($traslados_dr)){
                        $tmp_traslados_dr = $tmp_impuestos_dr->addTrasladosDR();
                        foreach($traslados_dr as $result2){
                            $tmp_traslados_dr->addTrasladoDR($result2);
                        }
                    }
                    if(!empty($retenciones_dr)){
                        $tmp_retenciones_dr = $tmp_impuestos_dr->addRetencionesDR();
                        foreach($retenciones_dr as $result2){
                            $tmp_retenciones_dr->addRetencionDR($result2);
                        }
                    }
                }
            }
            $tmp_impuestos_p = null;
            if(!empty($impuestos_traslados_p) || !empty($impuestos_retenciones_p)){
                $tmp_impuestos_p = $pago->addImpuestosP();
            }
            if(!empty($impuestos_traslados_p)){
                $tmp_traslados_p = $tmp_impuestos_p->addTrasladosP();
                foreach($impuestos_traslados_p as $result){
                    $tmp_traslados_p->addTrasladoP($result);
                }
            }
            if(!empty($impuestos_retenciones_p)){
                $tmp_retenciones_p = $tmp_impuestos_p->addRetencionesP();
                foreach($impuestos_retenciones_p as $result){
                    $tmp_retenciones_p->addRetencionP($result);
                }
            }
            $comprobante->addComplemento($pagos);

            //Método de ayuda para establecer las sumas del comprobante e impuestos con base en la suma de los conceptos y la agrupación de sus impuestos
            //$creator->addSumasConceptos(null, 2);
            //Método de ayuda para generar el sello (obtener la cadena de origen y firmar con la llave privada)
            $creator->addSello('file://' . \Storage::path($customer_payment->company->pathFileKeyPassPem()), Crypt::decryptString($customer_payment->company->password_key));
            //Valida la estructura
            //$creator->validate();

            //Guarda XML
            //dd($creator->asXml());
            $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
            $file_xml = Helper::makeDirectoryCfdi($path_xml) . '/' . Str::random(40) . '.xml';
            $creator->saveXml(\Storage::path($path_xml . $file_xml));

            //Arreglo temporal para actualizar Customer Invoice CFDI
            $tmp = [
                'pac_id' => $pac->id,
                'cfdi_version' => setting('cfdi_version'),
                'uuid' => '',
                'date' => '',
                'path_xml' => $path_xml,
                'file_xml' => $file_xml,
                'file_xml_pac' => '',
                'pac' => $pac,
            ];

            //Timbrado de XML
            $class_pac = $pac->code;
            $tmp = PacHelper::$class_pac($tmp, $creator);

            return $tmp;
        } catch (\Exception $e) {
            throw $e;
        }
        @libxml_disable_entity_loader($current_entity_loader);
    }

    /**
     * Descarga de archivo XML
     *
     * @param Request $request
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function downloadXml(Request $request, CustomerPayment $customer_payment)
    {
        //Ruta y validacion del XML
        $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
        $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;
        if (!empty($customer_payment->customerPaymentCfdi->file_xml_pac) && \Storage::exists($file_xml_pac)) {
            while (ob_get_level()) ob_end_clean();
            ob_start();

            return response()->download(\Storage::path($file_xml_pac), str_replace('/','',$customer_payment->name) . '.xml',['Cache-Control' => 'no-cache, must-revalidate']);
        }

        //Mensaje
        flash(__('sales/customer_payment.error_download_xml'))->error();

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Descarga de archivo PDF
     *
     * @param Request $request
     * @param CustomerInvoice $customer_invoice
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function downloadPdf(Request $request, CustomerPayment $customer_payment)
    {
        //Descarga archivo
        return $this->print($customer_payment, true);
    }

    /**
     * Cambiar estatus a abierta
     *
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function markOpen(CustomerPayment $customer_payment)
    {
        //Logica
        if ((int)$customer_payment->status == CustomerPayment::PER_RECONCILED || (int)$customer_payment->status == CustomerPayment::RECONCILED) {
            $customer_payment->updated_uid = \Auth::user()->id;
            $customer_payment->status = CustomerPayment::OPEN;
            $customer_payment->save();

            //Cancelacion del timbre fiscal

            //Mensaje
            flash(__('general.text_form_success_edit'))->success();
        } else {
            //Mensaje
            flash(__('sales/customer_payment.error_change_status'))->error();
        }

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Cambiar estatus a por aplicar
     *
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function markPerReconciled(CustomerPayment $customer_payment)
    {
        //Logica
        if ((int)$customer_payment->status == CustomerPayment::OPEN || (int)$customer_payment->status == CustomerPayment::RECONCILED) {
            $customer_payment->updated_uid = \Auth::user()->id;
            $customer_payment->status = CustomerPayment::PER_RECONCILED;
            $customer_payment->save();

            //Mensaje
            flash(__('general.text_form_success_edit'))->success();
        } else {
            //Mensaje
            flash(__('sales/customer_payment.error_change_status'))->error();
        }

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Cambiar estatus a enviada
     *
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function markSent(CustomerPayment $customer_payment)
    {
        //Logica
        $customer_payment->updated_uid = \Auth::user()->id;
        $customer_payment->mail_sent = 1;
        $customer_payment->save();

        //Mensaje
        flash(__('general.text_form_success_edit'))->success();

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Cambiar estatus a reconciliada
     *
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function markReconciled(CustomerPayment $customer_payment)
    {
        //Logica
        if ((int)$customer_payment->status == CustomerPayment::OPEN || (int)$customer_payment->status == CustomerPayment::PER_RECONCILED) {
            $customer_payment->updated_uid = \Auth::user()->id;
            $customer_payment->status = CustomerPayment::RECONCILED;
            $customer_payment->save();

            //Mensaje
            flash(__('general.text_form_success_edit'))->success();
        } else {
            //Mensaje
            flash(__('sales/customer_payment.error_change_status'))->error();
        }

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Clase generica de impresion
     *
     * @param CustomerPayment $customer_payment
     * @return mixed
     */
    public function print(CustomerPayment $customer_payment, $download = false, $save = false)
    {
        try {
            //Logica
            $tmp = 'default';
            if (!empty($customer_payment->customerPaymentCfdi->cfdi_version) && !empty($customer_payment->cfdi)) {
                $tmp = $customer_payment->customerPaymentCfdi->cfdi_version;
            }
            $class_print = 'print' . ucfirst($tmp);
            return $this->$class_print($customer_payment,$download,$save);
        } catch (\Exception $e) {
            flash($e->getMessage())->error();
            return redirect('/sales/customer-payments');
        }
    }

    /**
     * Impresion default
     *
     * @param $customer_payment
     * @return mixed
     */
    private function printDefault($customer_payment, $download = false, $save = false)
    {
        //PDF
        $pdf = \PDF::loadView('sales.customer_payments.pdf_default', compact('customer_payment'));

        //Guardar
        if ($save) {
            return $pdf->output();
        }

        //Redireccion
        if($download){
            return $pdf->download($customer_payment->documentType->name . '_' . str_replace('/','',$customer_payment->name) . '.pdf');
        }

        return $pdf->stream($customer_payment->documentType->name . '_' . str_replace('/','',$customer_payment->name) . '.pdf');
    }

    /**
     * Impresion CFDI 3.3
     *
     * @param $customer_payment
     * @param bool $download
     * @return mixed
     * @throws \Exception
     */
    private function printCfdi33($customer_payment, $download = false, $save = false)
    {
        //Datos
        $tipo_cadena_pagos = $this->tipo_cadena_pagos;

        //Datos del XML timbrado
        $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
        $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;
        $data = [];

        //Valida que el archivo exista
        if (!empty($customer_payment->customerPaymentCfdi->file_xml_pac) && \Storage::exists($file_xml_pac)) {
            //Crea arreglo
            $cfdi = \CfdiUtils\Cfdi::newFromString(\Storage::get($file_xml_pac));
            $data = Cfdi33Helper::getQuickArrayCfdi($cfdi);

            //Genera codigo QR
            $image = QrCode::format('png')->size(150)->margin(0)->generate($data['qr_cadena']);
            $data['qr'] = 'data:image/png;base64,' . base64_encode($image);

            //Regimen fiscal
            $data['tax_regimen'] = TaxRegimen::where('code', $data['cfdi33']->emisor['RegimenFiscal'])->first()->name_sat;
        }

        //PDF
        $pdf_template = \App\Helpers\Helper::companyPdfTemplate($customer_payment->company_id);//Plantilla PDF
        $pdf = \PDF::loadView('sales.customer_payments.pdf_cfdi33_' . $pdf_template, compact('customer_payment', 'data','tipo_cadena_pagos'));

        //Guardar
        if ($save) {
            return $pdf->output();
        }

        //Redireccion
        if($download){
            return $pdf->download(str_replace('/','',$customer_payment->name) . '.pdf');
        }

        //Redireccion
        return $pdf->stream(str_replace('/','',$customer_payment->name) . '.pdf');
    }

    /**
     * Impresion CFDI 3.3
     *
     * @param $customer_payment
     * @param bool $download
     * @return mixed
     * @throws \Exception
     */
    private function printCfdi40($customer_payment, $download = false, $save = false)
    {
        //Datos
        $tipo_cadena_pagos = $this->tipo_cadena_pagos;

        //Datos del XML timbrado
        $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
        $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;
        $data = [];
        $taxesDoctoRelacionado = [];

        //Valida que el archivo exista
        if (!empty($customer_payment->customerPaymentCfdi->file_xml_pac) && \Storage::exists($file_xml_pac)) {
            //Crea arreglo
            $cfdi = \CfdiUtils\Cfdi::newFromString(\Storage::get($file_xml_pac));
            $data = Cfdi33Helper::getQuickArrayCfdi($cfdi);
            $pagoTmp = $data['cfdi33']->complemento->pagos->pago;
            $pagoTmp = $pagoTmp('DoctoRelacionado');
            $list_tax_codes = [
                Tax::ISR => __('catalogs/tax.text_code_isr'),
                Tax::IVA => __('catalogs/tax.text_code_iva'),
                Tax::IEPS => __('catalogs/tax.text_code_ieps')
            ];
            foreach($pagoTmp as $element){
                $key = $element['IdDocumento'] . '_' . $element['NumParcialidad'];
                $tmp = [];
                $trasladosDR = ($element->ImpuestosDR->TrasladosDR)();
                $tmp['TrasladosDR'] = [];
                foreach($trasladosDR as $element2){
                    $tmp['TrasladosDR'][] = [
                        'BaseDR' => !empty($element2['BaseDR']) ? money($element2['BaseDR'], $customer_payment->currency->code, true)->format() : '',
                        'ImpuestoDR' => !empty($element2['ImpuestoDR']) ? ($list_tax_codes[$element2['ImpuestoDR']] ?? '') : '',
                        'TipoFactorDR' => !empty($element2['TipoFactorDR']) ? $element2['TipoFactorDR'] : '',
                        'TasaOCuotaDR' => !empty($element2['TasaOCuotaDR']) ? $element2['TasaOCuotaDR'] : '',
                        'ImporteDR' => !empty($element2['ImporteDR']) ? money($element2['ImporteDR'], $customer_payment->currency->code, true)->format() : ''
                    ];
                }
                $retencionesDR = ($element->ImpuestosDR->RetencionesDR)();
                $tmp['RetencionesDR'] = [];
                foreach($retencionesDR as $element2){
                    $tmp['RetencionesDR'][] = [
                        'BaseDR' => !empty($element2['BaseDR']) ? money($element2['BaseDR'], $customer_payment->currency->code, true)->format() : '',
                        'ImpuestoDR' => !empty($element2['ImpuestoDR']) ? ($list_tax_codes[$element2['ImpuestoDR']] ?? '') : '',
                        'TipoFactorDR' => !empty($element2['TipoFactorDR']) ? $element2['TipoFactorDR'] : '',
                        'TasaOCuotaDR' => !empty($element2['TasaOCuotaDR']) ? $element2['TasaOCuotaDR'] : '',
                        'ImporteDR' => !empty($element2['ImporteDR']) ? money($element2['ImporteDR'], $customer_payment->currency->code, true)->format() : ''
                    ];
                }
                $taxesDoctoRelacionado[$key] = $tmp;
            }

            //Genera codigo QR
            $image = QrCode::format('png')->size(150)->margin(0)->generate($data['qr_cadena']);
            $data['qr'] = 'data:image/png;base64,' . base64_encode($image);

            //Regimen fiscal
            $data['tax_regimen'] = TaxRegimen::where('code', $data['cfdi33']->emisor['RegimenFiscal'])->first()->name_sat;
            $data['tax_regimen_customer'] = TaxRegimen::where('code', $data['cfdi33']->receptor['RegimenFiscalReceptor'])->first()->name_sat;
        }

        //PDF
        $pdf_template = \App\Helpers\Helper::companyPdfTemplate($customer_payment->company_id);//Plantilla PDF
        $pdf = \PDF::loadView('sales.customer_payments.pdf_cfdi40_' . $pdf_template, compact('customer_payment', 'data','tipo_cadena_pagos', 'taxesDoctoRelacionado'));

        //Guardar
        if ($save) {
            return $pdf->output();
        }

        //Redireccion
        if($download){
            return $pdf->download(str_replace('/','',$customer_payment->name) . '.pdf');
        }

        //Redireccion
        return $pdf->stream(str_replace('/','',$customer_payment->name) . '.pdf');
    }

    /**
     * Modal para envio de correo de recibo de pago
     *
     * @param Request $request
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\JsonResponse
     * @throws \Throwable
     */
    public function modalSendMail(Request $request, CustomerPayment $customer_payment)
    {
        //Variables
        $id = $request->id;

        //Logica
        if ($request->ajax() && !empty($id)) {
            //Correo default del cliente
            $reply = Helper::replyMails();
            $to = [];
            $to_selected = [];
            if (!empty($customer_payment->customer->email)) {
                $tmp = explode(';',$customer_payment->customer->email);
                if(!empty($tmp)) {
                    foreach($tmp as $email) {
                        $email = trim($email);
                        $to[$email] = $email;
                        $to_selected [] = $email;
                    }
                }
            }
            //Etiquetas solo son demostrativas
            $files = [
                'pdf' => str_replace('/','',$customer_payment->name) . '.pdf',
                'xml' => str_replace('/','',$customer_payment->name) . '.xml'
            ];
            $files_selected = array_keys($files);

            //modal de mensaje
            $html = view('layouts.partials.customer_payments.modal_send_mail',
                compact('customer_payment', 'to', 'to_selected', 'files', 'files_selected', 'reply'))->render();

            //Mensaje predefinido
            $custom_message = view('layouts.partials.customer_payments.message_send_mail',
                compact('customer_payment'))->render();

            return response()->json(['html' => $html, 'custom_message' => $custom_message]);
        }

        return response()->json(['error' => __('general.error_general')], 422);
    }

    /**
     * Envio de recibo de pago por correo
     *
     * @param Request $request
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function sendMail(Request $request, CustomerPayment $customer_payment)
    {
        //Validaciones
        $validator = \Validator::make($request->all(), [
            'subject' => 'required',
            'to' => 'required',
            'to.*' => 'email',
            'message' => 'required'
        ], [
            'subject.*' => __('general.error_mail_subject'),
            'to.required' => __('general.error_mail_to'),
            'to.*.email' => __('general.error_mail_to_format'),
            'message.*' => __('general.error_mail_message'),
        ]);
        if ($validator->fails()) {
            $errors = '<ul>';
            foreach ($validator->errors()->all() as $message) {
                $errors .= '<li>'.$message . '</li>';
            }
            $errors .= '</ul>';
            return response()->json(['error' => $errors], 422);
        }

        //Creamos PDF en stream
        $pdf = $this->print($customer_payment);
        //Ruta del XML
        $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
        $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;

        //Envio de correo
        $to = $request->to;
        $reply = $request->reply;
        \Mail::to($to)->send(new SendCustomerPayment($customer_payment, $request->subject, $request->message, $pdf,
            $file_xml_pac, $reply));

        //Actualiza campo de enviado
        $customer_payment->updated_uid = \Auth::user()->id;
        $customer_payment->mail_sent = 1;
        $customer_payment->save();

        //Mensaje
        return response()->json([
            'success' => sprintf(__('sales/customer_payment.text_success_send_mail'), $customer_payment->name)
        ]);
    }

    /**
     * Modal para conciliar pago
     *
     * @param Request $request
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\JsonResponse
     * @throws \Throwable
     */
    public function modalReconciled(Request $request, CustomerPayment $customer_payment)
    {
        //Variables
        $id = $request->id;
        $filter_currency_id = 0; //Filtra solo facturas de la misma moneda

        //Logica
        if ($request->ajax() && !empty($id)) {

            //Saldos de facturas
            $currency = $customer_payment->currency;
            $filter =[
                'filter_balances' => true,
                'filter_customer_id' => $customer_payment->customer_id,
                'filter_currency_id' => ((!empty($currency) && $currency->code != 'MXN') || !empty($filter_currency_id)) ? $currency->id : '', //Solo pagos en MXN se pueden aplicar a otras monedas
                'filter_document_type_code' => ['customer.invoice','customer.lease','customer.fee'],
            ];
            $results = CustomerInvoice::filter($filter)->sortable('date')->get();

            $result_balances = view('layouts.partials.customer_invoices.balances', compact('results','currency','filter_currency_id'))->render();

            //modal de mensaje
            $html = view('layouts.partials.customer_payments.modal_reconciled',
                compact('customer_payment','result_balances'))->render();

            return response()->json(['html' => $html]);
        }

        return response()->json(['error' => __('general.error_general')], 422);
    }

    /**
     * Conciliar recibo de pago
     *
     * @param Request $request
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function reconciled(Request $request, CustomerPayment $customer_payment)
    {
        //Validaciones
        $validator = \Validator::make($request->all(), [
            'item_reconciled.*.currency_value' => 'nullable|numeric|min:0.00001',
            'item_reconciled.*.amount_reconciled' => 'nullable|numeric|min:0',
        ], [
            'item_reconciled.*.currency_value.*' => __('sales/customer_payment.error_reconciled_currency_value'),
            'item_reconciled.*.amount_reconciled.*' => __('sales/customer_payment.error_reconciled_amount_reconciled'),
        ]);
        //Validaciones manuales
        //$validator = \Validator::make([], []);

        //Valida que el monto total de las lineas no supere al pago
        $amount = (double)$request->amount;
        $amount_reconciled = !empty($request->amount_reconciled) ? (double)$request->amount_reconciled : 0;
        if (!empty($request->item_reconciled)) {
            foreach ($request->item_reconciled as $key => $item_reconciled) {
                if (!empty($item_reconciled['amount_reconciled'])) {
                    $item_reconciled_amount_reconciled = round((double)$item_reconciled['amount_reconciled'],2);
                    $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                    $amount_reconciled += $item_reconciled_amount_reconciled;

                    //Valida que el monto conciliado no supere el saldo de la factura
                    $currency = Currency::findOrFail($request->currency_id);
                    $customer_invoice = CustomerInvoice::findOrFail($item_reconciled['reconciled_id']);
                    $tmp = round(Helper::invertBalanceCurrency($currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);
                    if(($tmp - $customer_invoice->balance) > 0.01){
                        $ci_name = $customer_invoice->name;
                        $validator->after(function ($validator) use($key, $ci_name) {
                            $validator->errors()->add('item_reconciled.'.$key.'.amount_reconciled', __('sales/customer_payment.error_reconciled_amount_reconciled_customer_invoice_balance') .' '. $ci_name);
                        });
                    }
                }
            }
        }

        //Valida que el monto conciliado no supere el monto del pago
        if(($amount_reconciled - $amount) > 0.01){
            $validator->after(function ($validator) {
                $validator->errors()->add('amount', __('sales/customer_payment.error_amount_amount_reconciled'));
            });
        }
        if ($validator->fails()) {
            $errors = '<ul>';
            foreach ($validator->errors()->all() as $message) {
                $errors .= '<li>'.$message . '</li>';
            }
            $errors .= '</ul>';
            return response()->json(['error' => $errors], 422);
        }

        //Registro de lineas
        $amount = $customer_payment->amount;
        $amount_reconciled = $customer_payment->amount-$customer_payment->balance;
        //Lineas
        if (!empty($request->item_reconciled)) {
            foreach ($request->item_reconciled as $key => $item_reconciled) {
                if (!empty($item_reconciled['amount_reconciled'])) {
                    //Datos de factura
                    $customer_invoice = CustomerInvoice::findOrFail($item_reconciled['reconciled_id']);

                    //Logica
                    $item_reconciled_amount_reconciled = round((double)$item_reconciled['amount_reconciled'],2);
                    $amount_reconciled += $item_reconciled_amount_reconciled;
                    $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                    //Convertimos el monto aplicado si la moneda del documento es diferente a la de pago
                    $item_reconciled_amount_reconciled = round(Helper::invertBalanceCurrency($customer_payment->currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);

                    //Guardar linea
                    $customer_payment_reconciled = CustomerPaymentReconciled::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_payment_id' => $customer_payment->id,
                        'name' => $customer_invoice->name,
                        'reconciled_id' => $customer_invoice->id,
                        'currency_value' => $item_reconciled_currency_value,
                        'amount_reconciled' => $item_reconciled_amount_reconciled,
                        'last_balance' => $customer_invoice->balance,
                        'sort_order' => $key,
                        'status' => 1,
                    ]);

                    //Actualiza el saldo de la factura relacionada
                    $customer_invoice->balance -= $item_reconciled_amount_reconciled;
                    if ($customer_invoice->balance < 0) {
                        throw new \Exception(sprintf(__('sales/customer_payment.error_amount_balance_reconciled'), $item_reconciled_amount_reconciled, $customer_invoice->name));
                    }
                    if($customer_invoice->balance <= 0.1){
                        $customer_invoice->status = CustomerInvoice::PAID;
                    }
                    $customer_invoice->save();
                }
            }
        }

        //Actualiza registro principal con totales
        $customer_payment->balance = $amount - $amount_reconciled;
        if($customer_payment->balance <= 0){
            $customer_payment->status = CustomerPayment::RECONCILED;
        }
        $customer_payment->update();


        //Mensaje
        return response()->json([
            'success' => sprintf(__('sales/customer_payment.text_success_reconciled'), $customer_payment->name)
        ]);
    }

    /**
     * Obtener registro
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function getCustomerPayment(Request $request)
    {
        //Variables
        $id = $request->id;

        //Logica
        if ($request->ajax() && !empty($id)) {
            $customer_payment = CustomerPayment::findOrFail($id);
            $customer_payment->uuid = $customer_payment->customerPaymentCfdi->uuid ?? '';
            return response()->json($customer_payment, 200);
        }

        return response()->json(['error' => __('general.error_general')], 422);
    }

    /**
     * Autoacompletar select2 de facturas solo activas del SAT
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function autocompleteCfdi(Request $request)
    {
        //Variables
        $term = $request->term;
        $customer_id = !empty($request->customer_id) ? $request->customer_id : '111111111111'; //Filtra las facturas por cliente

        //Logica
        if ($request->ajax() && !empty($term)) {
            $tmp = CustomerPayment::filter([
                'filter_search_cfdi_select2' => $term,
                'filter_customer_id' => $customer_id
            ])->sortable('date')->limit(16)->get();
            $results = [];
            if ($tmp->isNotEmpty()) {
                foreach ($tmp as $result) {
                    $results[] = [
                        'id' => $result->id,
                        'text' => $result->text_select2,
                        'description' => $result->description_select2
                    ];
                }
            }
            return response()->json($results, 200);
        }

        return response()->json(['error' => __('general.error_general')], 422);
    }

    /**
     * Modal para cancelar pago
     *
     * @param Request $request
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\JsonResponse
     * @throws \Throwable
     */
    public function modalCancel(Request $request, CustomerPayment $customer_payment)
    {
        //Variables
        $id = $request->id;

        //Logica
        if ($request->ajax() && !empty($id)) {
            $reason_cancellations = ReasonCancellation::populateSelect()->get()->pluck('name_sat', 'id');

            //modal de cancelar
            $html = view('layouts.partials.customer_payments.modal_cancel',
                compact('customer_payment', 'reason_cancellations'))->render();


            return response()->json(['html' => $html]);
        }

        return response()->json(['error' => __('general.error_general')], 422);
    }

    /**
     * Calcula el total de las lineas
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function totalReconciledLines(Request $request)
    {
        //Variables
        $json = new \stdClass;
        $input_items_reconciled = $request->item_reconciled;
        $currency_code = 'MXN';

        if ($request->ajax()) {
            //Datos de moneda
            if (!empty($request->currency_id)) {
                $currency = Currency::findOrFail($request->currency_id);
                $currency_code = $currency->code;
            }
            //Variables de totales
            $amount = (double)$request->amount;
            $amount_reconciled = !empty($request->amount_reconciled) ? (double)$request->amount_reconciled : 0;
            $amount_per_reconciled = 0;
            $items_reconciled = [];
            if (!empty($input_items_reconciled)) {
                foreach ($input_items_reconciled as $key => $item_reconciled) {
                    //Logica
                    $item_reconciled_balance = (double)$item_reconciled['balance'];
                    $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                    $amount_reconciled += (double)$item_reconciled['amount_reconciled'];
                    $items_reconciled[$key] = money(Helper::convertBalanceCurrency($currency,$item_reconciled_balance,$item_reconciled['currency_code'],$item_reconciled_currency_value), $currency_code, true)->format();
                }
            }
            //Respuesta
            $json->items_reconciled = $items_reconciled;
            $json->amount = money($amount, $currency_code, true)->format();
            $json->amount_reconciled = money($amount_reconciled, $currency_code, true)->format();
            $json->amount_per_reconciled = money($amount - $amount_reconciled, $currency_code, true)->format();
            return response()->json($json);
        }

        return response()->json(['error' => __('general.error_general')], 422);
    }

    /**
     * Exportar datos a excel
     *
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function exportToExcel(Request $request)
    {
        while (ob_get_level()) ob_end_clean();
        ob_start();

        return Excel::download(new CustomerPaymentsExport($request),
            __('sales/customer_payment.document_title') . '-' . config('app.name') . '.xlsx');
    }

    /**
     * Funcion para timbrar factura con estatus de prefactura
     *
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function stamp(CustomerPayment $customer_payment){

        \DB::connection('tenant')->beginTransaction();
        try {
            $invoiced = false;
            if((int) $customer_payment->status == CustomerPayment::DRAFT && $customer_payment->cfdi) {
                //Valida que tenga exista la clase de facturacion
                $class_cfdi = setting('cfdi_version');
                if (empty($class_cfdi)) {
                    throw new \Exception(__('general.error_cfdi_version'));
                }
                if (!method_exists($this, $class_cfdi)) {
                    throw new \Exception(__('general.error_cfdi_class_exists'));
                }

                //Valida que tenga folios disponibles
                if(BaseHelper::getAvailableFolios()<=0){
                    throw new \Exception(__('general.error_available_folios'));
                }

                //CFDI
                $customer_payment_cfdi = $customer_payment->CustomerPaymentCfdi;

                //Actualiza registr principal
                $customer_payment->updated_uid = \Auth::user()->id;
                $customer_payment->date = Helper::dateTimeToSql(BaseHelper::getDateTimeBranchOffice($customer_payment->branch_office_id));
                $customer_payment->status = CustomerPayment::RECONCILED;
                $customer_payment->save();

                //Actualiza folios
                //Obtiene folio
                $document_type = Helper::getNextDocumentTypeByCode($this->document_type_code, $customer_payment->company->id, false, $customer_payment->branch_office_id);
                $customer_payment->draft = $customer_payment->name;
                $customer_payment->name = $document_type['name'];
                $customer_payment->serie = $document_type['serie'];
                $customer_payment->folio = $document_type['folio'];
                $customer_payment->save();
                $customer_payment_cfdi->name = $customer_payment->name;
                $customer_payment_cfdi->save();

                //Realiza aplicaciones a facturas
                if($customer_payment->customerPaymentReconcileds->isNotEmpty()){
                    foreach($customer_payment->customerPaymentReconcileds as $result){

                        if(!empty($result->reconciled_id)) {
                            //Datos de factura
                            $customer_invoice = CustomerInvoice::findOrFail($result->reconciled_id);
                            $tmp_customer_payment_reconciled = CustomerPaymentReconciled::where('status', '=', '1')
                                ->where('reconciled_id', '=', $customer_invoice->id)
                                ->where(function ($query) {
                                    $query->WhereHas('customerPayment', function ($q) {
                                        $q->whereIn('customer_payments.status',
                                            [CustomerPayment::OPEN, CustomerPayment::RECONCILED]);
                                    });
                                });
                            $result->number_of_payment = $tmp_customer_payment_reconciled->count(); //Guarda el numero de parcialidad
                            $result->save();


                            //Actualiza el saldo de la factura relacionada
                            $customer_invoice->balance -= $result->amount_reconciled;
                            if ($customer_invoice->balance < 0) {
                                throw new \Exception(sprintf(__('sales/customer_payment.error_amount_balance_reconciled'), $result->amount_reconciled, $customer_invoice->name));
                            }
                            if ($customer_invoice->balance <= 0.1) {
                                $customer_invoice->status = CustomerInvoice::PAID;
                            }
                            $customer_invoice->save();
                        }
                    }
                }

                //Valida Empresa y PAC para timbrado
                PacHelper::validateSatActions($customer_payment->company);

                //Crear XML y timbra
                $tmp = $this->$class_cfdi($customer_payment);

                //Guardar registros de CFDI
                $customer_payment_cfdi->fill(array_only($tmp, [
                    'pac_id',
                    'cfdi_version',
                    'uuid',
                    'date',
                    'file_xml',
                    'file_xml_pac',
                ]));
                $customer_payment_cfdi->save();

                //Disminuye folios
                BaseHelper::decrementFolios();

                //Mensaje
                flash(__('general.text_success_customer_payment_cfdi'))->success();

                $invoiced = true;
            }

            \DB::connection('tenant')->commit();

            if($invoiced) {
                $this->saveCfdiDownloads($customer_payment, $customer_payment_cfdi);
            }


        } catch (\Exception $e) {
            \DB::connection('tenant')->rollback();
            flash( __('general.error_cfdi_pac').'<br>'.$e->getMessage())->error();
            return back()->withInput();
        }

        //Almacenamiento dropbox
        self::dropboxBackup($customer_payment);

        //Redireccion
        return redirect('/sales/customer-payments');
    }

    /**
     * Modal para estatus de CFDI
     *
     * @param Request $request
     * @param CustomerPayment $customer_payment
     * @return \Illuminate\Http\JsonResponse
     * @throws \Throwable
     */
    public function modalStatusSat(Request $request, CustomerPayment $customer_payment)
    {
        //Variables
        $id = $request->id;

        //Logica
        if ($request->ajax() && !empty($id)) {
            //Obtener informacion de estatus
            $data_status_sat = [
                'cancelable' => 1,
                'pac_is_cancelable' => ''
            ];
            if (!empty($customer_payment->customerPaymentCfdi->cfdi_version) && !empty($customer_payment->customerPaymentCfdi->uuid)) {

                //Obtener el sellos del CFDI
                $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
                $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;
                $cfdi = \CfdiUtils\Cfdi::newFromString(\Storage::get($file_xml_pac));
                $data = Cfdi33Helper::getQuickArrayCfdi($cfdi);

                $tmp = [
                    'rfcR' => $data['cfdi33']->Receptor['Rfc'] ?? $customer_payment->customer->taxid,
                    'uuid' => $customer_payment->customerPaymentCfdi->uuid,
                    'total' => Helper::numberFormat(0, $customer_payment->currency->decimal_place, false),
                    'fe' => substr($data['cfdi33']->complemento->timbreFiscalDigital['SelloCFD'],-8), //Los últimos 8 caracteres del Atributo Sello del CFDI
                    'file_xml_pac' => $file_xml_pac
                ];
                $class_pac = $customer_payment->customerPaymentCfdi->pac->code . 'Status';
                $data_status_sat = PacHelper::$class_pac($tmp,$customer_payment->company,$customer_payment->customerPaymentCfdi->pac);
            }
            $is_cancelable = true;
            if($data_status_sat['cancelable'] == 3){
                $is_cancelable = false;
            }

            //modal de visualizar estatus en el SAT
            $html = view('layouts.partials.customer_payments.modal_status_sat', compact('customer_payment','data_status_sat','is_cancelable'))->render();

            return response()->json(['html' => $html]);
        }

        return response()->json(['error' => __('general.error_general')], 422);
    }

    /**
     * Calcula el total de las lineas
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function totalItemManualReconciled(Request $request)
    {

        //Variables
        $json = new \stdClass;
        $input_items_manual_reconciled = $request->item_manual_reconciled;
        $currency_id = $request->currency_id;
        $currency_code = 'MXN';

        if ($request->ajax()) {
            //Datos de moneda
            if (!empty($currency_id)) {
                $currency = Currency::findOrFail($currency_id);
                $currency_code = $currency->code;
            }
            //Variables de totales
            $items_manual_reconciled = [];
            if (!empty($input_items_manual_reconciled)) {
                foreach ($input_items_manual_reconciled as $key => $item) {
                    //Logica
                    $item_last_balance = (double)$item['last_balance'];
                    $item_amount_reconciled = (double)$item['amount_reconciled'];

                    //Subtotales por cada item
                    $items_manual_reconciled[$key] = Helper::numberFormat($item_last_balance-$item_amount_reconciled,2, false);
                }
            }
            //Respuesta
            $json->items_manual_reconciled = $items_manual_reconciled;
            return response()->json($json);
        }

        return response()->json(['error' => __('general.error_general')], 422);
    }

    /**
     * Respaldo en dropbox si esta activo
     *
     * @param $customer_invoice
     */
    public function dropboxBackup($customer_payment, $save_xml = true, $save_pdf = true){
        try{
            if(!empty($customer_payment) && !empty(setting('dropbox_backup')) && !empty(setting('dropbox_access_token'))){
                $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
                $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;
                if (!empty($customer_payment->customerPaymentCfdi->file_xml_pac) && \Storage::exists($file_xml_pac)) {

                    $folder = !empty(setting('dropbox_folder','')) ? setting('dropbox_folder','') . '/' : setting('dropbox_folder','');
                    $folder .= strtoupper(\Date::parse($customer_payment->date)->format('F Y'));
                    $folder .= '/' .preg_replace("/[^a-zA-Z0-9\s]/", '', $customer_payment->company->name);

                    //Guarda XML
                    if($save_xml) {
                        \Storage::disk('dropbox')->putFileAs($folder,
                            new \Illuminate\Http\File(\Storage::path($file_xml_pac)),
                            str_replace('/','',$customer_payment->name) . '.xml');
                    }

                    //Guarda PDF
                    if($save_pdf) {
                        $pdf = $this->print($customer_payment, false, true);
                        \Storage::disk('dropbox')->put($folder . '/' . str_replace('/','',$customer_payment->name) . '.pdf', $pdf);
                    }
                }

            }
        }catch (\Exception $e){
            flash($e->getMessage())->error();
        }
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function createImport()
    {
        $branch_offices = BranchOffice::populateSelect()->pluck('name', 'id');
        $currencies = Currency::populateSelect()->get()->pluck('name_sat', 'id');
        $payment_ways = PaymentWay::populateSelect()->get()->pluck('name_sat', 'id');
        $payment_methods = PaymentMethod::populateSelect()->get()->pluck('name_sat', 'id');
        $cfdi_relations = CfdiRelation::populateSelect()->get()->pluck('name_sat', 'id');
        $company_bank_accounts = CompanyBankAccount::populateSelect()->pluck('name', 'id');

        $import_results [] = (object)[
            'customer_taxid' => 'AMS840526THA',
            'customer' => 'Alberto Perez Perez',
            'branch_office' => 'Matriz',
            'currency' => 'MXN',
            'currency_value' => '',
            'date_payment' => '06-04-2022',
            'payment_way' => 'Transferencia electrónica de fondos',
            'amount' => '1000',
            'customer_bank_account' => 'BANAMEX 123',
            'reference' => '34734',
            'company_bank_account' => 'SANTANDER 456',
            'cfdi_relation' => 'Nota de crédito de los documentos relacionados',
            'uuids' => 'uuid-1, uuid-2',
            'reconciled_name' => 'FAC124',
            'reconciled_currency_value' => '',
            'reconciled_amount_reconciled2' => '100',
            'send_mail' => 'N',
        ];
        $import_results [] = (object)[
            'customer_taxid' => 'WTS780329TY2',
            'customer' => 'Aceros del centro SA DE CV',
            'branch_office' => 'Matriz',
            'currency' => 'MXN',
            'currency_value' => '',
            'date_payment' => '28-02-2022',
            'payment_way' => 'Efectivo',
            'amount' => '2500',
            'customer_bank_account' => '',
            'reference' => '',
            'company_bank_account' => '',
            'cfdi_relation' => '',
            'uuids' => '',
            'reconciled_name' => 'FAC162',
            'reconciled_currency_value' => '',
            'reconciled_amount_reconciled2' => '1500',
            'send_mail' => 'N',
        ];
        $import_results [] = (object)[
            'customer_taxid' => '',
            'customer' => '',
            'branch_office' => '',
            'currency' => '',
            'currency_value' => '',
            'date_payment' => '',
            'payment_way' => '',
            'amount' => '',
            'customer_bank_account' => '',
            'reference' => '',
            'company_bank_account' => '',
            'customer_bank_account' => '',
            'reference' => '',
            'company_bank_account' => '',
            'cfdi_relation' => '',
            'uuids' => '',
            'reconciled_name' => 'FAC169',
            'reconciled_currency_value' => '21.32',
            'reconciled_amount_reconciled2' => '1000',
            'send_mail' => '',
        ];
        $import_results [] = (object)[
            'customer_taxid' => 'XAXX010101000',
            'customer' => 'Martha Montiel Mendez',
            'branch_office' => 'Matriz',
            'currency' => 'USD',
            'currency_value' => '20.1234',
            'date_payment' => '01-04-2022',
            'payment_way' => 'Efectivo',
            'amount' => '3500',
            'customer_bank_account' => '',
            'reference' => '',
            'company_bank_account' => '',
            'cfdi_relation' => '',
            'uuids' => '',
            'reconciled_name' => 'FAC45',
            'reconciled_currency_value' => '',
            'reconciled_amount_reconciled2' => '50',
            'send_mail' => 'N',
        ];

        return view('sales.customer_payments.import',
            compact('branch_offices', 'currencies', 'payment_ways', 'payment_methods', 'cfdi_relations','import_results', 'company_bank_accounts'));
    }

    /**
     * Descargar plantilla
     *
     * @return \Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function downloadTemplateImport(Request $request)
    {
        //Datos
        $customers = Customer::filter($request->all())->active()->orderBy('sort_order')->orderBy('name')->get();
        //Descargar archivo
        return Excel::download(new CustomerPaymentTemplateImportExport($request, $customers), __('sales/customer_payment.text_template_import') . '-' . config('app.name') . '.xlsx',\Maatwebsite\Excel\Excel::XLSX);
    }

    /**
     * Obtener informacion antes de procesar el archivo
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function beforeImport(Request $request)
    {
        //Validaciones
        //Validacion de archivos por extension
        if ($request->hasFile('file_customer_payments_import')) {
            $request->merge(['file_customer_payments_import_ext' => request()->file('file_customer_payments_import')->getClientOriginalExtension()]);
        }
        $validator = \Validator::make($request->all(), [
            'file_customer_payments_import' => 'required|max:2048',
            'file_customer_payments_import_ext' => 'nullable|in:xls,xlsx'
        ], [
            'file_customer_payments_import.*' => __('sales/customer_payment.error_file_customer_payments_import'),
            'file_customer_payments_import_ext.*' => __('sales/customer_payment.error_file_customer_payments_import'),
        ]);
        //Validaciones manuales
        //$validator = \Validator::make([], []);

        //Errores
        if ($validator->fails()) {
            $errors = '';
            foreach ($validator->errors()->all() as $message) {
                $errors = $message;
            }
            return response()->json(['error' => $errors], 422);
        }

        try {
            //Lógica

            //Importar
            $import = new CustomerPaymentTemplateBeforeImportImport;
            Excel::import($import, request()->file('file_customer_payments_import'));

            //Mensaje
            return response()->json([
                'success' => 'ok',
                'total_import_customer_payments' => sprintf(__('sales/customer_payment.help_import_sweet_alert_1'),Helper::numberFormat($import->total_customer_payments),Helper::numberFormatMoney($import->total_amount_customer_payments)),
            ]);
        }catch (\Exception $e) {
            return response()->json(['error' => $e->getMessage()], 422);
        }
    }

    /**
     * Importar cuotas desde archivo
     *
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse
     * @throws \Illuminate\Validation\ValidationException
     */
    public function storeImport(Request $request)
    {
        //Validacion
        //Validacion de archivos por extension
        if ($request->hasFile('file_customer_payments_import')) {
            $request->merge(['file_customer_payments_import_ext' => request()->file('file_customer_payments_import')->getClientOriginalExtension()]);
        }
        $this->validate($request, [
            'file_customer_payments_import' => 'required|max:2048',
            'file_customer_payments_import_ext' => 'nullable|in:xls,xlsx'
        ], [
            'file_customer_payments_import.*' => __('sales/customer_payment.error_file_customer_payments_import'),
            'file_customer_payments_import_ext.*' => __('sales/customer_payment.error_file_customer_payments_import'),
        ]);

        //filas
        $rows = null;
        $data_customer_payments = [];
        $customer_payments = [];
        $customer_payments_send_mail = [];

        //Validaciones y arreglo de valores
        try {
            //Lógica

            //Importar
            $import = new CustomerPaymentTemplateImportImport;
            Excel::import($import, request()->file('file_customer_payments_import'));

            $rows = $import->rows;

            //
            $amount = 0;
            $amount_reconciled = 0;

            //Validacion
            foreach ($rows as $key => $row) {
                $num_row = $key + 2;

                if(!empty($row['fecha_de_pago'])){
                    if (strpos($row['fecha_de_pago'], "'") || strpos($row['fecha_de_pago'], "-") || strpos($row['fecha_de_pago'], "/")) {
                        $rows[$key]['fecha_de_pago'] = $row['fecha_de_pago'] = str_replace('\'', '', str_replace('/', '-', $row['fecha_de_pago']));
                    } else {
                        $rows[$key]['fecha_de_pago'] = \Date::instance(\PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($row['fecha_de_pago']))->format(setting('date_format', 'd-m-Y'));
                    }
                }

                \Validator::make($row->toArray(), [
                    'importe_pagado' => 'required|numeric|min:0.00001',
                ], [
                    'importe_pagado.*' => __('sales/customer_payment.error_reconciled_amount_reconciled2') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['importe_pagado']),
                ])->validate();
            }

            //Agrupamos los datos correctamente
            $count=0;
            foreach ($rows as $key => $row) {
                $num_row = $key + 2;

                //Factura
                $customer_invoice = null;
                if (!empty($row['factura'])) {
                    $customer_invoice = CustomerInvoice::where('name', '=', $row['factura'])->where('company_id', '=', Helper::defaultCompany()->id)->first();
                    if (empty($customer_invoice)) {
                        throw new \Exception(__('sales/customer_payment.error_reconciled_name2') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['factura']));
                    }else{

                        //Valido que el monto a reconciliar no supere el saldo de la factura
                        if(!empty($row['monto'])){
                            $amount = (double)$row['monto'];
                            $amount_reconciled = 0;
                        }
                        if(!empty($row['moneda']))
                            $currency = Currency::where('code', '=', $row['moneda'])->first();

                        $item_reconciled_amount_reconciled = round((double)$row['importe_pagado'],2);
                        $item_reconciled_currency_value = !empty($row['tc_de_pago']) ? round((double)$row['tc_de_pago'],4) : null;
                        $amount_reconciled += $item_reconciled_amount_reconciled;
                        $tmp = round(Helper::invertBalanceCurrency($currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);
                        if(($tmp - $customer_invoice->balance) > 0.01){
                            throw new \Exception(__('sales/customer_payment.error_reconciled_amount_reconciled_customer_invoice_balance') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['factura']));
                        }

                        if((!isset($rows[$key+1]) && $amount_reconciled != $amount) || (!empty($rows[$key+1]['monto']) && $amount_reconciled != $amount)){
                            throw new \Exception(sprintf(__('sales/customer_payment.error_amount_amount_reconciled'),$amount_reconciled, $amount) . sprintf(__('sales/customer_payment.error_row'), $num_row,''));
                        }

                    }
                }else{
                    throw new \Exception(__('sales/customer_payment.error_reconciled_name') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['factura']));
                }

                //Encabezado
                if (!empty($row['rfc_cliente']) && !empty($row['cliente'])) {

                    //Cliente
                    $customer = null;
                    if (!empty($row['rfc_cliente'])) {
                        if(in_array($row['rfc_cliente'], ['XAXX010101000','XEXX010101000'])){
                            if(!empty(setting('customers_per_taxid'))){
                                $customer = Customer::where('taxid', '=', $row['rfc_cliente'])->where('name', '=', $row['cliente'])->where('company_id', '=', Helper::defaultCompany()->id)->first();
                            }else{
                                $customer = Customer::where('taxid', '=', $row['rfc_cliente'])->where('name', '=', $row['cliente'])->where('company_id', '=', Helper::firstCompany()->id)->first();
                            }
                        }else{
                            if(!empty(setting('customers_per_taxid'))){
                                $customer = Customer::where('taxid', '=', $row['rfc_cliente'])->where('company_id', '=', Helper::defaultCompany()->id)->first();
                            }else{
                                $customer = Customer::where('taxid', '=', $row['rfc_cliente'])->where('company_id', '=', Helper::firstCompany()->id)->first();
                            }
                        }
                        if (empty($customer)) {
                            throw new \Exception(__('sales/customer_payment.error_customer_id2') . sprintf(__('sales/customer_payment.error_row'), $num_row, $row['rfc_cliente']));
                        }
                    }
                    //Sucursales
                    $branch_office = null;
                    if (!empty($row['sucursal'])) {
                        $branch_office = BranchOffice::where('name', '=', $row['sucursal'])->where('company_id', '=', Helper::defaultCompany()->id)->first();
                        if (empty($branch_office)) {
                            throw new \Exception(__('sales/customer_payment.error_branch_office_id2') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['sucursal']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_payment.error_branch_office_id') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['sucursal']));
                    }
                    //Moneda
                    $currency = null;
                    if (!empty($row['moneda'])) {
                        $currency = Currency::where('code', '=', $row['moneda'])->first();
                        if (empty($currency)) {
                            throw new \Exception(__('sales/customer_payment.error_currency_id2') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['moneda']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_payment.error_currency_id') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['moneda']));
                    }
                    //Termino de pago
                    $payment_way = null;
                    if (!empty($row['forma_de_pago'])) {
                        $payment_way = PaymentWay::where('name', '=', $row['forma_de_pago'])->first();
                        if (empty($payment_way)) {
                            throw new \Exception(__('sales/customer_payment.error_payment_way_id2') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['forma_de_pago']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_payment.error_payment_way_id') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['forma_de_pago']));
                    }
                    //Tipo de relacion
                    $cfdi_relation = null;
                    if (!empty($row['tipo_de_relacion'])) {
                        $cfdi_relation = CfdiRelation::where('name', '=', $row['tipo_de_relacion'])->first();
                        if (empty($cfdi_relation)) {
                            throw new \Exception(__('sales/customer_payment.error_cfdi_relation_id2') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['tipo_de_relacion']));
                        }
                        if(empty($row['uuids'])){
                            throw new \Exception(__('sales/customer_payment.error_cfdi_relation_id_uuids') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['uuids']));
                        }
                    }

                    //Cuenta beneficiaria
                    $company_bank_account = null;
                    if (!empty($row['cuenta_beneficiaria'])) {
                        $company_bank_account = CompanyBankAccount::where('name', '=', $row['cuenta_beneficiaria'])->where('company_id', '=', Helper::defaultCompany()->id)->first();
                        if (empty($company_bank_account)) {
                            throw new \Exception(__('sales/customer_payment.error_company_bank_account_id2') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['cuenta_beneficiaria']));
                        }
                    }

                    //Cuenta ordenante
                    $customer_bank_account = null;
                    if (!empty($row['cuenta_ordenante'])) {
                        $customer_bank_account = CustomerBankAccount::where('name', '=', $row['cuenta_ordenante'])->where('customer_id', '=', $customer->id)->first();
                        if (empty($customer_bank_account)) {
                            throw new \Exception(__('sales/customer_payment.error_customer_bank_account_id2') . sprintf(__('sales/customer_payment.error_row'), $num_row,$row['cuenta_ordenante']));
                        }
                    }

                    $data_customer_payments[$count] = [
                        'customer_id' => $customer->id,
                        'taxid' => $customer->taxid,
                        'customer' => $customer->name,
                        'branch_office_id' => $branch_office->id,
                        'currency_id' => $currency->id,
                        'currency_value' => !empty($row['tc']) ? $row['tc'] : 1,
                        'date_payment' => $row['fecha_de_pago'],
                        'payment_way_id' => $payment_way->id,
                        'amount' => (double)$row['monto'],
                        'customer_bank_account_id' => $customer_bank_account->id ?? null,
                        'reference' => $row['numero_de_operacion'],
                        'company_bank_account_id' => $company_bank_account->id ?? null,
                        'cfdi_relation_id' => !empty($cfdi_relation) ? $cfdi_relation->id : null,
                        'uuids' => !empty($row['uuids']) ? explode(',', $row['uuids']) : [],
                        'send_mail' => $row['enviar_correo'],
                        'tax_regimen_customer_id' => $customer->tax_regimen_id,
                    ];
                    if(!empty($row['factura']) && !empty($row['importe_pagado'])) {
                        $data_customer_payments[$count]['item'][] = [
                            'reconciled_id' => $customer_invoice->id,
                            'reconciled_name' => $row['factura'],
                            'currency_value' => !empty($row['tc_de_pago']) ? $row['tc_de_pago'] : 1,
                            'amount_reconciled' => (double)$row['importe_pagado'],
                        ];
                    }
                    $count++;
                }elseif(!empty($row['factura']) && !empty($row['importe_pagado'])){
                    $data_customer_payments[$count-1]['item'][] = [
                        'reconciled_id' => $customer_invoice->id,
                        'reconciled_name' => $row['factura'],
                        'currency_value' => !empty($row['tc_de_pago']) ? $row['tc_de_pago'] : 1,
                        'amount_reconciled' => $row['importe_pagado'],
                    ];
                }
            }

        } catch (\Illuminate\Validation\ValidationException $e ) {
            return back()->withErrors($e->errors());
        } catch (\Exception $e) {
            flash($e->getMessage())->error();
            return back()->withInput();
        }

        //Genera facturas
        $rfc_error = '';
        try {

            if(!empty($data_customer_payments)) {

                //Valida los folios a utilizar
                if(BaseHelper::getAvailableFolios()<$count){
                    throw new \Exception(__('general.error_available_folios'));
                }

                //Logica
                $company = Helper::defaultCompany(); //Empresa
                foreach($data_customer_payments as $request) {

                    \DB::connection('tenant')->beginTransaction();
                    $rfc_error = ($request->taxid ?? '') . ' ' . ($request->customer ?? '');

                    $request = (object)$request;

                    //Logica
                    $date = BaseHelper::getDateTimeBranchOffice($request->branch_office_id);
                    $date_payment = Helper::createDate($request->date_payment);

                    //Obtiene folio
                    $document_type = Helper::getNextDocumentTypeByCode($this->document_type_code,$company->id,false, $request->branch_office_id);

                    //Guardar
                    //Registro principal
                    $customer_payment = CustomerPayment::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'name' => $document_type['name'],
                        'serie' => $document_type['serie'],
                        'folio' => $document_type['folio'],
                        'date' => Helper::dateTimeToSql($date),
                        'date_payment' => Helper::dateToSql($date_payment),
                        'customer_bank_account_id' => $request->customer_bank_account_id,
                        'reference' => $request->reference,
                        'company_bank_account_id' => $request->company_bank_account_id,
                        'customer_id' => $request->customer_id,
                        'branch_office_id' => $request->branch_office_id,
                        'payment_way_id' => $request->payment_way_id,
                        'currency_id' => $request->currency_id,
                        'currency_value' => $request->currency_value,
                        'amount' => $request->amount,
                        'balance' => $request->amount,
                        'document_type_id' => $document_type['id'],
                        'status' => CustomerPayment::OPEN,
                        'company_id' => $company->id,
                        'cfdi' => 1,
                        'cfdi_relation_id' => $request->cfdi_relation_id,
                        'tax_regimen_customer_id' => $request->tax_regimen_customer_id,
                    ]);

                    //Registro de lineas
                    $amount = (double)$request->amount;
                    $amount_reconciled = 0;
                    $count = 1; //Contador de lineas
                    //Lineas
                    if (!empty($request->item)) {
                        foreach ($request->item as $key => $item_reconciled) {
                            if(!empty($item_reconciled['amount_reconciled'])) {

                                //Datos de factura
                                $customer_invoice = CustomerInvoice::findOrFail($item_reconciled['reconciled_id']);

                                //Logica
                                $item_reconciled_amount_reconciled = round((double)$item_reconciled['amount_reconciled'],2);
                                $amount_reconciled += $item_reconciled_amount_reconciled;
                                $item_reconciled_currency_value = !empty($item_reconciled['currency_value']) ? round((double)$item_reconciled['currency_value'],4) : null;
                                //Convertimos el monto aplicado si la moneda del documento es diferente a la de pago
                                $item_reconciled_amount_reconciled = round(Helper::invertBalanceCurrency($customer_payment->currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);

                                //Guardar linea
                                $customer_payment_reconciled = CustomerPaymentReconciled::create([
                                    'created_uid' => \Auth::user()->id,
                                    'updated_uid' => \Auth::user()->id,
                                    'customer_payment_id' => $customer_payment->id,
                                    'name' => $customer_invoice->name,
                                    'reconciled_id' => $customer_invoice->id,
                                    'currency_value' => $item_reconciled_currency_value,
                                    'amount_reconciled' => $item_reconciled_amount_reconciled,
                                    'last_balance' => $customer_invoice->balance,
                                    'sort_order' => $count,
                                    'status' => 1,
                                    'uuid_related' => $customer_invoice->customerInvoiceCfdi->uuid,
                                    'serie_related' => $customer_invoice->serie,
                                    'folio_related' => $customer_invoice->folio,
                                    'currency_code_related' => $customer_invoice->currency->code,
                                    'payment_method_code_related' => $customer_invoice->paymentMethod->code,
                                    'current_balance' => $customer_invoice->balance - $item_reconciled_amount_reconciled,
                                ]);

                                $tmp_customer_payment_reconciled = CustomerPaymentReconciled::where('status', '=', '1')
                                    ->where('reconciled_id', '=', $customer_invoice->id)
                                    ->where(function ($query) {
                                        $query->WhereHas('customerPayment', function ($q) {
                                            $q->whereIn('customer_payments.status',
                                                [CustomerPayment::OPEN, CustomerPayment::RECONCILED]);
                                        });
                                    });
                                $customer_payment_reconciled->number_of_payment = $tmp_customer_payment_reconciled->count(); //Guarda el numero de parcialidad
                                $customer_payment_reconciled->save();

                                //Actualiza el saldo de la factura relacionada
                                $customer_invoice->balance -= $item_reconciled_amount_reconciled;
                                if ($customer_invoice->balance < 0) {
                                    throw new \Exception(sprintf(__('sales/customer_payment.error_amount_balance_reconciled'), $item_reconciled_amount_reconciled, $customer_invoice->name));
                                }
                                if ($customer_invoice->balance <= 0.1) {
                                    $customer_invoice->status = CustomerInvoice::PAID;
                                }
                                $customer_invoice->save();
                            }
                        }
                    }

                    //Cfdi relacionados
                    if (!empty($request->uuids)) {
                        foreach ($request->uuids as $key => $uuid) {
                            //Guardar
                            if(!empty($uuid)){
                                $customer_payment_relation = CustomerPaymentRelation::create([
                                    'created_uid' => \Auth::user()->id,
                                    'updated_uid' => \Auth::user()->id,
                                    'customer_payment_id' => $customer_payment->id,
                                    'relation_id' => null,
                                    'sort_order' => $key,
                                    'status' => 1,
                                    'uuid_related' => $uuid,
                                ]);
                            }
                        }
                    }

                    //Valida que tenga exista la clase de facturacion
                    $class_cfdi = setting('cfdi_version');
                    if (empty($class_cfdi)) {
                        throw new \Exception(__('general.error_cfdi_version'));
                    }
                    if (!method_exists($this, $class_cfdi)) {
                        throw new \Exception(__('general.error_cfdi_class_exists'));
                    }

                    //Registros de cfdi
                    $customer_payment_cfdi = CustomerPaymentCfdi::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_payment_id' => $customer_payment->id,
                        'name' => $customer_payment->name,
                        'cfdi_version' => $class_cfdi,
                        'status' => 1,
                    ]);

                    //Actualiza estatus de acuerdo al monto conciliado
                    $customer_payment->balance = $amount - $amount_reconciled;
                    if(($customer_payment->balance <= 0.1 || !empty($request->cfdi)) && !isset($request->pre_payment)){ //Si es un CFDI lo marca como conciliado
                        $customer_payment->status = CustomerPayment::RECONCILED;
                    }
                    $customer_payment->update();

                    //Valida Empresa y PAC para timbrado
                    PacHelper::validateSatActions($company);

                    //Crear XML y timbra
                    $tmp = $this->$class_cfdi($customer_payment);

                    //Guardar registros de CFDI
                    $customer_payment_cfdi->fill(array_only($tmp, [
                        'pac_id',
                        'cfdi_version',
                        'uuid',
                        'date',
                        'file_xml',
                        'file_xml_pac',
                    ]));
                    $customer_payment_cfdi->save();

                    //Disminuye folios
                    BaseHelper::decrementFolios();

                    \DB::connection('tenant')->commit();

                    $this->saveCfdiDownloads($customer_payment, $customer_payment_cfdi);

                    //Guardamos facturas creadas
                    $customer_payments[] = $customer_payment;
                    if($request->send_mail == 'S'){
                        $customer_payments_send_mail[$customer_payment->id] = $customer_payment->id;
                    }
                }
            }


            //Mensaje
            flash(__('sales/customer_payment.text_form_success_import'))->success();

        } catch (\Illuminate\Validation\ValidationException $e ) {
            \DB::connection('tenant')->rollback();
            if(!empty($rfc_error))
                flash($rfc_error)->error();
            return back()->withErrors($e->errors());
        } catch (\Exception $e) {
            \DB::connection('tenant')->rollback();
            if(!empty($rfc_error))
                flash($rfc_error)->error();
            flash($e->getMessage())->error();
            return back()->withInput();
        }

        //Almacenamiento dropbox
        if(!empty($customer_payments)){
            foreach($customer_payments as $customer_payment){
                self::dropboxBackup($customer_payment);
            }
        }

        //Envio por correo electronico
        if(!empty($customer_payments_send_mail)){
            session(['customer_payments_send_mail' => $customer_payments_send_mail]);
            return redirect()->route('customer-payments/send-mails-import');
        }

        //Redireccion
        return redirect()->route('customer-payments.index');
    }

    private function autoSendMail($customer_payment){
        $to = [];
        if (!empty($customer_payment->customer->email)) {
            $tmp = explode(';',$customer_payment->customer->email);
            if(!empty($tmp)) {
                foreach($tmp as $email) {
                    $email = trim($email);
                    $to[$email] = $email;
                }
            }
        }
        if(!empty($to)) {
            $subject = $customer_payment->documentType->name .' ('.$customer_payment->name.') - ' . $customer_payment->company->name;
            $message = view('layouts.partials.customer_payments.message_send_mail',compact('customer_payment'))->render();
            $pdf = $this->print($customer_payment);
            $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
            $file_xml_pac = $path_xml . $customer_payment->customerPaymentCfdi->file_xml_pac;
            \Mail::to($to)->send(new SendCustomerPayment($customer_payment, $subject, $message, $pdf, $file_xml_pac, Helper::replyMails()));
            //Actualiza campo de enviado
            $customer_payment->updated_uid = \Auth::user()->id;
            $customer_payment->mail_sent = 1;
            $customer_payment->save();
        }
    }

    public function sendMailsImport(){
        try{
            $customer_payments_send_mail = session('customer_payments_send_mail', null);
            if(!empty($customer_payments_send_mail)){
                foreach($customer_payments_send_mail as $id){
                    $customer_payment = CustomerPayment::find($id);
                    $this->autoSendMail($customer_payment);
                    unset($customer_payments_send_mail[$id]);
                    session(['customer_payments_send_mail' => $customer_payments_send_mail]);
                }
            }
        }catch (\Exception $e){
            flash($e->getMessage())->error();
            return redirect()->route('customer-payments.index');
        }

        session()->forget('customer_payments_send_mail');

        return redirect()->route('customer-payments.index');
    }

    private function saveCfdiDownloads($customer_payment, $customer_payment_cfdi){
        //Directorio de XML
        $path_files = Helper::setDirectory(CfdiDownload::PATH_FILES, $customer_payment->company->id);
        $tmp_path = str_replace(['files/', '/xml'], '', CustomerPayment::PATH_XML_FILES);
        if (!\Storage::exists($path_files . '/' . $tmp_path)) {
            \Storage::makeDirectory($path_files . '/' . $tmp_path, 0777, true, true);
        }

        //Genera PDF
        $pdf = $this->print($customer_payment, false, true);
        $path_xml = Helper::setDirectory(CustomerPayment::PATH_XML_FILES, $customer_payment->company_id) . '/';
        $file_xml = $tmp_path . '/' . $customer_payment_cfdi->uuid . '.xml';
        $file_pdf = str_replace('.xml', '.pdf', $file_xml);
        \Storage::put($path_files . '/' . $file_pdf, $pdf);

        if(!empty($customer_payment_cfdi->file_xml_pac)){
            \Storage::copy($path_xml . $customer_payment_cfdi->file_xml_pac, $path_files . '/' . $file_xml);
        }

        //Guarda registro en CFDIS descargados
        $cfdi_download = CfdiDownload::create([
            'created_uid' => \Auth::user()->id,
            'updated_uid' => \Auth::user()->id,
            'type' => 2,
            'uuid' => $customer_payment_cfdi->uuid,
            'file_xml' => $file_xml,
            'file_pdf' => $file_pdf,
            'file_acuse' => null,
            'status' => 'Vigente',
            'is_cancelable' => 'Cancelable sin aceptación',
            'date_cancel' => null,
            'company_id' => $customer_payment->company_id,
        ]);
        $data_xml = Helper::parseXmlToArrayCfdi33($path_xml . '/' . $customer_payment_cfdi->file_xml_pac);
        $data_xml['data']['customer_id'] = $customer_payment->customer_id;
        $data_xml['data']['caccounting_type_id'] = $customer_payment->customer->caccounting_type_sale_id ?? null;
        $cfdi_download->fill($data_xml['data']);
        $cfdi_download->save();
    }

    public function addManualXml(Request $request){
        //Validaciones
        //Validacion de archivos por extension
        if ($request->hasFile('file_add_xml')) {
            $request->merge(['file_add_xml_ext' => strtolower(request()->file('file_add_xml')->getClientOriginalExtension())]);
        }
        $validator = \Validator::make($request->all(), [
            'file_add_xml' => 'required|max:512',
            'file_add_xml_ext' => 'nullable|in:xml'
        ], [
            'file_add_xml.*' => __('sales/customer_payment.error_file_add_xml'),
            'file_add_xml_ext.*' => __('sales/customer_payment.error_file_add_xml'),
        ]);
        //Errores
        if ($validator->fails()) {
            $errors = '';
            foreach ($validator->errors()->all() as $message) {
                $errors = $message;
            }
            return response()->json(['error' => $errors], 422);
        }

        $file_xml = request()->file('file_add_xml')->store('temp');
        $data = [];
        if (\Storage::exists($file_xml) && Helper::validateXmlToArrayCfdi33($file_xml)) {
            $xml_array = Helper::parseXmlToArrayCfdi33($file_xml);
            $xml_base_taxes = Helper::parseXmlToArrayCfdi33BaseImpuestos2($file_xml);
            $taxes = [];
            if(!empty($xml_base_taxes)){
                foreach($xml_base_taxes as $tax){
                    if($tax->amount_base > 0 ){
                        $taxes[] = [
                            'id' => $tax->id,
                            'amount_base' => $tax->amount_base
                        ];
                    }
                }
            }

            //
            $data['uuid'] = $xml_array['data']['uuid'];
            $data['serie'] = $xml_array['data']['serie'];
            $data['folio'] = $xml_array['data']['folio'];
            $data['currency_code'] = $xml_array['data']['currency_code'];
            $data['currency_value'] = $xml_array['data']['currency_value'];
            $data['payment_method_code'] = $xml_array['data']['payment_method_code'];
            $data['amount_total'] = $xml_array['data']['amount_total'];
            $data['taxes'] = $taxes;
        }

        //Mensaje
        return response()->json([
            'success' => 'ok',
            'data' => $data
        ]);
    }

    public function addManualUuid(Request $request){
        //Validaciones
        $data = [];
        if (!empty($request->uuid)){
            $cfdi_download = CfdiDownload::where('uuid', $request->uuid)->first();
            if(!empty($cfdi_download)){
                $path_files = Helper::setDirectory(CfdiDownload::PATH_FILES,$cfdi_download->company_id) . '/';
                $file_xml = $path_files . $cfdi_download->file_xml;
                if (\Storage::exists($file_xml) && Helper::validateXmlToArrayCfdi33($file_xml)) {
                    $xml_array = Helper::parseXmlToArrayCfdi33($file_xml);
                    $xml_base_taxes = Helper::parseXmlToArrayCfdi33BaseImpuestos2($file_xml);
                    $taxes = [];
                    if(!empty($xml_base_taxes)){
                        foreach($xml_base_taxes as $tax){
                            if($tax->amount_base > 0 ){
                                $taxes[] = [
                                    'id' => $tax->id,
                                    'amount_base' => $tax->amount_base
                                ];
                            }
                        }
                    }

                    //
                    $data['uuid'] = $xml_array['data']['uuid'];
                    $data['serie'] = $xml_array['data']['serie'];
                    $data['folio'] = $xml_array['data']['folio'];
                    $data['currency_code'] = $xml_array['data']['currency_code'];
                    $data['currency_value'] = $xml_array['data']['currency_value'];
                    $data['payment_method_code'] = $xml_array['data']['payment_method_code'];
                    $data['amount_total'] = $xml_array['data']['amount_total'];
                    $data['taxes'] = $taxes;
                }
            }
        }

        //Mensaje
        return response()->json([
            'success' => 'ok',
            'data' => $data
        ]);
    }

}
