Herramientas Informaticas

Etiqueta: CI4JCPOS

Creando CRUD de ubicaciones para la carta porte 3.0 en CodeIgniter 4 #19

La Carta Porte 3.0 es la versión más reciente del complemento que debe acompañar a los Comprobantes Fiscales Digitales por Internet (CFDI) que amparan el traslado de mercancías en México. Esta versión entró en vigor el 25 de noviembre de 2023 y es obligatoria a partir del 1 de enero de 2024.

La Carta Porte 3.0 tiene como objetivo mejorar la trazabilidad de las mercancías que se transportan en México, así como facilitar el cumplimiento de las obligaciones fiscales de los contribuyentes. Para ello, incluye nuevos campos y requisitos que deben ser proporcionados por los emisores de los CFDI.

Entre los principales cambios que introduce la Carta Porte 3.0 se encuentran los siguientes:

  • La inclusión de nuevos datos sobre el transporte de las mercancías, como el medio de transporte, la ruta y la fecha de salida y llegada.
  • La obligación de identificar al destinatario de las mercancías, incluso si es una persona física.
  • La posibilidad de utilizar un identificador único para el CFDI, lo que facilitará su consulta y verificación.

La Carta Porte 3.0 es un complemento obligatorio para todos los contribuyentes que realicen el traslado de mercancías en México, independientemente de su tamaño o actividad económica. Los contribuyentes que no cumplan con esta obligación podrán ser sancionados por el Servicio de Administración Tributaria (SAT).

A continuación, se presentan algunos ejemplos de los nuevos campos que deben ser proporcionados en la Carta Porte 3.0:

  • Medio de transporte: Se debe indicar el tipo de medio de transporte utilizado para el traslado de las mercancías, como camión, tren, barco o avión.
  • Ruta: Se debe indicar la ruta que se seguirá para el traslado de las mercancías, incluyendo los puntos de origen y destino.
  • Fecha de salida y llegada: Se debe indicar la fecha en que se iniciará y concluirá el traslado de las mercancías.
  • Destinatario: Se debe identificar al destinatario de las mercancías, incluyendo su nombre, RFC y domicilio.
  • Identificador único del CFDI: Se debe proporcionar un identificador único para el CFDI, el cual podrá ser generado por el SAT o por el emisor del CFDI.

Para obtener más información sobre la Carta Porte 3.0, se puede consultar el sitio web del SAT.

Cómo crear un módulo de ventas con factura electronica en CodeIgniter 4 #13

Un módulo de ventas con facturación electrónica es un software que permite a las empresas realizar ventas y emitir facturas electrónicas de manera automatizada. Este tipo de módulos suele incluir las siguientes funcionalidades:

  • Gestión de clientes: permite crear y gestionar una base de datos de clientes, incluyendo sus datos de contacto, información de facturación, etc.
  • Gestión de productos: permite crear y gestionar una base de datos de productos, incluyendo sus datos de inventario, precios, etc.
  • Módulo de ventas: permite realizar ventas, incluyendo la selección de productos, la aplicación de descuentos, etc.
  • Módulo de facturación electrónica: permite emitir facturas electrónicas, incluyendo la generación del XML y el envío a la autoridad fiscal.

Los módulos de ventas con facturación electrónica ofrecen a las empresas una serie de ventajas, entre las que se incluyen:

  • Automatización de los procesos: la automatización de los procesos de ventas y facturación permite a las empresas ahorrar tiempo y recursos.
  • Mejora de la precisión: la automatización de los procesos ayuda a reducir los errores humanos, lo que mejora la precisión de las facturas.
  • Cumplimiento normativo: los módulos de ventas con facturación electrónica ayudan a las empresas a cumplir con la normativa fiscal vigente.

En México, la facturación electrónica es obligatoria para todas las empresas que realicen ventas a clientes ubicados en el país. Por lo tanto, los módulos de ventas con facturación electrónica son una herramienta indispensable para las empresas mexicanas.

A continuación, se detallan algunas de las funcionalidades específicas que puede incluir un módulo de ventas con facturación electrónica:

  • Creación de facturas electrónicas: el módulo debe permitir crear facturas electrónicas de acuerdo con la normativa fiscal vigente. Esto incluye la información obligatoria que debe incluirse en la factura, como los datos del emisor y del receptor, los datos de los productos o servicios, el importe de la factura, etc.
  • Envío de facturas electrónicas: el módulo debe permitir enviar las facturas electrónicas a los clientes de forma segura y eficiente. Esto puede hacerse a través de correo electrónico, mensajería instantánea, etc.
  • Archivado de facturas electrónicas: el módulo debe permitir archivar las facturas electrónicas de forma segura y organizada. Esto facilitará su consulta y recuperación en el futuro.
  • Generación de informes: el módulo debe permitir generar informes sobre las ventas y la facturación electrónica. Estos informes pueden ser útiles para la toma de decisiones estratégicas.

Elegir un módulo de ventas con facturación electrónica adecuado para su empresa dependerá de una serie de factores, como el tamaño de su empresa, su volumen de ventas, sus necesidades específicas, etc. Es importante comparar diferentes módulos antes de tomar una decisión.

A continuación, se ofrecen algunos consejos para elegir un módulo de ventas con facturación electrónica:

  • Considere sus necesidades específicas: antes de empezar a buscar un módulo, es importante tener claro cuáles son sus necesidades específicas. ¿Qué funcionalidades necesita? ¿Qué tipo de informe necesita generar?
  • Compare diferentes módulos: compare diferentes módulos para encontrar el que mejor se adapte a sus necesidades. Tenga en cuenta el precio, las funcionalidades, el soporte técnico, etc.
  • Lea las opiniones de otros usuarios: leer las opiniones de otros usuarios puede ser una buena forma de conocer las ventajas y desventajas de un módulo concreto.
  • Pruebe el módulo antes de comprarlo: si es posible, pruebe el módulo antes de comprarlo. Esto le permitirá comprobar que cumple con sus expectativas.

Cómo crear un módulo de kardex de inventario en CodeIgniter 4 #12

El kardex de inventario es un documento o sistema de registro que permite llevar un control de las entradas y salidas de mercancías o productos en un almacén. En él se registran los datos básicos de cada producto, como el código, la descripción, la unidad de medida, el precio unitario y el stock.

El kardex de inventario es una herramienta fundamental para la gestión del inventario. Permite conocer la cantidad de cada producto en existencia, así como su valor total. También ayuda a identificar las tendencias de consumo y a detectar posibles problemas de desabastecimiento.

El kardex de inventario se puede llevar de forma manual o automatizada. En el caso de la gestión manual, el registro se realiza en una hoja de cálculo o en un libro. En el caso de la gestión automatizada, el registro se realiza en un sistema informático.

Los datos que se registran en el kardex de inventario son los siguientes:

  • Código: Identificador único del producto.
  • Descripción: Nombre o descripción del producto.
  • Unidad de medida: Unidad en la que se mide el producto (unidades, kilos, metros, etc.).
  • Precio unitario: Precio de venta o de compra del producto.
  • Stock inicial: Cantidad de producto en existencia al inicio del periodo.
  • Entradas: Cantidad de producto que ha entrado en el almacén durante el periodo.
  • Salidas: Cantidad de producto que ha salido del almacén durante el periodo.
  • Stock final: Cantidad de producto en existencia al final del periodo.

El kardex de inventario se actualiza con cada movimiento de inventario. Cuando se recibe un producto, se registra la entrada con la cantidad recibida y el precio unitario. Cuando se vende un producto, se registra la salida con la cantidad vendida y el precio unitario.

El kardex de inventario es una herramienta esencial para la gestión del inventario. Permite conocer la cantidad de cada producto en existencia, así como su valor total. También ayuda a identificar las tendencias de consumo y a detectar posibles problemas de desabastecimiento.

Creando CRUD de Tipos de movimiento #13

Para registrar los movimientos requerimos especificar que tipo de movimiento es como puede ser entradas por compra, salidas por venta, entrada por devolución de cliente, saluda por devolución a proveedor

Para ello crearemos el siguiente catalogo de Tipos de movimiento con los siguientes campos

  • Empresa
  • Descripción
  • tipo
  • Es traspaso

Primero creamos el archivo de migración App/Database/2023-08-17222335_Tipos_movimientos_inventario.php con el siguiente código

Creando CRUD de Almacenes #12

Es necesario para llevar el control correcto del inventario tener almacenes,

A continuación mostramos como crear el CRUD de almacenes

El CRUD tendrá los siguientes datos

  • Empresa
  • Clave
  • Nombre
  • Inicio de inventario

Creando CRUD de Series Electrónicas #10

Ahora para poder timbrar facturas CFDI necesitamos tener un catalogo de series electrónicas.

En el catalogo de series electrónicas vamos a necesitar los siguientes datos

  • Empresa
  • Sucursal
  • Tipo Serie (venta,pago,devolucion,bonificacion)
  • Serie
  • Desde Fecha
  • Hasta Fecha
  • Desde Folio
  • Hasta Folio
  • Ambiente Timbrado
  • Token pruebas
  • Token producción

Primero creamos el archivo de migración App/Database/migrations/2023-10-17120916_Seriesfacturaelectronica.php para la creación de la tabla en la base de datos

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Seriesfacturaelectronica extends Migration {

    public function up() {
        // Seriesfacturaelectronica
        $this->forge->addField([
            'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
            'idEmpresa' => ['type' => 'bigint', 'constraint' => 20, 'null' => false],
            'sucursal' => ['type' => 'bigint', 'constraint' => 20, 'null' => false],
            'tipoSerie' => ['type' => 'varchar', 'constraint' => 16, 'null' => false],
            'serie' => ['type' => 'varchar', 'constraint' => 16, 'null' => false],
            'desdeFecha' => ['type' => 'date', 'null' => false],
            'hastaFecha' => ['type' => 'date', 'null' => false],
            'desdeFolio' => ['type' => 'bigint', 'constraint' => 20, 'null' => false],
            'hastaFolio' => ['type' => 'bigint', 'constraint' => 20, 'null' => false],
            'ambienteTimbrado' => ['type' => 'varchar', 'constraint' => 32, 'null' => false],
            'tokenPruebas' => ['type' => 'text', 'null' => false],
            'tokenProduccion' => ['type' => 'text', 'null' => false],
            'created_at' => ['type' => 'datetime', 'null' => true],
            'updated_at' => ['type' => 'datetime', 'null' => true],
            'deleted_at' => ['type' => 'datetime', 'null' => true],
        ]);
        $this->forge->addKey('id', true);
        $this->forge->createTable('seriesfacturaelectronica', true);
    }

    public function down() {
        $this->forge->dropTable('seriesfacturaelectronica', true);
    }

}

Creamos el archivo modelo App/Models/SeriesfacturaelectronicaModel.php para el acceso a la tabla

<?php

namespace App\Models;

use CodeIgniter\Model;

class SeriesfacturaelectronicaModel extends Model {

    protected $table = 'seriesfacturaelectronica';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = ['id'
        , 'idEmpresa'
        , 'sucursal'
        , 'tipoSerie'
        , 'serie'
        , 'desdeFecha'
        , 'hastaFecha'
        , 'desdeFolio'
        , 'hastaFolio'
        , 'ambienteTimbrado'
        , 'tokenPruebas'
        , 'tokenProduccion'
        , 'created_at'
        , 'updated_at'
        , 'deleted_at'];
    protected $useTimestamps = true;
    protected $createdField = 'created_at';
    protected $deletedField = 'deleted_at';
    protected $validationRules = [
    ];
    protected $validationMessages = [];
    protected $skipValidation = false;

    public function mdlGetSeriesfacturaelectronica($idEmpresas) {

        $result = $this->db->table('seriesfacturaelectronica a, empresas b, branchoffices c')
                ->select('a.id
                            ,a.idEmpresa
                            ,a.sucursal
                            ,a.tipoSerie
                            ,a.serie
                            ,a.desdeFecha
                            ,a.hastaFecha
                            ,a.desdeFolio
                            ,a.hastaFolio
                            ,a.ambienteTimbrado
                            ,a.tokenPruebas
                            ,a.tokenProduccion
                            ,a.created_at
                            ,a.updated_at
                            ,a.deleted_at 
                            ,b.nombre as nombreEmpresa
                            ,c.name as nombreSucursal
                            ')
                ->where('a.idEmpresa', 'b.id', FALSE)
                ->where('a.sucursal', 'c.id', FALSE)
                ->whereIn('a.idEmpresa', $idEmpresas);

        return $result;
    }

}

Creamos el archivo controlador App/Controllers/SeriesfacturaelectronicaController.php

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use \App\Models\{
    SeriesfacturaelectronicaModel
};
use App\Models\LogModel;
use CodeIgniter\API\ResponseTrait;
use App\Models\EmpresasModel;
use App\Models\BranchofficesModel;

class SeriesfacturaelectronicaController extends BaseController {

    use ResponseTrait;

    protected $log;
    protected $seriesfacturaelectronica;
    protected $sucursales;

    public function __construct() {
        $this->seriesfacturaelectronica = new SeriesfacturaelectronicaModel();
        $this->log = new LogModel();
        $this->empresa = new EmpresasModel();
        $this->sucursales = new BranchofficesModel();
                
        helper('menu');
        helper('utilerias');
    }

    public function index() {



        helper('auth');

        $idUser = user()->id;
        $titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);

        if (count($titulos["empresas"]) == "0") {

            $empresasID[0] = "0";
        } else {

            $empresasID = array_column($titulos["empresas"], "id");
        }




        if ($this->request->isAJAX()) {
            $datos = $this->seriesfacturaelectronica->mdlGetSeriesfacturaelectronica($empresasID);

            return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
        }
        $titulos["title"] = lang('seriesfacturaelectronica.title');
        $titulos["subtitle"] = lang('seriesfacturaelectronica.subtitle');
        return view('seriesfacturaelectronica', $titulos);
    }

    /**
     * Read Seriesfacturaelectronica
     */
    public function getSeriesfacturaelectronica() {

        helper('auth');

        $idUser = user()->id;
        $titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);

        if (count($titulos["empresas"]) == "0") {

            $empresasID[0] = "0";
        } else {

            $empresasID = array_column($titulos["empresas"], "id");
        }


        $idSeriesfacturaelectronica = $this->request->getPost("idSeriesfacturaelectronica");
        $datosSeriesfacturaelectronica = $this->seriesfacturaelectronica->whereIn('idEmpresa', $empresasID)
                        ->where("id", $idSeriesfacturaelectronica)->first();
        
        $datosSucursal = $this->sucursales->select("*")->where("id",$datosSeriesfacturaelectronica["sucursal"])->first();
        
        if($datosSucursal["name"]!=null){
            
            $datosSeriesfacturaelectronica["nombreSucursal"] = $datosSucursal["name"];
            
        }else{
            
            $datosSucursal["name"] =""; 
            
        }
        
        
        echo json_encode($datosSeriesfacturaelectronica);
    }

    /**
     * Save or update Seriesfacturaelectronica
     */
    public function save() {
        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;
        $datos = $this->request->getPost();
        if ($datos["idSeriesfacturaelectronica"] == 0) {
            try {
                if ($this->seriesfacturaelectronica->save($datos) === false) {
                    $errores = $this->seriesfacturaelectronica->errors();
                    foreach ($errores as $field => $error) {
                        echo $error . " ";
                    }
                    return;
                }
                $dateLog["description"] = lang("vehicles.logDescription") . json_encode($datos);
                $dateLog["user"] = $userName;
                $this->log->save($dateLog);
                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {
                echo "Error al guardar " . $ex->getMessage();
            }
        } else {
            if ($this->seriesfacturaelectronica->update($datos["idSeriesfacturaelectronica"], $datos) == false) {
                $errores = $this->seriesfacturaelectronica->errors();
                foreach ($errores as $field => $error) {
                    echo $error . " ";
                }
                return;
            } else {
                $dateLog["description"] = lang("seriesfacturaelectronica.logUpdated") . json_encode($datos);
                $dateLog["user"] = $userName;
                $this->log->save($dateLog);
                echo "Actualizado Correctamente";
                return;
            }
        }
        return;
    }

    /**
     * Delete Seriesfacturaelectronica
     * @param type $id
     * @return type
     */
    public function delete($id) {
        $infoSeriesfacturaelectronica = $this->seriesfacturaelectronica->find($id);
        helper('auth');
        $userName = user()->username;
        if (!$found = $this->seriesfacturaelectronica->delete($id)) {
            return $this->failNotFound(lang('seriesfacturaelectronica.msg.msg_get_fail'));
        }
        $this->seriesfacturaelectronica->purgeDeleted();
        $logData["description"] = lang("seriesfacturaelectronica.logDeleted") . json_encode($infoSeriesfacturaelectronica);
        $logData["user"] = $userName;
        $this->log->save($logData);
        return $this->respondDeleted($found, lang('seriesfacturaelectronica.msg_delete'));
    }

}

Para los archivos de la vista ya se la saben, es un archivo principal en el que incluimos los archivos secundarios

Creamos el archivo principal App/Views/seriesfacturaelectronica.php con el siguiente código

<?= $this->include('load/toggle') ?>
<?= $this->include('julio101290\boilerplate\Views\load\select2') ?>
<?= $this->include('julio101290\boilerplate\Views\load\datatables') ?>
<?= $this->include('julio101290\boilerplate\Views\load\nestable') ?>
<!-- Extend from layout index -->
<?= $this->extend('julio101290\boilerplate\Views\layout\index') ?>

<!-- Section content -->
<?= $this->section('content') ?>

<?= $this->include('modulesSeriesfacturaelectronica/modalCaptureSeriesfacturaelectronica') ?>

<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

                <button class="btn btn-primary btnAddSeriesfacturaelectronica" data-toggle="modal" data-target="#modalAddSeriesfacturaelectronica"><i class="fa fa-plus"></i>

                    <?= lang('seriesfacturaelectronica.add') ?>

                </button>

            </div>
        </div>
    </div>
    <div class="card-body">
        <div class="row">
            <div class="col-md-12">
                <div class="table-responsive">
                    <table id="tableSeriesfacturaelectronica" class="table table-striped table-hover va-middle tableSeriesfacturaelectronica">
                        <thead>
                            <tr>

                                <th>#</th>
                                <th><?= lang('seriesfacturaelectronica.fields.empresa') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.sucursal') ?></th>
                                <th>Tipo Serie</th>
                                <th><?= lang('seriesfacturaelectronica.fields.desdeFecha') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.hastaFecha') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.desdeFolio') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.ambienteTimbrado') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.tokenPruebas') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.tokenProduccion') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.created_at') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.updated_at') ?></th>
                                <th><?= lang('seriesfacturaelectronica.fields.deleted_at') ?></th>

                                <th><?= lang('seriesfacturaelectronica.fields.actions') ?> </th>

                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- /.card -->

<?= $this->endSection() ?>


<?= $this->section('js') ?>
<script>

    /**
     * Cargamos la tabla
     */

    var tableSeriesfacturaelectronica = $('#tableSeriesfacturaelectronica').DataTable({
        processing: true,
        serverSide: true,
        responsive: true,
        autoWidth: false,
        order: [[1, 'asc']],

        ajax: {
            url: '<?= base_url('admin/seriesfacturaelectronica') ?>',
            method: 'GET',
            dataType: "json"
        },
        columnDefs: [{
                orderable: false,
                targets: [12],
                searchable: false,
                targets: [12]

            }],
        columns: [{
                'data': 'id'
            },

            {
                'data': 'nombreEmpresa'
            },

            {
                'data': 'nombreSucursal'
            },

            {
                'data': 'tipoSerie'
            },

            {
                'data': 'desdeFecha'
            },

            {
                'data': 'hastaFecha'
            },

            {
                'data': 'desdeFolio'
            },

            {
                'data': 'ambienteTimbrado'
            },

            {
                'data': 'tokenPruebas'
            },

            {
                'data': 'tokenProduccion'
            },

            {
                'data': 'created_at'
            },

            {
                'data': 'updated_at'
            },

            {
                'data': 'deleted_at'
            },

            {
                "data": function (data) {
                    return `<td class="text-right py-0 align-middle">
                         <div class="btn-group btn-group-sm">
                             <button class="btn btn-warning btnEditSeriesfacturaelectronica" data-toggle="modal" idSeriesfacturaelectronica="${data.id}" data-target="#modalAddSeriesfacturaelectronica">  <i class=" fa fa-edit"></i></button>
                             <button class="btn btn-danger btn-delete" data-id="${data.id}"><i class="fas fa-trash"></i></button>
                         </div>
                         </td>`
                }
            }
        ]
    });



    $(document).on('click', '#btnSaveSeriesfacturaelectronica', function (e) {


        var idSeriesfacturaelectronica = $("#idSeriesfacturaelectronica").val();
        var empresa = $("#empresa").val();
        var sucursal = $("#sucursal").val();
        var tipoSerie = $("#tipoSerie").val();
        var serie = $("#serie").val();
        var desdeFecha = $("#desdeFecha").val();
        var hastaFecha = $("#hastaFecha").val();
        var desdeFolio = $("#desdeFolio").val();
        var hastaFolio = $("#hastaFolio").val();

        if ($("#ambienteTimbrado").is(':checked')) {

            var ambienteTimbrado = "on";

        } else {

            var ambienteTimbrado = "off";

        }

        var tokenPruebas = $("#tokenPruebas").val();
        var tokenProduccion = $("#tokenProduccion").val();


        $("#btnSaveSeriesfacturaelectronica").attr("disabled", true);

        var datos = new FormData();
        datos.append("idSeriesfacturaelectronica", idSeriesfacturaelectronica);
        datos.append("idEmpresa", empresa);
        datos.append("sucursal", sucursal);
        datos.append("tipoSerie", tipoSerie);
        datos.append("serie", serie);
        datos.append("desdeFecha", desdeFecha);
        datos.append("hastaFecha", hastaFecha);
        datos.append("desdeFolio", desdeFolio);
        datos.append("hastaFolio", hastaFolio);
        datos.append("ambienteTimbrado", ambienteTimbrado);
        datos.append("tokenPruebas", tokenPruebas);
        datos.append("tokenProduccion", tokenProduccion);


        $.ajax({

            url: "<?= base_url('admin/seriesfacturaelectronica/save') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            success: function (respuesta) {
                if (respuesta.match(/Correctamente.*/)) {

                    Toast.fire({
                        icon: 'success',
                        title: "Guardado Correctamente"
                    });

                    tableSeriesfacturaelectronica.ajax.reload();
                    $("#btnSaveSeriesfacturaelectronica").removeAttr("disabled");


                    $('#modalAddSeriesfacturaelectronica').modal('hide');
                } else {

                    Toast.fire({
                        icon: 'error',
                        title: respuesta
                    });

                    $("#btnSaveSeriesfacturaelectronica").removeAttr("disabled");


                }

            }

        }

        )

    });



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR Seriesfacturaelectronica
     =============================================*/
    $(".tableSeriesfacturaelectronica").on("click", ".btnEditSeriesfacturaelectronica", function () {

        var idSeriesfacturaelectronica = $(this).attr("idSeriesfacturaelectronica");

        var datos = new FormData();
        datos.append("idSeriesfacturaelectronica", idSeriesfacturaelectronica);

        $.ajax({

            url: "<?= base_url('admin/seriesfacturaelectronica/getSeriesfacturaelectronica') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                $("#idSeriesfacturaelectronica").val(respuesta["id"]);

                $("#empresa").val(respuesta["idEmpresa"]);

                var newOptionSucursal = new Option(respuesta["nombreSucursal"], respuesta["sucursal"], true, true);
                $('#sucursal').append(newOptionSucursal).trigger('change');
                $("#sucursal").val(respuesta["sucursal"]);


                $("#sucursal").val(respuesta["sucursal"]);
                $("#tipoSerie").val(respuesta["tipoSerie"]);
                $("#tipoSerie").trigger("change");
                $("#serie").val(respuesta["serie"]);
                $("#desdeFecha").val(respuesta["desdeFecha"]);
                $("#hastaFecha").val(respuesta["hastaFecha"]);
                $("#desdeFolio").val(respuesta["desdeFolio"]);
                $("#hastaFolio").val(respuesta["hastaFolio"]);

                $("#ambienteTimbrado").bootstrapToggle(respuesta["ambienteTimbrado"]);
                $("#tokenPruebas").val(respuesta["tokenPruebas"]);
                $("#tokenProduccion").val(respuesta["tokenProduccion"]);


            }

        })

    })


    /*=============================================
     ELIMINAR seriesfacturaelectronica
     =============================================*/
    $(".tableSeriesfacturaelectronica").on("click", ".btn-delete", function () {

        var idSeriesfacturaelectronica = $(this).attr("data-id");

        Swal.fire({
            title: '<?= lang('boilerplate.global.sweet.title') ?>',
            text: "<?= lang('boilerplate.global.sweet.text') ?>",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: '<?= lang('boilerplate.global.sweet.confirm_delete') ?>'
        })
                .then((result) => {
                    if (result.value) {
                        $.ajax({
                            url: `<?= base_url('admin/seriesfacturaelectronica') ?>/` + idSeriesfacturaelectronica,
                            method: 'DELETE',
                        }).done((data, textStatus, jqXHR) => {
                            Toast.fire({
                                icon: 'success',
                                title: jqXHR.statusText,
                            });


                            tableSeriesfacturaelectronica.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })

    $(function () {
        $("#modalAddSeriesfacturaelectronica").draggable();

    });


    $("#tipoSerie").select2();

</script>
<?= $this->endSection() ?>
        

En el archivo secundario App/Views/modulesSeriesfacturaelectronica/modalCaptureSeriesfacturaelectronica.php

<!-- Modal Seriesfacturaelectronica -->
<div class="modal fade" id="modalAddSeriesfacturaelectronica" tabindex="-1" role="dialog" aria-labelledby="modalAddSeriesfacturaelectronica" aria-hidden="true">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title"><?= lang('seriesfacturaelectronica.createEdit') ?></h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <form id="form-seriesfacturaelectronica" class="form-horizontal">
                    <input type="hidden" id="idSeriesfacturaelectronica" name="idSeriesfacturaelectronica" value="0">

                    <div class="form-group row">
                        <label for="emitidoRecibido" class="col-sm-2 col-form-label">Empresa</label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>

                                <select class="form-control empresa" name="empresa" id="empresa" style = "width:80%;">
                                    <option value="0">Seleccione empresa</option>
                                    <?php
                                    foreach ($empresas as $key => $value) {

                                        echo "<option value='$value[id]' selected>$value[id] - $value[nombre] </option>  ";
                                    }
                                    ?>

                                </select>

                            </div>
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="sucursal" class="col-sm-2 col-form-label">Sucursal</label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>

                                <select class="form-control sucursal" name="sucursal" id="sucursal" style = "width:80%;">
                                    <option value="0">Seleccione sucursal</option>


                                </select>

                            </div>
                        </div>
                    </div>


                    <div class="form-group row">
                        <label for="sucursal" class="col-sm-2 col-form-label">Tipo Serie</label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>

                                <select class="form-control tipoSerie" name="tipoSerie" id="tipoSerie" style = "width:80%;">
                                    <option value="ven">Venta</option>
                                    <option value="pag">Pago</option>
                                    <option value="dev">Devolución</option>
                                    <option value="bon">Bonificaciónn</option>

                                </select>

                            </div>
                        </div>
                    </div>


                    <div class="form-group row">
                        <label for="desdeFecha" class="col-sm-2 col-form-label"><?= lang('seriesfacturaelectronica.fields.desdeFecha') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="date" name="desdeFecha" id="desdeFecha" class="form-control <?= session('error.desdeFecha') ? 'is-invalid' : '' ?>" value="<?= old('desdeFecha') ?>" placeholder="<?= lang('seriesfacturaelectronica.fields.desdeFecha') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="hastaFecha" class="col-sm-2 col-form-label"><?= lang('seriesfacturaelectronica.fields.hastaFecha') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="date" name="hastaFecha" id="hastaFecha" class="form-control <?= session('error.hastaFecha') ? 'is-invalid' : '' ?>" value="<?= old('hastaFecha') ?>" placeholder="<?= lang('seriesfacturaelectronica.fields.hastaFecha') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="serie" class="col-sm-2 col-form-label">Serie</label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="text" name="serie" id="serie" class="form-control <?= session('error.serie') ? 'is-invalid' : '' ?>" value="<?= old('desdeFolio') ?>" placeholder="Inserte serie" autocomplete="off">
                            </div>
                        </div>
                    </div>


                    <div class="form-group row">
                        <label for="desdeFolio" class="col-sm-2 col-form-label"><?= lang('seriesfacturaelectronica.fields.desdeFolio') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="number" name="desdeFolio" id="desdeFolio" class="form-control <?= session('error.desdeFolio') ? 'is-invalid' : '' ?>" value="<?= old('desdeFolio') ?>" placeholder="<?= lang('seriesfacturaelectronica.fields.desdeFolio') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="hastaFolio" class="col-sm-2 col-form-label">Hasta Folio</label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="number" name="hastaFolio" id="hastaFolio" class="form-control <?= session('error.desdeFolio') ? 'is-invalid' : '' ?>" value="<?= old('hastaFolio') ?>" placeholder="Hasta Folio" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="ambienteTimbrado" class="col-sm-2 col-form-label"><?= lang('seriesfacturaelectronica.fields.ambienteTimbrado') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="checkbox" name="ambienteTimbrado" id="ambienteTimbrado" class="form-control " autocomplete="off" data-width="250" data-height="40" checked data-toggle="toggle" data-on="produccion" data-off="pruebas" data-onstyle="success" data-offstyle="danger">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="tokenPruebas" class="col-sm-2 col-form-label"><?= lang('seriesfacturaelectronica.fields.tokenPruebas') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="text" name="tokenPruebas" id="tokenPruebas" class="form-control <?= session('error.tokenPruebas') ? 'is-invalid' : '' ?>" value="<?= old('tokenPruebas') ?>" placeholder="<?= lang('seriesfacturaelectronica.fields.tokenPruebas') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="tokenProduccion" class="col-sm-2 col-form-label"><?= lang('seriesfacturaelectronica.fields.tokenProduccion') ?></label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="text" name="tokenProduccion" id="tokenProduccion" class="form-control <?= session('error.tokenProduccion') ? 'is-invalid' : '' ?>" value="<?= old('tokenProduccion') ?>" placeholder="<?= lang('seriesfacturaelectronica.fields.tokenProduccion') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>


                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal"><?= lang('boilerplate.global.close') ?></button>
                <button type="button" class="btn btn-primary btn-sm" id="btnSaveSeriesfacturaelectronica"><?= lang('boilerplate.global.save') ?></button>
            </div>
        </div>
    </div>
</div>

<?= $this->section('js') ?>


<script>

    $(document).on('click', '.btnAddSeriesfacturaelectronica', function (e) {


        $(".form-control").val("");

        $("#idSeriesfacturaelectronica").val("0");

        $("#empresa").val("0");
        $("#empresa").trigger("change");

        $("#ambienteTimbrado").bootstrapToggle("off");

        $("#btnSaveSeriesfacturaelectronica").removeAttr("disabled");

    });

    /* 
     * AL hacer click al editar
     */



    $(document).on('click', '.btnEditSeriesfacturaelectronica', function (e) {


        var idSeriesfacturaelectronica = $(this).attr("idSeriesfacturaelectronica");

        //LIMPIAMOS CONTROLES
        $(".form-control").val("");

        $("#idSeriesfacturaelectronica").val(idSeriesfacturaelectronica);
        $("#btnGuardarSeriesfacturaelectronica").removeAttr("disabled");

    });


    $("#empresa").select2();

    $("#empresa").trigger("change");



    $("#sucursal").select2({
        ajax: {
            url: "<?= site_url('admin/sucursales/getSucursalesAjax') ?>",
            type: "post",
            dataType: 'json',
            delay: 250,
            data: function (params) {
                // CSRF Hash
                var csrfName = $('.txt_csrfname').attr('name'); // CSRF Token name
                var csrfHash = $('.txt_csrfname').val(); // CSRF hash
                var idEmpresa = $('.empresa').val(); // CSRF hash

                return {
                    searchTerm: params.term, // search term
                    [csrfName]: csrfHash, // CSRF Token
                    idEmpresa: idEmpresa // search term
                };
            },
            processResults: function (response) {

                // Update CSRF Token
                $('.txt_csrfname').val(response.token);
                return {
                    results: response.data
                };
            },
            cache: true
        }
    });

</script>


<?= $this->endSection() ?>
        

En las rutas App/config/Routes.php dentro del grupo admin agregamos el siguiente código

    $routes->resource('seriesfacturaelectronica', [
        'filter' => 'permission:seriesfacturaelectronica-permission',
        'controller' => 'seriesfacturaelectronicaController',
        'except' => 'show'
    ]);
    $routes->post('seriesfacturaelectronica/save', 'SeriesfacturaelectronicaController::save');
    $routes->post('seriesfacturaelectronica/getSeriesfacturaelectronica', 'SeriesfacturaelectronicaController::getSeriesfacturaelectronica');

Creamos el menú para las series electrónicas con los siguientes datos

Y listo ya tenemos nuestro CRUD de Series Electronicas

Creando CRUD de productos #07

Bien pues para hacer una venta necesitamos un catalogo de los productos los cuales vamos a vender.

En el catalogo de productos necesitamos los siguientes datos en datos generales:

  • Empresa
  • Sucursal
  • Categoria
  • Descripción
  • Unidad
  • Código de barras
  • Imagen de producto

Para los datos de control de inventario necesitaremos los siguientes datos:

  • stock
  • Validar stock (checkbox)
  • Inventario Riguroso(checkbox)

Para los datos del calculo de precios agregaremos los siguientes datos:

  • Precio de compra
  • Precio de venta
  • Porcentaje de utilidad
  • Porcentaje de IVA
  • Porcentaje IVA retenido
  • Porcentaje ISR retenido

También vamos necesitar los datos de facturación:

  • Clave unidad SAT
  • Clave Producto SAT
Leer Mas: Creando CRUD de productos #07

Bien primero que nada necesitamos crear la tabla en la base de datos, normalmente creamos la tabla directamente en la base de datos, pero como estamos trabajando en CodeIgniter 4 creamos el archivo de migración en app/Database/Migrations/2023-04-24060002_Products.php

El código quedaría de la siguiente manera

<?php
namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Products extends Migration
{
    public function up()
    {
        // Products
        $this->forge->addField([
            'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
            'idEmpresa' => ['type' => 'bigint', 'null' => true],
            'idCategory' => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'code' => ['type' => 'varchar', 'constraint' => 64, 'null' => true],
            'barcode' => ['type' => 'varchar', 'constraint' => 64, 'null' => true],
            'unidad' => ['type' => 'varchar', 'constraint' => 64, 'null' => true],
            'description' => ['type' => 'varchar', 'constraint' => 512, 'null' => true],
            'stock' => ['type' => 'decimal', 'constraint' => '18,2', 'null' => true],
            'validateStock' => ['type' => 'varchar', 'constraint' => 4, 'null' => true],
            'inventarioRiguroso' => ['type' => 'varchar', 'constraint' => 4, 'null' => true],
            'buyPrice' => ['type' => 'decimal', 'constraint' => '18,2', 'null' => true],
            'salePrice' => ['type' => 'decimal', 'constraint' => '18,2', 'null' => true],
            'porcentSale' => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'porcentTax' => ['type' => 'int', 'constraint' => 11, 'null' => true],

            'unidadSAT' => ['type' => 'varchar', 'constraint' => 64, 'null' => true],
            'claveProductoSAT' => ['type' => 'varchar', 'constraint' => 64, 'null' => true],

            'nombreUnidadSAT' => ['type' => 'varchar', 'constraint' => 256, 'null' => true],
            'nombreClaveProducto' => ['type' => 'varchar', 'constraint' => 256, 'null' => true],

            'porcentIVARetenido' => ['type' => 'decimal', 'constraint' => '18,4', 'null' => true],
            'porcentISRRetenido' => ['type' => 'decimal', 'constraint' => '18,4', 'null' => true],
            
            'routeImage' => ['type' => 'varchar', 'constraint' => 256, 'null' => true],

            'created_at' => ['type' => 'datetime', 'null' => true],
            'updated_at' => ['type' => 'datetime', 'null' => true],
            'deleted_at' => ['type' => 'datetime', 'null' => true],
        ]);
        $this->forge->addKey('id', true);
        $this->forge->createTable('products', true);
    }
    public function down()
    {
        $this->forge->dropTable('products', true);
    }
}

Una vez creado ejecutan el comando para generar la tabla

php spark migrate

Ahora creamos el archivo del modelo con las funciones necesarias para el manejo de la tabla en app/models/productosModel.php

<?php

namespace App\Models;

use CodeIgniter\Model;

class ProductsModel extends Model {

    protected $table = 'products';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = [
        'id'
        , 'idEmpresa'
        , 'code'
        , 'idCategory'
        , 'description'
        , 'stock'
        , 'validateStock'
        , 'inventarioRiguroso'
        , 'buyPrice'
        , 'salePrice'
        , 'porcentSale'
        , 'porcentTax'
        , 'porcentIVARetenido'
        , 'porcentISRRetenido'
        , 'routeImage'
        , 'created_at'
        , 'deleted_at'
        , 'updated_at'
        , 'unidadSAT'
        , 'claveProductoSAT'
        , 'unidad'
        , 'nombreUnidadSAT'
        , 'nombreClaveProducto'
        , 'barcode'
    ];
    protected $useTimestamps = true;
    protected $createdField = 'created_at';
    protected $deletedField = 'deleted_at';
    protected $validationRules = [];
    protected $validationMessages = [];
    protected $skipValidation = false;

    public function mdlProductos($empresas) {
        $resultado = $this->db->table('products a, empresas b')
                ->select('a.id
            ,a.code
            ,a.idCategory
            ,a.validateStock
            ,a.inventarioRiguroso
            ,a.description
            ,a.stock
            ,a.buyPrice
            ,a.salePrice
            ,a.porcentSale
            ,a.porcentTax
            ,a.routeImage
            ,a.created_at
            ,a.deleted_at
            ,a.updated_at
            ,a.barcode
            ,a.unidad
            ,b.nombre as nombreEmpresa
            ,a.porcentIVARetenido
            ,a.porcentISRRetenido
            ,a.nombreUnidadSAT
            ,a.nombreClaveProducto
            ,a.unidadSAT
            ,a.claveProductoSAT')
                ->where('a.idEmpresa', 'b.id', FALSE)
                ->whereIn('idEmpresa', $empresas)
                ->where('a.deleted_at', null);

        return $resultado;
    }

    public function mdlProductosEmpresa($empresas, $empresa) {


        $resultado2 = $this->db->table('products a, empresas b, saldos c, storages d')
                ->select('a.id,a.code
          ,a.idCategory
          ,a.validateStock
          ,a.inventarioRiguroso
          ,a.description
          ,c.cantidad as stock
          ,a.buyPrice
          ,a.salePrice
          ,a.porcentSale
          ,a.porcentTax
          ,a.routeImage
          ,a.created_at
          ,a.deleted_at
          ,a.updated_at
          ,a.barcode
          ,a.unidad
          ,b.nombre as nombreEmpresa
          ,a.porcentIVARetenido
          ,a.porcentISRRetenido
          ,a.nombreUnidadSAT
          ,a.nombreClaveProducto
          ,a.unidadSAT
          ,c.lote as lote
          ,c.idAlmacen
          ,d.name as almacen
          ,a.claveProductoSAT')
                ->where('c.idProducto', 'a.id', FALSE)
                ->where('a.idEmpresa', 'b.id', FALSE)
                 ->where('c.cantidad >', '0')
                ->where('a.idEmpresa', 'c.idEmpresa', FALSE)
                ->where('c.idAlmacen', 'd.id', FALSE)
                ->where('a.idEmpresa', $empresa)
                ->where('a.deleted_at', null)
                ->where('a.inventarioRiguroso', 'on')
                ->where('a.validateStock', 'on')
                ->whereIn('c.idEmpresa', $empresas);

        $resultado = $this->db->table('products a, empresas b')
                ->select('a.id,a.code
          ,a.idCategory
          ,a.validateStock
          ,a.inventarioRiguroso
          ,a.description
          ,a.stock as stock
          ,a.buyPrice
          ,a.salePrice
          ,a.porcentSale
          ,a.porcentTax
          ,a.routeImage
          ,a.created_at
          ,a.deleted_at
          ,a.updated_at
          ,a.barcode
          ,a.unidad
          ,b.nombre as nombreEmpresa
          ,a.porcentIVARetenido
          ,a.porcentISRRetenido
          ,a.nombreUnidadSAT
          ,a.nombreClaveProducto
          ,a.unidadSAT
          ,\'\' as lote
          , 0 as idAlmacen
          ,\'\' as almacen
          ,a.claveProductoSAT')
                ->where('a.idEmpresa', 'b.id', FALSE)
                ->where('a.idEmpresa', $empresa)
                ->groupStart()
                ->where('a.inventarioRiguroso', "off")
                ->orWhere("a.inventarioRiguroso","NULL")
                  ->orWhere("a.inventarioRiguroso",NULL)
                 ->groupEnd()
                ->where('a.deleted_at', null)
                ->whereIn('idEmpresa', $empresas);

        $resultado->union($resultado2);
        $this->db->query("DROP TABLE IF EXISTS tempProducts");

        $this->db->query("create table tempProducts " . $resultado->getCompiledSelect());

        return $this->db->table('tempProducts');
    }

    /**
     * Lista Para inventario Riguroso
     * @param type $empresas
     * @param type $empresa
     * @return type
     */
    public function mdlProductosEmpresaInventarioEntrada($empresas, $empresa) {
        $resultado = $this->db->table('products a, empresas b')
                ->select('a.id,a.code
            ,a.idCategory
            ,a.validateStock
            ,a.inventarioRiguroso
            ,a.description
            ,a.stock
            ,a.buyPrice
            ,a.salePrice
            ,a.porcentSale
            ,a.porcentTax
            ,a.routeImage
            ,a.created_at
            ,a.deleted_at
            ,a.updated_at
            ,a.barcode
            ,a.unidad
            , b.nombre as nombreEmpresa
            ,a.porcentIVARetenido
            ,a.porcentISRRetenido
            ,a.nombreUnidadSAT
            ,a.nombreClaveProducto
            ,a.unidadSAT
            ,"" as lote
            ,"" as almacen
            ,a.claveProductoSAT')
                ->where('a.idEmpresa', 'b.id', FALSE)
                ->where('a.idEmpresa', $empresa)
                ->where('a.deleted_at', null)
                ->where('a.inventarioRiguroso', 'on')
                ->where('a.validateStock', 'on')
                ->whereIn('idEmpresa', $empresas);

        return $resultado;
    }

    public function mdlProductosEmpresaInventarioSalida($empresas, $empresa) {

        $resultado = $this->db->table('products a, empresas b, saldos c, storages d')
                ->select('a.id,a.code
                         ,a.idCategory
                         ,a.validateStock
                         ,a.inventarioRiguroso
                         ,a.description
                         ,c.cantidad as stock
                         ,a.buyPrice
                         ,a.salePrice
                         ,a.porcentSale
                         ,a.porcentTax
                         ,a.routeImage
                         ,a.created_at
                         ,a.deleted_at
                         ,a.updated_at
                         ,a.barcode
                         ,a.unidad
                         ,b.nombre as nombreEmpresa
                         ,a.porcentIVARetenido
                         ,a.porcentISRRetenido
                         ,a.nombreUnidadSAT
                         ,a.nombreClaveProducto
                         ,a.unidadSAT
                         ,c.lote as lote
                         ,c.idAlmacen
                         ,d.name as almacen
                         ,a.claveProductoSAT')
                ->where('c.idProducto', 'a.id', FALSE)
                ->where('a.idEmpresa', 'b.id', FALSE)
                ->where('a.idEmpresa', 'c.idEmpresa', FALSE)
                ->where('c.idAlmacen', 'd.id', FALSE)
                ->where('a.idEmpresa', $empresa)
                ->where('a.deleted_at', null)
                ->where('a.inventarioRiguroso', 'on')
                ->where('a.validateStock', 'on')
                ->whereIn('c.idEmpresa', $empresas);

        return $resultado;
    }

    public function mdlGetProductoEmpresa($empresas, $idProducto) {
        $resultado = $this->db->table('products a, empresas b, categorias c ')
                        ->select('a.id
            ,a.code
            ,a.idEmpresa
            ,a.validateStock
            ,a.inventarioRiguroso
            ,a.idCategory
            ,c.clave
            ,c.descripcion as descripcionCategoria
            ,a.description
            ,a.stock
            ,a.buyPrice
            ,a.salePrice,a.porcentSale
            ,a.porcentTax,a.routeImage
            ,a.created_at,a.deleted_at
            ,a.updated_at
            ,a.barcode
            ,a.unidad
            , b.nombre as nombreEmpresa
            ,a.porcentIVARetenido
            ,a.porcentISRRetenido
            ,a.nombreUnidadSAT
            ,a.nombreClaveProducto
            ,a.unidadSAT
            ,a.claveProductoSAT')
                        ->where('a.idEmpresa', 'b.id', FALSE)
                        ->where('a.idCategory', 'c.id', FALSE)
                        ->where('a.id', $idProducto)
                        ->where('a.deleted_at', null)
                        ->whereIn('a.idEmpresa', $empresas)->get()->getFirstRow();

        return $resultado;
    }

}

Agregamos el archivo del controlador para las operaciones de validacion altas bajas y cambios en app/controllers/ProductsController.php

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\ProductsModel;
use App\Models\LogModel;
use CodeIgniter\API\ResponseTrait;
use App\Models\CategoriasModel;
use App\Models\EmpresasModel;
use App\Models\QuotesDetailsModel;
use App\Models\SellsDetailsModel;
use App\Models\Tipos_movimientos_inventarioModel;

class ProductsController extends BaseController {

    use ResponseTrait;

    protected $log;
    protected $products;
    protected $empresa;
    protected $categorias;
    protected $sellsDetails;
    protected $quoteDetails;
    protected $tiposMovimientoInventario;

    public function __construct() {
        $this->products = new ProductsModel();
        $this->log = new LogModel();
        $this->categorias = new CategoriasModel();
        $this->empresa = new EmpresasModel();
        $this->sellsDetails = new SellsDetailsModel();
        $this->quoteDetails = new QuotesDetailsModel();
        $this->tiposMovimientoInventario = new Tipos_movimientos_inventarioModel();
        helper('menu');
    }

    public function index() {


        helper('auth');

        $idUser = user()->id;
        $titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);

        if (count($titulos["empresas"]) == "0") {

            $empresasID[0] = "0";
        } else {

            $empresasID = array_column($titulos["empresas"], "id");
        }


        if ($this->request->isAJAX()) {
            $datos = $this->products->mdlProductos($empresasID);
            return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
        }

        $titulos["categorias"] = $this->categorias->select("*")->where("deleted_at", null)->asArray()->findAll();
        $titulos["title"] = lang('products.title');
        $titulos["subtitle"] = lang('products.subtitle');
        return view('products', $titulos);
    }

    public function getAllProducts($empresa) {


        helper('auth');

        $idUser = user()->id;
        $titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);

        if (count($titulos["empresas"]) == "0") {

            $empresasID[0] = "0";
        } else {

            $empresasID = array_column($titulos["empresas"], "id");
        }


        if ($this->request->isAJAX()) {
            $datos = $this->products->mdlProductosEmpresa($empresasID, $empresa);
            return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
        }
    }

    public function getAllProductsInventory($empresa, $idStorage, $idTipoMovimiento) {


        helper('auth');

        $idUser = user()->id;
        $titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);

        if (count($titulos["empresas"]) == "0") {

            $empresasID[0] = "0";
        } else {

            $empresasID = array_column($titulos["empresas"], "id");
        }


        //BUSCAMOS EL TIPO DE MOVIMIENTO SI ES ENTRADA O SALIDA
        $tiposMovimiento = $this->tiposMovimientoInventario->select("*")
                        ->wherein("idEmpresa", $empresasID)
                        ->where("id", $idTipoMovimiento)->first();

        if ($tiposMovimiento == null) {

            $datos = $this->products->mdlProductosEmpresaInventarioEntrada($empresasID, $empresa);
            return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
        }

        if ($tiposMovimiento["tipo"] == "ENT") {

            if ($this->request->isAJAX()) {
                $datos = $this->products->mdlProductosEmpresaInventarioEntrada($empresasID, $empresa);
                return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
            }
        }


        if ($tiposMovimiento["tipo"] == "SAL") {

            if ($this->request->isAJAX()) {
                $datos = $this->products->mdlProductosEmpresaInventarioSalida($empresasID, $empresa);
                return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
            }
        }


        $datos = $this->products->mdlProductosEmpresaInventarioEntrada($empresasID, $empresa);
        return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
    }

    /**
     * Get Unidad SAT via AJax
     */
    public function getUnidadSATAjax() {

        $request = service('request');
        $postData = $request->getPost();

        $response = array();

        // Read new token and assign in $response['token']
        $response['token'] = csrf_hash();

        if (!isset($postData['searchTerm'])) {
            // Fetch record

            $listUnidadesSAT = $this->catalogosSAT->clavesUnidades40()->searchByField("texto", "%$%", 100);
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record

            $listUnidadesSAT = $this->catalogosSAT->clavesUnidades40()->searchByField("texto", "%$searchTerm%", 100);
        }

        $data = array();
        foreach ($listUnidadesSAT as $unidadSAT => $value) {

            $data[] = array(
                "id" => $value->id(),
                "text" => $value->id() . ' ' . $value->texto(),
            );
        }

        $response['data'] = $data;

        return $this->response->setJSON($response);
    }

    /**
     * Get Unidad SAT via AJax
     */
    public function getProductosSATAjax() {

        $request = service('request');
        $postData = $request->getPost();

        $response = array();

        // Read new token and assign in $response['token']
        $response['token'] = csrf_hash();

        if (!isset($postData['searchTerm'])) {
            // Fetch record

            $listProducts = $this->catalogosSAT->productosServicios40()->searchByField("texto", "%$searchTerm%", 50);
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record

            $listProducts = $this->catalogosSAT->productosServicios40()->searchByField("texto", "%$searchTerm %", 50);
        }

        $data = array();
        foreach ($listProducts as $productosSAT => $value) {

            $data[] = array(
                "id" => $value->id(),
                "text" => $value->id() . ' ' . $value->texto(),
            );
        }

        $response['data'] = $data;

        return $this->response->setJSON($response);
    }

    /**
     * Get Products via AJax
     */
    public function getProductsAjaxSelect2() {

        $request = service('request');
        $postData = $request->getPost();

        $response = array();

        // Read new token and assign in $response['token']
        $response['token'] = csrf_hash();
        $products = new ProductsModel();
        $idEmpresa = $postData['idEmpresa'];

        if (!isset($postData['searchTerm'])) {
            // Fetch record

            $listProducts = $products->select('id,code,description')->where("deleted_at", null)
                    ->where('idEmpresa', $idEmpresa)
                    ->orderBy('id')
                    ->orderBy('code')
                    ->orderBy('description')
                    ->findAll(1000);
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record

            $listProducts = $products->select('id,code,description')->where("deleted_at", null)
                    ->where('idEmpresa', $idEmpresa)
                    ->groupStart()
                    ->like('description', $searchTerm)
                    ->orLike('id', $searchTerm)
                    ->orLike('code', $searchTerm)
                    ->groupEnd()
                    ->findAll(1000);
        }

        $data = array();
        $data[] = array(
            "id" => 0,
            "text" => "0 Todos Los Productos",
        );
        foreach ($listProducts as $product) {
            $data[] = array(
                "id" => $product['id'],
                "text" => $product['id'] . ' ' . $product['id'] . ' ' . $product['code'] . ' ' . $product['description'],
            );
        }

        $response['data'] = $data;

        return $this->response->setJSON($response);
    }

    /**
     * Read Products
     */
    public function getProducts() {

        helper('auth');

        $idUser = user()->id;
        $titulos["empresas"] = $this->empresa->mdlEmpresasPorUsuario($idUser);

        if (count($titulos["empresas"]) == "0") {

            $empresasID[0] = "0";
        } else {

            $empresasID = array_column($titulos["empresas"], "id");
        }
        $idProducts = $this->request->getPost("idProducts");

        $datosProducts = $this->products->mdlGetProductoEmpresa($empresasID, $idProducts);

        echo json_encode($datosProducts);
    }

    /**
     * Save or update Products
     */
    public function save() {
        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;
        $datos = $this->request->getPost();

        var_dump($datos);
        $imagenProducto = $this->request->getFile("imagenProducto");
        $datos["routeImage"] = "";
        if ($imagenProducto) {

            if ($imagenProducto->getClientExtension() <> "png") {

                return lang("empresas.pngFileExtensionIncorrect");
            }
            $datos["routeImage"] = $imagenProducto->getRandomName();
        }


        if ($datos["idProducts"] == 0) {
            try {
                if ($this->products->save($datos) === false) {
                    $errores = $this->products->errors();
                    foreach ($errores as $field => $error) {
                        echo $error . " ";
                    }
                    return;
                }
                $dateLog["description"] = lang("vehicles.logDescription") . json_encode($datos);
                $dateLog["user"] = $userName;
                $this->log->save($dateLog);

                if ($imagenProducto <> null) {
                    $imagenProducto->move("images/products", $datos["routeImage"]);
                }

                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {
                echo "Error al guardar " . $ex->getMessage();
            }
        } else {

            $dataPrevious = $this->products->find($datos["idProducts"]);

            if ($this->products->update($datos["idProducts"], $datos) == false) {
                $errores = $this->products->errors();
                foreach ($errores as $field => $error) {
                    echo $error . " ";
                }
                return;
            } else {
                $dateLog["description"] = lang("products.logUpdated") . json_encode($datos);
                $dateLog["user"] = $userName;
                $this->log->save($dateLog);

                if ($imagenProducto <> null) {

                    if (file_exists("images/products/" . $dataPrevious["routeImage"])) {
                        unlink("images/products/" . $dataPrevious["routeImage"]);
                    }
                    $imagenProducto->move("images/products", $datos["routeImage"]);
                }



                echo "Actualizado Correctamente";
                return;
            }
        }
        return;
    }

    /**
     * Delete Products
     * @param type $id
     * @return type
     */
    public function delete($id) {



        if ($this->sellsDetails->select("id")->where("idProduct", $id)->countAllResults() > 0) {

            $this->products->db->transRollback();
            return $this->failValidationError("No se puede borrar ya que hay ventas con este producto");
        }

        if ($this->quoteDetails->select("id")->where("idProduct", $id)->countAllResults() > 0) {

            $this->products->db->transRollback();
            return $this->failValidationError("No se puede borrar ya que hay cotizaciones con este producto");
        }

        $infoProducts = $this->products->find($id);
        helper('auth');
        $userName = user()->username;
        if (!$found = $this->products->delete($id)) {

            $this->products->db->transRollback();
            return $this->failNotFound(lang('products.msg.msg_get_fail'));
        }

        if ($infoProducts["routeImage"] != "") {

            if (file_exists("images/products/" . $infoProducts["routeImage"])) {

                unlink("images/products/" . $infoProducts["routeImage"]);
            }
        }


        $this->products->purgeDeleted();
        $logData["description"] = lang("products.logDeleted") . json_encode($infoProducts);
        $logData["user"] = $userName;
        $this->log->save($logData);
        $this->products->db->transCommit();

        return $this->respondDeleted($found, lang('products.msg_delete'));
    }

    /**
     * Get Vehiculos via AJax
     */
    public function getProductsAjax() {

        $request = service('request');
        $postData = $request->getPost();

        $response = array();

        // Read new token and assign in $response['token']
        $response['token'] = csrf_hash();
        $custumers = new VehiculosModel();
        $idEmpresa = $postData['idEmpresa'];

        if (!isset($postData['searchTerm'])) {
            // Fetch record

            $listProducts = $products->select('id,description')->where("deleted_at", null)
                    ->where('idEmpresa', $idEmpresa)
                    ->orderBy('id')
                    ->orderBy('descripcion')
                    ->findAll(1000);
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record

            $listProducts = $products->select('id,description')->where("deleted_at", null)
                    ->where('idEmpresa', $idEmpresa)
                    ->groupStart()
                    ->like('descripcion', $searchTerm)
                    ->orLike('id', $searchTerm)
                    ->groupEnd()
                    ->findAll(1000);
        }

        $data = array();
        foreach ($listProducts as $product) {
            $data[] = array(
                "id" => $custumers['id'],
                "text" => $custumers['id'] . ' ' . $product['description'],
            );
        }

        $response['data'] = $data;

        return $this->response->setJSON($response);
    }

}

Ahora creamos la interfaz es decir la vista, para ello tendremos el archivo principal app/views/products.php y este incluirá los archivos secundarios, una forma que a mi parecer se organiza mejor para no tener todo en un solo archivo.

En el diseño estaran los bloques separados en pestañas “TABS”

Los archivos secundarios serán los siguientes

  • app/views/modulesProducts/generalsProducts.php
  • app/views/modulesProducts/imageProduct.php
  • app/views/modulesProducts/inventoryProducts.php
  • app/views/modulesProducts/modalCaptureProducts.php
  • app/views/modulesProducts/priceProducts.php
  • app/views/modulesProducts/productsCFDIV4.php

El archivo principal app/views/products.php contiene el siguiente código, incluye a los demas y genera el datable

<?= $this->include('load/toggle') ?>
<?= $this->include('julio101290\boilerplate\Views\load\select2') ?>
<?= $this->include('julio101290\boilerplate\Views\load\datatables') ?>
<?= $this->include('julio101290\boilerplate\Views\load\nestable') ?>
<!-- Extend from layout index -->
<?= $this->extend('julio101290\boilerplate\Views\layout\index') ?>

<!-- Section content -->
<?= $this->section('content') ?>

<?= $this->include('modulesProducts/modalCaptureProducts') ?>

<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

                <button class="btn btn-primary btnAddProducts" data-toggle="modal" data-target="#modalAddProducts"><i class="fa fa-plus"></i>

                    <?= lang('products.add') ?>

                </button>

            </div>
        </div>
    </div>
    <div class="card-body">
        <div class="row">
            <div class="col-md-12">
                <div class="table-responsive">
                    <table id="tableProducts" class="table table-striped table-hover va-middle tableProducts">
                        <thead>
                            <tr>

                                <th>#</th>
                                <th>
                                    Empresa
                                </th>
                                <th>
                                    Clave
                                </th>

                                <th>
                                    <?= lang('products.fields.idCategory') ?>
                                </th>

                                <th>
                                    <?= lang('products.fields.barcode') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.description') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.stock') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.buyPrice') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.salePrice') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.porcentSale') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.porcentTax') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.routeImage') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.created_at') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.deleted_at') ?>
                                </th>
                                <th>
                                    <?= lang('products.fields.updated_at') ?>
                                </th>

                                <th>
                                    <?= lang('products.fields.actions') ?>
                                </th>

                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- /.card -->

<?= $this->endSection() ?>


<?= $this->section('js') ?>
<script>
    /**
     * Cargamos la tabla
     */

    var tableProducts = $('#tableProducts').DataTable({
        processing: true,
        serverSide: true,
        responsive: true,
        autoWidth: false,
        order: [
            [1, 'asc']
        ],

        ajax: {
            url: '<?= base_url('admin/products') ?>',
            method: 'GET',
            dataType: "json"
        },
        columnDefs: [{
                orderable: false,
                targets: [11, 15],
                searchable: false,
                targets: [11, 15]

            }],
        columns: [{
                'data': 'id'
            },

            {
                'data': 'nombreEmpresa'
            },

            {
                'data': 'code'
            },

            {
                'data': 'idCategory'
            },

            {
                'data': 'barcode'
            },

            {
                'data': 'description'
            },

            {
                'data': 'stock'
            },

            {
                'data': 'buyPrice'
            },

            {
                'data': 'salePrice'
            },

            {
                'data': 'porcentSale'
            },

            {
                'data': 'porcentTax'
            },

            {
                "data": function (data) {

                    if (data.routeImage == "") {
                        data.routeImage = "anonymous.png";
                    }

                    return `<td class="text-right py-0 align-middle">
                         <div class="btn-group btn-group-sm">
                         <img src="<?= base_URL("images/products") ?>/${data.routeImage}" data-action="zoom" width="40px" class="" style="">
                         </div>
                         </td>`
                }
            },

            {
                'data': 'created_at'
            },

            {
                'data': 'deleted_at'
            },

            {
                'data': 'updated_at'
            },

            {
                "data": function (data) {
                    return `<td class="text-right py-0 align-middle">
                         <div class="btn-group btn-group-sm">
                             <button class="btn btn-warning btnEditProducts" data-toggle="modal" idProducts="${data.id}" data-target="#modalAddProducts">  <i class=" fa fa-edit"></i></button>
                             <button class="btn btn-danger btn-delete" data-id="${data.id}"><i class="fas fa-trash"></i></button>
                         </div>
                         </td>`
                }
            }
        ]
    });







    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR Products
     =============================================*/
    $(".tableProducts").on("click", ".btnEditProducts", function () {

        var idProducts = $(this).attr("idProducts");

        var datos = new FormData();
        datos.append("idProducts", idProducts);

        if (idEmpresa == 0) {

            Toast.fire({
                icon: 'error',
                title: "Tiene que seleccionar la empresa"
            });

        }

        $.ajax({

            url: "<?= base_url('admin/products/getProducts') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                $("#idProducts").val(respuesta["id"]);
                $("#idEmpresa").val(respuesta["idEmpresa"]);
                $("#idEmpresa").trigger("change");


                var newOption = new Option(respuesta["clave"] + ' ' + respuesta["descripcionCategoria"], respuesta["idCategory"], true, true);
                $('#idCategory').append(newOption).trigger('change');
                $("#idCategory").val(respuesta["idCategory"]);



                var newOptionUnidad = new Option(respuesta["nombreUnidadSAT"], respuesta["unidadSAT"], true, true);
                $('#unidadSAT').append(newOptionUnidad).trigger('change');
                $("#unidadSAT").val(respuesta["unidadSAT"]);

                $("#unidad").val(respuesta["unidad"]);


                var newOptionClaveProducto = new Option(respuesta["nombreClaveProducto"], respuesta["claveProductoSAT"], true, true);
                $('#claveProductoSAT').append(newOptionClaveProducto).trigger('change');
                $("#claveProductoSAT").val(respuesta["claveProductoSAT"]);




                $("#clave").val(respuesta["clave"]);
                $("#description").val(respuesta["description"]);
                $("#stock").val(respuesta["stock"]);
                $("#buyPrice").val(respuesta["buyPrice"]);
                $("#salePrice").val(respuesta["salePrice"]);
                $("#porcentSale").val(respuesta["porcentSale"]);
                $("#porcentTax").val(respuesta["porcentTax"]);
                $("#porcentIVARetenido").val(respuesta["porcentIVARetenido"]);
                $("#porcentISRRetenido").val(respuesta["porcentISRRetenido"]);

                $("#barcode").val(respuesta["barcode"]);


                $("#validateStock").bootstrapToggle(respuesta["validateStock"]);
                $("#inventarioRiguroso").bootstrapToggle(respuesta["inventarioRiguroso"]);


                //$("#routeImage").val(respuesta["routeImage"]);
                if (respuesta["routeImage"] == "") {
                    $(".previsualizarLogo").attr("src", '<?= base_URL("images/products/") ?>anonymous.png');

                } else {

                    $(".previsualizarLogo").attr("src", '<?= base_URL("images/products") ?>/' + respuesta["routeImage"]);

                }

                $("#code").val(respuesta["code"]);


            }

        })

    })


    /*=============================================
     ELIMINAR products
     =============================================*/
    $(".tableProducts").on("click", ".btn-delete", function () {

        var idProducts = $(this).attr("data-id");

        Swal.fire({
            title: '<?= lang('boilerplate.global.sweet.title') ?>',
            text: "<?= lang('boilerplate.global.sweet.text') ?>",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: '<?= lang('boilerplate.global.sweet.confirm_delete') ?>'
        })
                .then((result) => {
                    if (result.value) {
                        $.ajax({
                            url: `<?= base_url('admin/products') ?>/` + idProducts,
                            method: 'DELETE',
                        }).done((data, textStatus, jqXHR) => {
                            Toast.fire({
                                icon: 'success',
                                title: jqXHR.statusText,
                            });


                            tableProducts.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })

    $(function () {
        $("#modalAddProducts").draggable();

    });



    /*=============================================
     SUBIENDO LA FOTO DEL USUARIO
     =============================================*/
    $(".imagenProducto").change(function () {



        var imagen = this.files[0];

        /*=============================================
         VALIDAMOS EL FORMATO DE LA IMAGEN SEA JPG O PNG
         =============================================*/

        if (imagen["type"] != "image/png") {

            $(".imagenProducto").val("");

            Toast.fire({
                icon: 'error',
                title: "<?= lang('empresas.imagenesFormato') ?>",
            });


        } else if (imagen["size"] > 2000000) {

            $(".imagenProducto").val("");

            Toast.fire({
                icon: 'error',
                title: "<?= lang('empresas.imagenesPeso') ?>",
            });


        } else {

            var datosImagen = new FileReader;
            datosImagen.readAsDataURL(imagen);

            $(datosImagen).on("load", function (event) {

                var rutaImagen = event.target.result;

                $(".previsualizarLogo").attr("src", rutaImagen);

            })

        }
    });


</script>
<?= $this->endSection() ?>

Luego tenemos app/views/modulesProducts/generalsProducts.php que son los datos generales del producto

<p>
<h3>Datos Generales</h3>
<div class="form-group row">
    <label for="emitidoRecibido" class="col-sm-2 col-form-label">Empresa</label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>

            <select class="form-control idEmpresa form-controlProducts" name="idEmpresa" id="idEmpresa" style="width:80%;">
                <option value="0">Seleccione empresa</option>
                <?php
                foreach ($empresas as $key => $value) {

                    echo "<option value='$value[id]'>$value[id] - $value[nombre] </option>  ";
                }
                ?>

            </select>

        </div>
    </div>
</div>

<div class="form-group row">
    <label for="idCategory" class="col-sm-2 col-form-label">
        <?= lang('products.fields.idCategory') ?>
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>
            <select name="idCategory" id="idCategory" style="width: 90%;" class="form-control idCategory form-controlProducts">
                <option value="0" selected>
                    <?= lang('products.fields.idSelectCategory') ?>
                </option>



            </select>


        </div>
    </div>

</div>
<div class="form-group row">
    <label for="code" class="col-sm-2 col-form-label">
        Clave
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>
            <input type="text" name="code" id="code" class="form-control form-controlProducts <?= session('error.code') ? 'is-invalid' : '' ?>" value="" placeholder="Clave" autocomplete="off" readonly>
        </div>
    </div>
</div>


<div class="form-group row">
    <label for="barcode" class="col-sm-2 col-form-label">
        <?= lang('products.fields.barcode') ?>
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>
            <input type="text" name="barcode" id="barcode" class="form-control form-controlProducts <?= session('error.barcode') ? 'is-invalid' : '' ?>" value="<?= old('barcode') ?>" placeholder="<?= lang('products.fields.barcode') ?>" autocomplete="off">
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="description" class="col-sm-2 col-form-label">
        <?= lang('products.fields.description') ?>
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>
            <input type="text" name="description" id="description" class="form-control form-controlProducts <?= session('error.description') ? 'is-invalid' : '' ?>" value="<?= old('description') ?>" placeholder="<?= lang('products.fields.description') ?>" autocomplete="off">
        </div>
    </div>
</div>


<div class="form-group row">
    <label for="unidad" class="col-sm-2 col-form-label">
        Unidad
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>
            <input type="text" name="unidad" id="unidad" class="form-control form-controlProducts <?= session('error.unidad') ? 'is-invalid' : '' ?>" value="<?= old('unidad') ?>" placeholder="Unidad" autocomplete="off">
        </div>
    </div>
</div>



</p>

Agregamos el código donde capturamos los datos del inventario app/views/modulesProducts/inventoryProducts.php

<p>
<h3>Datos Inventario</h3>
<div class="form-group row">
    <label for="stock" class="col-sm-2 col-form-label">
        <?= lang('products.fields.stock') ?>
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
            </div>
            <input type="number" name="stock" id="stock" class="form-control form-controlProducts <?= session('error.stock') ? 'is-invalid' : '' ?>" value="<?= old('stock') ?>" placeholder="<?= lang('products.fields.stock') ?>" autocomplete="off" min="0">
        </div>
    </div>
</div>


<div class="form-group row">
    <label for="stock" class="col-sm-2 col-form-label">
        Valida Stock
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">

            </div>
            <input type="checkbox" id="validateStock" name="validateStock" class="validateStock" data-width="250" data-height="40" checked data-toggle="toggle" data-on="Valida Stock" data-off="No valida stock" data-onstyle="success" data-offstyle="danger">
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="stock" class="col-sm-2 col-form-label">
        Inventario Riguroso
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">

            </div>
            <input type="checkbox" id="inventarioRiguroso" name="inventarioRiguroso" class="inventarioRiguroso" data-width="250" data-height="40" checked data-toggle="toggle" data-on="Inventario Riguroso" data-off="Inventario básico" data-onstyle="success" data-offstyle="danger">
        </div>
    </div>
</div>

</p>

Este es el código para capturar la imagen del producto app/views/modulesProducts/imageProduct.php

<p>
<h3>Imagenes</h3>
<div class="form-group ">

    <input type="file" class="imagenProducto" name="imagenProducto" id="imagenProducto">

    <p class="help-block">
        <?= lang("empresas.imagenesPesoMaximo") ?>
    </p>

    <img src="<?= base_url("images/products/anonymous.png") ?>" class="img-thumbnail previsualizarLogo" width="100px">

    <input type="hidden" name="imagenActual" id="imagenActual">


</div>

</p>

Capturamos el archivo base del modal aquí van configuradas las pestañas “TABS” app/views/modulesProducts/modalCaptureProducts.php

<!-- Modal Products -->
<div class="modal fade" id="modalAddProducts" tabindex="-1" role="dialog" aria-labelledby="modalAddProducts" aria-hidden="true">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">
                    <?= lang('products.createEdit') ?>
                </h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">

                <ul class="nav nav-tabs" id="myTab" role="tablist">
                    <li class="nav-item" role="presentation">
                        <button class="nav-link active" id="home-tab" data-toggle="tab" data-target="#generales" type="button" role="tab" aria-controls="home" aria-selected="true">Generales</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link" id="profile-tab" data-toggle="tab" data-target="#inventoryProducts" type="button" role="tab" aria-controls="profile" aria-selected="false">Inventario</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link" id="emailsetting-tab" data-toggle="tab" data-target="#precios" type="button" role="tab" aria-controls="emailSettings" aria-selected="false">Precios</button>
                    </li>
                    <li class="nav-item" role="presentation">
                        <button class="nav-link" id="contact-tab" data-toggle="tab" data-target="#imageProduct" type="button" role="tab" aria-controls="contact" aria-selected="false">Logos / Imagenes</button>
                    </li>

                    <li class="nav-item" role="presentation">
                        <button class="nav-link" id="contact-tab" data-toggle="tab" data-target="#productsCFDIV4" type="button" role="tab" aria-controls="contact" aria-selected="false">Datos CFDI V4 </button>
                    </li>

                </ul>
                <form id="form-products" class="form-horizontal">


                    <div class="tab-content" id="myTabContent">



                        <div class="tab-pane fade show active" id="generales" role="tabpanel" aria-labelledby="generales">

                            <?= $this->include('modulesProducts/generalsProducts') ?>

                        </div>
                        <div class="tab-pane fade" id="inventoryProducts" role="tabpanel" aria-labelledby="datosFacturacion">

                            <?= $this->include('modulesProducts/inventoryProducts') ?>

                        </div>
                        <div class="tab-pane fade" id="precios" role="tabpanel" aria-labelledby="contact-tab">

                            <?= $this->include('modulesProducts/priceProducts') ?>

                        </div>

                        <div class="tab-pane fade" id="imageProduct" role="tabpanel" aria-labelledby="contact-tab">

                            <?= $this->include('modulesProducts/imageProduct') ?>

                        </div>

                        <div class="tab-pane fade" id="productsCFDIV4" role="tabpanel" aria-labelledby="contact-tab">

                            <?= $this->include('modulesProducts/productsCFDIV4') ?>

                        </div>


                    </div>



                    <input type="hidden" id="idProducts" name="idProducts" value="0">






                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal">
                    <?= lang('boilerplate.global.close') ?>
                </button>
                <button type="button" class="btn btn-primary btn-sm" id="btnSaveProducts">
                    <?= lang('boilerplate.global.save') ?>
                </button>
            </div>
        </div>
    </div>
</div>

<?= $this->section('js') ?>


<script>
    $("#idEmpresa").select2();




    /**
     * Categorias por empresa
     */

    $(".unidadSAT").select2({
        ajax: {
            url: "<?= base_url('admin/products/getUnidadSATAjax') ?>",
            type: "post",
            dataType: 'json',
            delay: 250,


            data: function(params) {
                // CSRF Hash
                var csrfName = $('.txt_csrfname').attr('name'); // CSRF Token name
                var csrfHash = $('.txt_csrfname').val(); // CSRF hash
                var idEmpresa = $('.idEmpresa').val(); // CSRF hash

                return {
                    searchTerm: params.term, // search term
                    [csrfName]: csrfHash, // CSRF Token
                    idEmpresa: idEmpresa // search term
                };
            },
            processResults: function(response) {

                // Update CSRF Token
                $('.txt_csrfname').val(response.token);

                return {
                    results: response.data
                };
            },


            cache: true
        }
    });


    /**
     * Categorias por empresa
     */

    $(".claveProductoSAT").select2({
        ajax: {
            url: "<?= base_url('admin/products/getProductosSATAjax') ?>",
            type: "post",
            dataType: 'json',
            delay: 250,
            data: function(params) {
                // CSRF Hash
                var csrfName = $('.txt_csrfname').attr('name'); // CSRF Token name
                var csrfHash = $('.txt_csrfname').val(); // CSRF hash
                var idEmpresa = $('.idEmpresa').val(); // CSRF hash

                return {
                    searchTerm: params.term, // search term
                    [csrfName]: csrfHash, // CSRF Token
                    idEmpresa: idEmpresa // search term
                };
            },
            processResults: function(response) {

                // Update CSRF Token
                $('.txt_csrfname').val(response.token);

                return {
                    results: response.data
                };
            },
            cache: true
        }
    });


    /**
     * Categorias por empresa
     */

    $(".idCategory").select2({
        ajax: {
            url: "<?= base_url('admin/categorias/getCategoriasAjax') ?>",
            type: "post",
            dataType: 'json',
            delay: 250,
            data: function(params) {
                // CSRF Hash
                var csrfName = $('.txt_csrfname').attr('name'); // CSRF Token name
                var csrfHash = $('.txt_csrfname').val(); // CSRF hash
                var idEmpresa = $('.idEmpresa').val(); // CSRF hash

                return {
                    searchTerm: params.term, // search term
                    [csrfName]: csrfHash, // CSRF Token
                    idEmpresa: idEmpresa // search term
                };
            },
            processResults: function(response) {

                // Update CSRF Token
                $('.txt_csrfname').val(response.token);

                return {
                    results: response.data
                };
            },
            cache: true
        }
    });




    /**
     * When change id Control
     */

    $("#idCategory").on("change", function() {

        var idCategoria = $(this).val();
        var idEmpresa = $(".idEmpresa").val();

        var idProduct = $("#idProducts").val();

        var datos = new FormData();
        datos.append("idCategoria", idCategoria);
        datos.append("idEmpresa", idEmpresa);

        $.ajax({

                url: "<?= base_url('admin/categorias/buscarFolio') ?>",
                method: "POST",
                data: datos,
                cache: false,
                contentType: false,
                processData: false,
                success: function(respuesta) {


                    if (idProduct == 0) {
                        $("#code").val(respuesta);
                    }

                }

            }

        )



    });




    $("#porcentSale").on("keyup", function() {

        var porcentaje = $(this).val() / 100;
        var precioCompra = $("#buyPrice").val()
        var precioVenta = $("#salePrice").val()


        if (!(precioCompra > 0)) {


            return;
        }


        precioVenta = precioCompra * (porcentaje + 1)

        $("#salePrice").val(precioVenta);


    });

    $(document).on('click', '.btnAddProducts', function(e) {


        $(".form-controlProducts").val("");

        $("#idProducts").val("0");

        $("#btnSaveProducts").removeAttr("disabled");

        $("#idCategory").val("0");

        $("#idCategory").trigger("change");

        $("#idEmpresa").val("0");

        $("#idEmpresa").trigger("change");

        $("#porcentSale").val("40");
        $("#porcentTax").val("0");

        $("#porcentIVARetenido").val("0");
        $("#porcentISRRetenido").val("0");

        $("#unidadSAT").val("0");

        $("#unidadSAT").trigger("change");

        $("#claveProductoSAT").val("0");

        $("#claveProductoSAT").trigger("change");


        $("#facturacionRD").bootstrapToggle("off");


    });

    /* 
     * AL hacer click al editar
     */



    $(document).on('click', '.btnEditProducts', function(e) {


        var idProducts = $(this).attr("idProducts");

        //LIMPIAMOS CONTROLES
        $(".form-control").val("");

        $("#idProducts").val(idProducts);
        $("#btnGuardarProducts").removeAttr("disabled");

    });




    $(document).on('click', '#btnSaveProducts', function(e) {

        var idEmpresa = $("#idEmpresa").val();
        var idProducts = $("#idProducts").val();
        var clave = $("#code").val();
        var idCategory = $("#idCategory").val();
        var barcode = $("#barcode").val();
        var description = $("#description").val();
        var stock = $("#stock").val();
        var buyPrice = $("#buyPrice").val();
        var salePrice = $("#salePrice").val();
        var porcentSale = $("#porcentSale").val();
        var porcentTax = $("#porcentTax").val();
        var porcentIVARetenido = $("#porcentIVARetenido").val();
        var porcentISRRetenido = $("#porcentISRRetenido").val();
        var routeImage = $("#routeImage").val();

        var unidadSAT = $("#unidadSAT").val();
        var unidad = $("#unidad").val();
        var claveProductoSAT = $("#claveProductoSAT").val();

        var nombreUnidadSAT = $("#unidadSAT option:selected").text();
        var nombreClaveProducto = $("#claveProductoSAT option:selected").text();



        var imagenProducto = $("#imagenProducto").prop("files")[0];

        if ($("#validateStock").is(':checked')) {

            var validateStock = "on";

        } else {

            var validateStock = "off";

        }


        if ($("#inventarioRiguroso").is(':checked')) {

            var inventarioRiguroso = "on";

        } else {

            var inventarioRiguroso = "off";

        }




        if (idEmpresa == 0 || idEmpresa == "") {

            Toast.fire({
                icon: 'error',
                title: "Tiene que seleccionar la empresa"
            });

            return;

        }

        if (idCategory == 0 || idCategory == "") {

            Toast.fire({
                icon: 'error',
                title: "Tiene que seleccionar la categoria"
            });

            return;

        }

        if (porcentTax == "") {

            Toast.fire({
                icon: 'error',
                title: "Tiene que ingregar el porcentaje de impuesto"
            });

            return;

        }



        $("#btnSaveProducts").attr("disabled", true);

        var datos = new FormData();
        datos.append("idEmpresa", idEmpresa);
        datos.append("idProducts", idProducts);
        datos.append("code", clave);
        datos.append("idCategory", idCategory);
        datos.append("description", description);
        datos.append("stock", stock);
        datos.append("buyPrice", buyPrice);
        datos.append("salePrice", salePrice);
        datos.append("porcentSale", porcentSale);
        datos.append("porcentTax", porcentTax);
        datos.append("porcentIVARetenido", porcentIVARetenido);
        datos.append("porcentISRRetenido", porcentISRRetenido);

        datos.append("imagenProducto", imagenProducto);
        datos.append("barcode", barcode);
        datos.append("validateStock", validateStock);
        datos.append("inventarioRiguroso", inventarioRiguroso);

        datos.append("unidadSAT", unidadSAT);
        datos.append("unidad", unidad);
        datos.append("claveProductoSAT", claveProductoSAT);

        datos.append("nombreUnidadSAT", nombreUnidadSAT);
        datos.append("nombreClaveProducto", nombreClaveProducto);


        $.ajax({

                url: "<?= base_url('admin/products/save') ?>",
                method: "POST",
                data: datos,
                cache: false,
                contentType: false,
                processData: false,
                success: function(respuesta) {
                    if (respuesta.match(/Correctamente.*/)) {

                        Toast.fire({
                            icon: 'success',
                            title: "Guardado Correctamente"
                        });

                        tableProducts.ajax.reload();
                        $("#btnSaveProducts").removeAttr("disabled");


                        $('#modalAddProducts').modal('hide');
                    } else {

                        Toast.fire({
                            icon: 'error',
                            title: respuesta
                        });

                        $("#btnSaveProducts").removeAttr("disabled");


                    }

                }

            }

        )

    });
</script>


<?= $this->endSection() ?>

Este es el codigo para capturar el precio de los productos app/views/modulesProducts/priceProducts.php

<p>
<h3>Precios e Impuestos</h3>

<div class="form-group row ">
    <div class="col-sm-6 ">
        <label for="buyPrice" class="col-sm-12 col-form-label">
            <?= lang('products.fields.buyPrice') ?>
        </label>
        <div class="col-sm-12">
            <div class="input-group">
                <div class="input-group-prepend">
                    <span class="input-group-text"><i class="fas fa-arrow-down"></i></span>
                </div>
                <input type="number" name="buyPrice" id="buyPrice" class="form-control form-controlProducts <?= session('error.buyPrice') ? 'is-invalid' : '' ?>" value="<?= old('buyPrice') ?>" step=".01" placeholder="<?= lang('products.fields.buyPrice') ?>" autocomplete="off">
            </div>
        </div>
    </div>


    <div class="col-sm-6 ">
        <label for="salePrice" class="col-sm-12 col-form-label">
            <?= lang('products.fields.salePrice') ?>
        </label>
        <div class="col-sm-12">
            <div class="input-group">
                <div class="input-group-prepend">
                    <span class="input-group-text"><i class="fas fa-arrow-up"></i></span>
                </div>
                <input type="number" name="salePrice" id="salePrice" class="form-control form-controlProducts <?= session('error.salePrice') ? 'is-invalid' : '' ?>" value="<?= old('salePrice') ?>" step=".01" pattern="^\d*(\.\d{0,2})?$" placeholder="<?= lang('products.fields.salePrice') ?>" autocomplete="off">
            </div>
        </div>
    </div>





</div>

<div class="form-group row ">

    <div class="col-sm-6"></div>



    <div class="col-sm-6 ">
        <label for="porcentSale" class="col-sm-12 col-form-label">
            <?= lang('products.fields.porcentSale') ?>
        </label>
        <div class="col-sm-12">
            <div class="input-group">
                <div class="input-group-prepend">
                    <span class="input-group-text"><i class="fas fa-percent"></i></span>
                </div>
                <input type="number" name="porcentSale" id="porcentSale" class="form-control form-controlProducts <?= session('error.porcentSale') ? 'is-invalid' : '' ?>" value="<?= old('porcentSale') ?>" placeholder="<?= lang('products.fields.porcentSale') ?>" autocomplete="off" min="0" value="40">
            </div>
        </div>
    </div>

</div>



<div class="form-group row">
    <label for="porcentTax" class="col-sm-2 col-form-label">
        <?= lang('products.fields.porcentTax') ?>
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-percent"></i></span>
            </div>
            <input type="number" name="porcentTax" id="porcentTax" class="form-control  form-controlProducts <?= session('error.porcentTax') ? 'is-invalid' : '' ?>" value="<?= old('porcentTax') ?>" placeholder="<?= lang('products.fields.porcentTax') ?>" autocomplete="off">
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="porcentTax" class="col-sm-2 col-form-label">
        Iva Retenido
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-percent"></i></span>
            </div>
            <input type="number" name="porcentIVARetenido" id="porcentIVARetenido" class="form-control form-controlProducts <?= session('error.porcentIVARetenido') ? 'is-invalid' : '' ?>" value="<?= old('porcentIVARetenido') ?>" placeholder="Iva Retenido" autocomplete="off">
        </div>
    </div>
</div>

<div class="form-group row">
    <label for="porcentTax" class="col-sm-2 col-form-label">
        ISR Retenido
    </label>
    <div class="col-sm-10">
        <div class="input-group">
            <div class="input-group-prepend">
                <span class="input-group-text"><i class="fas fa-percent"></i></span>
            </div>
            <input type="number" name="porcentISRRetenido" id="porcentISRRetenido" class="form-control form-controlProducts <?= session('error.porcentISRRetenido') ? 'is-invalid' : '' ?>" value="<?= old('porcentISRRetenido') ?>" placeholder="ISR Retenido" autocomplete="off">
        </div>
    </div>
</div>
</p>

Con este código generamos los controles para capturar los datos del SAT del producto app/views/modulesProducts/productsCFDIV4.php

Creamos las rutas necesarias en app/Config/routes.php

    $routes->post('products/save', 'ProductsController::save');
    $routes->post('products/getProducts', 'ProductsController::getProducts');
    $routes->get('products/getAllProducts/(:any)', 'ProductsController::getAllProducts/$1');

    $routes->get('products/getAllProductsInventory/(:any)/(:any)/(:any)', 'ProductsController::getAllProductsInventory/$1/$2/$3');

    $routes->post('products/getUnidadSATAjax', 'ProductsController::getUnidadSATAjax');
    $routes->post('products/getProductosSATAjax', 'ProductsController::getProductosSATAjax');

    $routes->post('products/getProductsAjax', 'ProductsController::getProductsAjaxSelect2');

Creamos el menú para entrar a productos

Y listo ya tenemos nuestro modulo hecho

Como el tutorial se hizo ya con el fuente avanzado algunos catálogos necesarios como sucursales puede que no estén a la fecha, pero se agregaran

Video Demostrativo

Creado con WordPress & Tema de Anders Norén