<?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\CfdiUse;
use App\Models\Catalogs\Product;
use App\Models\Catalogs\Project;
use App\Models\Base\BranchOffice;
use App\Models\Base\CfdiDownload;
use App\Models\Catalogs\Currency;
use App\Models\Sales\Salesperson;
use App\Models\Catalogs\PaymentWay;
use App\Models\Catalogs\SatProduct;
use App\Models\Catalogs\TaxRegimen;
use App\Http\Controllers\Controller;
use App\Mail\SendCustomerCreditNote;
use App\Models\Catalogs\PaymentTerm;
use App\Models\Catalogs\UnitMeasure;
use Maatwebsite\Excel\Facades\Excel;
use App\Models\Catalogs\CfdiRelation;
use App\Models\Sales\CustomerInvoice;
use Illuminate\Support\Facades\Crypt;
use App\Models\Catalogs\PaymentMethod;
use App\Exports\CustomerCreditNotesExport;
use App\Models\Catalogs\ReasonCancellation;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
use Illuminate\Validation\ValidationException;
use App\Exports\CustomerCreditNoteTemplateImportExport;
use App\Imports\CustomerCreditNoteTemplateImportImport;
use App\Models\Sales\CustomerInvoice as CustomerCreditNote;
use App\Imports\CustomerCreditNoteTemplateBeforeImportImport;
use App\Models\Sales\CustomerInvoiceTax as CustomerCreditNoteTax;
use App\Models\Sales\CustomerInvoiceCfdi as CustomerCreditNoteCfdi;
use App\Models\Sales\CustomerInvoiceLine as CustomerCreditNoteLine;
use App\Models\Sales\CustomerInvoiceRelation as CustomerCreditNoteRelation;
use App\Models\Sales\CustomerInvoiceReconciled as CustomerCreditNoteReconciled;

class CustomerCreditNoteController extends Controller
{
    private $list_status = [];
    private $document_type_code = 'customer.credit_note';

    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this->list_status = [
            CustomerCreditNote::DRAFT => __('sales/customer_credit_note.text_status_draft'),
            CustomerCreditNote::OPEN => __('sales/customer_credit_note.text_status_open'),
            CustomerCreditNote::PER_RECONCILED => __('sales/customer_credit_note.text_status_per_reconciled'),
            CustomerCreditNote::RECONCILED => __('sales/customer_credit_note.text_status_reconciled'),
            CustomerCreditNote::CANCEL => __('sales/customer_credit_note.text_status_cancel'),
        ];
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index(Request $request)
    {
        //Variables
        $limit = ($request->has('limit') ? $request->get('limit') : 100);
        $salespersons = Salesperson::populateSelect()->pluck('name', 'id');
        $branch_offices = BranchOffice::populateSelect()->pluck('name', 'id');
        $list_status = $this->list_status;
        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 = CustomerCreditNote::filter($request->all())
            ->with('customerInvoiceCfdi')
            ->with('customer')
            ->with('salesperson')
            ->with('currency')
            ->sortable(['date' => 'desc'])->paginate($limit);

        //Vista
        return view('sales.customer_credit_notes.index',
            compact('results', 'salespersons', 'branch_offices', 'list_status'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        $salespersons = Salesperson::populateSelect()->pluck('name', 'id');
        $branch_offices = BranchOffice::populateSelect()->pluck('name', 'id');
        $currencies = Currency::populateSelect()->get()->pluck('name_sat', 'id');
        $payment_terms = PaymentTerm::populateSelect()->pluck('name', 'id');
        $payment_ways = PaymentWay::populateSelect()->get()->pluck('name_sat', 'id');
        $payment_methods = PaymentMethod::populateSelect()->get()->pluck('name_sat', 'id');
        $cfdi_uses = CfdiUse::populateSelect()->get()->pluck('name_sat', 'id');
        $taxes = Tax::populateSelect()->pluck('name', 'id');
        $cfdi_relations = CfdiRelation::populateSelect()->get()->pluck('name_sat', 'id');
        $projects = Project::populateSelect()->pluck('name', 'id');
        $company = Helper::defaultCompany(); //Empresa
        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');
        $tax_objects = __('general.text_tax_objects');

        return view('sales.customer_credit_notes.create',
            compact('salespersons', 'branch_offices', 'currencies', 'payment_ways', 'payment_terms', 'payment_methods',
                'cfdi_uses', 'taxes', 'cfdi_relations', 'projects','tax_regimens','tax_regimen_customers','tax_objects'));
    }

    /**
     * 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_credit_note = 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_credit_note) ? CustomerInvoice::DRAFT : CustomerInvoice::OPEN]);
            $request->merge(['company_id' => $company->id]);
            //Ajusta fecha y genera fecha de vencimiento
            $date = Helper::createDateTime($request->date);
            $request->merge(['date' => Helper::dateTimeToSql($date)]);
            $date_due = $date; //La fecha de vencimiento por default
            $request->merge(['date_due' => Helper::dateToSql($date_due)]);

            //Obtiene folio
            $document_type = Helper::getNextDocumentTypeByCode($this->document_type_code,$company->id,isset($request->pre_credit_note) ? 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(BaseHelper::getAvailableFolios()<=0 && !isset($request->pre_credit_note)){
                throw new \Exception(__('general.error_available_folios'));
            }

            //Guardar
            //Registro principal
            $customer_credit_note = CustomerCreditNote::create($request->input());

            //Registro de lineas
            $amount_discount = 0;
            $amount_untaxed = 0;
            $amount_tax = 0;
            $amount_tax_ret = 0;
            $amount_total = 0;
            $balance = 0;
            $taxes = array();
            //Lineas
            if (!empty($request->item)) {
                foreach ($request->item as $key => $item) {
                    //Logica
                    $item_quantity = (double)$item['quantity'];
                    $item_price_unit = (double)$item['price_unit'];
                    $item_quota_ieps = (double)$item['quota_ieps'];
                    $item_discount = 0;
                    $item_price_reduce = ($item_price_unit * (100 - $item_discount) / 100);
                    $item_amount_untaxed = round($item_quantity * $item_price_reduce, 2);
                    if($item_quota_ieps > 0){
                        $item_amount_untaxed_per_taxes = round($item_quantity * (($item_price_unit * (100 - $item_discount) / 100) - $item_quota_ieps), 2);
                    }else{
                        $item_amount_untaxed_per_taxes = $item_amount_untaxed;
                    }
                    $item_amount_discount = round($item_quantity * $item_price_unit, 2) - $item_amount_untaxed;
                    $item_amount_tax = 0;
                    $item_amount_tax_ret = 0;
                    if (!empty($item['taxes'])) {
                        foreach ($item['taxes'] as $tax_id) {
                            if (!empty($tax_id)) {
                                $tax = Tax::findOrFail($tax_id);
                                $tmp = 0;
                                if ($tax->factor == 'Tasa') {
                                    $tmp = $item_amount_untaxed_per_taxes * $tax->rate / 100;
                                } elseif ($tax->factor == 'Cuota') {
                                    $tmp = $tax->rate;
                                }
                                $tmp = round($tmp, 2);
                                if ($tax->type == 'R') { //Retenciones
                                    $item_amount_tax_ret += $tmp;
                                } else { //Traslados
                                    $item_amount_tax += $tmp;
                                }

                                //Sumatoria de impuestos
                                $taxes[$tax_id] = array(
                                    'amount_base' => $item_amount_untaxed_per_taxes + (isset($taxes[$tax_id]['amount_base']) ? $taxes[$tax_id]['amount_base'] : 0),
                                    'amount_tax' => $tmp + (isset($taxes[$tax_id]['amount_tax']) ? $taxes[$tax_id]['amount_tax'] : 0),
                                );
                            }
                        }
                    }
                    $item_amount_total = $item_amount_untaxed + $item_amount_tax + $item_amount_tax_ret;
                    //Sumatoria totales
                    $amount_discount += $item_amount_discount;
                    $amount_untaxed += $item_amount_untaxed;
                    $amount_tax += $item_amount_tax;
                    $amount_tax_ret += $item_amount_tax_ret;
                    $amount_total += $item_amount_total;

                    //Guardar linea
                    $customer_credit_note_line = CustomerCreditNoteLine::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_invoice_id' => $customer_credit_note->id,
                        'name' => $item['name'],
                        'product_id' => $item['product_id'],
                        'sat_product_id' => $item['sat_product_id'],
                        'unit_measure_id' => $item['unit_measure_id'],
                        'quantity' => $item_quantity,
                        'price_unit' => $item_price_unit,
                        'discount' => $item_discount,
                        'price_reduce' => $item_price_reduce,
                        'amount_discount' => $item_amount_discount,
                        'amount_untaxed' => $item_amount_untaxed,
                        'amount_tax' => $item_amount_tax,
                        'amount_tax_ret' => $item_amount_tax_ret,
                        'amount_total' => $item_amount_total,
                        'sort_order' => $key,
                        'status' => 1,
                        'quota_ieps' => $item_quota_ieps,
                        'tax_object' => !empty($item['tax_object']) ? $item['tax_object'] : (empty($item['taxes']) ? '01' : '02'),
                    ]);

                    //Guardar impuestos por linea
                    if (!empty($item['taxes'])) {
                        $customer_credit_note_line->taxes()->sync($item['taxes']);
                    } else {
                        $customer_credit_note_line->taxes()->sync([]);
                    }
                }
            }

            //Resumen de impuestos
            if (!empty($taxes)) {
                $i = 0;
                foreach ($taxes as $tax_id => $result) {
                    $tax = Tax::findOrFail($tax_id);
                    $customer_credit_note_tax = CustomerCreditNoteTax::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_invoice_id' => $customer_credit_note->id,
                        'name' => $tax->name,
                        'tax_id' => $tax_id,
                        'amount_base' => $result['amount_base'],
                        'amount_tax' => $result['amount_tax'],
                        'sort_order' => $i,
                        'status' => 1,
                    ]);
                    $i++;
                }
            }

            //Facturas conciliadas
            $amount_reconciled = 0;
            //Lineas
            if (!empty($request->item_reconciled)) {
                foreach ($request->item_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;
                        $amount_reconciled += $item_reconciled_amount_reconciled;

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

                        //Guardar linea
                        $customer_credit_note_reconciled = CustomerCreditNoteReconciled::create([
                            'created_uid' => \Auth::user()->id,
                            'updated_uid' => \Auth::user()->id,
                            'customer_invoice_id' => $customer_credit_note->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,
                        ]);

                        if(!isset($request->pre_credit_note)) {
                            //Actualiza el saldo de la factura relacionada
                            $customer_invoice->balance -= round(Helper::invertBalanceCurrency($customer_credit_note->currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);
                            if($customer_invoice->balance <= 0){
                                $customer_invoice->status = CustomerInvoice::PAID;
                            }
                            $customer_invoice->save();
                        }
                    }
                }
            }

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

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

            //Crear 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
            $customer_credit_note_cfdi = CustomerCreditNoteCfdi::create([
                'created_uid' => \Auth::user()->id,
                'updated_uid' => \Auth::user()->id,
                'customer_invoice_id' => $customer_credit_note->id,
                'name' => $customer_credit_note->name,
                'cfdi_version' => $class_cfdi,
                'status' => 1,
            ]);

            //Actualiza registro principal con totales
            $customer_credit_note->amount_discount = $amount_discount;
            $customer_credit_note->amount_untaxed = $amount_untaxed;
            $customer_credit_note->amount_tax = $amount_tax;
            $customer_credit_note->amount_tax_ret = $amount_tax_ret;
            $customer_credit_note->amount_total = $amount_total;
            $customer_credit_note->balance = $amount_total;
            $customer_credit_note->update();

            //Actualiza estatus de acuerdo al monto conciliado
            $customer_credit_note->balance = $amount_total - $amount_reconciled;
            if($customer_credit_note->balance <= 0 && !isset($request->pre_credit_note)){
                $customer_credit_note->status = CustomerCreditNote::RECONCILED;
            }
            $customer_credit_note->update();

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

            }else{ //Crear CFDI

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

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

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

                //Disminuye folios
                BaseHelper::decrementFolios();

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

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

            if(!isset($request->pre_credit_note)) {
                $this->saveCfdiDownloads($customer_credit_note, $customer_credit_note_cfdi);
            }


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

            \DB::connection('tenant')->rollback();
            $company = Helper::defaultCompany(); //Empresa
            \Log::error('(' . $company->taxid . ') ' . $e->getMessage());
            flash((isset($request->pre_credit_note) ? '' : __('general.error_cfdi_pac').'<br>').$e->getMessage())->error();
            return back()->withInput();
        }

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

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

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

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

            $path_xml = Helper::setDirectory(CustomerCreditNote::PATH_XML_FILES_CCN, $customer_credit_note->company_id) . '/';
            $file_xml_pac = $path_xml . $customer_credit_note->customerInvoiceCfdi->file_xml_pac;

            //Valida que el archivo exista
            if(!empty($customer_credit_note->customerInvoiceCfdi->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_credit_notes.show', compact('customer_credit_note','data'));
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  \App\Models\Sales\CustomerCreditNote as CustomerCreditNote  $customer_credit_note
     * @return \Illuminate\Http\Response
     */
    public function edit(CustomerCreditNote $customer_credit_note)
    {
        $salespersons = Salesperson::populateSelect()->pluck('name', 'id');
        $branch_offices = BranchOffice::populateSelect()->pluck('name', 'id');
        $currencies = Currency::populateSelect()->get()->pluck('name_sat', 'id');
        $payment_terms = PaymentTerm::populateSelect()->pluck('name', 'id');
        $payment_ways = PaymentWay::populateSelect()->get()->pluck('name_sat', 'id');
        $payment_methods = PaymentMethod::populateSelect()->get()->pluck('name_sat', 'id');
        $cfdi_uses = CfdiUse::populateSelect()->get()->pluck('name_sat', 'id');
        $taxes = Tax::populateSelect()->pluck('name', 'id');
        $cfdi_relations = CfdiRelation::populateSelect()->get()->pluck('name_sat', 'id');
        $projects = Project::populateSelect()->pluck('name', 'id');
        $company = $customer_credit_note->company; //Empresa
        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');
        $tax_objects = __('general.text_tax_objects');

        return view('sales.customer_credit_notes.edit',
            compact('customer_credit_note', 'salespersons', 'branch_offices', 'currencies', 'payment_ways', 'payment_terms', 'payment_methods',
                'cfdi_uses', 'taxes', 'cfdi_relations', 'projects','tax_regimens','tax_regimen_customers','tax_objects'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Models\Sales\CustomerCreditNote as CustomerCreditNote  $customer_credit_note
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, CustomerCreditNote $customer_credit_note)
    {
        //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_credit_note) ? CustomerInvoice::DRAFT : CustomerInvoice::OPEN]);
            //Ajusta fecha y genera fecha de vencimiento
            $date = Helper::createDateTime($request->date);
            $request->merge(['date' => Helper::dateTimeToSql($date)]);
            $date_due = $date; //La fecha de vencimiento por default
            $request->merge(['date_due' => Helper::dateToSql($date_due)]);
            $customer_credit_note->fill($request->only([
                'updated_uid',
                'date',
                'date_due',
                'reference',
                'customer_id',
                'branch_office_id',
                'payment_term_id',
                'payment_way_id',
                'payment_method_id',
                'cfdi_use_id',
                'salesperson_id',
                'currency_id',
                'currency_value',
                'cfdi_relation_id',
                'comment',
                'status',
                'title',
                'project_id',
                'tax_regimen_id',
                'tax_regimen_customer_id'
            ]));

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

            //Actualiza folios
            if(!isset($request->pre_credit_note)) {
                //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_credit_note->company->id, false, $customer_credit_note->branch_office_id);
                $customer_credit_note->draft = $customer_credit_note->name;
                $customer_credit_note->name = $document_type['name'];
                $customer_credit_note->serie = $document_type['serie'];
                $customer_credit_note->folio = $document_type['folio'];
                $customer_credit_note->save();
            }

            //Elimina partidas
            if (!empty($request->delete_item)) {
                foreach ($request->delete_item as $key => $result) {
                    //Actualizar status
                    $customer_credit_note_line = CustomerCreditNoteLine::findOrFail($result);
                    $customer_credit_note_line->updated_uid = \Auth::user()->id;
                    $customer_credit_note_line->status = 0;
                    $customer_credit_note_line->save();
                }
            }
            //Registro de lineas
            $amount_discount = 0;
            $amount_untaxed = 0;
            $amount_tax = 0;
            $amount_tax_ret = 0;
            $amount_total = 0;
            $balance = 0;
            $taxes = array();
            //Lineas
            if (!empty($request->item)) {
                foreach ($request->item as $key => $item) {
                    //Logica
                    $item_quantity = (double)$item['quantity'];
                    $item_price_unit = (double)$item['price_unit'];
                    $item_quota_ieps = (double)$item['quota_ieps'];
                    $item_discount = 0;
                    $item_price_reduce = ($item_price_unit * (100 - $item_discount) / 100);
                    $item_amount_untaxed = round($item_quantity * $item_price_reduce, 2);
                    if($item_quota_ieps > 0){
                        $item_amount_untaxed_per_taxes = round($item_quantity * (($item_price_unit * (100 - $item_discount) / 100) - $item_quota_ieps), 2);
                    }else{
                        $item_amount_untaxed_per_taxes = $item_amount_untaxed;
                    }
                    $item_amount_discount = round($item_quantity * $item_price_unit, 2) - $item_amount_untaxed;
                    $item_amount_tax = 0;
                    $item_amount_tax_ret = 0;
                    if (!empty($item['taxes'])) {
                        foreach ($item['taxes'] as $tax_id) {
                            if (!empty($tax_id)) {
                                $tax = Tax::findOrFail($tax_id);
                                $tmp = 0;
                                if ($tax->factor == 'Tasa') {
                                    $tmp = $item_amount_untaxed_per_taxes * $tax->rate / 100;
                                } elseif ($tax->factor == 'Cuota') {
                                    $tmp = $tax->rate;
                                }
                                $tmp = round($tmp, 2);
                                if ($tax->type == 'R') { //Retenciones
                                    $item_amount_tax_ret += $tmp;
                                } else { //Traslados
                                    $item_amount_tax += $tmp;
                                }

                                //Sumatoria de impuestos
                                $taxes[$tax_id] = array(
                                    'amount_base' => $item_amount_untaxed_per_taxes + (isset($taxes[$tax_id]['amount_base']) ? $taxes[$tax_id]['amount_base'] : 0),
                                    'amount_tax' => $tmp + (isset($taxes[$tax_id]['amount_tax']) ? $taxes[$tax_id]['amount_tax'] : 0),
                                );
                            }
                        }
                    }
                    $item_amount_total = $item_amount_untaxed + $item_amount_tax + $item_amount_tax_ret;
                    //Sumatoria totales
                    $amount_discount += $item_amount_discount;
                    $amount_untaxed += $item_amount_untaxed;
                    $amount_tax += $item_amount_tax;
                    $amount_tax_ret += $item_amount_tax_ret;
                    $amount_total += $item_amount_total;

                    //Guardar linea
                    $data = [
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_invoice_id' => $customer_credit_note->id,
                        'name' => $item['name'],
                        'product_id' => $item['product_id'],
                        'sat_product_id' => $item['sat_product_id'],
                        'unit_measure_id' => $item['unit_measure_id'],
                        'quantity' => $item_quantity,
                        'price_unit' => $item_price_unit,
                        'discount' => $item_discount,
                        'price_reduce' => $item_price_reduce,
                        'amount_discount' => $item_amount_discount,
                        'amount_untaxed' => $item_amount_untaxed,
                        'amount_tax' => $item_amount_tax,
                        'amount_tax_ret' => $item_amount_tax_ret,
                        'amount_total' => $item_amount_total,
                        'sort_order' => $key,
                        'status' => 1,
                        'quota_ieps' => $item_quota_ieps,
                        'tax_object' => !empty($item['tax_object']) ? $item['tax_object'] : (empty($item['taxes']) ? '01' : '02'),
                    ];
                    if (!empty($item['id'])) {
                        $customer_credit_note_line = CustomerCreditNoteLine::findOrFail($item['id']);
                        $customer_credit_note_line->fill(array_only($data, [
                            'updated_uid',
                            'name',
                            'product_id',
                            'sat_product_id',
                            'unit_measure_id',
                            'quantity',
                            'price_unit',
                            'discount',
                            'price_reduce',
                            'amount_discount',
                            'amount_untaxed',
                            'amount_tax',
                            'amount_tax_ret',
                            'amount_total',
                            'sort_order',
                            'quota_ieps',
                            'tax_object'
                        ]));
                        $customer_credit_note_line->save();
                    }else{
                        $customer_credit_note_line = CustomerCreditNoteLine::create($data);
                    }

                    //Guardar impuestos por linea
                    if (!empty($item['taxes'])) {
                        $customer_credit_note_line->taxes()->sync($item['taxes']);
                    } else {
                        $customer_credit_note_line->taxes()->sync([]);
                    }
                }
            }

            //Resumen de impuestos
            CustomerCreditNoteTax::where('customer_invoice_id','=',$customer_credit_note->id)->delete(); //Borra todo e inserta nuevamente
            if (!empty($taxes)) {
                $i = 0;
                foreach ($taxes as $tax_id => $result) {
                    $tax = Tax::findOrFail($tax_id);
                    $customer_credit_note_tax = CustomerCreditNoteTax::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_invoice_id' => $customer_credit_note->id,
                        'name' => $tax->name,
                        'tax_id' => $tax_id,
                        'amount_base' => $result['amount_base'],
                        'amount_tax' => $result['amount_tax'],
                        'sort_order' => $i,
                        'status' => 1,
                    ]);
                    $i++;
                }
            }

            //Facturas conciliadas
            $amount_reconciled = 0;
            //Lineas
            CustomerCreditNoteReconciled::where('customer_invoice_id','=',$customer_credit_note->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'])) {
                        //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;
                        $amount_reconciled += $item_reconciled_amount_reconciled;

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

                        //Guardar linea
                        $customer_credit_note_reconciled = CustomerCreditNoteReconciled::create([
                            'created_uid' => \Auth::user()->id,
                            'updated_uid' => \Auth::user()->id,
                            'customer_invoice_id' => $customer_credit_note->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,
                        ]);

                        if(!isset($request->pre_credit_note)){
                            //Actualiza el saldo de la factura relacionada
                            $customer_invoice->balance -= round(Helper::invertBalanceCurrency($customer_credit_note->currency,$item_reconciled_amount_reconciled,$customer_invoice->currency->code,$item_reconciled_currency_value),2);
                            if($customer_invoice->balance <= 0){
                                $customer_invoice->status = CustomerInvoice::PAID;
                            }
                            $customer_invoice->save();
                        }
                    }
                }
            }

            //Cfdi relacionados
            CustomerCreditNoteRelation::where('customer_invoice_id','=',$customer_credit_note->id)->delete(); //Borra todo e inserta nuevamente
            if (!empty($request->item_relation)) {
                foreach ($request->item_relation as $key => $result) {
                    $customer_credit_note_cfdi_relation = CustomerCreditNoteCfdi::where('customer_invoice_id','=',$result['relation_id'])->first();

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

            //Crear 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
            CustomerCreditNoteCfdi::where('customer_invoice_id','=',$customer_credit_note->id)->delete(); //Borra todo e inserta nuevamente
            $customer_credit_note_cfdi = CustomerCreditNoteCfdi::create([
                'created_uid' => \Auth::user()->id,
                'updated_uid' => \Auth::user()->id,
                'customer_invoice_id' => $customer_credit_note->id,
                'name' => $customer_credit_note->name,
                'cfdi_version' => $class_cfdi,
                'status' => 1,
            ]);

            //Actualiza registro principal con totales
            $customer_credit_note->amount_discount = $amount_discount;
            $customer_credit_note->amount_untaxed = $amount_untaxed;
            $customer_credit_note->amount_tax = $amount_tax;
            $customer_credit_note->amount_tax_ret = $amount_tax_ret;
            $customer_credit_note->amount_total = $amount_total;
            $customer_credit_note->balance = $amount_total;
            $customer_credit_note->update();

            //Actualiza estatus de acuerdo al monto conciliado
            $customer_credit_note->balance = $amount_total - $amount_reconciled;
            if($customer_credit_note->balance <= 0 && !isset($request->pre_credit_note)){
                $customer_credit_note->status = CustomerCreditNote::RECONCILED;
            }
            $customer_credit_note->update();


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

            }else{ //Crear CFDI

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

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

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

                //Disminuye folios
                BaseHelper::decrementFolios();

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

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

            if(!isset($request->pre_credit_note)) {
                $this->saveCfdiDownloads($customer_credit_note, $customer_credit_note_cfdi);
            }


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

            \DB::connection('tenant')->rollback();
            \Log::error('(' . $customer_credit_note->company->taxid . ') ' . $e->getMessage());
            flash((isset($request->pre_credit_note) ? '' : __('general.error_cfdi_pac').'<br>').$e->getMessage())->error();
            return back()->withInput();
        }

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

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

    /**
     * Remove the specified resource from storage.
     *
     * @param  \App\Models\Sales\CustomerCreditNote as CustomerCreditNote  $customer_credit_note
     * @return \Illuminate\Http\Response
     */
    public function destroy(Request $request, CustomerCreditNote $customer_credit_note)
    {
        \DB::connection('tenant')->beginTransaction();
        try {
            //Logica
            if ((int)$customer_credit_note->status != CustomerCreditNote::CANCEL) {

                //Actualiza saldos de facturas
                if(!in_array($customer_credit_note->status, [CustomerCreditNote::DRAFT])){
                    if($customer_credit_note->customerInvoiceReconcileds->isNotEmpty()){
                        foreach($customer_credit_note->customerInvoiceReconcileds as $result){
                            //Datos de factura
                            $customer_invoice = CustomerInvoice::findOrFail($result->reconciled_id);

                            //Actualiza el saldo de la factura relacionada
                            $customer_invoice->balance += round(Helper::invertBalanceCurrency($customer_credit_note->currency,$result->amount_reconciled,$customer_invoice->currency->code,$result->currency_value),2);
                            if($customer_invoice->balance > 0){
                                $customer_invoice->status = CustomerInvoice::OPEN;
                            }
                            $customer_invoice->save();
                        }
                    }
                }

                //Actualiza status
                $customer_credit_note->updated_uid = \Auth::user()->id;
                $customer_credit_note->status = CustomerCreditNote::CANCEL;
                $customer_credit_note->save();

                //Actualiza status CFDI
                $customer_credit_note->customerInvoiceCfdi->status = 0;
                $customer_credit_note->customerInvoiceCfdi->reason_cancellation_id = $request->reason_cancellation_id;
                $customer_credit_note->customerInvoiceCfdi->reason_cancellation_uuid = $request->reason_cancellation_uuid;
                $customer_credit_note->customerInvoiceCfdi->save();

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

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

                    //Arreglo temporal para actualizar Customer Credit Note CFDI
                    $tmp = [
                        'cancel_date' => Helper::dateTimeToSql(\Date::now()),
                        'cancel_response' => '',
                        'cancel_state' => '',
                        'rfcR' => $data['cfdi33']->Receptor['Rfc'] ?? $customer_credit_note->customer->taxid,
                        'uuid' => $customer_credit_note->customerInvoiceCfdi->uuid,
                        'total' => Helper::numberFormat($customer_credit_note->amount_total,
                            $customer_credit_note->currency->decimal_place, false),
                        'cfdi_type' => $customer_credit_note->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_credit_note->customerInvoiceCfdi->reasonCancellation->code ?? '',
                        'reason_cancellation_uuid' => $customer_credit_note->customerInvoiceCfdi->reason_cancellation_uuid ?? '',
                    ];

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

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

                    $cfdi_download = CfdiDownload::where('uuid', $customer_credit_note->customerInvoiceCfdi->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_credit_note->company->taxid . ') ' . $e->getMessage());
            flash($e->getMessage())->error();
            return redirect('/sales/customer-credit-notes');
        }

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

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

    /**
     * Validacion de formulario
     *
     * @param Request $request
     */
    public function validation(Request $request)
    {
        $this->validate($request, [
            'customer_id' => 'required|integer',
            'branch_office_id' => 'required|integer',
            'date' => 'required|date|date_format:"'.setting('datetime_format', 'd-m-Y H:i:s').'"',
            'currency_id' => 'required|integer',
            'currency_value' => 'required|numeric|min:0.1',
            'payment_term_id' => 'required|integer',
            'payment_way_id' => 'required|integer',
            'payment_method_id' => 'required|integer',
            'cfdi_use_id' => 'required|integer',
            'cfdi_relation_id' => 'nullable|integer|required_with:item_relation',
            'item' => 'required',
            'item.*.name' => 'required',
            'item.*.unit_measure_id' => 'required',
            'item.*.sat_product_id' => 'required',
            'item.*.quantity' => 'required|numeric|min:0.00001',
            'item.*.price_unit' => 'required|numeric|min:0.00001',
            'item.*.discount' => 'nullable|numeric|min:0.001',
            'item_reconciled.*.amount_reconciled' => 'nullable|numeric|min:0',
            'item_relation.*.uuid_related' => 'required',

        ], [
            'customer_id.*' => __('sales/customer_credit_note.error_customer_id'),
            'branch_office_id.*' => __('sales/customer_credit_note.error_branch_office_id'),
            'date.required' => __('sales/customer_credit_note.error_date'),
            'date.date' => __('sales/customer_credit_note.error_date_format'),
            'date.date_format' => __('sales/customer_credit_note.error_date_format'),
            'currency_id.*' => __('sales/customer_credit_note.error_currency_id'),
            'currency_value.*' => __('sales/customer_credit_note.error_currency_value'),
            'payment_term_id.*' => __('sales/customer_credit_note.error_payment_term_id'),
            'payment_way_id.*' => __('sales/customer_credit_note.error_payment_way_id'),
            'payment_method_id.*' => __('sales/customer_credit_note.error_payment_method_id'),
            'cfdi_use_id.*' => __('sales/customer_credit_note.error_cfdi_use_id'),
            'cfdi_relation_id.*' => __('sales/customer_credit_note.error_cfdi_relation_id'),
            'item.required' => __('sales/customer_credit_note.error_item'),
            'item.*.name.*' => __('sales/customer_credit_note.error_line_name'),
            'item.*.unit_measure_id.*' => __('sales/customer_credit_note.error_line_unit_measure_id'),
            'item.*.sat_product_id.*' => __('sales/customer_credit_note.error_line_sat_product_id'),
            'item.*.quantity.*' => __('sales/customer_credit_note.error_line_quantity'),
            'item.*.price_unit.*' => __('sales/customer_credit_note.error_line_price_unit'),
            'item.*.discount.*' => __('sales/customer_credit_note.error_line_discount'),
            'item_reconciled.*.amount_reconciled.*' => __('sales/customer_credit_note.error_reconciled_amount_reconciled'),
            'item_relation.*.uuid_related.*' => __('sales/customer_credit_note.error_relation_relation_id'),
        ]);
        //Validaciones manuales
        $validator = \Validator::make([], []);

        //Valida que el monto total de las lineas no supere al pago
        $amount_total = (double)$request->amount_total_tmp;
        $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){
                        $validator->after(function ($validator) use($key) {
                            $validator->errors()->add('item_reconciled.'.$key.'.amount_reconciled', __('sales/customer_credit_note.error_reconciled_amount_reconciled_customer_invoice_balance'));
                        });
                    }
                }
            }
        }

        //Valida que el monto conciliado no supere el monto del pago
        if($amount_reconciled > ($amount_total + 0.0001)){
            $validator->after(function ($validator) {
                $validator->errors()->add('amount_total', __('sales/customer_credit_note.error_amount_total_amount_reconciled'));
            });
        }

        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_credit_note.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_credit_note.error_postcode_customer'));
                });
            }
        }

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

    /**
     * Crear XML y enviar a timbrar CFDI 3.3
     *
     * @param CustomerCreditNote $customer_credit_note
     * @return array|\CfdiUtils\Elements\Cfdi33\Concepto|float|int
     * @throws \Exception
     */
    private function cfdi33(CustomerCreditNote $customer_credit_note)
    {
        $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_credit_note->serie)) {
                $cfdi33['Serie'] = $customer_credit_note->serie;
            }
            $cfdi33['Folio'] = $customer_credit_note->folio;
            $cfdi33['Fecha'] = \Date::parse($customer_credit_note->date)->format('Y-m-d\TH:i:s');
            //$cfdi33['Sello']
            $cfdi33['FormaPago'] = $customer_credit_note->paymentWay->code;
            $cfdi33['NoCertificado'] = $customer_credit_note->company->certificate_number;
            //$cfdi33['Certificado']
            $cfdi33['CondicionesDePago'] = $customer_credit_note->paymentTerm->name;
            $cfdi33['SubTotal'] = Helper::numberFormat($customer_credit_note->amount_untaxed + $customer_credit_note->amount_discount,
                $customer_credit_note->currency->decimal_place, false);
            if($customer_credit_note->amount_discount>0) {
                $cfdi33['Descuento'] = Helper::numberFormat($customer_credit_note->amount_discount,
                    $customer_credit_note->currency->decimal_place, false);
            }
            $cfdi33['Moneda'] = $customer_credit_note->currency->code;
            if ($customer_credit_note->currency->code != 'MXN') {
                $cfdi33['TipoCambio'] = Helper::numberFormat($customer_credit_note->currency_value, 4, false);
            }
            $cfdi33['Total'] = Helper::numberFormat($customer_credit_note->amount_total,
                $customer_credit_note->currency->decimal_place, false);
            $cfdi33['TipoDeComprobante'] = $customer_credit_note->documentType->cfdiType->code;
            $cfdi33['MetodoPago'] = $customer_credit_note->paymentMethod->code;
            $cfdi33['LugarExpedicion'] = $customer_credit_note->branchOffice->postcode;
            if (!empty($customer_credit_note->confirmacion)) {
                $cfdi33['Confirmacion'] = $customer_credit_note->confirmacion;
            }
            //---Cfdi Relacionados
            $cfdi33_relacionados = [];
            $cfdi33_relacionado = [];
            if (!empty($customer_credit_note->cfdi_relation_id)) {
                $cfdi33_relacionados['TipoRelacion'] = $customer_credit_note->cfdiRelation->code;
                if ($customer_credit_note->customerInvoiceRelations->isNotEmpty()) {
                    foreach ($customer_credit_note->customerInvoiceRelations as $key => $result) {
                        $cfdi33_relacionado[$key] = [];
                        $cfdi33_relacionado[$key]['UUID'] = $result->uuid_related;
                    }
                }
            }
            //---Emisor
            $cfdi33_emisor = [];
            $cfdi33_emisor['Rfc'] = $customer_credit_note->company->taxid;
            $cfdi33_emisor['Nombre'] = trim($customer_credit_note->company->name);
            $cfdi33_emisor['RegimenFiscal'] = !empty($customer_credit_note->tax_regimen_id) ? $customer_credit_note->taxRegimen->code : $customer_credit_note->company->taxRegimen->code;
            //---Receptor
            $cfdi33_receptor = [];
            $cfdi33_receptor['Rfc'] = $customer_credit_note->customer->taxid;
            $cfdi33_receptor['Nombre'] = trim($customer_credit_note->customer->name);
            if ($customer_credit_note->customer->taxid == 'XEXX010101000') {
                $cfdi33_receptor['ResidenciaFiscal'] = $customer_credit_note->customer->country->code;
                $cfdi33_receptor['NumRegIdTrib'] = $customer_credit_note->customer->numid;
            }
            $cfdi33_receptor['UsoCFDI'] = $customer_credit_note->cfdiUse->code;
            //---Conceptos
            $cfdi33_conceptos = [];
            $cfdi33_conceptos_traslados = [];
            $cfdi33_conceptos_retenciones = [];
            foreach ($customer_credit_note->customerActiveInvoiceLines as $key => $result) {
                $cfdi33_conceptos [$key]['ClaveProdServ'] = $result->satProduct->code;
                if (!empty($result->product->code)) {
                    $cfdi33_conceptos [$key]['NoIdentificacion'] = trim($result->product->code);
                }
                $cfdi33_conceptos [$key]['Cantidad'] = Helper::numberFormat($result->quantity, 6, false);
                $cfdi33_conceptos [$key]['ClaveUnidad'] = trim($result->unitMeasure->code);
                $cfdi33_conceptos [$key]['Unidad'] = str_limit(trim($result->unitMeasure->name),20,'');
                $cfdi33_conceptos [$key]['Descripcion'] = trim($result->name);
                $cfdi33_conceptos [$key]['ValorUnitario'] = Helper::numberFormat($result->price_unit, 6, false);
                $cfdi33_conceptos [$key]['Importe'] = Helper::numberFormat($result->amount_untaxed + $result->amount_discount,
                    2, false);
                if($result->amount_discount>0) {
                    $cfdi33_conceptos [$key]['Descuento'] = Helper::numberFormat($result->amount_discount, 2, false);
                }
                //['InformacionAduanera']
                //['CuentaPredial']
                //['ComplementoConcepto']
                //['Parte']

                //Impuestos por concepto
                $cfdi33_conceptos_traslados[$key] = [];
                $cfdi33_conceptos_retenciones[$key] = [];
                if ($result->taxes) {
                    foreach ($result->taxes as $key2 => $result2) {
                        $tmp = 0;
                        $rate = $result2->rate;
                        if($result->quota_ieps > 0){
                            $item_amount_untaxed_per_taxes = round($result->quantity * (($result->price_unit * (100 - $result->discount) / 100) - $result->quota_ieps), 2);
                        }else{
                            $item_amount_untaxed_per_taxes = $result->amount_untaxed;
                        }
                        if ($result2->factor == 'Tasa') {
                            $rate /= 100;
                            $tmp = $item_amount_untaxed_per_taxes * $rate;
                        } elseif ($result2->factor == 'Cuota') {
                            $tmp = $rate;
                        }
                        $tmp = round($tmp, 2);
                        if ($result2->type == 'R') { //Retenciones
                            $cfdi33_conceptos_retenciones[$key][$key2] = [];
                            $cfdi33_conceptos_retenciones[$key][$key2]['Base'] = Helper::numberFormat($item_amount_untaxed_per_taxes,
                                2, false);
                            $cfdi33_conceptos_retenciones[$key][$key2]['Impuesto'] = $result2->code;
                            $cfdi33_conceptos_retenciones[$key][$key2]['TipoFactor'] = $result2->factor; //Para retenciones no hay exento
                            $cfdi33_conceptos_retenciones[$key][$key2]['TasaOCuota'] = Helper::numberFormat(abs($rate),
                                6, false);
                            $cfdi33_conceptos_retenciones[$key][$key2]['Importe'] = Helper::numberFormat(abs($tmp), 2,
                                false);
                        } else { //Traslados
                            $cfdi33_conceptos_traslados[$key][$key2] = [];
                            $cfdi33_conceptos_traslados[$key][$key2]['Base'] = Helper::numberFormat($item_amount_untaxed_per_taxes,
                                2, false);
                            $cfdi33_conceptos_traslados[$key][$key2]['Impuesto'] = $result2->code;
                            $cfdi33_conceptos_traslados[$key][$key2]['TipoFactor'] = $result2->factor; //Para retenciones no hay exento
                            if ($result2->factor != 'Exento') {
                                $cfdi33_conceptos_traslados[$key][$key2]['TasaOCuota'] = Helper::numberFormat(abs($rate),
                                    6, false);
                                $cfdi33_conceptos_traslados[$key][$key2]['Importe'] = Helper::numberFormat(abs($tmp), 2,
                                    false);
                            }
                        }
                    }
                }
            }
            //Impuestos
            $cfdi33_retenciones = [];
            $cfdi33_traslados = [];
            if ($customer_credit_note->customerInvoiceTaxes->isNotEmpty()) {
                foreach ($customer_credit_note->customerInvoiceTaxes as $key => $result) {
                    $tmp = $result->amount_tax;
                    $rate = $result->tax->rate;
                    if ($result->tax->factor == 'Tasa') {
                        $rate /= 100;
                    }
                    if ($result->tax->type == 'R') { //Retenciones
                        $cfdi33_retenciones[$key] = [];
                        $cfdi33_retenciones[$key]['Impuesto'] = $result->tax->code;
                        $cfdi33_retenciones[$key]['Importe'] = Helper::numberFormat(abs($tmp),
                            $customer_credit_note->currency->decimal_place, false);
                    } else { //Traslados
                        if ($result->tax->factor != 'Exento') {
                            $cfdi33_traslados[$key] = [];
                            $cfdi33_traslados[$key]['Impuesto'] = $result->tax->code;
                            $cfdi33_traslados[$key]['TipoFactor'] = $result->tax->factor; //Para retenciones no hay exento
                            $cfdi33_traslados[$key]['TasaOCuota'] = Helper::numberFormat(abs($rate), 6, false);
                            $cfdi33_traslados[$key]['Importe'] = Helper::numberFormat(abs($tmp), 2, false);
                        }
                    }
                }
            }
            $cfdi33_impuestos = [];
            if (abs($customer_credit_note->amount_tax_ret) > 0 || !empty($cfdi33_retenciones)) {
                $cfdi33_impuestos['TotalImpuestosRetenidos'] = Helper::numberFormat(abs($customer_credit_note->amount_tax_ret),
                    $customer_credit_note->currency->decimal_place, false);
            }
            if (abs($customer_credit_note->amount_tax) > 0 || !empty($cfdi33_traslados)) {
                $cfdi33_impuestos['TotalImpuestosTrasladados'] = Helper::numberFormat(abs($customer_credit_note->amount_tax),
                    $customer_credit_note->currency->decimal_place, false);
            }

            //Genera XML
            $certificado = new \CfdiUtils\Certificado\Certificado(\Storage::path($customer_credit_note->company->pathFileCer()));
            $creator = new \CfdiUtils\CfdiCreator33($cfdi33, $certificado);
            $creator->setXmlResolver(PacHelper::resourcePathCfdiUtils()); //Almacenamiento local
            $comprobante = $creator->comprobante();
            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);
                if (!empty($cfdi33_conceptos_traslados[$key])) {
                    foreach ($cfdi33_conceptos_traslados[$key] as $result2) {
                        $concepto->multiTraslado($result2);
                    }
                }
                if (!empty($cfdi33_conceptos_retenciones[$key])) {
                    foreach ($cfdi33_conceptos_retenciones[$key] as $result2) {
                        $concepto->multiRetencion($result2);
                    }
                }
            }
            //Impuestos
            $comprobante->addImpuestos($cfdi33_impuestos);
            if (!empty($cfdi33_retenciones)) {
                foreach ($cfdi33_retenciones as $result2) {
                    $comprobante->multiRetencion($result2);
                }
            }
            if (!empty($cfdi33_traslados)) {
                foreach ($cfdi33_traslados as $result2) {
                    $comprobante->multiTraslado($result2);
                }
            }
            //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_credit_note->company->pathFileKeyPassPem()), Crypt::decryptString($customer_credit_note->company->password_key));
            //Valida la estructura
            //$creator->validate();

            //Guarda XML
            //dd($creator->asXml());
            $path_xml = Helper::setDirectory(CustomerCreditNote::PATH_XML_FILES_CCN, $customer_credit_note->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 Credit Note 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 CustomerCreditNote $customer_credit_note
     * @return array|\CfdiUtils\Elements\Cfdi33\Concepto|float|int
     * @throws \Exception
     */
    private function cfdi40(CustomerCreditNote $customer_credit_note)
    {
        $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_credit_note->serie)) {
                $cfdi33['Serie'] = $customer_credit_note->serie;
            }
            $cfdi33['Folio'] = $customer_credit_note->folio;
            $cfdi33['Fecha'] = \Date::parse($customer_credit_note->date)->format('Y-m-d\TH:i:s');
            //$cfdi33['Sello']
            $cfdi33['FormaPago'] = $customer_credit_note->paymentWay->code;
            $cfdi33['NoCertificado'] = $customer_credit_note->company->certificate_number;
            //$cfdi33['Certificado']
            $cfdi33['CondicionesDePago'] = $customer_credit_note->paymentTerm->name;
            $cfdi33['SubTotal'] = Helper::numberFormat($customer_credit_note->amount_untaxed + $customer_credit_note->amount_discount,
                $customer_credit_note->currency->decimal_place, false);
            if($customer_credit_note->amount_discount>0) {
                $cfdi33['Descuento'] = Helper::numberFormat($customer_credit_note->amount_discount,
                    $customer_credit_note->currency->decimal_place, false);
            }
            $cfdi33['Moneda'] = $customer_credit_note->currency->code;
            if ($customer_credit_note->currency->code != 'MXN') {
                $cfdi33['TipoCambio'] = Helper::numberFormat($customer_credit_note->currency_value, 4, false);
            }
            $cfdi33['Total'] = Helper::numberFormat($customer_credit_note->amount_total,
                $customer_credit_note->currency->decimal_place, false);
            $cfdi33['TipoDeComprobante'] = $customer_credit_note->documentType->cfdiType->code;
            $cfdi33['Exportacion'] = '01';
            $cfdi33['MetodoPago'] = $customer_credit_note->paymentMethod->code;
            $cfdi33['LugarExpedicion'] = $customer_credit_note->branchOffice->postcode;
            if (!empty($customer_credit_note->confirmacion)) {
                $cfdi33['Confirmacion'] = $customer_credit_note->confirmacion;
            }
            //---Informacion global
            //---Cfdi Relacionados
            $cfdi33_relacionados = [];
            $cfdi33_relacionado = [];
            if (!empty($customer_credit_note->cfdi_relation_id)) {
                $cfdi33_relacionados['TipoRelacion'] = $customer_credit_note->cfdiRelation->code;
                if ($customer_credit_note->customerInvoiceRelations->isNotEmpty()) {
                    foreach ($customer_credit_note->customerInvoiceRelations as $key => $result) {
                        $cfdi33_relacionado[$key] = [];
                        $cfdi33_relacionado[$key]['UUID'] = $result->uuid_related;
                    }
                }
            }
            //---Emisor
            $cfdi33_emisor = [];
            $cfdi33_emisor['Rfc'] = $customer_credit_note->company->taxid;
            $cfdi33_emisor['Nombre'] = trim($customer_credit_note->company->name);
            $cfdi33_emisor['RegimenFiscal'] = !empty($customer_credit_note->tax_regimen_id) ? $customer_credit_note->taxRegimen->code : $customer_credit_note->company->taxRegimen->code;
            $cfdi33_emisor['FacAtrAdquirente'] = null;
            //---Receptor
            $cfdi33_receptor = [];
            $cfdi33_receptor['Rfc'] = $customer_credit_note->customer->taxid;
            $cfdi33_receptor['Nombre'] = trim($customer_credit_note->customer->name);
            if (!in_array($customer_credit_note->customer->taxid, ['XAXX010101000','XEXX010101000'])) {
                $cfdi33_receptor['DomicilioFiscalReceptor'] = $customer_credit_note->customer->postcode;
            }else{
                $cfdi33_receptor['DomicilioFiscalReceptor'] = $customer_credit_note->branchOffice->postcode;
            }
            if ($customer_credit_note->customer->taxid == 'XEXX010101000') {
                $cfdi33_receptor['ResidenciaFiscal'] = $customer_credit_note->customer->country->code;
                $cfdi33_receptor['NumRegIdTrib'] = $customer_credit_note->customer->numid;
            }
            $cfdi33_receptor['RegimenFiscalReceptor'] = !empty($customer_credit_note->tax_regimen_customer_id) ? $customer_credit_note->taxRegimenCustomer->code : $customer_credit_note->customer->taxRegimen->code;
            $cfdi33_receptor['UsoCFDI'] = $customer_credit_note->cfdiUse->code;
            //---Conceptos
            $cfdi33_conceptos = [];
            $cfdi33_conceptos_traslados = [];
            $cfdi33_conceptos_retenciones = [];
            $local_taxes_total = 0;
            $local_taxes_total_ret = 0;
            foreach ($customer_credit_note->customerActiveInvoiceLines as $key => $result) {
                $cfdi33_conceptos [$key]['ClaveProdServ'] = $result->satProduct->code;
                if (!empty($result->product->code)) {
                    $cfdi33_conceptos [$key]['NoIdentificacion'] = trim($result->product->code);
                }
                $cfdi33_conceptos [$key]['Cantidad'] = Helper::numberFormat($result->quantity, 6, false);
                $cfdi33_conceptos [$key]['ClaveUnidad'] = trim($result->unitMeasure->code);
                $cfdi33_conceptos [$key]['Unidad'] = str_limit(trim($result->unitMeasure->name),20,'');
                $cfdi33_conceptos [$key]['Descripcion'] = trim($result->name);
                $cfdi33_conceptos [$key]['ValorUnitario'] = Helper::numberFormatRealDecimalPlace($result->price_unit, false);
                $cfdi33_conceptos [$key]['Importe'] = Helper::numberFormat($result->amount_untaxed + $result->amount_discount,
                    2, false);
                if($result->amount_discount>0) {
                    $cfdi33_conceptos [$key]['Descuento'] = Helper::numberFormat($result->amount_discount, 2, false);
                }
                //['InformacionAduanera']
                //['CuentaPredial']
                //['ComplementoConcepto']
                //['Parte']

                //Impuestos por concepto
                $cfdi33_conceptos_traslados[$key] = [];
                $cfdi33_conceptos_retenciones[$key] = [];
                if ($result->taxes) {
                    foreach ($result->taxes as $key2 => $result2) {
                        $tmp = 0;
                        $rate = $result2->rate;
                        if($result->quota_ieps > 0){
                            $item_amount_untaxed_per_taxes = round($result->quantity * (($result->price_unit * (100 - $result->discount) / 100) - $result->quota_ieps), 2);
                        }else{
                            $item_amount_untaxed_per_taxes = $result->amount_untaxed;
                        }
                        if ($result2->factor == 'Tasa') {
                            $rate /= 100;
                            $tmp = $item_amount_untaxed_per_taxes * $rate;
                        } elseif ($result2->factor == 'Cuota') {
                            $tmp = $rate;
                        }
                        $tmp = round($tmp, 2);
                        if ($result2->type == 'R') { //Retenciones
                            if($result2->local_taxes) {
                                $local_taxes_total_ret += abs($tmp);
                                continue;
                            }
                            $cfdi33_conceptos_retenciones[$key][$key2] = [];
                            $cfdi33_conceptos_retenciones[$key][$key2]['Base'] = Helper::numberFormat($item_amount_untaxed_per_taxes,
                                2, false);
                            $cfdi33_conceptos_retenciones[$key][$key2]['Impuesto'] = $result2->code;
                            $cfdi33_conceptos_retenciones[$key][$key2]['TipoFactor'] = $result2->factor; //Para retenciones no hay exento
                            $cfdi33_conceptos_retenciones[$key][$key2]['TasaOCuota'] = Helper::numberFormat(abs($rate),
                                6, false);
                            $cfdi33_conceptos_retenciones[$key][$key2]['Importe'] = Helper::numberFormat(abs($tmp), 2,
                                false);
                        } else { //Traslados
                            if($result2->local_taxes) {
                                $local_taxes_total += abs($tmp);
                                continue;
                            }
                            $cfdi33_conceptos_traslados[$key][$key2] = [];
                            $cfdi33_conceptos_traslados[$key][$key2]['Base'] = Helper::numberFormat($item_amount_untaxed_per_taxes,
                                2, false);
                            $cfdi33_conceptos_traslados[$key][$key2]['Impuesto'] = $result2->code;
                            $cfdi33_conceptos_traslados[$key][$key2]['TipoFactor'] = $result2->factor; //Para retenciones no hay exento
                            if ($result2->factor != 'Exento') {
                                $cfdi33_conceptos_traslados[$key][$key2]['TasaOCuota'] = Helper::numberFormat(abs($rate),
                                    6, false);
                                $cfdi33_conceptos_traslados[$key][$key2]['Importe'] = Helper::numberFormat(abs($tmp), 2,
                                    false);
                            }
                        }
                    }
                }
                $cfdi33_conceptos [$key]['ObjetoImp'] = !empty($result->tax_object) ? $result->tax_object  : ((empty($cfdi33_conceptos_traslados[$key]) && empty($cfdi33_conceptos_retenciones[$key])) ? '01' : '02');
            }
            //Impuestos
            $cfdi33_retenciones = [];
            $cfdi33_traslados = [];
            $cfdi33_exento = null;
            if ($customer_credit_note->customerInvoiceTaxes->isNotEmpty()) {
                foreach ($customer_credit_note->customerInvoiceTaxes as $key => $result) {
                    $tmp = $result->amount_tax;
                    $rate = $result->tax->rate;
                    if ($result->tax->factor == 'Tasa') {
                        $rate /= 100;
                    }
                    //Omite los impuestos locales
                    if($result->tax->local_taxes) {
                        continue;
                    }
                    if ($result->tax->type == 'R') { //Retenciones
                        $cfdi33_retenciones[$key] = [];
                        $cfdi33_retenciones[$key]['Impuesto'] = $result->tax->code;
                        $cfdi33_retenciones[$key]['Importe'] = Helper::numberFormat(abs($tmp),
                            $customer_credit_note->currency->decimal_place, false);
                    } else { //Traslados
                        $cfdi33_traslados[$key] = [];
                        $cfdi33_traslados[$key]['Base'] = Helper::numberFormat(abs($result->amount_base), 2, false);
                        $cfdi33_traslados[$key]['Impuesto'] = $result->tax->code;
                        $cfdi33_traslados[$key]['TipoFactor'] = $result->tax->factor; //Para retenciones no hay exento
                        if ($result->tax->factor != 'Exento') {
                            $cfdi33_traslados[$key]['TasaOCuota'] = Helper::numberFormat(abs($rate), 6, false);
                            $cfdi33_traslados[$key]['Importe'] = Helper::numberFormat(abs($tmp), 2, false);
                        }
                        if($result->tax->factor == 'Exento' && ($cfdi33_exento || is_null($cfdi33_exento)))
                        {
                            $cfdi33_exento = true;
                        }
                        if($result->tax->factor != 'Exento')
                        {
                            $cfdi33_exento = false;
                        }
                    }
                }
            }
            $cfdi33_impuestos = [];
            if (abs($customer_credit_note->amount_tax_ret) > 0 || !empty($cfdi33_retenciones)) {
                $cfdi33_impuestos['TotalImpuestosRetenidos'] = Helper::numberFormat(abs($customer_credit_note->amount_tax_ret) - $local_taxes_total_ret,
                    $customer_credit_note->currency->decimal_place, false);
            }
            if (abs($customer_credit_note->amount_tax) > 0 || !empty($cfdi33_traslados)) {
                $cfdi33_impuestos['TotalImpuestosTrasladados'] = Helper::numberFormat(abs($customer_credit_note->amount_tax) - $local_taxes_total,
                    $customer_credit_note->currency->decimal_place, false);
            }
            if($cfdi33_exento){
                $cfdi33_impuestos['TotalImpuestosTrasladados'] = null;
            }

            //Genera XML
            $certificado = new \CfdiUtils\Certificado\Certificado(\Storage::path($customer_credit_note->company->pathFileCer()));
            $creator = new \CfdiUtils\CfdiCreator40($cfdi33, $certificado);
            $creator->setXmlResolver(PacHelper::resourcePathCfdiUtils()); //Almacenamiento local
            $comprobante = $creator->comprobante();

            $schema_location_implocal='';
            //Impuestos locales
            if($local_taxes_total > 0 || $local_taxes_total_ret > 0) {
                $schema_location_implocal=' http://www.sat.gob.mx/implocal http://www.sat.gob.mx/sitio_internet/cfd/implocal/implocal.xsd';
            }
            if(!empty($schema_location_implocal)){
                $comprobante->addAttributes([
                    'xsi:schemaLocation' => 'http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd' . $schema_location_implocal,
                ]);
                if(!empty($schema_location_implocal)){
                    $comprobante->addAttributes([
                        'xmlns:implocal' => 'http://www.sat.gob.mx/implocal'
                    ]);
                }
            }

            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);
                if (!empty($cfdi33_conceptos_traslados[$key])) {
                    foreach ($cfdi33_conceptos_traslados[$key] as $result2) {
                        $concepto->multiTraslado($result2);
                    }
                }
                if (!empty($cfdi33_conceptos_retenciones[$key])) {
                    foreach ($cfdi33_conceptos_retenciones[$key] as $result2) {
                        $concepto->multiRetencion($result2);
                    }
                }
            }
            //Impuestos
            if(!empty($cfdi33_impuestos)) {
                $comprobante->addImpuestos($cfdi33_impuestos);
                if (!empty($cfdi33_retenciones)) {
                    foreach ($cfdi33_retenciones as $result2) {
                        $comprobante->multiRetencion($result2);
                    }
                }
                if (!empty($cfdi33_traslados)) {
                    foreach ($cfdi33_traslados as $result2) {
                        $comprobante->multiTraslado($result2);
                    }
                }
            }

            //Complemento de impuestos locales
            if($local_taxes_total > 0 || $local_taxes_total_ret > 0) {
                $implocal = [];
                $implocal['version'] = '1.0';
                $implocal['TotaldeRetenciones'] = Helper::numberFormat($local_taxes_total_ret, 2, false);
                $implocal['TotaldeTraslados'] = Helper::numberFormat($local_taxes_total, 2, false);
                $nodo_implocal = new \CfdiUtils\Nodes\Node(
                    'implocal:ImpuestosLocales', // nombre del elemento raíz
                    $implocal
                );
                if ($customer_credit_note->customerInvoiceTaxes->isNotEmpty()) {
                    foreach ($customer_credit_note->customerInvoiceTaxes as $key => $result) {
                        $tmp = $result->amount_tax;
                        $rate = $result->tax->rate;
                        if ($result->tax->factor == 'Tasa') {
                            $rate = $rate;
                        }
                        //Solo los impuestos locales
                        if ($result->tax->local_taxes) {
                            if($result->tax->type == 'R'){
                                $implocal_retenciones = [];
                                $implocal_retenciones['ImpLocRetenido'] = $result->tax->name;
                                $implocal_retenciones['TasadeRetencion'] = Helper::numberFormat(abs($rate), 2, false);
                                $implocal_retenciones['Importe'] = Helper::numberFormat(abs($tmp), 2, false);
                                $nodo_implocal_retenciones = new \CfdiUtils\Nodes\Node(
                                    'implocal:RetencionesLocales', // nombre del elemento raíz
                                    $implocal_retenciones
                                );
                                $nodo_implocal->addChild($nodo_implocal_retenciones);
                            }else {
                                $implocal_traslados = [];
                                $implocal_traslados['ImpLocTrasladado'] = $result->tax->name;
                                $implocal_traslados['TasadeTraslado'] = Helper::numberFormat(abs($rate), 2, false);
                                $implocal_traslados['Importe'] = Helper::numberFormat(abs($tmp), 2, false);
                                $nodo_implocal_traslados = new \CfdiUtils\Nodes\Node(
                                    'implocal:TrasladosLocales', // nombre del elemento raíz
                                    $implocal_traslados
                                );
                                $nodo_implocal->addChild($nodo_implocal_traslados);
                            }
                        }
                    }
                }
                //Agregamos al complemento
                $comprobante->addComplemento($nodo_implocal);
            }

            //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_credit_note->company->pathFileKeyPassPem()), Crypt::decryptString($customer_credit_note->company->password_key));
            //Valida la estructura
            //$creator->validate();

            //Guarda XML
            //dd($creator->asXml());
            $path_xml = Helper::setDirectory(CustomerCreditNote::PATH_XML_FILES_CCN, $customer_credit_note->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 Credit Note 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 CustomerCreditNote $customer_credit_note
     * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\BinaryFileResponse
     */
    public function downloadXml(Request $request, CustomerCreditNote $customer_credit_note)
    {
        //Ruta y validacion del XML
        $path_xml = Helper::setDirectory(CustomerCreditNote::PATH_XML_FILES_CCN, $customer_credit_note->company_id) . '/';
        $file_xml_pac = $path_xml . $customer_credit_note->customerInvoiceCfdi->file_xml_pac;
        if (!empty($customer_credit_note->customerInvoiceCfdi->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_credit_note->name) . '.xml',['Cache-Control' => 'no-cache, must-revalidate']);
        }

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

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

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

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

            //Cancelacion del timbre fiscal

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * Impresion CFDI 3.3
     *
     * @param $customer_credit_note
     * @param bool $download
     * @return mixed
     * @throws \Exception
     */
    private function printCfdi33($customer_credit_note, $download = false, $save = false)
    {
        //Datos del XML timbrado
        $path_xml = Helper::setDirectory(CustomerCreditNote::PATH_XML_FILES_CCN, $customer_credit_note->company_id) . '/';
        $file_xml_pac = $path_xml . $customer_credit_note->customerInvoiceCfdi->file_xml_pac;
        $data = [];

        //Valida que el archivo exista
        if (!empty($customer_credit_note->customerInvoiceCfdi->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_credit_note->company_id);//Plantilla PDF
        $pdf = \PDF::loadView('sales.customer_credit_notes.pdf_cfdi33_' . $pdf_template, compact('customer_credit_note', 'data'));

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

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


        throw new \Exception(__('sales/customer_credit_note.error_read_xml'));
    }

    /**
     * Impresion CFDI 4.0
     *
     * @param $customer_credit_note
     * @param bool $download
     * @return mixed
     * @throws \Exception
     */
    private function printCfdi40($customer_credit_note, $download = false, $save = false)
    {
        //Datos del XML timbrado
        $path_xml = Helper::setDirectory(CustomerCreditNote::PATH_XML_FILES_CCN, $customer_credit_note->company_id) . '/';
        $file_xml_pac = $path_xml . $customer_credit_note->customerInvoiceCfdi->file_xml_pac;
        $data = [];

        //Valida que el archivo exista
        if (!empty($customer_credit_note->customerInvoiceCfdi->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;
            $data['tax_regimen_customer'] = TaxRegimen::where('code', $data['cfdi33']->receptor['RegimenFiscalReceptor'])->first()->name_sat;

        }

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

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

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


        throw new \Exception(__('sales/customer_credit_note.error_read_xml'));
    }

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

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

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

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

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

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

    /**
     * Envio de factura por correo
     *
     * @param Request $request
     * @param CustomerCreditNote $customer_credit_note
     * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
     */
    public function sendMail(Request $request, CustomerCreditNote $customer_credit_note)
    {
        //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_credit_note);
        //Ruta del XML
        $path_xml = Helper::setDirectory(CustomerCreditNote::PATH_XML_FILES_CCN, $customer_credit_note->company_id) . '/';
        $file_xml_pac = $path_xml . $customer_credit_note->customerInvoiceCfdi->file_xml_pac;

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

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

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

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

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

        //Logica
        if ($request->ajax() && !empty($id)) {
            $customer_credit_note = CustomerCreditNote::findOrFail($id);
            $customer_credit_note->uuid = $customer_credit_note->customerInvoiceCfdi->uuid ?? '';
            return response()->json($customer_credit_note, 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 = CustomerCreditNote::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);
    }

    /**
     * Calcula el total de las lineas
     *
     * @param Request $request
     * @return \Illuminate\Http\JsonResponse
     */
    public function totalLines(Request $request)
    {
        //Variables
        $json = new \stdClass;
        $input_items = $request->item;
        $currency_id = $request->currency_id;
        $currency_code = 'MXN';
        $taxes = [];

        if ($request->ajax()) {
            //Datos de moneda
            if (!empty($currency_id)) {
                $currency = Currency::findOrFail($currency_id);
                $currency_code = $currency->code;
            }
            //Variables de totales
            $amount_discount = 0;
            $amount_untaxed = 0;
            $amount_tax = 0;
            $amount_tax_ret = 0;
            $amount_total = 0;
            $balance = 0;
            $items = [];
            if (!empty($input_items)) {
                foreach ($input_items as $key => $item) {
                    //Logica
                    $item_quantity = (double)$item['quantity'];
                    $item_price_unit = (double)$item['price_unit'];
                    $item_quota_ieps = (double)$item['quota_ieps'];
                    $item_discount = 0;
                    $item_price_reduce = ($item_price_unit * (100 - $item_discount) / 100);
                    $item_amount_untaxed = round($item_quantity * $item_price_reduce, 2);
                    if($item_quota_ieps > 0){
                        $item_amount_untaxed_per_taxes = round($item_quantity * (($item_price_unit * (100 - $item_discount) / 100) - $item_quota_ieps), 2);
                    }else{
                        $item_amount_untaxed_per_taxes = $item_amount_untaxed;
                    }
                    $item_amount_discount = round($item_quantity * $item_price_unit, 2) - $item_amount_untaxed;
                    $item_amount_tax = 0;
                    $item_amount_tax_ret = 0;
                    if (!empty($item['taxes'])) {
                        foreach ($item['taxes'] as $tax_id) {
                            if (!empty($tax_id)) {
                                $tax = Tax::findOrFail($tax_id);
                                $tmp = 0;
                                if ($tax->factor == 'Tasa') {
                                    $tmp = $item_amount_untaxed_per_taxes * $tax->rate / 100;
                                } elseif ($tax->factor == 'Cuota') {
                                    $tmp = $tax->rate;
                                }
                                $tmp = round($tmp, 2);
                                if ($tax->type == 'R') { //Retenciones
                                    $item_amount_tax_ret += $tmp;
                                } else { //Traslados
                                    $item_amount_tax += $tmp;
                                }

                                //Sumatoria de impuestos
                                $taxes[$tax_id] = array(
                                    'amount_base' => $item_amount_untaxed_per_taxes + (isset($taxes[$tax_id]['amount_base']) ? $taxes[$tax_id]['amount_base'] : 0),
                                    'amount_tax' => $tmp + (isset($taxes[$tax_id]['amount_tax']) ? $taxes[$tax_id]['amount_tax'] : 0),
                                );
                            }
                        }
                    }
                    $item_amount_total = $item_amount_untaxed + $item_amount_tax + $item_amount_tax_ret;
                    //Sumatoria totales
                    $amount_discount += $item_amount_discount;
                    $amount_untaxed += $item_amount_untaxed;
                    $amount_tax += $item_amount_tax;
                    $amount_tax_ret += $item_amount_tax_ret;
                    $amount_total += $item_amount_total;
                    //Subtotales por cada item
                    $items[$key] = money($item_amount_untaxed, $currency_code, true)->format();
                }
            }
            //Respuesta
            $taxes_tmp = [];
            if (!empty($taxes)) {
                foreach ($taxes as $tax_id => $result) {
                    $tax = Tax::findOrFail($tax_id);
                    $taxes_tmp[] = [
                        'name' => $tax->name,
                        'tax_id' => $tax_id,
                        'amount_base' => money($result['amount_base'], $currency_code, true)->format(),
                        'amount_tax' => money($result['amount_tax'], $currency_code, true)->format(),
                    ];
                }
            }

            //
            $json->items = $items;
            $json->taxes = $taxes_tmp;
            $json->amount_discount = money($amount_discount, $currency_code, true)->format();
            $json->amount_untaxed = money($amount_untaxed, $currency_code, true)->format();
            $json->amount_tax = money($amount_tax + $amount_tax_ret, $currency_code, true)->format();
            $json->amount_total = money($amount_total, $currency_code, true)->format();
            $json->amount_total_tmp = $amount_total;
            return response()->json($json);
        }

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

    /**
     * Modal para cancelar nota de credito
     *
     * @param Request $request
     * @param CustomerCreditNote $customer_credit_note
     * @return \Illuminate\Http\JsonResponse
     * @throws \Throwable
     */
    public function modalCancel(Request $request, CustomerCreditNote $customer_credit_note)
    {
        //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_credit_notes.modal_cancel',
                compact('customer_credit_note', '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_total = (double)$request->amount_total_tmp; //Total de la nota de credito
            $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_total = money($amount_total, $currency_code, true)->format();
            $json->amount_reconciled = money($amount_reconciled, $currency_code, true)->format();
            $json->amount_per_reconciled = money($amount_total - $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 CustomerCreditNotesExport($request),
            __('sales/customer_credit_note.document_title') . '-' . config('app.name') . '.xlsx');
    }

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

        \DB::connection('tenant')->beginTransaction();
        try {
            $invoiced = false;
            if((int) $customer_credit_note->status == CustomerCreditNote::DRAFT) {
                //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_credit_note_cfdi = $customer_credit_note->CustomerInvoiceCfdi;

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

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

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

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

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

                //Disminuye folios
                BaseHelper::decrementFolios();

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

                $invoiced = true;
            }

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

            if($invoiced) {
                $this->saveCfdiDownloads($customer_credit_note, $customer_credit_note_cfdi);
            }

        } catch (\Exception $e) {
            //Fix fechas

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

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

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

    /**
     * Modal para estatus de CFDI
     *
     * @param Request $request
     * @param CustomerCreditNote $customer_credit_note
     * @return \Illuminate\Http\JsonResponse
     * @throws \Throwable
     */
    public function modalStatusSat(Request $request, CustomerCreditNote $customer_credit_note)
    {
        //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_credit_note->customerInvoiceCfdi->cfdi_version) && !empty($customer_credit_note->customerInvoiceCfdi->uuid)) {

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

                $tmp = [
                    'rfcR' => $data['cfdi33']->Receptor['Rfc'] ?? $customer_credit_note->customer->taxid,
                    'uuid' => $customer_credit_note->customerInvoiceCfdi->uuid,
                    'total' => Helper::numberFormat($customer_credit_note->amount_total, $customer_credit_note->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_credit_note->customerInvoiceCfdi->pac->code . 'Status';
                $data_status_sat = PacHelper::$class_pac($tmp,$customer_credit_note->company,$customer_credit_note->customerInvoiceCfdi->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_credit_notes.modal_status_sat', compact('customer_credit_note','data_status_sat','is_cancelable'))->render();

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

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

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

        $import_results [] = (object)[
            'customer_taxid' => 'AMS840526THA',
            'customer' => 'Alberto Perez Perez',
            'branch_office' => 'Matriz',
            'currency' => 'MXN',
            'payment_term' => 'Contado',
            'salesperson' => 'Angel Hernandez Cruz',
            'payment_way' => 'Transferencia electrónica de fondos',
            'payment_method' => 'PUE',
            'cfdi_use' => 'Por definir',
            'cfdi_relation' => 'Nota de crédito de los documentos relacionados',
            'uuids' => 'uuid-1, uuid-2',
            'quantity' => '1',
            'product' => 'PRO001',
            'name' => 'Descuento general',
            'unit_measure' => 'H48-Unidad de servicio',
            'sat_product' => '84111506', //80101500
            'price_unit' => '325',
            'includes_iva' => 'S',
            'taxes' => 'IVA 16%',
            'send_mail' => 'N',
        ];
        $import_results [] = (object)[
            'customer_taxid' => 'WTS780329TY2',
            'customer' => 'Aceros del centro SA DE CV',
            'branch_office' => 'Matriz',
            'currency' => 'MXN',
            'payment_term' => 'Contado',
            'salesperson' => '',
            'payment_way' => 'Efectivo',
            'payment_method' => 'PUE',
            'cfdi_use' => 'Gastos en general',
            'cfdi_relation' => '',
            'uuids' => '',
            'quantity' => '1',
            'product' => 'PRO001',
            'name' => 'Descuento general',
            'unit_measure' => 'H48-Unidad de servicio',
            'sat_product' => '84111506', //80101500
            'price_unit' => '525',
            'includes_iva' => 'S',
            'taxes' => 'IVA 16%',
            'send_mail' => 'N',
        ];
        $import_results [] = (object)[
            'customer_taxid' => 'XAXX010101000',
            'customer' => 'Martha Montiel Mendez',
            'branch_office' => 'Matriz',
            'currency' => 'MXN',
            'payment_term' => 'Contado',
            'salesperson' => '',
            'payment_way' => 'Efectivo',
            'payment_method' => 'PUE',
            'cfdi_use' => 'Gastos en general',
            'cfdi_relation' => '',
            'uuids' => '',
            'quantity' => '1',
            'product' => 'PRO001',
            'name' => 'Descuento general',
            'unit_measure' => 'H48-Unidad de servicio',
            'sat_product' => '84111506',
            'price_unit' => '525',
            'includes_iva' => 'S',
            'taxes' => 'IVA 16%',
            'send_mail' => 'N',
        ];

        return view('sales.customer_credit_notes.import',
            compact('salespersons', 'branch_offices', 'currencies', 'payment_ways', 'payment_terms', 'payment_methods',
                'cfdi_uses', 'taxes', 'cfdi_relations','import_results'));
    }

    /**
     * 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
        while (ob_get_level()) ob_end_clean();
        ob_start();

        return Excel::download(new CustomerCreditNoteTemplateImportExport($request, $customers), __('sales/customer_credit_note.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_credit_notes_import')) {
            $request->merge(['file_customer_credit_notes_import_ext' => request()->file('file_customer_credit_notes_import')->getClientOriginalExtension()]);
        }
        $validator = \Validator::make($request->all(), [
            'file_customer_credit_notes_import' => 'required|max:2048',
            'file_customer_credit_notes_import_ext' => 'nullable|in:xls,xlsx'
        ], [
            'file_customer_credit_notes_import.*' => __('sales/customer_credit_note.error_file_customer_credit_notes_import'),
            'file_customer_credit_notes_import_ext.*' => __('sales/customer_credit_note.error_file_customer_credit_notes_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 CustomerCreditNoteTemplateBeforeImportImport;
            Excel::import($import, request()->file('file_customer_credit_notes_import'));

            //Mensaje
            return response()->json([
                'success' => 'ok',
                'total_import_customer_credit_notes' => sprintf(__('sales/customer_credit_note.help_import_sweet_alert_1'),Helper::numberFormat($import->total_customer_credit_notes),Helper::numberFormatMoney($import->total_amount_customer_credit_notes)),
            ]);
        }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_credit_notes_import')) {
            $request->merge(['file_customer_credit_notes_import_ext' => request()->file('file_customer_credit_notes_import')->getClientOriginalExtension()]);
        }
        $this->validate($request, [
            'file_customer_credit_notes_import' => 'required|max:2048',
            'file_customer_credit_notes_import_ext' => 'nullable|in:xls,xlsx'
        ], [
            'file_customer_credit_notes_import.*' => __('sales/customer_credit_note.error_file_customer_credit_notes_import'),
            'file_customer_credit_notes_import_ext.*' => __('sales/customer_credit_note.error_file_customer_credit_notes_import'),
        ]);

        //filas
        $rows = null;
        $data_customer_credit_notes = [];
        $customer_credit_notes = [];
        $customer_credit_notes_send_mail = [];

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

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

            $rows = $import->rows;

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

                \Validator::make($row->toArray(), [
                    'cantidad' => 'required|numeric|min:0.00001',
                    'descripcion' => 'required',
                    'u_m' => 'required',
                    'prodserv_sat' => 'required',
                    'precio' => 'required|numeric|min:0.00001',
                ], [
                    'cantidad.*' => __('sales/customer_credit_note.error_line_quantity') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['cantidad']),
                    'descripcion.*' => __('sales/customer_credit_note.error_line_name') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['descripcion']),
                    'u_m.*' => __('sales/customer_credit_note.error_line_unit_measure_id') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['u_m']),
                    'prodserv_sat.*' => __('sales/customer_credit_note.error_line_sat_product_id') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['prodserv_sat']),
                    'precio.*' => __('sales/customer_credit_note.error_line_price_unit') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['precio']),
                ])->validate();
            }

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

                //Valida producto
                $product = null;
                if (!empty($row['producto'])) {
                    if(!empty(setting('products_per_taxid'))){
                        $product = Product::where('code', '=', $row['producto'])->where('company_id', '=', Helper::defaultCompany()->id)->first();
                    }else{
                        $product = Product::where('code', '=', $row['producto'])->where('company_id', '=', Helper::firstCompany()->id)->first();
                    }
                    if (empty($product)) {
                        throw new \Exception(__('sales/customer_credit_note.error_line_product_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['producto']));
                    }
                }
                //Unidades de medida
                $unit_measure = null;
                if (!empty($row['u_m'])) {
                    $tpm = explode('-',$row['u_m']);
                    $unit_measure = UnitMeasure::where('code', '=', $tpm[0])->first();
                    if (empty($unit_measure)) {
                        throw new \Exception(__('sales/customer_credit_note.error_line_unit_measure_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$tpm[0]));
                    }
                }
                //Catalogo sat
                $sat_product = null;
                if (!empty($row['prodserv_sat'])) {
                    $sat_product = SatProduct::where('code', '=', $row['prodserv_sat'])->first();
                    if (empty($sat_product)) {
                        throw new \Exception(__('sales/customer_credit_note.error_line_sat_product_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['prodserv_sat']));
                    }
                }
                //Valida impuestos
                $tax = null;
                $taxes = [];
                $taxesArr = [];
                if (!empty($row['impuestos'])) {
                    $taxesArr = explode(', ', $row['impuestos']);
                    for($i = 0; $i < count($taxesArr); $i++){
                        $tax = Tax::where('name', '=', $taxesArr[$i])->first();
                        if(!empty($tax)){
                            array_push($taxes, $tax->id);
                        }else{
                            throw new \Exception(__('sales/customer_credit_note.error_line_taxes2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['impuestos']));
                        }
                    }
                }
                //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_credit_note.error_customer_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row, $row['rfc_cliente']));
                        }else{
                            if(setting('cfdi_version') == 'cfdi40'){
                                if (empty($customer->tax_regimen_id)) {
                                    throw new \Exception(__('sales/customer_credit_note.error_tax_regimen_customer_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row, $row['cliente']));
                                }
                                if (empty($customer->postcode)) {
                                    throw new \Exception(__('sales/customer_credit_note.error_postcode_customer') . sprintf(__('sales/customer_credit_note.error_row'), $num_row, $row['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_credit_note.error_branch_office_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['sucursal']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_credit_note.error_branch_office_id') . sprintf(__('sales/customer_credit_note.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_credit_note.error_currency_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['moneda']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_credit_note.error_currency_id') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['moneda']));
                    }
                    //Termino de pago
                    $payment_term = null;
                    if (!empty($row['terminos_de_pago'])) {
                        $payment_term = PaymentTerm::where('name', '=', $row['terminos_de_pago'])->first();
                        if (empty($payment_term)) {
                            throw new \Exception(__('sales/customer_credit_note.error_payment_term_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['terminos_de_pago']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_credit_note.error_payment_term_id') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['terminos_de_pago']));
                    }
                    //Vendedor
                    $salesperson = null;
                    if (!empty($row['vendedor'])) {
                        $salesperson = Salesperson::where('name', '=', $row['vendedor'])->first();
                        if (empty($salesperson)) {
                            throw new \Exception(__('sales/customer_credit_note.error_salesperson_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['vendedor']));
                        }
                    }
                    //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_credit_note.error_payment_way_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['forma_de_pago']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_credit_note.error_payment_way_id') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['forma_de_pago']));
                    }
                    //Metodo de pago
                    $payment_method = null;
                    if (!empty($row['metodo_de_pago'])) {
                        $payment_method = PaymentMethod::where('code', '=', $row['metodo_de_pago'])->first();
                        if (empty($payment_method)) {
                            throw new \Exception(__('sales/customer_credit_note.error_payment_method_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['metodo_de_pago']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_credit_note.error_payment_method_id') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['metodo_de_pago']));
                    }
                    //Uso de CFDI
                    $cfdi_use = null;
                    if (!empty($row['uso_de_cfdi'])) {
                        $cfdi_use = CfdiUse::where('name', '=', $row['uso_de_cfdi'])->first();
                        if (empty($cfdi_use)) {
                            throw new \Exception(__('sales/customer_credit_note.error_cfdi_use_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['uso_de_cfdi']));
                        }
                    }else{
                        throw new \Exception(__('sales/customer_credit_note.error_cfdi_use_id') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['uso_de_cfdi']));
                    }
                    //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_credit_note.error_cfdi_relation_id2') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['tipo_de_relacion']));
                        }
                        if(empty($row['uuids'])){
                            throw new \Exception(__('sales/customer_credit_note.error_cfdi_relation_id_uuids') . sprintf(__('sales/customer_credit_note.error_row'), $num_row,$row['uuids']));
                        }
                    }

                    $data_customer_credit_notes[$count] = [
                        'customer_id' => $customer->id,
                        'taxid' => $customer->taxid,
                        'customer' => $customer->name,
                        'branch_office_id' => $branch_office->id,
                        'currency_id' => $currency->id,
                        'payment_term_id' => $payment_term->id,
                        'date_due' => Helper::date(\Date::now()),
                        'salesperson_id' => !empty($salesperson) ? $salesperson->id : null,
                        'payment_way_id' => $payment_way->id,
                        'payment_method_id' => $payment_method->id,
                        'cfdi_use_id' => $cfdi_use->id,
                        '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['descripcion']) && !empty($row['precio'])) {
                        $data_customer_credit_notes[$count]['item'][] = [
                            'quantity' => $row['cantidad'],
                            'product_id' => !empty($product) ? $product->id : null,
                            'name' => $row['descripcion'],
                            'unit_measure_id' => $unit_measure->id,
                            'sat_product_id' => $sat_product->id,
                            'price_unit' => $row['precio'],
                            'includes_iva' => $row['iva_incluido'] == 'S' ? 1 : 0,
                            'taxes' => $taxes,
                            'discount' => 0,
                            'quota_ieps' => !empty($product) ? $product->quota_ieps : 0
                        ];
                    }
                    $count++;
                }elseif(!empty($row['descripcion']) && !empty($row['precio'])){
                    $data_customer_credit_notes[$count-1]['item'][] = [
                        'quantity' => $row['cantidad'],
                        'product_id' => !empty($product) ? $product->id : null,
                        'name' => $row['descripcion'],
                        'unit_measure_id' => $unit_measure->id,
                        'sat_product_id' => $sat_product->id,
                        'price_unit' => $row['precio'],
                        'includes_iva' => $row['iva_incluido'] == 'S' ? 1 : 0,
                        'taxes' => $taxes,
                        'discount' => 0,
                        'quota_ieps' => !empty($product) ? $product->quota_ieps : 0
                    ];
                }
            }

        } 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_credit_notes)) {

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

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

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

                    $request = (object)$request;

                    //Logica
                    $date = BaseHelper::getDateTimeBranchOffice($request->branch_office_id);
                    $date_due = $date;
                    if (!empty($request->date_due)) {
                        $date_due = Helper::createDate($request->date_due);
                    } else {
                        $payment_term = PaymentTerm::findOrFail($request->payment_term_id);
                        $date_due = $payment_term->days > 0 ? $date->copy()->addDays($payment_term->days) : $date->copy();
                    }

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

                    //Guardar
                    //Registro principal
                    $customer_credit_note = CustomerCreditNote::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_due' => Helper::dateToSql($date_due),
                        'customer_id' => $request->customer_id,
                        'branch_office_id' => $request->branch_office_id,
                        'payment_term_id' => $request->payment_term_id,
                        'payment_way_id' => $request->payment_way_id,
                        'payment_method_id' => $request->payment_method_id,
                        'cfdi_use_id' => $request->cfdi_use_id,
                        'salesperson_id' => $request->salesperson_id,
                        'currency_id' => $request->currency_id,
                        'currency_value' => 1,
                        'document_type_id' => $document_type['id'],
                        'status' => CustomerInvoice::OPEN,
                        'company_id' => $company->id,
                        'cfdi_relation_id' => $request->cfdi_relation_id,
                        'tax_regimen_customer_id' => $request->tax_regimen_customer_id,
                    ]);

                    //Registro de lineas
                    $amount_discount = 0;
                    $amount_untaxed = 0;
                    $amount_tax = 0;
                    $amount_tax_ret = 0;
                    $amount_total = 0;
                    $balance = 0;
                    $taxes = array();
                    //Lineas
                    if (!empty($request->item)) {
                        foreach ($request->item as $key => $item) {
                            //Logica
                            $item_quantity = (double)$item['quantity'];
                            $item_price_unit = (double)$item['price_unit'];
                            $item_quota_ieps = (double)$item['quota_ieps'];
                            //Ajuste para cuando tiene IVA incluido
                            if (!empty($item['taxes']) && !empty($item['includes_iva'])) {
                                foreach ($item['taxes'] as $tax_id) {
                                    if (!empty($tax_id)) {
                                        $tax = Tax::findOrFail($tax_id);
                                        if($tax->code == '002' && $tax->rate>0) {
                                            if ($tax->factor == 'Tasa') {
                                                $item_price_unit = $item_price_unit / (1+($tax->rate/100));
                                            } elseif ($tax->factor == 'Cuota') {
                                                $item_price_unit -= $tax->rate;
                                            }
                                            $item_price_unit = round($item_price_unit, \App\Helpers\Helper::companyProductPriceDecimalPlace());
                                            $request->item[$key]['price_unit'] = $item_price_unit;
                                        }
                                    }
                                    break;
                                }
                            }
                            $item_discount = (double)$item['discount'];
                            $item_price_reduce = ($item_price_unit * (100 - $item_discount) / 100);
                            $item_amount_untaxed = round($item_quantity * $item_price_reduce, 2);
                            if($item_quota_ieps > 0){
                                $item_amount_untaxed_per_taxes = round($item_quantity * (($item_price_unit * (100 - $item_discount) / 100) - $item_quota_ieps), 2);
                            }else{
                                $item_amount_untaxed_per_taxes = $item_amount_untaxed;
                            }
                            $item_amount_discount = round($item_quantity * $item_price_unit, 2) - $item_amount_untaxed;
                            $item_amount_tax = 0;
                            $item_amount_tax_ret = 0;
                            if (!empty($item['taxes'])) {
                                foreach ($item['taxes'] as $tax_id) {
                                    if (!empty($tax_id)) {
                                        $tax = Tax::findOrFail($tax_id);
                                        $tmp = 0;
                                        if ($tax->factor == 'Tasa') {
                                            $tmp = $item_amount_untaxed_per_taxes * $tax->rate / 100;
                                        } elseif ($tax->factor == 'Cuota') {
                                            $tmp = $tax->rate;
                                        }
                                        $tmp = round($tmp, 2);
                                        if ($tax->type == 'R') { //Retenciones
                                            $item_amount_tax_ret += $tmp;
                                        } else { //Traslados
                                            $item_amount_tax += $tmp;
                                        }

                                        //Sumatoria de impuestos
                                        $taxes[$tax_id] = array(
                                            'amount_base' => $item_amount_untaxed_per_taxes + (isset($taxes[$tax_id]['amount_base']) ? $taxes[$tax_id]['amount_base'] : 0),
                                            'amount_tax' => $tmp + (isset($taxes[$tax_id]['amount_tax']) ? $taxes[$tax_id]['amount_tax'] : 0),
                                        );
                                    }
                                }
                            }
                            $item_amount_total = $item_amount_untaxed + $item_amount_tax + $item_amount_tax_ret;
                            //Sumatoria totales
                            $amount_discount += $item_amount_discount;
                            $amount_untaxed += $item_amount_untaxed;
                            $amount_tax += $item_amount_tax;
                            $amount_tax_ret += $item_amount_tax_ret;
                            $amount_total += $item_amount_total;

                            //Guardar linea
                            $customer_credit_note_line = CustomerCreditNoteLine::create([
                                'created_uid' => \Auth::user()->id,
                                'updated_uid' => \Auth::user()->id,
                                'customer_invoice_id' => $customer_credit_note->id,
                                'name' => $item['name'],
                                'product_id' => $item['product_id'],
                                'sat_product_id' => $item['sat_product_id'],
                                'unit_measure_id' => $item['unit_measure_id'],
                                'quantity' => $item_quantity,
                                'price_unit' => $item_price_unit,
                                'discount' => $item_discount,
                                'price_reduce' => $item_price_reduce,
                                'amount_discount' => $item_amount_discount,
                                'amount_untaxed' => $item_amount_untaxed,
                                'amount_tax' => $item_amount_tax,
                                'amount_tax_ret' => $item_amount_tax_ret,
                                'amount_total' => $item_amount_total,
                                'sort_order' => $key,
                                'status' => 1,
                                'quota_ieps' => $item_quota_ieps,
                                'tax_object' => empty($item['taxes']) ? '01' : '02',
                            ]);

                            //Guardar impuestos por linea
                            if (!empty($item['taxes'])) {
                                $customer_credit_note_line->taxes()->sync($item['taxes']);
                            } else {
                                $customer_credit_note_line->taxes()->sync([]);
                            }
                        }
                    }

                    //Resumen de impuestos
                    if (!empty($taxes)) {
                        $i = 0;
                        foreach ($taxes as $tax_id => $result) {
                            $tax = Tax::findOrFail($tax_id);
                            $customer_credit_note_tax = CustomerCreditNoteTax::create([
                                'created_uid' => \Auth::user()->id,
                                'updated_uid' => \Auth::user()->id,
                                'customer_invoice_id' => $customer_credit_note->id,
                                'name' => $tax->name,
                                'tax_id' => $tax_id,
                                'amount_base' => $result['amount_base'],
                                'amount_tax' => $result['amount_tax'],
                                'sort_order' => $i,
                                'status' => 1,
                            ]);
                            $i++;
                        }
                    }

                    //Cfdi relacionados
                    if (!empty($request->uuids)) {
                        foreach ($request->uuids as $key => $uuid) {
                            //Guardar
                            if(!empty($uuid)){
                                $customer_credit_note_relation = CustomerCreditNoteRelation::create([
                                    'created_uid' => \Auth::user()->id,
                                    'updated_uid' => \Auth::user()->id,
                                    'customer_invoice_id' => $customer_credit_note->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_credit_note_cfdi = CustomerCreditNoteCfdi::create([
                        'created_uid' => \Auth::user()->id,
                        'updated_uid' => \Auth::user()->id,
                        'customer_invoice_id' => $customer_credit_note->id,
                        'name' => $customer_credit_note->name,
                        'cfdi_version' => $class_cfdi,
                        'status' => 1,
                    ]);

                    //Actualiza registro principal con totales
                    $customer_credit_note->amount_discount = $amount_discount;
                    $customer_credit_note->amount_untaxed = $amount_untaxed;
                    $customer_credit_note->amount_tax = $amount_tax;
                    $customer_credit_note->amount_tax_ret = $amount_tax_ret;
                    $customer_credit_note->amount_total = $amount_total;
                    $customer_credit_note->balance = $amount_total;
                    $customer_credit_note->update();

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

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

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

                    //Disminuye folios
                    BaseHelper::decrementFolios();

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

                    $this->saveCfdiDownloads($customer_credit_note, $customer_credit_note_cfdi);

                    //Guardamos facturas creadas
                    $customer_credit_notes[] = $customer_credit_note;
                    if($request->send_mail == 'S'){
                        $customer_credit_notes_send_mail[$customer_credit_note->id] = $customer_credit_note->id;
                    }

                }
            }


            //Mensaje
            flash(__('sales/customer_invoice.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_credit_notes)){
            foreach($customer_credit_notes as $customer_credit_note){
                self::dropboxBackup($customer_credit_note);
            }
        }

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

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

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

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

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

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

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

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

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

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

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

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

        if(!empty($customer_credit_note_cfdi->file_xml_pac)){
            \Storage::copy($path_xml . $customer_credit_note_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_credit_note_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_credit_note->company_id,
        ]);
        $data_xml = Helper::parseXmlToArrayCfdi33($path_xml . '/' . $customer_credit_note_cfdi->file_xml_pac);
        $data_xml['data']['customer_id'] = $customer_credit_note->customer_id;
        $data_xml['data']['caccounting_type_id'] = $customer_credit_note->customer->caccounting_type_sale_id ?? null;
        $cfdi_download->fill($data_xml['data']);
        $cfdi_download->save();
    }

}
