Herramientas Informaticas

Etiqueta: MySQL Página 5 de 7

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 sucursales #09

Bien como ya saben para hacer una venta se requiere saber de que sucursal es así como ciertas configuraciones particulares por sucursal como la configuración de las series electrónicas para el timbrado del CFDI, así como los datos que saldran en las impresiones de los reportes, como la dirección etc

Por esta ocasión agregaremos lo siguientes datos, posterior mente se pueden agregar mas si se necesitan

  • Empresa
  • Llave
  • Nombre
  • Colonia
  • Ciudad
  • Diferencia horaria
  • Impuesto
  • Fecha de apertura
  • Telefono
  • Fax
  • Arqueo de caja
Leer Mas: Creando CRUD de sucursales #09

Primeramente creamos el archivo de migración app/database/migrations/2023-02-14110147_Branchoffices.php , se dan cuenta que dice Branchoffice, es por que en su momento lo quise empezar en ingles para practicar

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Branchoffices extends Migration {

    public function up() {
        // Branchoffices
        $this->forge->addField([
                'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
                'key' => ['type' => 'varchar', 'constraint' => 8, 'null' => false],
                'name' => ['type' => 'varchar', 'constraint' => 256, 'null' => true],
                'cologne' => ['type' => 'varchar', 'constraint' => 64, 'null' => true],
                'city' => ['type' => 'varchar', 'constraint' => 128, 'null' => true],
                'postalCode' => ['type' => 'varchar', 'constraint' => 5, 'null' => true],
                'timeDifference' => ['type' => 'varchar', 'constraint' => 4, 'null' => true],
                'tax' => ['type' => 'varchar', 'constraint' => 4, 'null' => true],
                'dateAp' => ['type' => 'date', 'null'  => true],
                'phone'  => ['type' => 'varchar', 'constraint'  => 16, 'null'  => true],
                'fax'  => ['type' => 'varchar', 'constraint'  => 16, 'null'  => true],
                'companie'  => ['type' => 'varchar', 'constraint'  => 8, 'null'  => true],
                'arqueoCaja'  => ['type' => 'varchar', 'constraint'  => 5, '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('branchoffices', true);
    }

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

}

Una ve creado el archivo ejecutamos el comando que crea la tabla

también necesitamos la tabla de sucursales por usuario app/database/migrations/2023-02-14110147_Branchoffices.php

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class UsuariosSucursal extends Migration
{
    public function up()
    {
        // Usuariosempresa
        $this->forge->addField([
            'id'                    => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
            'idEmpresa'             => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'idSucursal'             => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'idUsuario'             => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'status'             => ['type' => 'varchar', 'constraint' => 8, '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('usuarios_sucursal', true);
    }
    public function down()
    {
        $this->forge->dropTable('usuarios_sucursal', true);
    }
}
php spark migrate

Creamos el archivo app/models/BranchofficeModel.php con los métodos necesarios de lectura y reglas de validación

<?php

namespace App\Models;

use CodeIgniter\Model;

class BranchofficesModel extends Model{

    protected $table      = 'branchoffices';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType     = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = ['id'
    ,'key'
    ,'name'
    ,'cologne'
    ,'city'
    ,'postalCode'
    ,'timeDifference'
    ,'tax'
    ,'dateAp'
    ,'phone'
    ,'fax'
    ,'companie'
    ,'arqueoCaja'
    ,'created_at
    ','deleted_at'
    ,'updated_at'];
    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $deletedField  = 'deleted_at';

    protected $validationRules    =  [
         'key ' => 'is_unique[branchoffices.key]',

    ];
    protected $validationMessages = [];
    protected $skipValidation     = false;
    
    
        public function mdlSucursalesPorUsuario($usuario){


        $resultado =$this->db->table('branchoffices a, usuarios_sucursal b')
        ->select('a.id,a.name,key,a.created_at,a.updated_at,a.deleted_at')
        ->where('a.id', 'b.idSucursal', FALSE)
        ->where('b.status', 'on')
        ->where('b.idUsuario', $usuario)->get()->getResultArray();

        return $resultado;

    }

}
        

Creamos el archivo del modelo usuarios por sucursal app/model/UsuariosSucursalModel.php

<?php

namespace App\Models;

use CodeIgniter\Model;

class UsuariosSucursalModel extends Model
{
    protected $table      = 'usuarios_sucursal';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType     = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = ['id', 'idEmpresa', 'idSucursal', 'idUsuario', 'status', '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 mdlSucursalesPorUsuario($sucursal, $empresasID)
    {

        $result = $this->db->table('users a, usuariosempresa b')
            ->select(
                'ifnull(a.id,0) as id
                ,a.username
                ,b.idEmpresa
                ,' . $sucursal . ' as idSucursal
                ,ifnull((select status 
                            from usuarios_sucursal z
                            where z.idUsuario = a.id
                                and z.idEmpresa=b.idEmpresa
                                    and z.idSucursal=' . $sucursal . '
                                    ),\'off\') as status
                                        
                ,ifnull((select id 
                        from usuarios_sucursal z
                        where z.idUsuario = a.id
                            and z.idEmpresa=b.idEmpresa
                                and z.idSucursal=' . $sucursal . '
                                ),0) as idSucursalUsuario
                '

            )

            ->where('a.id', 'b.idUsuario', FALSE)
            ->where('b.idEmpresa', $empresasID);

        return $result;
    }
}

Creamos el archivo app/controller/BranchofficesController.php

<?php

namespace App\Controllers;

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

class BranchofficesController extends BaseController {

    use ResponseTrait;

    protected $log;
    protected $branchoffices;
    protected $empresas;
    protected $usuariosPorSucursal;

    public function __construct() {
        $this->branchoffices = new BranchofficesModel();
        $this->log = new LogModel();
        $this->empresas = new EmpresasModel();
        $this->usuariosPorSucursal = new UsuariosSucursalModel();
        helper('menu');
    }

    public function index() {

        helper('auth');

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

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

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

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



        if ($this->request->isAJAX()) {




            $datos = $this->branchoffices->select('id
            ,key
            ,name
            ,cologne
            ,city
            ,postalCode
            ,timeDifference
            ,tax,dateAp
            ,phone
            ,fax
            ,companie
            ,created_at
            ,deleted_at
            ,updated_at')->where('deleted_at', null)
            ->whereIn('companie', $empresasID);;

            return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
        }


       // $empresas = $this->empresas->select("id,nombre")->asObject()->findAll();

       // $titulos["empresas"] = $empresas;
        $titulos["title"] = lang('branchoffices.title');
        $titulos["subtitle"] = lang('branchoffices.subtitle');

        return view('branchoffices', $titulos);
    }

    /**
     * Read Branchoffices
     */
    public function getBranchoffices() {


        $idBranchoffices = $this->request->getPost("idBranchoffices");
        $datosBranchoffices = $this->branchoffices->find($idBranchoffices);

        echo json_encode($datosBranchoffices);
    }

    /**
     * Save or update Branchoffices
     */
    public function save() {


        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;

        $datos = $this->request->getPost();

        if ($datos["idBranchoffices"] == 0) {


            try {


                if ($this->branchoffices->save($datos) === false) {

                    $errores = $this->branchoffices->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->branchoffices->update($datos["idBranchoffices"], $datos) == false) {

                $errores = $this->branchoffices->errors();
                foreach ($errores as $field => $error) {

                    echo $error . " ";
                }

                return;
            } else {

                $dateLog["description"] = lang("branchoffices.logUpdated") . json_encode($datos);
                $dateLog["user"] = $userName;

                $this->log->save($dateLog);
                echo "Actualizado Correctamente";

                return;
            }
        }

        return;
    }

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

        $infoBranchoffices = $this->branchoffices->find($id);
        helper('auth');
        $userName = user()->username;

        if (!$found = $this->branchoffices->delete($id)) {
            return $this->failNotFound(lang('branchoffices.msg.msg_get_fail'));
        }



        $logData["description"] = lang("branchoffices.logDeleted") . json_encode($infoBranchoffices);
        $logData["user"] = $userName;

        $this->log->save($logData);
        return $this->respondDeleted($found, lang('branchoffices.msg_delete'));
    }

    public function usuariosPorSucursal($sucursal) {

        helper('auth');

        $idUser = user()->id;

        $datosSucursal = $this->branchoffices->select("companie as empresa")->where("id",$sucursal)->first();

        if(isset($datosSucursal["empresa"])){

            $idEmpresa = $datosSucursal["empresa"];

        }else{

            $idEmpresa = -1;

        }
        

        $usuarios = $this->usuariosPorSucursal->mdlSucursalesPorUsuario($sucursal, $idEmpresa);

        return \Hermawan\DataTables\DataTable::of($usuarios)->toJson(true);
    }

    /**
     * Activar Desactivar Usuario Por Empresa
     */
    public function ActivarDesactivar() {

        $datos = $this->request->getPost();

        if ($datos["id"] > 0) {

            //ACTUALIZA SI  EXISTE

            if ($this->usuariosPorSucursal->update($datos["id"], $datos) === false) {
                $errores = $this->usuariosPorSucursal->errors();
                foreach ($errores as $field => $error) {
                    echo $error . " ";
                }
                return;
            }

            echo "ok";
        } else {

            //INSERTA SI  NO EXISTE
            if ($this->usuariosPorSucursal->save($datos) === false) {

                $errores = $this->usuariosPorSucursal->errors();

                foreach ($errores as $key => $error) {

                    echo $error . " ";
                }

                return;
            }



            echo "ok";
        }
    }

    /**
     * Get Storages via AJax
     */
    public function getSucursalesAjax() {

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

        $response = array();

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

        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;

        $sucursalesPorUsuario = $this->usuariosPorSucursal->select("*")
                        ->where("idUsuario", $idUser)
                        ->where("status", "on")->findAll();

        $sucursalesPorUsuario = array_column($sucursalesPorUsuario, "idSucursal");
        if (!isset($postData['searchTerm'])) {
            // Fetch record
            $sucursales = new BranchofficesModel();
            $listSucursales = $sucursales->select('id,key,name')->where("deleted_at", null)
                    ->whereIn("id", $sucursalesPorUsuario)
                    ->where("companie", $postData["idEmpresa"])
                    ->orderBy('id')
                    ->orderBy('key')
                    ->orderBy('name')
                    ->findAll();
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record
            $sucursales = new BranchofficesModel();
            $listSucursales = $sucursales->select('id,key,name')
                    ->where("deleted_at", null)
                    ->whereIn("id", $sucursalesPorUsuario)
                    ->where("companie", $postData["idEmpresa"])
                    ->like('name', $searchTerm)
                    ->orLike('id', $searchTerm)
                    ->orLike('key', $searchTerm)
                    ->findAll();
        }

        $data = array();
        $data[] = array(
            "id" => 0,
            "text" => "0 Todas las sucursales",
        );

        foreach ($listSucursales as $sucursal) {
            $data[] = array(
                "id" => $sucursal['id'],
                "text" => $sucursal['key'] . ' ' . $sucursal['name'],
            );
        }

        $response['data'] = $data;

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

}

Creamos el archivo del controlador para usuarios por sucursal app/controller/UsuariosSucursalController.php

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use \App\Models\{
    UsuariosSucursalModel
};
use App\Models\LogModel;
use CodeIgniter\API\ResponseTrait;

class UsuariosSucursalController extends BaseController {

    use ResponseTrait;

    protected $log;
    protected $usuariosSucursal;

    public function __construct() {
        $this->usuariosSucursal = new UsuariosSucursalModel();
        $this->log = new LogModel();
        helper('menu');
    }

    public function index() {
        if ($this->request->isAJAX()) {
            $datos = $this->usuariosSucursal>select('id,idEmpresa,idSucursal,idUsuario,status,created_at,updated_at,deleted_at')->where('deleted_at', null);
            return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
        }
        $titulos["title"] = "Usuarios Sucursal";
        $titulos["subtitle"] = "Usuarios Por Sucursal";
        return view('usuariosAlmacen', $titulos);
    }

    /**
     * Read Usuariosempresa
     */
    public function getUsuariosAlmacen() {
        $idUsuariosAlmacen = $this->request->getPost("idUsuariosSucursal");
        $datosUsuariosAlmacen = $this->usuariosAlmacen->find($idUsuariosAlmacen);
        echo json_encode($datosUsuariosAlmacen);
    }

    /**
     * Save or update Usuariosempresa
     */
    public function save() {
        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;
        $datos = $this->request->getPost();
        if ($datos["idUsuariosSucursal"] == 0) {
            try {
                if ($this->usuariosSucursal->save($datos) === false) {
                    $errores = $this->usuariosSucursal->errors();
                    foreach ($errores as $field => $error) {
                        echo $error . " ";
                    }
                    return;
                }
                $dateLog["description"] = "Usuarios Por Sucursal" . 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->usuariosSucursal->update($datos["idUsuariossucursal"], $datos) == false) {
                $errores = $this->usuariosSucursal->errors();
                foreach ($errores as $field => $error) {
                    echo $error . " ";
                }
                return;
            } else {
                $dateLog["description"] = lang("usuariosSucursal.logUpdated") . json_encode($datos);
                $dateLog["user"] = $userName;
                $this->log->save($dateLog);
                echo "Actualizado Correctamente";
                return;
            }
        }
        return;
    }

    /**
     * Delete Usuariosempresa
     * @param type $id
     * @return type
     */
    public function delete($id) {
        $infoUsuariosSucursal = $this->usuariosSucursal->find($id);
        helper('auth');
        $userName = user()->username;
        if (!$found = $this->usuariosSucursal->delete($id)) {
            return $this->failNotFound(lang('usuariosempresa.msg.msg_get_fail'));
        }
        $this->usuariosSucursal->purgeDeleted();
        $logData["description"] = "Datos Anteriores Usuarios Por Sucursal" . json_encode($infoUsuariosSucursal);
        $logData["user"] = $userName;
        $this->log->save($logData);
        return $this->respondDeleted($found, lang('usuariossucursal.msg_delete'));
    }

}

Creamos el archivo principal de la vista de sucursales app/views/branchoffice.php este archivo hará una inclusión a los modales de usuarios por sucursal y captura de sucursales

<?= $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('modulesBranchoffices/modalCaptureBranchoffices') ?>
<?= $this->include('modulesBranchoffices/usuariosSucursalModal') ?>

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

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

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

                </button>

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

                                <th>#</th>
                                <th><?= lang('branchoffices.fields.key') ?></th>
                                <th><?= lang('branchoffices.fields.name') ?></th>
                                <th><?= lang('branchoffices.fields.cologne') ?></th>
                                <th><?= lang('branchoffices.fields.city') ?></th>
                                <th><?= lang('branchoffices.fields.postalCode') ?></th>
                                <th><?= lang('branchoffices.fields.timeDifference') ?></th>
                                <th><?= lang('branchoffices.fields.tax') ?></th>
                                <th><?= lang('branchoffices.fields.dateAp') ?></th>
                                <th><?= lang('branchoffices.fields.phone') ?></th>
                                <th><?= lang('branchoffices.fields.fax') ?></th>
                                <th><?= lang('branchoffices.fields.companie') ?></th>
                                <th><?= lang('branchoffices.fields.created_at') ?></th>
                                <th><?= lang('branchoffices.fields.deleted_at') ?></th>
                                <th><?= lang('branchoffices.fields.updated_at') ?></th>

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

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

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


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

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

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

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

            {
                'data': 'key'
            },

            {
                'data': 'name'
            },

            {
                'data': 'cologne'
            },

            {
                'data': 'city'
            },

            {
                'data': 'postalCode'
            },

            {
                'data': 'timeDifference'
            },

            {
                'data': 'tax'
            },

            {
                'data': 'dateAp'
            },

            {
                'data': 'phone'
            },

            {
                'data': 'fax'
            },

            {
                'data': 'companie'
            },

            {
                '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 btnEditBranchoffices" data-toggle="modal" idBranchoffices="${data.id}" data-target="#modalAddBranchoffices">  <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>
                             <button class="btn btn-success btn-users" data-id="${data.id}" data-toggle="modal" data-target="#modalUsuariosSucursal"><i class="fas fa-users"></i></button>
                         </div>
                         </td>`
                }
            }
        ]
    });



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


        var idBranchoffices = $("#idBranchoffices").val();
        var key = $("#key").val();
        var name = $("#name").val();
        var cologne = $("#cologne").val();
        var city = $("#city").val();
        var postalCode = $("#postalCode").val();
        var timeDifference = $("#timeDifference").val();
        var tax = $("#tax").val();
        var dateAp = $("#dateAp").val();
        var phone = $("#phone").val();
        var fax = $("#fax").val();
        var companie = $("#companie").val();

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

            var arqueoCaja = "on";

        } else {

            var arqueoCaja = "off";

        }


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

        var datos = new FormData();
        datos.append("idBranchoffices", idBranchoffices);
        datos.append("key", key);
        datos.append("name", name);
        datos.append("cologne", cologne);
        datos.append("city", city);
        datos.append("postalCode", postalCode);
        datos.append("timeDifference", timeDifference);
        datos.append("tax", tax);
        datos.append("dateAp", dateAp);
        datos.append("phone", phone);
        datos.append("fax", fax);
        datos.append("companie", companie);
        datos.append("arqueoCaja", arqueoCaja);


        $.ajax({

            url: "<?= base_url('admin/branchoffices/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"
                    });

                    tableBranchoffices.ajax.reload();
                    $("#btnSaveBranchoffices").removeAttr("disabled");


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

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

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


                }

            }

        }

        )

    });



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR Branchoffices
     =============================================*/
    $(".tableBranchoffices").on("click", ".btnEditBranchoffices", function () {

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

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

        $.ajax({

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


                console.log(respuesta["dateAp"]);
                $("#idBranchoffices").val(respuesta["id"]);

                $("#key").val(respuesta["key"]);
                $("#name").val(respuesta["name"]);
                $("#cologne").val(respuesta["cologne"]);
                $("#city").val(respuesta["city"]);
                $("#postalCode").val(respuesta["postalCode"]);
                $("#timeDifference").val(respuesta["timeDifference"]);
                $("#tax").val(respuesta["tax"]);
                $("#dateAp").val(respuesta["dateAp"]);
                $("#phone").val(respuesta["phone"]);
                $("#fax").val(respuesta["fax"]);
                $("#companie").val(respuesta["companie"]);
                $("#companie").trigger("change");

                var arqueoCaja = respuesta["arqueoCaja"];

                if (arqueoCaja == "null" || arqueoCaja == "NULL") {

                    arqueoCaja = respuesta["arqueoCaja"];
                }

                $("#arqueoCaja").bootstrapToggle(arqueoCaja);


            }

        })

    })


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

        var idBranchoffices = $(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/branchoffices') ?>/` + idBranchoffices,
                            method: 'DELETE',
                        }).done((data, textStatus, jqXHR) => {
                            Toast.fire({
                                icon: 'success',
                                title: jqXHR.statusText,
                            });


                            tableBranchoffices.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })
</script>
<?= $this->endSection() ?>

ahora creamos el archivo app/modulosBranchoffices/modalCaptureBranchoffice.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('modulesBranchoffices/modalCaptureBranchoffices') ?>
<?= $this->include('modulesBranchoffices/usuariosSucursalModal') ?>

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

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

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

                </button>

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

                                <th>#</th>
                                <th><?= lang('branchoffices.fields.key') ?></th>
                                <th><?= lang('branchoffices.fields.name') ?></th>
                                <th><?= lang('branchoffices.fields.cologne') ?></th>
                                <th><?= lang('branchoffices.fields.city') ?></th>
                                <th><?= lang('branchoffices.fields.postalCode') ?></th>
                                <th><?= lang('branchoffices.fields.timeDifference') ?></th>
                                <th><?= lang('branchoffices.fields.tax') ?></th>
                                <th><?= lang('branchoffices.fields.dateAp') ?></th>
                                <th><?= lang('branchoffices.fields.phone') ?></th>
                                <th><?= lang('branchoffices.fields.fax') ?></th>
                                <th><?= lang('branchoffices.fields.companie') ?></th>
                                <th><?= lang('branchoffices.fields.created_at') ?></th>
                                <th><?= lang('branchoffices.fields.deleted_at') ?></th>
                                <th><?= lang('branchoffices.fields.updated_at') ?></th>

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

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

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


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

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

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

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

            {
                'data': 'key'
            },

            {
                'data': 'name'
            },

            {
                'data': 'cologne'
            },

            {
                'data': 'city'
            },

            {
                'data': 'postalCode'
            },

            {
                'data': 'timeDifference'
            },

            {
                'data': 'tax'
            },

            {
                'data': 'dateAp'
            },

            {
                'data': 'phone'
            },

            {
                'data': 'fax'
            },

            {
                'data': 'companie'
            },

            {
                '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 btnEditBranchoffices" data-toggle="modal" idBranchoffices="${data.id}" data-target="#modalAddBranchoffices">  <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>
                             <button class="btn btn-success btn-users" data-id="${data.id}" data-toggle="modal" data-target="#modalUsuariosSucursal"><i class="fas fa-users"></i></button>
                         </div>
                         </td>`
                }
            }
        ]
    });



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


        var idBranchoffices = $("#idBranchoffices").val();
        var key = $("#key").val();
        var name = $("#name").val();
        var cologne = $("#cologne").val();
        var city = $("#city").val();
        var postalCode = $("#postalCode").val();
        var timeDifference = $("#timeDifference").val();
        var tax = $("#tax").val();
        var dateAp = $("#dateAp").val();
        var phone = $("#phone").val();
        var fax = $("#fax").val();
        var companie = $("#companie").val();

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

            var arqueoCaja = "on";

        } else {

            var arqueoCaja = "off";

        }


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

        var datos = new FormData();
        datos.append("idBranchoffices", idBranchoffices);
        datos.append("key", key);
        datos.append("name", name);
        datos.append("cologne", cologne);
        datos.append("city", city);
        datos.append("postalCode", postalCode);
        datos.append("timeDifference", timeDifference);
        datos.append("tax", tax);
        datos.append("dateAp", dateAp);
        datos.append("phone", phone);
        datos.append("fax", fax);
        datos.append("companie", companie);
        datos.append("arqueoCaja", arqueoCaja);


        $.ajax({

            url: "<?= base_url('admin/branchoffices/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"
                    });

                    tableBranchoffices.ajax.reload();
                    $("#btnSaveBranchoffices").removeAttr("disabled");


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

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

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


                }

            }

        }

        )

    });



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR Branchoffices
     =============================================*/
    $(".tableBranchoffices").on("click", ".btnEditBranchoffices", function () {

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

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

        $.ajax({

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


                console.log(respuesta["dateAp"]);
                $("#idBranchoffices").val(respuesta["id"]);

                $("#key").val(respuesta["key"]);
                $("#name").val(respuesta["name"]);
                $("#cologne").val(respuesta["cologne"]);
                $("#city").val(respuesta["city"]);
                $("#postalCode").val(respuesta["postalCode"]);
                $("#timeDifference").val(respuesta["timeDifference"]);
                $("#tax").val(respuesta["tax"]);
                $("#dateAp").val(respuesta["dateAp"]);
                $("#phone").val(respuesta["phone"]);
                $("#fax").val(respuesta["fax"]);
                $("#companie").val(respuesta["companie"]);
                $("#companie").trigger("change");

                var arqueoCaja = respuesta["arqueoCaja"];

                if (arqueoCaja == "null" || arqueoCaja == "NULL") {

                    arqueoCaja = respuesta["arqueoCaja"];
                }

                $("#arqueoCaja").bootstrapToggle(arqueoCaja);


            }

        })

    })


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

        var idBranchoffices = $(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/branchoffices') ?>/` + idBranchoffices,
                            method: 'DELETE',
                        }).done((data, textStatus, jqXHR) => {
                            Toast.fire({
                                icon: 'success',
                                title: jqXHR.statusText,
                            });


                            tableBranchoffices.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })
</script>
<?= $this->endSection() ?>

Ahora sigue el código del modal para capturar/editar los datos de la sucursal

<?= $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('modulesBranchoffices/modalCaptureBranchoffices') ?>
<?= $this->include('modulesBranchoffices/usuariosSucursalModal') ?>

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

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

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

                </button>

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

                                <th>#</th>
                                <th><?= lang('branchoffices.fields.key') ?></th>
                                <th><?= lang('branchoffices.fields.name') ?></th>
                                <th><?= lang('branchoffices.fields.cologne') ?></th>
                                <th><?= lang('branchoffices.fields.city') ?></th>
                                <th><?= lang('branchoffices.fields.postalCode') ?></th>
                                <th><?= lang('branchoffices.fields.timeDifference') ?></th>
                                <th><?= lang('branchoffices.fields.tax') ?></th>
                                <th><?= lang('branchoffices.fields.dateAp') ?></th>
                                <th><?= lang('branchoffices.fields.phone') ?></th>
                                <th><?= lang('branchoffices.fields.fax') ?></th>
                                <th><?= lang('branchoffices.fields.companie') ?></th>
                                <th><?= lang('branchoffices.fields.created_at') ?></th>
                                <th><?= lang('branchoffices.fields.deleted_at') ?></th>
                                <th><?= lang('branchoffices.fields.updated_at') ?></th>

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

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

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


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

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

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

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

            {
                'data': 'key'
            },

            {
                'data': 'name'
            },

            {
                'data': 'cologne'
            },

            {
                'data': 'city'
            },

            {
                'data': 'postalCode'
            },

            {
                'data': 'timeDifference'
            },

            {
                'data': 'tax'
            },

            {
                'data': 'dateAp'
            },

            {
                'data': 'phone'
            },

            {
                'data': 'fax'
            },

            {
                'data': 'companie'
            },

            {
                '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 btnEditBranchoffices" data-toggle="modal" idBranchoffices="${data.id}" data-target="#modalAddBranchoffices">  <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>
                             <button class="btn btn-success btn-users" data-id="${data.id}" data-toggle="modal" data-target="#modalUsuariosSucursal"><i class="fas fa-users"></i></button>
                         </div>
                         </td>`
                }
            }
        ]
    });



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


        var idBranchoffices = $("#idBranchoffices").val();
        var key = $("#key").val();
        var name = $("#name").val();
        var cologne = $("#cologne").val();
        var city = $("#city").val();
        var postalCode = $("#postalCode").val();
        var timeDifference = $("#timeDifference").val();
        var tax = $("#tax").val();
        var dateAp = $("#dateAp").val();
        var phone = $("#phone").val();
        var fax = $("#fax").val();
        var companie = $("#companie").val();

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

            var arqueoCaja = "on";

        } else {

            var arqueoCaja = "off";

        }


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

        var datos = new FormData();
        datos.append("idBranchoffices", idBranchoffices);
        datos.append("key", key);
        datos.append("name", name);
        datos.append("cologne", cologne);
        datos.append("city", city);
        datos.append("postalCode", postalCode);
        datos.append("timeDifference", timeDifference);
        datos.append("tax", tax);
        datos.append("dateAp", dateAp);
        datos.append("phone", phone);
        datos.append("fax", fax);
        datos.append("companie", companie);
        datos.append("arqueoCaja", arqueoCaja);


        $.ajax({

            url: "<?= base_url('admin/branchoffices/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"
                    });

                    tableBranchoffices.ajax.reload();
                    $("#btnSaveBranchoffices").removeAttr("disabled");


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

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

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


                }

            }

        }

        )

    });



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR Branchoffices
     =============================================*/
    $(".tableBranchoffices").on("click", ".btnEditBranchoffices", function () {

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

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

        $.ajax({

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


                console.log(respuesta["dateAp"]);
                $("#idBranchoffices").val(respuesta["id"]);

                $("#key").val(respuesta["key"]);
                $("#name").val(respuesta["name"]);
                $("#cologne").val(respuesta["cologne"]);
                $("#city").val(respuesta["city"]);
                $("#postalCode").val(respuesta["postalCode"]);
                $("#timeDifference").val(respuesta["timeDifference"]);
                $("#tax").val(respuesta["tax"]);
                $("#dateAp").val(respuesta["dateAp"]);
                $("#phone").val(respuesta["phone"]);
                $("#fax").val(respuesta["fax"]);
                $("#companie").val(respuesta["companie"]);
                $("#companie").trigger("change");

                var arqueoCaja = respuesta["arqueoCaja"];

                if (arqueoCaja == "null" || arqueoCaja == "NULL") {

                    arqueoCaja = respuesta["arqueoCaja"];
                }

                $("#arqueoCaja").bootstrapToggle(arqueoCaja);


            }

        })

    })


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

        var idBranchoffices = $(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/branchoffices') ?>/` + idBranchoffices,
                            method: 'DELETE',
                        }).done((data, textStatus, jqXHR) => {
                            Toast.fire({
                                icon: 'success',
                                title: jqXHR.statusText,
                            });


                            tableBranchoffices.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })
</script>
<?= $this->endSection() ?>

Bien ahora en app/config/routes.php agregamos las siguientes rutas dentro del grupo admin

    
    $routes->resource('branchoffices', [
        'filter' => 'permission:branchoffices-permission',
        'controller' => 'branchofficesController',
        'except' => 'show'
    ]);

    $routes->post('branchoffices/save', 'BranchofficesController::save');
    $routes->post('branchoffices/getBranchoffices', 'BranchofficesController::getBranchoffices');

$routes->get('sucursales/usuariosPorSucursal/(:any)', 'BranchofficesController::usuariosPorSucursal/$1');
    $routes->post('sucursales/activarDesactivar', 'BranchofficesController::activarDesactivar');
    $routes->post('sucursales/getSucursalesAjax', 'BranchofficesController::getSucursalesAjax');

Creamos el menú de sucursales con los siguientes datos

Y listo ya tenemos nuestro modulo hecho

Video demostrativo

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

Creando CRUD de clientes #06

Ya vimos como usar AutoCrud para crear los catálogos, en esta ocasión tambien usamos autocrud, aun así dejaremos el código fuente completo de este modulo

Creamos el archivo modelo al app/models/CustumersModel.php con el siguiente contenido

Leer Mas: Creando CRUD de clientes #06
<?php
namespace App\Models;
use CodeIgniter\Model;
class CustumersModel extends Model{
    protected $table      = 'custumers';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType     = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = ['id','firstname','lastname','taxID','email','direction','birthdate','created_at','updated_at','deleted_at'];
    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $deletedField  = 'deleted_at';
    protected $validationRules    =  [
    ];
    protected $validationMessages = [];
    protected $skipValidation     = false;
}
        

Creamos nuestro archivo controlador app/controllers/CustumersControllers.php

<?php
 namespace App\Controllers;
 use App\Controllers\BaseController;
 use \App\Models\{CustumersModel};
 use App\Models\LogModel;
 use CodeIgniter\API\ResponseTrait;
 class CustumersController extends BaseController {
     use ResponseTrait;
     protected $log;
     protected $custumers;
     public function __construct() {
         $this->custumers = new CustumersModel();
         $this->log = new LogModel();
         helper('menu');
         helper('utilerias');
     }
     public function index() {
         if ($this->request->isAJAX()) {
             $datos = $this->custumers->select('id,firstname,lastname,taxID,email,direction,birthdate,created_at,updated_at,deleted_at')->where('deleted_at', null);
             return \Hermawan\DataTables\DataTable::of($datos)->toJson(true);
         }


         $fechaActual =  fechaMySQLADateTimeHTML5(fechaHoraActual());

         $titulos["title"] = lang('custumers.title');
         $titulos["subtitle"] = lang('custumers.subtitle');
         $titulos["fecha"] = $fechaActual;
         return view('custumers', $titulos);
     }
     /**
      * Read Custumers
      */
     public function getCustumers() {
         $idCustumers = $this->request->getPost("idCustumers");
         $datosCustumers = $this->custumers->find($idCustumers);
         echo json_encode($datosCustumers);
     }
     /**
      * Save or update Custumers
      */
     public function save() {
         helper('auth');
         $userName = user()->username;
         $idUser = user()->id;
         $datos = $this->request->getPost();
         if ($datos["idCustumers"] == 0) {
             try {
                 if ($this->custumers->save($datos) === false) {
                     $errores = $this->custumers->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->custumers->update($datos["idCustumers"], $datos) == false) {
                 $errores = $this->custumers->errors();
                 foreach ($errores as $field => $error) {
                     echo $error . " ";
                 }
                 return;
             } else {
                 $dateLog["description"] = lang("custumers.logUpdated") . json_encode($datos);
                 $dateLog["user"] = $userName;
                 $this->log->save($dateLog);
                 echo "Actualizado Correctamente";
                 return;
             }
         }
         return;
     }
     /**
      * Delete Custumers
      * @param type $id
      * @return type
      */
     public function delete($id) {
         $infoCustumers = $this->custumers->find($id);
         helper('auth');
         $userName = user()->username;
         if (!$found = $this->custumers->delete($id)) {
             return $this->failNotFound(lang('custumers.msg.msg_get_fail'));
         }
         $this->custumers->purgeDeleted();
         $logData["description"] = lang("custumers.logDeleted") . json_encode($infoCustumers);
         $logData["user"] = $userName;
         $this->log->save($logData);
         return $this->respondDeleted($found, lang('custumers.msg_delete'));
     }
 }
        

Creamos nuestro archivo de la visa en app/views/custumers.php con el siguiente contenido

<?= $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('modulesCustumers/modalCaptureCustumers') ?>

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

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

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

             </button>

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

                             <th>#</th>
                             <th><?= lang('custumers.fields.firstname') ?></th>
<th><?= lang('custumers.fields.lastname') ?></th>
<th><?= lang('custumers.fields.taxID') ?></th>
<th><?= lang('custumers.fields.email') ?></th>
<th><?= lang('custumers.fields.direction') ?></th>
<th><?= lang('custumers.fields.birthdate') ?></th>
<th><?= lang('custumers.fields.created_at') ?></th>
<th><?= lang('custumers.fields.updated_at') ?></th>
<th><?= lang('custumers.fields.deleted_at') ?></th>

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

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

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


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

 /**
  * Cargamos la tabla
  */

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

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

         }],
     columns: [{
             'data': 'id'
         },
        
          
{
    'data': 'firstname'
},
 
{
    'data': 'lastname'
},
 
{
    'data': 'taxID'
},
 
{
    'data': 'email'
},
 
{
    'data': 'direction'
},
 
{
    'data': 'birthdate'
},
 
{
    '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 btnEditCustumers" data-toggle="modal" idCustumers="${data.id}" data-target="#modalAddCustumers">  <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', '#btnSaveCustumers', function (e) {

     
var idCustumers = $("#idCustumers").val();
var firstname = $("#firstname").val();
var lastname = $("#lastname").val();
var taxID = $("#taxID").val();
var email = $("#email").val();
var direction = $("#direction").val();
var birthdate = $("#birthdate").val();

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

     var datos = new FormData();
    datos.append("idCustumers", idCustumers);
    datos.append("firstname", firstname);
    datos.append("lastname", lastname);
    datos.append("taxID", taxID);
    datos.append("email", email);
    datos.append("direction", direction);
    datos.append("birthdate", birthdate);


     $.ajax({

         url: "<?= route_to('admin/custumers/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"
                 });

                 tableCustumers.ajax.reload();
                 $("#btnSaveCustumers").removeAttr("disabled");


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

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

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

             }

         }

     }

     )

 });



 /**
  * Carga datos actualizar
  */


 /*=============================================
  EDITAR Custumers
  =============================================*/
 $(".tableCustumers").on("click", ".btnEditCustumers", function () {

     var idCustumers = $(this).attr("idCustumers");
        
     var datos = new FormData();
     datos.append("idCustumers", idCustumers);

     $.ajax({

         url: "<?= base_url(route_to('admin/custumers/getCustumers')) ?>",
         method: "POST",
         data: datos,
         cache: false,
         contentType: false,
         processData: false,
         dataType: "json",
         success: function (respuesta) {
             $("#idCustumers").val(respuesta["id"]);
             
             $("#firstname").val(respuesta["firstname"]);
$("#lastname").val(respuesta["lastname"]);
$("#taxID").val(respuesta["taxID"]);
$("#email").val(respuesta["email"]);
$("#direction").val(respuesta["direction"]);
$("#birthdate").val(respuesta["birthdate"]);


         }

     })

 })


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

     var idCustumers = $(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(route_to('admin/custumers')) ?>/` + idCustumers,
                         method: 'DELETE',
                     }).done((data, textStatus, jqXHR) => {
                         Toast.fire({
                             icon: 'success',
                             title: jqXHR.statusText,
                         });


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

 $(function () {
    $("#modalAddCustumers").draggable();
    
});


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

Para el modal creamos el siguiente archivo app/views/modulesCustumers/modalCaptureCustumers.php con el siguiente contenido

<!-- Modal Custumers -->
  <div class="modal fade" id="modalAddCustumers" tabindex="-1" role="dialog" aria-labelledby="modalAddCustumers" aria-hidden="true">
      <div class="modal-dialog modal-lg" role="document">
          <div class="modal-content">
              <div class="modal-header">
                  <h5 class="modal-title"><?= lang('custumers.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-custumers" class="form-horizontal">
                      <input type="hidden" id="idCustumers" name="idCustumers" value="0">

                      <div class="form-group row">
    <label for="firstname" class="col-sm-2 col-form-label"><?= lang('custumers.fields.firstname') ?></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="firstname" id="firstname" class="form-control <?= session('error.firstname') ? 'is-invalid' : '' ?>" value="<?= old('firstname') ?>" placeholder="<?= lang('custumers.fields.firstname') ?>" autocomplete="off">
        </div>
    </div>
</div>
<div class="form-group row">
    <label for="lastname" class="col-sm-2 col-form-label"><?= lang('custumers.fields.lastname') ?></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="lastname" id="lastname" class="form-control <?= session('error.lastname') ? 'is-invalid' : '' ?>" value="<?= old('lastname') ?>" placeholder="<?= lang('custumers.fields.lastname') ?>" autocomplete="off">
        </div>
    </div>
</div>
<div class="form-group row">
    <label for="taxID" class="col-sm-2 col-form-label"><?= lang('custumers.fields.taxID') ?></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="taxID" id="taxID" class="form-control <?= session('error.taxID') ? 'is-invalid' : '' ?>" value="<?= old('taxID') ?>" placeholder="<?= lang('custumers.fields.taxID') ?>" autocomplete="off">
        </div>
    </div>
</div>
<div class="form-group row">
    <label for="email" class="col-sm-2 col-form-label"><?= lang('custumers.fields.email') ?></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="email" id="email" class="form-control <?= session('error.email') ? 'is-invalid' : '' ?>" value="<?= old('email') ?>" placeholder="<?= lang('custumers.fields.email') ?>" autocomplete="off">
        </div>
    </div>
</div>
<div class="form-group row">
    <label for="direction" class="col-sm-2 col-form-label"><?= lang('custumers.fields.direction') ?></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="direction" id="direction" class="form-control <?= session('error.direction') ? 'is-invalid' : '' ?>" value="<?= old('direction') ?>" placeholder="<?= lang('custumers.fields.direction') ?>" autocomplete="off">
        </div>
    </div>
</div>
<div class="form-group row">
    <label for="birthdate" class="col-sm-2 col-form-label"><?= lang('custumers.fields.birthdate') ?></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="datetime-local" name="birthdate" id="birthdate" class="form-control <?= session('error.birthdate') ? 'is-invalid' : '' ?>" value="<?= $fecha ?>" placeholder="<?= lang('custumers.fields.birthdate') ?>" 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="btnSaveCustumers"><?= lang('boilerplate.global.save') ?></button>
              </div>
          </div>
      </div>
  </div>

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


  <script>

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


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

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

          $("#birthdate").val("<?= $fecha ?>");

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

      });

      /* 
       * AL hacer click al editar
       */



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


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

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

          $("#idCustumers").val(idCustumers);
          $("#btnGuardarCustumers").removeAttr("disabled");

      });




  </script>


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

Para la traducción en ingles creamos el archivo app/languaje/es/custumers.php con el siguiente contenido

 <?php

$custumers["logDescription"] = "The custumers was saved with the following data:";
$custumers["logUpdate"] = "The custumers was updated  with the following data:";
$custumers["logDeleted"] = "The custumers was deleted  with the following data:";
$custumers["msg_delete"] = "The custumers was deleted  correctly:";

$custumers["add"] = "Add Custumers";
$custumers["edit"] = "Edit custumers";
$custumers["createEdit"] = "Create / Edit";
$custumers["title"] = "custumers management";
$custumers["subtitle"] = "custumers list";
$custumers["fields"]["firstname"] = "Firstname";
$custumers["fields"]["lastname"] = "Lastname";
$custumers["fields"]["taxID"] = "TaxID";
$custumers["fields"]["email"] = "Email";
$custumers["fields"]["direction"] = "Direction";
$custumers["fields"]["birthdate"] = "Birthdate";
$custumers["fields"]["created_at"] = "Created_at";
$custumers["fields"]["updated_at"] = "Updated_at";
$custumers["fields"]["deleted_at"] = "Deleted_at";

$custumers["fields"]["actions"] = "Actions";
$custumers["msg"]["msg_insert"] = "The custumers has been correctly added.";
$custumers["msg"]["msg_update"] = "The custumers has been correctly modified.";
$custumers["msg"]["msg_delete"] = "The custumers has been correctly deleted.";
$custumers["msg"]["msg_get"] = "The Custumers has been successfully get.";
$custumers["msg"]["msg_get_fail"] = "The custumers not found or already deleted.";

return $custumers;
        

Para nuestra traduccion en español creamos el archivo app/languaje/es/custumers.php

 <?php
$custumers["logDescription"] = "El registro en clientes fue guardado con los siguientes datos:";
$custumers["logUpdate"] = "El registro en clientes fue actualizado con los siguientes datos:";
$custumers["logDeleted"] = "El registro en clientes fue eliminado con los siguientes datos:";
$custumers["msg_delete"] = "El Registro en clientes fue eliminado correctamente:";
$custumers["add"] = "Agregar Cliente";
$custumers["edit"] = "Editar Cliente";
$custumers["createEdit"] = "Crear / Editar";
$custumers["title"] = "Admon. Clientes";
$custumers["subtitle"] = "Lista de Clientes";
$custumers["fields"]["firstname"] = "Nombre";
$custumers["fields"]["lastname"] = "Apellido";
$custumers["fields"]["taxID"] = "RFC";
$custumers["fields"]["email"] = "Correo Electronico";
$custumers["fields"]["direction"] = "Direccion";
$custumers["fields"]["birthdate"] = "Fecha de nacimiento";
$custumers["fields"]["created_at"] = "Fecha de creacion";
$custumers["fields"]["updated_at"] = "Ultima modificacion";
$custumers["fields"]["deleted_at"] = "Fecha de eliminacion";

 $custumers["fields"]["actions"] = "Acciones";       
$custumers["msg"]["msg_insert"] = "Registro agregado correctamente.";
$custumers["msg"]["msg_update"] = "Registro modificado correctamente.";
$custumers["msg"]["msg_delete"] = "Registro eliminado correctamente.";
$custumers["msg"]["msg_get"] = "Registro obtenido correctamente.";
$custumers["msg"]["msg_get_fail"] = "Registro no encontrado o eliminado.";
return $custumers;
        

Creamos nuestro archivo de migración en app/databases/migrations/2023-04-21063335_Custumers.php con el siguiente contenido

<?php
    namespace App\Database\Migrations;
    use CodeIgniter\Database\Migration;
    class Custumers extends Migration
    {
    public function up()
    {
     // Custumers
    $this->forge->addField([
        'id'                    => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
        'firstname'             => ['type' => 'varchar', 'constraint' => 128, 'null' => true],
        'lastname'             => ['type' => 'varchar', 'constraint' => 128, 'null' => true],
        'taxID'             => ['type' => 'varchar', 'constraint' => 64, 'null' => true],
        'email'             => ['type' => 'varchar', 'constraint' => 128, 'null' => true],
        'direction'             => ['type' => 'varchar', 'constraint' => 1024, 'null' => true],
        'birthdate'             => ['type' => 'datetime' , '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('custumers', true);
    }
    public function down(){
        $this->forge->dropTable('custumers', true);
        }
    }

Corremos el comando php serve migrate para levantar la tabla custumers

Listo ya solo queda configurar los permisos y menú

Demostración en video

Creando CRUD de categorías usando AutoCrud #05

Ya vimos como crear módulos y catalogo ahora mostraremos como automatizar esa parte del proceso de desarrollo para ser un poco mas eficientes para ello escribimos un código que escribe el código repetitivo

Creamos el archivo app/controller/AutoCrudController.php con el siguiente código

Leer Mas: Creando CRUD de categorías usando AutoCrud #05
<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Controllers\Time;
use CodeIgniter\API\ResponseTrait;
use Faker\Provider\zh_CN\DateTime;

class AutoCrudController extends BaseController {

    use ResponseTrait;

    protected $db;

    public function __construct() {
        $this->db = \Config\Database::connect();
        helper('utilerias');
    }

    public function index($table) {
     /*
       $this->generateModel($table);
       $this->generateController($table);
       $this->generateView($table);
       $this->generateViewModal($table);
       $this->generateLanguage($table);
        */
        $this->generateMigration($table);
       
     //$this->generateLanguageES($table);

        $tableUpCase = ucfirst($table);
        $route = <<<EOF
                 \$routes->resource('$table', [
                                'filter' => 'permission:$table-permission',
                                'controller' => '{$table}Controller',
                                'except' => 'show'
                            ]);
                \$routes->post('$table/save', '{$tableUpCase}Controller::save');
                \$routes->post('{$table}/get{$tableUpCase}', '{$tableUpCase}Controller::get$tableUpCase');
        EOF;

        echo $route;
    }

    /**
     * Generate Model
     */
    public function generateModel($table) {

        $tableUpCase = ucfirst($table);
        $query = $this->db->getFieldNames($table);

        $fields = "";

        foreach ($query as $field) {
            $fields .= "'" . $field . "'" . ",";
        }


        $fields = substr($fields, 0, strlen($fields) - 1);

        $nombreClase = ucfirst($table) . "Model";

        $model = <<<EOF
        <?php
        namespace App\Models;
        use CodeIgniter\Model;
        class $nombreClase extends Model{
            protected \$table      = '$table';
            protected \$primaryKey = 'id';
            protected \$useAutoIncrement = true;
            protected \$returnType     = 'array';
            protected \$useSoftDeletes = true;
            protected \$allowedFields = [$fields];
            protected \$useTimestamps = true;
            protected \$createdField  = 'created_at';
            protected \$deletedField  = 'deleted_at';
            protected \$validationRules    =  [
            ];
            protected \$validationMessages = [];
            protected \$skipValidation     = false;
        }
                
        EOF;

        file_put_contents(ROOTPATH . "app/Models/$nombreClase.php", $model);
    }

    /**
     * Generate Controller
     * 
     */

    /**
     * Generate Controller
     */
    public function generateController($table) {

        $query = $this->db->getFieldNames($table);

        $fields = "";

        foreach ($query as $field) {
            $fields .= $field . ",";
        }


        $fields = substr($fields, 0, strlen($fields) - 1);

        $nameClassModel = ucfirst($table) . "Model";

        $tableUpCase = ucfirst($table);

        $controller = <<<EOF
        <?php
         namespace App\Controllers;
         use App\Controllers\BaseController;
         use \App\Models\{$nameClassModel};
         use App\Models\LogModel;
         use CodeIgniter\API\ResponseTrait;
         class {$tableUpCase}Controller extends BaseController {
             use ResponseTrait;
             protected \$log;
             protected $$table;
             public function __construct() {
                 \$this->$table = new $nameClassModel();
                 \$this->log = new LogModel();
                 helper('menu');
             }
             public function index() {
                 if (\$this->request->isAJAX()) {
                     \$datos = \$this->{$table}->select('$fields')->where('deleted_at', null);
                     return \Hermawan\DataTables\DataTable::of(\$datos)->toJson(true);
                 }
                 \$titulos["title"] = lang('$table.title');
                 \$titulos["subtitle"] = lang('$table.subtitle');
                 return view('$table', \$titulos);
             }
             /**
              * Read $tableUpCase
              */
             public function get$tableUpCase() {
                 \$id$tableUpCase = \$this->request->getPost("id$tableUpCase");
                 \$datos$tableUpCase = \$this->{$table}->find(\$id$tableUpCase);
                 echo json_encode(\$datos$tableUpCase);
             }
             /**
              * Save or update $tableUpCase
              */
             public function save() {
                 helper('auth');
                 \$userName = user()->username;
                 \$idUser = user()->id;
                 \$datos = \$this->request->getPost();
                 if (\$datos["id$tableUpCase"] == 0) {
                     try {
                         if (\$this->{$table}->save(\$datos) === false) {
                             \$errores = \$this->{$table}->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->{$table}->update(\$datos["id$tableUpCase"], \$datos) == false) {
                         \$errores = \$this->{$table}->errors();
                         foreach (\$errores as \$field => \$error) {
                             echo \$error . " ";
                         }
                         return;
                     } else {
                         \$dateLog["description"] = lang("$table.logUpdated") . json_encode(\$datos);
                         \$dateLog["user"] = \$userName;
                         \$this->log->save(\$dateLog);
                         echo "Actualizado Correctamente";
                         return;
                     }
                 }
                 return;
             }
             /**
              * Delete $tableUpCase
              * @param type \$id
              * @return type
              */
             public function delete(\$id) {
                 \$info$tableUpCase = \$this->{$table}->find(\$id);
                 helper('auth');
                 \$userName = user()->username;
                 if (!\$found = \$this->{$table}->delete(\$id)) {
                     return \$this->failNotFound(lang('$table.msg.msg_get_fail'));
                 }
                 \$this->{$table}->purgeDeleted();
                 \$logData["description"] = lang("{$table}.logDeleted") . json_encode(\$info$tableUpCase);
                 \$logData["user"] = \$userName;
                 \$this->log->save(\$logData);
                 return \$this->respondDeleted(\$found, lang('$table.msg_delete'));
             }
         }
                
        EOF;

        file_put_contents(ROOTPATH . "app/Controllers/{$tableUpCase}Controller.php", $controller);
    }

    /**
     * Generate View
     */
    public function generateView($table) {

        $tableUpCase = ucfirst($table);
        $query = $this->db->getFieldNames($table);

        $fields = "";

        $columnaDatatable = "";
        $datosDatatable = "";
        $variablesGuardar1 = "";
        $variablesFormData = "";
        $inputsEdit = "";
        $contador = 0;
        foreach ($query as $field) {
            $fields .= $field . ",";

            if ($contador > 0) {

                $columnaDatatable .= "<th><?= lang('$table.fields.$field') ?></th>" . PHP_EOL;

                $datosDatatable .= <<<EOF
                         
                        {
                            'data': '$field'
                        },
                        EOF . PHP_EOL;
                if ($field <> "created_at" && $field <> "updated_at" && $field <> "deleted_at") {
                    $variablesGuardar1 .= "var $field = $(\"#$field\").val();" . PHP_EOL;
                    $variablesFormData .= "datos.append(\"$field\", $field);" . PHP_EOL;
                    $inputsEdit .= "$(\"#$field\").val(respuesta[\"$field\"]);" . PHP_EOL;
                    ;
                }
            } else {
                $variablesGuardar1 .= <<<EOF
                    
                    var id$tableUpCase = $("#id$tableUpCase").val();
                    EOF . PHP_EOL;

                $variablesFormData .= <<<EOF
                        var datos = new FormData();
                        datos.append("id$tableUpCase", id$tableUpCase);
                        EOF . PHP_EOL;
            }
            $contador++;
        }
        $fields = substr($fields, 0, strlen($fields) - 1);
        $nameClassModel = ucfirst($table) . "Model";
        $view = <<<EOF
        <?= \$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('modules$tableUpCase/modalCapture$tableUpCase') ?>

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

                     <button class="btn btn-primary btnAdd$tableUpCase" data-toggle="modal" data-target="#modalAdd$tableUpCase"><i class="fa fa-plus"></i>

                         <?= lang('$table.add') ?>

                     </button>

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

                                     <th>#</th>
                                     $columnaDatatable
                                     <th><?= lang('$table.fields.actions') ?> </th>

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

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


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

         /**
          * Cargamos la tabla
          */

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

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

                 }],
             columns: [{
                     'data': 'id'
                 },
                
                 $datosDatatable
                 {
                     "data": function (data) {
                         return `<td class="text-right py-0 align-middle">
                                 <div class="btn-group btn-group-sm">
                                     <button class="btn btn-warning btnEdit$tableUpCase" data-toggle="modal" id$tableUpCase="\${data.id}" data-target="#modalAdd$tableUpCase">  <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', '#btnSave$tableUpCase', function (e) {

             $variablesGuardar1
             $("#btnSave$tableUpCase").attr("disabled", true);

             $variablesFormData

             $.ajax({

                 url: "<?= route_to('admin/$table/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"
                         });

                         table$tableUpCase.ajax.reload();
                         $("#btnSave$tableUpCase").removeAttr("disabled");


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

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

                         $("#btnSave$tableUpCase").removeAttr("disabled");
                        

                     }

                 }

             }

             )

         });



         /**
          * Carga datos actualizar
          */


         /*=============================================
          EDITAR $tableUpCase
          =============================================*/
         $(".table$tableUpCase").on("click", ".btnEdit$tableUpCase", function () {

             var id$tableUpCase = $(this).attr("id$tableUpCase");
                
             var datos = new FormData();
             datos.append("id$tableUpCase", id$tableUpCase);

             $.ajax({

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

                 }

             })

         })


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

             var id$tableUpCase = $(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(route_to('admin/$table')) ?>/` + id$tableUpCase,
                                 method: 'DELETE',
                             }).done((data, textStatus, jqXHR) => {
                                 Toast.fire({
                                     icon: 'success',
                                     title: jqXHR.statusText,
                                 });


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

         $(function () {
            $("#modalAdd$tableUpCase").draggable();
            
        });


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

        file_put_contents(ROOTPATH . "app/Views/{$table}.php", $view);
    }

    /**
     * Generate ViewModal
     */
    public function generateViewModal($table) {

        $tableUpCase = ucfirst($table);
        $query = $this->db->getFieldNames($table);

        $fields = "";

        $campos = "";
        $contador = 0;
        foreach ($query as $field) {
            $fields .= $field . ",";

            if ($contador > 0) {




                if ($field <> "created_at" && $field <> "updated_at" && $field <> "deleted_at") {

                    $campos .= <<<EOF
                        <div class="form-group row">
                            <label for="$field" class="col-sm-2 col-form-label"><?= lang('$table.fields.$field') ?></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="$field" id="$field" class="form-control <?= session('error.$field') ? 'is-invalid' : '' ?>" value="<?= old('$field') ?>" placeholder="<?= lang('$table.fields.$field') ?>" autocomplete="off">
                                </div>
                            </div>
                        </div>
                        EOF . PHP_EOL;
                }
            }




            $contador++;
        }


        $fields = substr($fields, 0, strlen($fields) - 1);

        $nameClassModel = ucfirst($table) . "Model";

        $viewModal = <<<EOF
        <!-- Modal $tableUpCase -->
          <div class="modal fade" id="modalAdd$tableUpCase" tabindex="-1" role="dialog" aria-labelledby="modalAdd$tableUpCase" aria-hidden="true">
              <div class="modal-dialog modal-lg" role="document">
                  <div class="modal-content">
                      <div class="modal-header">
                          <h5 class="modal-title"><?= lang('$table.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-$table" class="form-horizontal">
                              <input type="hidden" id="id$tableUpCase" name="id$tableUpCase" value="0">

                              $campos
                
                          </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="btnSave$tableUpCase"><?= lang('boilerplate.global.save') ?></button>
                      </div>
                  </div>
              </div>
          </div>

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


          <script>

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


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

                  $("#id$tableUpCase").val("0");

                  $("#btnSave$tableUpCase").removeAttr("disabled");

              });

              /* 
               * AL hacer click al editar
               */



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


                  var id$tableUpCase = $(this).attr("id$tableUpCase");

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

                  $("#id$tableUpCase").val(id$tableUpCase);
                  $("#btnGuardar$tableUpCase").removeAttr("disabled");

              });




          </script>


          <?= \$this->endSection() ?>
                
        EOF;

        $path = ROOTPATH . "app/Views/modules$tableUpCase/";
        if (!is_dir($path))
            mkdir($path, 0777, TRUE);

        file_put_contents($path . "modalCapture{$tableUpCase}.php", $viewModal);
    }

    /**
     * Generate Languaje EN
     */
    public function generateLanguage($table) {

        $tableUpCase = ucfirst($table);
        $query = $this->db->getFieldNames($table);

        $fields = "";

        $campos = "";
        $contador = 0;
        foreach ($query as $field) {
            $fields .= $field . ",";

            if ($contador > 0) {






                $campos .= "\${$table}[\"fields\"][\"$field\"] = \"" . ucfirst($field) . "\";" . PHP_EOL;
            }




            $contador++;
        }


        $fields = substr($fields, 0, strlen($fields) - 1);

        $nameClassModel = ucfirst($table) . "Model";

        $languaje = <<<EOF
         <?php

        \${$table}["logDescription"] = "The $table was saved with the following data:";
        \${$table}["logUpdate"] = "The $table was updated  with the following data:";
        \${$table}["logDeleted"] = "The $table was deleted  with the following data:";
        \${$table}["msg_delete"] = "The $table was deleted  correctly:";

        \${$table}["add"] = "Add $tableUpCase";
        \${$table}["edit"] = "Edit $table";
        \${$table}["createEdit"] = "Create / Edit";
        \${$table}["title"] = "$table management";
        \${$table}["subtitle"] = "$table list";
        $campos
        \${$table}["fields"]["actions"] = "Actions";
        \${$table}["msg"]["msg_insert"] = "The $table has been correctly added.";
        \${$table}["msg"]["msg_update"] = "The $table has been correctly modified.";
        \${$table}["msg"]["msg_delete"] = "The $table has been correctly deleted.";
        \${$table}["msg"]["msg_get"] = "The $tableUpCase has been successfully get.";
        \${$table}["msg"]["msg_get_fail"] = "The $table not found or already deleted.";

        return $$table;
                
        EOF;

        $path = ROOTPATH . "app/Language/en/";
        if (!is_dir($path))
            mkdir($path, 0777, TRUE);

        file_put_contents($path . "$table.php", $languaje);
    }

    /**
     * Lenguaje ES
     * @param type $table
     */
    public function generateLanguageES($table) {

        $tableUpCase = ucfirst($table);
        $query = $this->db->getFieldNames($table);

        $fields = "";

        $campos = "";
        $contador = 0;
        foreach ($query as $field) {
            $fields .= $field . ",";

            if ($contador > 0) {


                $campos .= "\${$table}[\"fields\"][\"$field\"] = \"" . ucfirst($field) . "\";" . PHP_EOL;
            }




            $contador++;
        }


        $fields = substr($fields, 0, strlen($fields) - 1);

        $nameClassModel = ucfirst($table) . "Model";

        $languaje = <<<EOF
         <?php
        \${$table}["logDescription"] = "El registro en $table fue guardado con los siguientes datos:";
        \${$table}["logUpdate"] = "El registro en $table fue actualizado con los siguientes datos:";
        \${$table}["logDeleted"] = "El registro en $table fue eliminado con los siguientes datos:";
        \${$table}["msg_delete"] = "El Registro en $table fue eliminado correctamente:";
        \${$table}["add"] = "Agregar $tableUpCase";
        \${$table}["edit"] = "Editar $table";
        \${$table}["createEdit"] = "Crear / Editar";
        \${$table}["title"] = "Admon. $table";
        \${$table}["subtitle"] = "Lista $table";
        $campos
         \${$table}["fields"]["actions"] = "Acciones";       
        \${$table}["msg"]["msg_insert"] = "Registro agregado correctamente.";
        \${$table}["msg"]["msg_update"] = "Registro modificado correctamente.";
        \${$table}["msg"]["msg_delete"] = "Registro eliminado correctamente.";
        \${$table}["msg"]["msg_get"] = "Registro obtenido correctamente.";
        \${$table}["msg"]["msg_get_fail"] = "Registro no encontrado o eliminado.";
        return $$table;
                
        EOF;

        $path = ROOTPATH . "app/Language/es/";
        if (!is_dir($path))
            mkdir($path, 0777, TRUE);

        file_put_contents($path . "$table.php", $languaje);
    }

    /**
     * Generate Migration
     * @param type $table
     */
    public function generateMigration($table) {

        $tableUpCase = ucfirst($table);
        $query = $this->db->getFieldData($table);

        $campos = "";
        $contador = 0;
        foreach ($query as $field) {


            
            if ($contador > 0) {

                
                if($field->nullable==1){
                    
                    $nullable="true";
                    
                }   else{
                    
                    $nullable="false";
                    
                } 
                if ($field->name <> "created_at" && $field->name <> "updated_at" && $field->name <> "deleted_at") {
                    $campos .= "'{$field->name}'             => ['type' => '{$field->type}', 'constraint' => {$field->max_length}, 'null' => $nullable]," . PHP_EOL;
                }
            } else {

                    $campos .= "'id'                    => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true]," . PHP_EOL;
            }



            $contador++;
        }




        $nameClassModel = ucfirst($table) . "Model";

        $migration = <<<EOF
            <?php
            namespace App\Database\Migrations;
            use CodeIgniter\Database\Migration;
            class $tableUpCase extends Migration
            {
            public function up()
            {
             // $tableUpCase
            \$this->forge->addField([
                $campos
                '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('$table', true);
            }
            public function down(){
                \$this->forge->dropTable('$table', true);
                }
            }
        EOF;

        $path = ROOTPATH . "app/Database/Migrations/";
        if (!is_dir($path))
            mkdir($path, 0777, TRUE);

        $fechaActual = fechaParaMigraciones(fechaHoraActual());

        file_put_contents($path . "{$fechaActual}_{$tableUpCase}.php", $migration);

        //file_put_contents($path . "2023-02-07-165411_$tableUpCase.php", $viewModal);
    }
    
    

}

Creamos el archivo app/helpers/utilerias_helper.php con el siguiente codigo

<?php

function strMenuActivo($strMenu1, $strMenu2) {
    if ($strMenu1 == $strMenu2) {
        $respuesta = 'class="active"';
    } else {
        $respuesta = "";
    }
    return $respuesta;
}

//SI LA VARIABLE ESTA VACIA O NO SETA DECLARADA MANDARA CERO SIEMPRE, ES COMO EL VAL DE VISUAL BASIC 6.0

function esCero($value) {

    if (empty($value)) {
        return "0";
    } else {
        return $value;
    }
}

//FECHA SQL PARA GUARDAR EN BASE DE DATOS

function fechaSQL($fecha) {

    return date("Ymd", strtotime($fecha));
}

// CONVIERTE FECHA MYSQLDATETIME A HTML5
function fechaMySQLADateTimeHTML5($fecha) {

    return date("Y-m-d", strtotime($fecha)) . "T" . date("H:i:s", strtotime($fecha));
}

function agregarMinutos($fecha, $minutos) {


    return date("Y/m/d h:i:s", strtotime($fecha . "+ $minutos minutes"));

    /*
      $fecha1= new DateTime($fecha);
      $fecha1->add(new DateInterval('PT10H30S'));
      return $date->format('Y-m-d H:i:s') . "\n";
     * 
     */
}

//CONVIERTE LA FECHA EN PERIODO
function fechaPeriodo($fecha) {

    return date("Ym", strtotime($fecha));
}

//OBTIENE FECHA ACTUAL
function fechaActual() {

    return date("Y/m/d");
}

//OBTIENE FECHA HORA ACTUAL

function fechaHoraActual() {

    return date("Y-m-d H:i:s ", time());
}

//DIFERENCIA ENTRE MINUTOS
function diferenciaMinutos($fecha_i, $fecha_f) {
    $minutos = (strtotime($fecha_i) - strtotime($fecha_f)) / 60;

    $minutos = abs($minutos);
    $minutos = floor($minutos);

    return $minutos;
}

function strSellar($llave, $password, $cadenaOriginal) {


    $archivoPem = "/tmp/llave.key.pem";
    $comando = "openssl pkcs8 -inform DER -in $llave -passin pass:$password -out $archivoPem";

    exec($comando);
    $sello = "ok";

    //Sellar
    $archivo = openssl_pkey_get_private(file_get_contents($archivoPem));
    $sig = "";
    openssl_sign($cadenaOriginal, $sig, $archivo, OPENSSL_ALGO_SHA256);

    $sello = base64_encode($sig);

    return $sello;
}

//SOLO DIA
function dia($fecha) {

    return date("d", strtotime($fecha));
}

//SOLO MES
function mes($fecha) {

    return date("m", strtotime($fecha));
}

//SOLO AÑO
function año($fecha) {

    return date("Y", strtotime($fecha));
}

//GENERA UUID
function generaUUID() {


    $uuid = service('uuid');
    $uuid4 = $uuid->uuid4();
    $string = $uuid4->toString();

    return $string;
}

function satinizar($var, $type) {
    switch ($type) {
        case 'html':
            $safe = htmlspecialchars($var);
            break;
        case 'sql':
            $safe = mysql_real_escape_string($var);
            break;
        case 'file':
            $safe = preg_replace('/(\/|-|_)/', '', $var);
            break;
        case 'shell':
            $safe = escapeshellcmd($var);
            break;
        default:
            $safe = htmlspecialchars($var);
    }
    return $safe;
}

function limpiaCadena($cadena) {

    $cadena = str_replace('"', "", $cadena);
    $cadena = str_replace('\n', "", $cadena);
    $cadena = str_replace('\t', "", $cadena);
    $cadena = trim($cadena);

    $cadena = preg_replace("[\n|\r|\n\r]", "", $cadena);

    $descripcion = preg_replace("[\n|\r|\n\r]", "", $descripcion);

    return $cadena;
}


// CONVIERTE FECHA MYSQLDATETIME A HTML5
function fechaParaMigraciones($fecha) {

    return date("Y-m-d", strtotime($fecha)) . date("His", strtotime($fecha));
    
}

En rutas ponemos el siguiente codigo

$routes->get('generateCRUD/(:any)', 'AutoCrudController::index/$1');

Creamos la tabla de categorías

-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Servidor: 127.0.0.1
-- Tiempo de generación: 21-04-2023 a las 20:19:25
-- Versión del servidor: 10.4.17-MariaDB
-- Versión de PHP: 7.4.15

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- Base de datos: `ci4jcpos`
--

-- --------------------------------------------------------

--
-- Estructura de tabla para la tabla `categorias`
--

CREATE TABLE `categorias` (
  `id` int(11) NOT NULL,
  `descripcion` varchar(128) COLLATE utf8mb4_spanish2_ci DEFAULT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_spanish2_ci;

--
-- Índices para tablas volcadas
--

--
-- Indices de la tabla `categorias`
--
ALTER TABLE `categorias`
  ADD PRIMARY KEY (`id`);

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `categorias`
--
ALTER TABLE `categorias`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=2;
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

Corremos la siguiente URL en el navegador para crear todo el catalogo

http://localhost:8080/admin/generateCRUD/categorias

Esto nos generara los archivos necesarios y ademas la ruta para agregarlo en routes.php


         $routes->resource('categorias', [
                        'filter' => 'permission:categorias-permission',
                        'controller' => 'categoriasController',
                        'except' => 'show'
                    ]);
        $routes->post('categorias/save', 'CategoriasController::save');
        $routes->post('categorias/getCategorias', 'CategoriasController::getCategorias');
Ya solo hacemos los cambios estéticos necesarios, agregamos los permisos necesarios corregimos la ruta en el menú y listo
Video Demostrativo

JCPOS Ultimate – Nuevo Repositorio #1

JCPOS: Una solución de punto de venta en constante evolución

En una reciente transmisión en vivo, el equipo de desarrollo detrás de JCPOS discutió los últimos cambios y mejoras en su solución de punto de venta. JCPOS es un sistema de punto de venta que ha estado en desarrollo desde el 2020, y ha evolucionado constantemente desde entonces para satisfacer las necesidades de sus usuarios.

Uno de los cambios más notables que el equipo ha realizado es el cambio de nombre de JC post a JCPOS. Esto se hizo para mantener la coherencia en el nombre a lo largo de los años, y evitar confusiones al cambiar el nombre cada año.

Otro de los cambios que se discutió en la transmisión fue la reciente publicación del repositorio de JCPOS en Github. Ahora está disponible para que cualquier persona interesada lo pueda descargar y explorar el código fuente. Además, se hizo hincapié en que los usuarios pueden contribuir al proyecto si encuentran algún problema o tienen alguna sugerencia.

El equipo también habló de algunos cambios específicos que han realizado en JCPOS, como la corrección de un problema en la bitácora que no estaba ordenando los botones de manera correcta. También se discutió la implementación de descuentos en las ventas, para asegurarse de que funcionen correctamente. Además, se está trabajando en la corrección de detalles menores, como la ordenación de la lista de ventas.

El equipo de JCPOS también habló sobre su intención de usar Composer para gestionar las dependencias del proyecto, lo que les permitirá mantenerse actualizados con las últimas versiones de las librerías que utilizan. También se mencionó la adición de impuestos a los productos, lo que permitirá la implementación de facturación electrónica en el futuro.

Finalmente, el equipo mencionó a sus patrocinadores y clientes, agradeciendo su apoyo y mencionando que los nombres de los patrocinadores se agregarán al repositorio en el futuro. También se proporcionó información sobre cómo patrocinar el proyecto.

En resumen, JCPOS es un proyecto de punto de venta en constante evolución. El equipo detrás de JCPOS está trabajando constantemente para mejorar la solución, escuchar las necesidades de los usuarios y mantenerse actualizado con las últimas tecnologías y herramientas de desarrollo. Si estás interesado en probar JCPOS o contribuir al proyecto, ¡asegúrate de visitar su repositorio en Github!

CURSO MARIADB -PROCEDIMIENTOS ALMACENADOS #10

Un procedimiento almacenado (stored procedure en inglés) es un programa (o procedimiento) almacenado físicamente en una base de datos. Su implementación varía de un gestor de bases de datos a otro. La ventaja de un procedimiento almacenado es que al ser ejecutado, en respuesta a una petición de usuario, es ejecutado directamente en el motor de bases de datos, el cual usualmente corre en un servidor separado. Como tal, posee acceso directo a los datos que necesita manipular y sólo necesita enviar sus resultados de regreso al usuario, deshaciéndose de la sobrecarga resultante de comunicar grandes cantidades de datos salientes y entrantes.

Los procedimientos pueden ser ventajosos: cuando una base de datos es manipulada desde muchos programas externos. Al incluir la lógica de la aplicación en la base de datos utilizando procedimientos almacenados, la necesidad de embeber la misma lógica en todos los programas que acceden a los datos es reducida. Esto puede simplificar la creación y, particularmente, el mantenimiento de los programas involucrados.

Podemos ver un claro ejemplo de estos procedimientos cuando requerimos realizar una misma operación en un servidor dentro de algunas o todas las bases de datos y a la vez dentro de todas o algunas de las tablas de las bases de datos del mismo. Para ello podemos utilizar a los Procedimientos almacenados auto creables que es una forma de generar ciclos redundantes a través de los procedimientos almacenados.

Hoy en día existe un debate si se debe de usar procedimientos almacenados, en mi opinión digo que si, hay pros y contras en usar procedimientos almacenados, pero al final es mejor mejor practica usarlos ya que funcionara mas eficientemente siempre y cuando se haga bien, también si se da el caso de que sea una aplicación de escritorio la mayor parte de las veces no se necesitara recompilar y actualizar el ejecutable, si no que solo se tendrá que modificar la base de datos .

Bien como prueba vamos a hacer un procedimiento almacenado básico, tal procedimiento va a insertar en la tabla clientes y luego ejecutara una consulta.

CREATE PROCEDURE `SPR_InsertaCliente`(IN `strNombre` VARCHAR(256)
				    , IN `strRFC` VARCHAR(15)
                                    , IN `strDireccion` VARCHAR(15)) 

BEGIN 

	insert into clientes(nombres 
			    ,RFC
			    ,direccion ) 
			
		    values( strNombre 
			    ,strRFC
			    ,strDireccion ); 

		select * from clientes; 
END;

Ahora mandamos llamar el procedimiento almacenado que hara 2 cosas, una es insertar

SET
    @p0 = 'Cesar';
SET
    @p1 = 'XXXA';
SET
    @p2 = 'Conocido';
CALL
    `SPR_InsertaCliente`(@p0, @p1, @p2);

O simplemente lo podemos invocar de la siguiente manera

CALL SPR_InsertaCliente('Cesar', 'XXXA', 'Conocido');

El resultado será el siguiente



id	nombres	RFC	direccion	
1	Julio	xxx	conocido	
2	kakaroto	xxe	vegita	
3	Cesar	XXXA	Conocido	
4	Cesar	XXXA	Conocido	
5	Cesar	XXXA	Conocido	

Nos arroja varias ya que hemos ejecutado varias veces el procedimiento

Vídeo Demostrativo

CURSO MARIADB 10 – VISTAS

Las vistas en MariaDB o MySQL son tablas virtuales que no almacenan ningún dato sino que es el resultado de la consulta de varias tablas o una según se allá hecho la consulta, por ejemplo, tenemos la tabla clientes y la tabla ventas, en ventas aparte de los demás campos propios de una venta tenemos el idCliente que se relaciona con el campo id de la tabla cliente y de allí podemos tomar el nombre, por ejemplo generamos el siguiente guion para obtener todos los datos.

SELECT a.id as idVenta
	, a.idCliente
    , a.Concepto
    , a.total
    , b.nombres as nombreCliente
    , b.RFC as RFCCliente
    , b.direccion	as direccion
FROM ventas a
	,clientes b 
   where idCliente=b.id

Nos dará como resultado lo siguiente

idVenta	idCliente	Concepto	total	nombreCliente	RFCCliente	direccion	
1	1	venta de equipo de computo	10000.00	Julio	xxx	conocido	
2	2	Venta de semillas del hermitaño	5.00	kakaroto	xxe	vegita	

Bien en base a esa consulta podemos crear la vista

CREATE
 VIEW `vw_vistaVenta`
 AS SELECT a.id as idVenta
    , a.idCliente
    , a.Concepto
    , a.total
    , b.nombres as nombreCliente
    , b.RFC as RFCCliente
    , b.direccion   as direccion
FROM ventas a
    ,clientes b 
   where idCliente=b.id;

Ahora solo en lugar de escribir todo el código que pusimos al principio solo pondremos lo siguiente

select * from vw_vistaVenta;

Y nos arroja el mismo resultado sin tanto código

idVenta	idCliente	Concepto	total	nombreCliente	RFCCliente	direccion	
1	1	venta de equipo de computo	10000.00	Julio	xxx	conocido	
2	2	Venta de semillas del hermitaño	5.00	kakaroto	xxe	vegita	

CURSO MARIADB 10 – RESPALDOS AUTOMATICOS DIARIOS #8

Bien ya explicamos como generar respaldos manuales, pero lo importante es que se generen automáticamente sin tener que intervenir nosotros, también es importante comprimir el archivo ya que conforme pasa el tiempo y se ingresan datos la base de datos crece en tamaño, lo bueno de comprimirla es que baja su tamaño hasta 10 veces .

Bien primero para el caso de Windows vamos a descargar un compresor para Windows que se llama XZ puede descargarlo desde source forge XZ Utils for Windows download | SourceForge.net

Copiamos el archivo en c:\xampp\mysql\bin

Quedaría el ejecutable en esa carpeta

Ahora abrimos el bloc de notas y metemos el siguiente texto

cd c:\xampp\mysql\bin
mysqldump --opt --events --routines --triggers --default-character-set=utf8 -u root pos | xz > c:\respaldo\respaldo.sql.xz

Lo guardamos en la carpeta respaldos o en la carpeta de su preferencia, lo guardamos con extensión .bat o

Vemos como guardo el archivo respaldo.sql.xz
A abrir el archivo comprimido y ver el archivo SQL nos debe mostrar algo similar a la imagen

Ahora sigue para programar para que se ejecute automáticamente

Abrimos el programador de tareas ya esta instalado en Windows Solo nos vamos al Inicio y escribimos programador de tareas, luego vamos al menú acción y le damos clic en Crear Tarea básica
Le ponemos el nombre de la tarea programada y le damos siguiente
Le ponemos cada tanto tiempo se va ejecutar el archivo .bat,le damos siguiente
Escogemos cada tanto tiempo se va ejecutar y a que hora
Escogemos que programa o script se va a ejecutar

Y listo nuestro programa se estará ejecutando diariamente si la PC o Servidor se encuentra encendido

CURSO MARIADB 10 – GENERAR RESPALDOS #7

Una función importante para los administradores de la base de datos es la de generar respaldos de bases de datos y levantar respaldos, tanto para trabajar en ambientes de prueba como recurarse de errores críticos.

Primero que nada veremos como hacer el respaldos desde PHPMYADMIN es realmente muy sencillo.

Primero seleccionamos la base de datos a la cual querernos realizar el respaldo, después nos vamos a exportar y continuar, nos genera un archivo SQL el cual se descargara en el destino que se eliga
Para importar/levantar la base datos nos vamos a importar elegimos la base de datos y nos vamos al menú importar, elegimos el archivo y le damos continuar

También podemos generar respaldos y levantarlos desde HeidiSQL

Primero le damos click derecho a la base de datos que queremos realizar el respaldo y seleccionamos Exportar Base de datos como SQL
Le ponemos el nombre y ubicación a la base de datos que queremos crear y elegimos si deseamos crear la base de datos y si existe suprimirla para volverla crear, al darle exportar generar el archivo en la ruta elegida
Para levantar la base de datos desde HeidiSQL nomas nos vamos al menú Archivo y seleccionamos Ejecutar archivo SQL
Seleccionamos el archivo SQL y le damos Abrir
Nos pregunta si queremos que auto-detecte la codificación del archivo le damos click al boton Si
Y vemos como se genera la base de datos con sus tablas e información

También podemos generar el respaldos desde la consola de comandos

Primero nos posicionamos en la carpeta bin de mysql, en mi caso es la siguiente ruta

cd c:\xampp\mysql\bin

Ahora ejecutamos el siguiente comando para realizar el respaldo

mysqldump --opt --events --routines --triggers --default-character-set=utf8 -u root pos > c:\respaldo\respaldo.sql

En caso de requerir contraseña solo agregamos –password CONTRASEÑA

Para levantar la base de datos primero la creamos con el siguiente comando

/opt/lampp/bin/mysql -u root -e "CREATE SCHEMA pos DEFAULT CHARACTER SET utf8 COLLATE utf8_spanish2_ci ;"

Luego levantamos la base de datos con el siguiente comando

mysql -u root pos < c:\respaldo\respaldo.sql

Bien espero que les sirva y en la próxima publicación veremos como comprimir el respaldos y hacer respaldos automáticos

Página 5 de 7

Creado con WordPress & Tema de Anders Norén