Herramientas Informaticas

Etiqueta: CODEIGNITER Página 4 de 5

Creando CRUD de Proveedores #11

Antes de empezar con el inventario necesitaremos los catálogos principales, uno de ellos es el catalogo de proveedores.

El catalogo es similar al de clientes

Para ello necesitamos en los siguientes campos:

Datos Generales

  • Empresa
  • Nombre
  • Apellido
  • Correo Electrónico
  • Dirección
  • Código Postal
  • Fecha de nacimiento

Datos Extra Facturación

  • Razón social
  • RFC
  • Forma de pago
  • Método de pago
  • Uso CFDI
  • Regimen Fiscal
Leer Mas: Creando CRUD de Proveedores #11

Creamos el archivo de migración App/Database/Migrations/2023-04-21063336_Proveedores con el siguiente codigo

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Proveedores extends Migration {

    public function up() {
        // Custumers
        $this->forge->addField([
            'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
            'idEmpresa' => ['type' => 'varchar', 'constraint' => 128, 'null' => true],
            'firstname' => ['type' => 'varchar', 'constraint' => 128, 'null' => true],
            'lastname' => ['type' => 'varchar', 'constraint' => 128, 'null' => true],
            'razonSocial' => ['type' => 'varchar', 'constraint' => 512, '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],
            'formaPago' => ['type' => 'varchar', 'constraint' => 16, 'null' => true],
            'metodoPago' => ['type' => 'varchar', 'constraint' => 16, 'null' => true],
            'usoCFDI' => ['type' => 'varchar', 'constraint' => 16, 'null' => true],
            'codigoPostal' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true],
            'regimenFiscal' => ['type' => 'varchar', 'constraint' => 128, '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('proveedores', true);
    }

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

}

Creamos el archivo modelo en App/Models/ProveedoresModel.php con el siguiente código

<?php

namespace App\Models;

use CodeIgniter\Model;

class ProveedoresModel extends Model {

    protected $table = 'proveedores';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = ['id'
        , 'idEmpresa'
        , 'firstname'
        , 'lastname'
        , 'taxID'
        , 'email'
        , 'direction'
        , 'birthdate'
        , 'created_at'
        , 'updated_at'
        , 'deleted_at'
        , 'metodoPago'
        , 'formaPago'
        , 'codigoPostal'
        , 'regimenFiscal'
        , 'razonSocial'
        , 'usoCFDI'];
    protected $useTimestamps = true;
    protected $createdField = 'created_at';
    protected $deletedField = 'deleted_at';
    protected $validationRules = [
    ];
    protected $validationMessages = [];
    protected $skipValidation = false;

    /**
     * Obtener Clientes
     */
    public function mdlGetProveedores($empresas) {


        $resultado = $this->db->table('proveedores a, empresas b')
                ->select('a.id
                    ,a.idEmpresa
                    ,b.nombre as nombreEmpresa
                    ,a.firstname
                    ,a.lastname
                    ,a.taxID
                    ,a.email
                    ,a.direction
                    ,a.birthdate
                    ,a.metodoPago
                    ,a.formaPago
                    ,a.usoCFDI
                    ,a.created_at
                    ,a.updated_at
                    ,a.codigoPostal
                    ,a.regimenFiscal
                    ,a.razonSocial
                    ,a.deleted_at')
                ->where('a.idEmpresa', 'b.id', FALSE)
                ->whereIn('a.idEmpresa', $empresas);

        return $resultado;
    }

}

Creamos el archivo controlador en App/Controllers/ProveedoresController.php con el siguiente código.

<?php

namespace App\Controllers;

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

class ProveedoresController extends BaseController {

    use ResponseTrait;

    protected $log;
    protected $proveedores;
    protected $empresa;

    public function __construct() {
        $this->proveedores = new ProveedoresModel();
        $this->log = new LogModel();
        $this->empresa = new EmpresasModel();
        helper('menu');
        helper('utilerias');
    }

    public function index() {


        helper('auth');

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

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

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

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

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

        $fechaActual = fechaMySQLADateTimeHTML5(fechaHoraActual());

        $titulos["title"] = lang('proveedores.title');
        $titulos["subtitle"] = lang('proveedores.subtitle');
        $titulos["fecha"] = $fechaActual;

        $titulos["formaPago"] = $this->catalogosSAT->formasDePago40()->searchByField("texto", "%%", 99999);
        $titulos["usoCFDI"] = $this->catalogosSAT->usosCfdi40()->searchByField("texto", "%%", 99999);
        $titulos["metodoPago"] = $this->catalogosSAT->metodosDePago40()->searchByField("texto", "%%", 99999);
        $titulos["regimenFiscal"] = $this->catalogosSAT->regimenesFiscales40()->searchByField("texto", "%%", 99999);
        return view('proveedores', $titulos);
    }

    /**
     * Read Custumers
     */
    public function getProveedores() {
        $idProveedor = $this->request->getPost("idProveedor");

        $datosProveedor = $this->proveedores->find($idProveedor);
        echo json_encode($datosProveedor);
    }

    /**
     * Get Custumers via AJax
     */
    public function getProveedoresAjax() {

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

        $response = array();

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

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

            $listProveedores = $proveedores->select('id,firstname,lastname')->where("deleted_at", null)
                    ->where('idEmpresa', $idEmpresa)
                    ->orderBy('id')
                    ->orderBy('firstname')
                    ->orderBy('lastname')
                    ->findAll(10);
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record

            $listProveedores = $proveedores->select('id,firstname,lastname')->where("deleted_at", null)
                    ->where('idEmpresa', $idEmpresa)
                    ->groupStart()
                    ->like('firstname', $searchTerm)
                    ->orLike('id', $searchTerm)
                    ->orLike('lastname', $searchTerm)
                    ->groupEnd()
                    ->findAll(10);
        }

        $data = array();
        foreach ($listProveedores as $proveedores) {
            $data[] = array(
                "id" => $proveedores['id'],
                "text" => $proveedores['id'] . ' ' . $proveedores['firstname'] . ' ' . $proveedores['lastname'],
            );
        }

        $response['data'] = $data;

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

    /**
     * Save or update Custumers
     */
    public function save() {
        helper('auth');
        $userName = user()->username;
        $idUser = user()->id;
        $datos = $this->request->getPost();
        if ($datos["idProveedor"] == 0) {
            try {
                if ($this->proveedores->save($datos) === false) {
                    $errores = $this->proveedores->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->proveedores->update($datos["idProveedor"], $datos) == false) {
                $errores = $this->proveedores->errors();
                foreach ($errores as $field => $error) {
                    echo $error . " ";
                }
                return;
            } else {
                $dateLog["description"] = lang("proveedores.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->proveedores->find($id);
        helper('auth');
        $userName = user()->username;
        if (!$found = $this->proveedores->delete($id)) {
            return $this->failNotFound(lang('custumers.msg.msg_get_fail'));
        }
        $this->proveedores->purgeDeleted();
        $logData["description"] = lang("proveedores.logDeleted") . json_encode($infoCustumers);
        $logData["user"] = $userName;
        $this->log->save($logData);
        return $this->respondDeleted($found, lang('custumers.msg_delete'));
    }

}

Creamos el archivo principal de la vista en App/Views/proveedores.php con el siguiente código.

<?= $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('modulesProveedores/modalCaptureProveedores') ?>

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

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

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

                </button>

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

                                <th>#</th>

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

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

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

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


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

    /**
     * Cargamos la tabla
     */

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

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

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

            {
                'data': 'nombreEmpresa'
            },

            {
                '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 btnEditProveedores" data-toggle="modal" idProveedor="${data.id}" data-target="#modalAddProveedores">  <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 Proveedores
     =============================================*/
    $(".tableProveedores").on("click", ".btnEditProveedores", function () {

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

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

        $.ajax({

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

                $("#idProveedor").val(respuesta["id"]);
                $("#idEmpresaProveedor").val(respuesta["idEmpresa"]);
                $("#idEmpresaProveedor").trigger("change");
                $("#firstname").val(respuesta["firstname"]);
                $("#razonSocial").val(respuesta["razonSocial"]);
                $("#lastname").val(respuesta["lastname"]);
                $("#taxID").val(respuesta["taxID"]);
                $("#email").val(respuesta["email"]);
                $("#direction").val(respuesta["direction"]);
                $("#birthdate").val(respuesta["birthdate"]);

                $("#formaPago").val(respuesta["formaPago"]);
                $("#formaPago").trigger("change");
                $("#metodoPago").val(respuesta["metodoPago"]);
                $("#metodoPago").trigger("change");
                $("#usoCFDI").val(respuesta["usoCFDI"]);
                $("#usoCFDI").trigger("change");

                $("#codigoPostal").val(respuesta["codigoPostal"]);
                $("#regimenFiscal").val(respuesta["regimenFiscal"]);
                $("#regimenFiscal").trigger("change");

            }

        })

    })

    $("#idEmpresaProveedor ").select2();
    /*=============================================
     ELIMINAR proveedores
     =============================================*/
    $(".tableProveedores").on("click", ".btn-delete", function () {

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


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

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

    });


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

Creamos el archivo secundario que contiene el modal en App/Views/modulesProveedores/modalCaptureProveedores.php con el siguiente codigo.

<?= $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('modulesProveedores/modalCaptureProveedores') ?>

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

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

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

                </button>

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

                                <th>#</th>

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

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

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

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


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

    /**
     * Cargamos la tabla
     */

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

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

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

            {
                'data': 'nombreEmpresa'
            },

            {
                '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 btnEditProveedores" data-toggle="modal" idProveedor="${data.id}" data-target="#modalAddProveedores">  <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 Proveedores
     =============================================*/
    $(".tableProveedores").on("click", ".btnEditProveedores", function () {

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

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

        $.ajax({

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

                $("#idProveedor").val(respuesta["id"]);
                $("#idEmpresaProveedor").val(respuesta["idEmpresa"]);
                $("#idEmpresaProveedor").trigger("change");
                $("#firstname").val(respuesta["firstname"]);
                $("#razonSocial").val(respuesta["razonSocial"]);
                $("#lastname").val(respuesta["lastname"]);
                $("#taxID").val(respuesta["taxID"]);
                $("#email").val(respuesta["email"]);
                $("#direction").val(respuesta["direction"]);
                $("#birthdate").val(respuesta["birthdate"]);

                $("#formaPago").val(respuesta["formaPago"]);
                $("#formaPago").trigger("change");
                $("#metodoPago").val(respuesta["metodoPago"]);
                $("#metodoPago").trigger("change");
                $("#usoCFDI").val(respuesta["usoCFDI"]);
                $("#usoCFDI").trigger("change");

                $("#codigoPostal").val(respuesta["codigoPostal"]);
                $("#regimenFiscal").val(respuesta["regimenFiscal"]);
                $("#regimenFiscal").trigger("change");

            }

        })

    })

    $("#idEmpresaProveedor ").select2();
    /*=============================================
     ELIMINAR proveedores
     =============================================*/
    $(".tableProveedores").on("click", ".btn-delete", function () {

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


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

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

    });


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

Creamos el archivo de lenguaje en español en App/Languaje/es/proveedores.php con el siguiente código.

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

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

Creamos el archivo de lenguaje en ingles en App/Languaje/en/proveedores.php con el siguiente código.

 <?php

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

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

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

return $proveedores;
        

En App/Config/Routes.php en el grupo admin agregamos las siguientes rutas

    $routes->resource('proveedores', [
        'filter' => 'permission:proveedores-permission',
        'controller' => 'ProveedoresController',
        'except' => 'show'
    ]);
    $routes->post('proveedores/save', 'ProveedoresController::save');
    $routes->post('proveedores/getProveedores', 'ProveedoresController::getProveedores');
    $routes->post('proveedores/getProveedoresAjax', 'ProveedoresController::getProveedoresAjax');

Creamos el menú de proveedores tal cual se ve en la imagen

Creamos el permiso y lo asignamos al rol correspondiente

Y listo ya tenemos nuestro catalogo de proveedores

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 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

CI4JCPOS Construyendo Menú #2

Ya tenemos la base del proyecto, ahora lo que sigue es darle forma creando los menús

Realmente aquí no hay nada complejo, el menú soporta 2 niveles, podemos escoger nuestro icono de font awesome le pones la ruta y que roles tendrán acceso a ese nivel, para acomodar el menú solo basta con arrastrar y soltar los items
Realmente aquí no hay nada complejo, el menú soporta 2 niveles, podemos escoger nuestro icono de font awesome le pones la ruta y que roles tendrán acceso a ese nivel, para acomodar el menú solo basta con arrastrar y soltar los items
Video Demostrativo

CodeIgniter 4 Aplicacion Boilerplate Plantilla

Que es boilerplate? normalmente en el ámbito de desarrollo de aplicaciones se le llama boilerplate a todo ese código repetitivo que usamos en un proyecto, o mas bien lo que siempre usamos en todos los proyectos, como login, menús, permisos.

A continuación les dejo un boilerplate para CodeIgniter el cual hice una bifurcación para adaptarlo a nuestras necesidades.

Este paquete para CodeIgniter 4 sirve como plataforma básica para crear rápidamente una aplicación administrativa. Incluye creación y gestión de perfiles, gestión de usuarios, roles, permisos y un menú generado dinámicamente.

Características

  • Tema backend configurable AdminLTE 3
  • CSS framework Bootstrap 4
  • Iconos de Font Awesome 5
  • Permisos basados en roles (RBAC) proporcionados porMyth/Auth
  • Generación de menús dinamicos
  • Traducciones en English / Indonesian / Spanish

Este proyecto aún está en sus primeras etapas de desarrollo… ¡no dude en contribuir!

El autor original es agungsugiarto/boilerplate https://github.com/agungsugiarto/boilerplate, solo hicimos un fork para adaptarlo a nuevas necesidades como traducirlo al español y hacerlo funcional en xampp/lampp

El fuente con los cambios mas recientes de fork estan en github https://github.com/julio101290/boilerplate

Instalación

  1. Obtener el modulo mediante composer
composer require julio101290/boilerplate

2. Establezca CI_ENVIRONMENT, baseURL, página de índice y configuración de la base de datos en su archivo .env según su base de datos existente (si no tiene un archivo .env, puede copiar primero desde el archivo env: cp env .env primero). Si la base de datos no existe, primero cree la base de datos.

# .env file
CI_ENVIRONMENT = development

app.baseURL = 'http://localhost:8080'
app.indexPage = ''

database.default.hostname = localhost
database.default.database = boilerplate
database.default.username = root
database.default.password =
database.default.DBDriver = MySQLi

3. Correr el comando para publicar de auth

php spark auth:publish

Publish Migration? [y, n]: y
  created: Database/Migrations/2017-11-20-223112_create_auth_tables.php
  Remember to run `spark migrate -all` to migrate the database.
Publish Models? [y, n]: n
Publish Entities? [y, n]: n
Publish Controller? [y, n]: n
Publish Views? [y, n]: n
Publish Filters? [y, n]: n
Publish Config file? [y, n]: y
  created: Config/Auth.php
Publish Language file? [y, n]: n

NOTA: Todo lo relacionado con cómo configurar la autenticación puede encontrar en Myth/Auth.

¿Ya está listo? ¡¡No tan rapido!! 😉 Después de publicar Config/Auth.php, debe cambiar las $views públicas con estas líneas a continuación:

public $views = [
    'login'           => 'julio101290\boilerplate\Views\Authentication\login',
    'register'        => 'julio101290\boilerplate\Views\Authentication\register',
    'forgot'          => 'julio101290\boilerplate\Views\Authentication\forgot',
    'reset'           => 'julio101290\boilerplate\Views\Authentication\reset',
    'emailForgot'     => 'julio101290\boilerplate\Views\Authentication\emails\forgot',
    'emailActivation' => 'julio101290\boilerplate\Views\Authentication\emails\activation',
];

Abra app\Config\Filters.php, busque $aliases y agregue estas líneas a continuación:

public $aliases = [
    'login'      => \Myth\Auth\Filters\LoginFilter::class,
    'role'       => \julio101290\boilerplate\Filters\RoleFilter::class,
    'permission' => \julio101290\boilerplate\Filters\PermissionFilter::class,
];

4. Ejecute publish, migrate y seed del boilerplate

php spark boilerplate:install

Abra app\Config\validation.php, busque $ruleSets y agregue estas líneas a continuación:

public $$ruleSets = [
    \Myth\Auth\Authentication\Passwords\ValidationRules::class,
];

Abra app\entities\Users.php, busque $casts y agregue estas líneas a continuación:


   protected $casts = [
        'username' => 'string',
        'email' => 'string',
        'firstname' => 'string',
        'lastname' => 'string',
        'active' => 'boolean',
        'force_pass_reset' => 'boolean',
    ];

Ejecute el servidor de desarrollo:

php spark serve

Abrir en el navegador http://localhost:8080/admin

Default user and password
+----+--------+-------------+
| No | User   | Password    |
+----+--------+-------------+
| 1  | admin  | super-admin |
| 2  | user   | super-user  |
+----+--------+-------------+

Configuraciones

Plantilla de configuración

Puede configurar el controlador de tablero predeterminado y el tema de back-end en app\Config\Boilerplate.php

class Boilerplate extends BaseConfig
{
    public $appName = 'Boilerplate';

    public $dashboard = [
        'namespace'  => 'julio101290\boilerplate\Controllers',
        'controller' => 'DashboardController::index',
        'filter'     => 'permission:back-office',
    ];
// App/Config/Boilerplate.php

Uso

Puede encontrar cómo funciona con las rutas de código de lectura, el controlador y las vistas, etc. Finalmente… ¡Feliz codificación!

Contribuciones

Las contribuciones son muy bienvenidas.

Licencia

Este paquete es software gratuito distribuido bajo los términos de la licencia MIT.

CI 4.0 MedicalSoft Modulo Para Cita y Creando nuestros Propios Helpers personalizados

En este apartado vamos a crear nuestro modulo de citas medicas que posteriormente podremos convertir en consulta, a su vez veremos como crear nuestros propios helpers y usarlos

Que es un helper en codeigniter, pues no es mas que funciones o utilerías que nos pueden servir en diferentes partes del sistema

Creamos la tabla para citas

-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Servidor: 127.0.0.1
-- Tiempo de generación: 08-01-2023 a las 23:44:15
-- 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: `medicalsoft`
--

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

--
-- Estructura de tabla para la tabla `citas`
--

CREATE TABLE `citas` (
  `id` int(11) NOT NULL,
  `idPaciente` int(11) DEFAULT NULL,
  `fechaHora` datetime NOT NULL,
  `hastaFechaHora` datetime DEFAULT NULL,
  `observaciones` varchar(1024) COLLATE utf8_spanish2_ci NOT NULL,
  `created_at` datetime NOT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  `estado` varchar(15) COLLATE utf8_spanish2_ci DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;

--
-- Índices para tablas volcadas
--

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

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `citas`
--
ALTER TABLE `citas`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
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 */;

Y como de costumbre creamos primeramente el archiv CitasModel.php en la carpeta app/models y le ponemos el siguiente código

<?php

namespace App\Models;

use CodeIgniter\Model;

class CitasModel extends Model {

    protected $table = 'citas';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = ['id', 'idPaciente', 'fechaHora', 'hastaFechaHora', 'observaciones', 'created_at', 'updated_at'];
    protected $useTimestamps = true;
    protected $createdField = 'created_at';
    protected $deletedField = 'deleted_at';
    protected $validationRules = [
        'observaciones' => 'required|alpha_numeric_space|min_length[3]'
    ];
    protected $validationMessages = [];
    protected $skipValidation = false;

    public function mdlObtenerDatos() {

        $resultado = $this->db->table('citas a, pacientes b')
                ->select('a.id,concat(b.nombres,\' \',b.apellidos) as nombrePaciente,a.observaciones,a.fechaHora,a.hastaFechaHora,estado,a.created_at,a.updated_at')
                ->where('a.idPaciente', 'b.id', false)
                ->where('a.deleted_at', null);

        return $resultado;
    }

    public function mdlObtenerCita($idCita) {

        $resultado = $this->db->table('citas a, pacientes b')
                ->select('a.id,concat(b.nombres,\' \',b.apellidos) as nombrePaciente,a.observaciones,a.fechaHora,a.hastaFechaHora,estado,a.created_at,a.updated_at,a.idPaciente')
                ->where('a.idPaciente', 'b.id', false)
                ->where('a.deleted_at', null)
                ->where('a.id', $idCita,null)->get()->getResultArray();

        return $resultado[0];
    }

}

Posteriormente creamos el archivo CitasController.php en la carpeta app/controller y le insertamos el siguiente código

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\CitasModel;
use \App\Models\BitacoraModel;
use CodeIgniter\API\ResponseTrait;

class CitasController extends BaseController {

    use ResponseTrait;

    protected $bitacora;
    protected $citas;

    public function __construct() {
        $this->citas = new CitasModel();
        $this->bitacora = new BitacoraModel();
        helper('menu');
      
    }

    public function index() {


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

            /*
              $start = $this->request->getGet('start');
              $length = $this->request->getGet('length');
              $search = $this->request->getGet('search[value]');
              $order = BitacoraModel::ORDERABLE[$this->request->getGet('order[0][column]')];
              $dir = $this->request->getGet('order[0][dir]');
             */

            $datos = $this->citas->mdlObtenerDatos();  
                    //where('deleted_at', null);
            // $resultado = $this->bitacora->findAll();
            // $this->bitacora->getResource()->countAllResults(),
            // $this->bitacora->getResource($search)->countAllResults()
            //      var_dump($datos);


            return \Hermawan\DataTables\DataTable::of($datos)->add('action', function ($row) {
                                return " <div class=\"btn-group\">
                          
                  <button class=\"btn btn-warning btnEditarCita\" data-toggle=\"modal\" idCita=\"$row->id\" data-target=\"#modalAgregarCitas\">  <i class=\" fa fa-edit \"></i></button>
                  <button class=\"btn btn-danger btnEliminarCita\" idCita=\"$row->id\"><i class=\"fa fa-times\"></i></button></div>";
                            }, 'last')
                            ->toJson();
        }

        
        $fechaActual =  fechaMySQLADateTimeHTML5(fechaHoraActual());
        
        $titulos["fecha"] =  $fechaActual;
        $titulos["title"] = lang('citas.title');
        $titulos["subtitle"] = lang('citas.subtitle');

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

    /*
     * Lee paciente
     */

    public function traeCita() {


        $idCita = $this->request->getPost("idCita");
        $datosCita = $this->citas->mdlObtenerCita($idCita);

        echo json_encode($datosCita);
    }

    /*
     * Guarda o actualiza paciente
     */

    public function guardar() {


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

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

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


            try {


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

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

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

                        echo $error . " ";
                    }

                    return;
                }

                $datosBitacora["descripcion"] = "Se guardo la medicamento con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;

                $this->bitacora->save($datosBitacora);

                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {


                echo "Error al guardar " . $ex->getMessage();
            }
        } else {


            if ($this->citas->update($datos["idCita"], $datos) == false) {
                
                $errores = $this->citas->errors();
                foreach ($errores as $field => $error) {

                    echo $error . " ";
                }
                
                return;
               
            } else {

                $datosBitacora["descripcion"] = "Se actualizo el medicamento con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;
                $this->bitacora->save($datosBitacora);
                echo "Actualizado Correctamente";

                return;
            }
        }

        return;


    }
    
    
    
     public function delete($id)
    {
        if (!$found = $this->citas->delete($id)) {
            return $this->failNotFound(lang('citas.msg.msg_get_fail'));
        }
        
        $infoCita = $this->citas->find($id);
        
        $datosBitacora["descripcion"] = "Se elimino la medicamento que contenia los siguientes datos ". json_encode($infoCita);
        
        $this->bitacora->save($datosBitacora);
        return $this->respondDeleted($found, lang('citas.msg.msg_delete'));
    }

}

Luego Creamos la vista citas.php en app/views

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

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

<?= $this->include('citasModulos\modalCaptura') ?>
<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

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

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

                </button>

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



                                <th>#</th>

                                <th><?= lang('citas.nombrePaciente') ?></th>
                                <th><?= lang('citas.observaciones') ?></th>
                                <th><?= lang('citas.fechaHora') ?></th>
                                <th><?= lang('citas.hastaFechaHora') ?></th>
                                <th><?= lang('citas.estado') ?></th>


                                <th><?= lang('citas.createdAt') ?></th>
                                <th><?= lang('citas.updateAt') ?></th>
                                <th><?= lang('citas.actions') ?> </th>


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

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




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

    /**
     * Cargamos la tabla
     */


    function cargaTabla() {



        $('.tablaCitas').DataTable({
            "ajax": "<?= route_to('admin/citas') ?>",
            "deferRender": true,
            "serverSide": true,
            "retrieve": true,
            "processing": true

        });

    }


    cargaTabla();



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR PACIENTE
     =============================================*/
    $(".tablaCitas").on("click", ".btnEditarCita", function () {

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


        console.log("idCita ", idCita);

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

        $.ajax({

            url: "<?= route_to('admin/citas/traeCita') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                console.log(respuesta);
                $("#idCita").val(respuesta["id"]);
                $("#observaciones").val(respuesta["observaciones"]);
                $("#fechaHora").val(respuesta["fechaHora"]);
                $("#hastaFechaHora").val(respuesta["hastaFechaHora"]);


                //$("#pacientes").val(respuesta["idPaciente"]);

                $("#pacientes").empty() //empty select
                        .append($("<option/>") //add option tag in select
                                .val(respuesta["idPaciente"]) //set value for option to post it
                                .text(respuesta["nombrePaciente"])) //set a text for show in select
                        .val(respuesta["idPaciente"]) //select option of select2
                        .trigger("change");


            }

        })

    })


    /*=============================================
     ELIMINAR PACIENTE
     =============================================*/
    $(".tablaCitas").on("click", ".btnEliminarCita", function () {

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




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

                            $(".tablaCitas").DataTable().destroy();
                            cargaTabla();
                            //tableUser.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })




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

Luego en la carpeta app/views creamos la carpeta citasModulos, creamos el archivo modalCaptura.php y metemos el siguiente código

<!-- Modal Citas -->

<div class="modal fade" id="modalAgregarCitas" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title"><?= lang('citas.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-enfermedad" class="form-horizontal">
                    <!-- CSRF token --> 
                    <input type="hidden" class="txt_csrfname" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>" />

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




                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('citas.nombrePaciente') ?></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 id='pacientes'  name='pacientes' style='width: 80%;'>
                                    <option value='0'><?= lang('citas.seleccionePaciente') ?></option>
                                </select>


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


                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('citas.observaciones') ?></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>

                                <textarea class="form-control <?= session('error.observaciones ') ? 'is-invalid' : '' ?>" rows="3" placeholder="<?= lang('citas.observaciones') ?>" id="observaciones" name="observaciones" autocomplete="off"></textarea>

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



                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('citas.fechaHora') ?></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" id="fechaHora" name="fechaHora" value="<?= $fecha ?>">


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


                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('citas.hastaFechaHora') ?></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" id="hastaFechaHora" name="hastaFechaHora" value="<?= $fecha ?>">


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

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


<script>

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

        console.log("asdasd");
        $(".form-control").val("");

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

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

    })

    /* 
     * AL hacer click al editar
     */



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


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

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

        $("#idCita").val(idCita);
        $("#btnGuardarCita").removeAttr("disabled");

    })

    /**
     * Guardar paciente
     */

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

        var idCita = $("#idCita").val();
        var observaciones = $("#observaciones").val();
        var idPaciente = $("#pacientes").val();
        var fechaHora = $("#fechaHora").val();
        var hastaFechaHora = $("#hastaFechaHora").val();
       

        console.log("OBSERVACIONES:",observaciones);

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


        var datos = new FormData();
        datos.append("idCita", idCita);
        datos.append("observaciones", observaciones);
        datos.append("idPaciente", idPaciente);
        datos.append("fechaHora", fechaHora);
        datos.append("hastaFechaHora", hastaFechaHora);


        $.ajax({

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


                if (respuesta.match(/Correctamente.*/)) {


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


                    $('.tablaCitas').DataTable().destroy();
                    cargaTabla();
                    $("#btnGuardarCita").removeAttr("disabled");


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

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

                    $("#btnGuardarCita").removeAttr("disabled");
                    //  $('#modalAgregarCita').modal('hide');

                }

            }

        }

        )




    });



    // Initialize select2
    $("#pacientes").select2({
        ajax: {
            url: "<?= site_url('admin/pacientes/traerPacientesAjax') ?>",
            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

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

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

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



</script>


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

En este modulo como se vieron el archivo CitasController.php se usaron unas funciones para el formato de las fechas dichas funciones estan en el helper, estas funciones nos sirvieron anteriormente en JCPOS2021, entonces en la carpeta app/Helpers creamos el archivo utilerias_helper.php y metemos el siguiente código

<?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));
}

Ahora en app/lenguajes/en/ creamos el archivo citas.php y metemos el siguiente archivo

<?php

$citas["observaciones"] = "Observations";
$citas["nombrePaciente"] = "Patient´s Name";
$citas["fechaHora"] = "Start Date";
$citas["hastaFechaHora"] = "End Date";

$citas["createdAt"] = "Date Creation";
$citas["updateAt"] = "Date Update";
$citas["add"] = "add appointment";
$citas["actions"] = "Acciones";
$citas["estado"] = "status";
$citas["createEdit"] = "add  / update appointment";
$citas["seleccionePaciente"] = "Select an Patient";
$citas["title"] = "appointment";
$citas["subtitle"] = "List of appointments";

$citas["msg_delete"] = "The quote has been removed .";
$citas["msg_get_fail"] = "The appointment does not exist or was deleted.";





return $citas;

Ahora en app/lenguajes/es/ creamos el archivo citas.php y metemos el siguiente archivo

<?php

$citas["observaciones"] = "Observaciones";
$citas["nombrePaciente"] = "Nombre Paciente";
$citas["fechaHora"] = "Fecha Inicio Cita";
$citas["obsevaciones"] = "Observaciones";
$citas["hastaFechaHora"] = "Fin Cita";

$citas["createdAt"] = "Fecha Creación";
$citas["updateAt"] = "Fecha de Modificación";
$citas["add"] = "Agregar Cita";
$citas["actions"] = "Acciones";
$citas["estado"] = "Estado";
$citas["createEdit"] = "Crear  / Editar Citas";
$citas["seleccionePaciente"] = "Seleccione un paciente";
$citas["title"] = "Citas";
$citas["subtitle"] = "Lista de Citas";

$citas["msg_delete"] = "La cita ha sido eliminada .";
$citas["msg_get_fail"] = "La cita no existe o fue eliminada.";

return $citas;

Luego ya por ultimo creamos las rutas en app/config/routes.php metemos el siguiente codigo

    $routes->resource('citas', [
        'filter' => 'permission:citas-permiso',
        'controller' => 'CitasController',
        'except' => 'show'
    ]);

    $routes->post('citas/guardar', 'CitasController::guardar');

    $routes->post('citas/traeCita', 'CitasController::traeCita');
Y listo ya tenemos nuestro modulo de citas y aprendimos como usar los Helpers que creamos

CI 4.0 MedicalSoft CRUD Para Enfermedades

Ya por ultimo creamos un CRUD super básico para las enfermedades

Primero creamos la tabla

-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Servidor: 127.0.0.1
-- Tiempo de generación: 07-01-2023 a las 17:11:20
-- 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: `medicalsoft`
--

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

--
-- Estructura de tabla para la tabla `enfermedades`
--

CREATE TABLE `enfermedades` (
  `id` int(11) NOT NULL,
  `descripcion` varchar(256) COLLATE utf8_spanish2_ci NOT NULL,
  `created_at` datetime NOT NULL,
  `deleted_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;

--
-- Índices para tablas volcadas
--

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

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `enfermedades`
--
ALTER TABLE `enfermedades`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
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 */;

Creamos el archivo EnfermedadesModel.php en app/models

<?php

namespace App\Models;

use CodeIgniter\Model;

class EnfermedadesModel extends Model
{
    protected $table      = 'enfermedades';
    protected $primaryKey = 'id';

    protected $useAutoIncrement = true;

    protected $returnType     = 'array';
    protected $useSoftDeletes = true;

    protected $allowedFields = ['id','descripcion', 'created_at','updated_at'];

    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $deletedField  = 'deleted_at';

    protected $validationRules    =  [
        'descripcion'     => 'required|alpha_numeric_space|min_length[3]'
        
    ];
    protected $validationMessages = [];
    protected $skipValidation     = false;
    
   
}

Creamos el archivo EnfermedadesController.php en app/controller

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\EnfermedadesModel;
use \App\Models\BitacoraModel;
use CodeIgniter\API\ResponseTrait;

class EnfermedadesController extends BaseController {

    use ResponseTrait;

    protected $bitacora;
    protected $enfermedades;

    public function __construct() {
        $this->enfermedades = new EnfermedadesModel();
        $this->bitacora = new BitacoraModel();
        helper('menu');
    }

    public function index() {


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

            /*
              $start = $this->request->getGet('start');
              $length = $this->request->getGet('length');
              $search = $this->request->getGet('search[value]');
              $order = BitacoraModel::ORDERABLE[$this->request->getGet('order[0][column]')];
              $dir = $this->request->getGet('order[0][dir]');
             */

            $datos = $this->enfermedades->select('id,descripcion,created_at,updated_at')->where('deleted_at', null);
            // $resultado = $this->bitacora->findAll();
            // $this->bitacora->getResource()->countAllResults(),
            // $this->bitacora->getResource($search)->countAllResults()
            //      var_dump($datos);

            $dataTable =  \Hermawan\DataTables\DataTable::of($datos);
            return \Hermawan\DataTables\DataTable::of($datos)->add('action', function ($row) {
                                return " <div class=\"btn-group\">
                          
                  <button class=\"btn btn-warning btnEditarEnfermedad\" data-toggle=\"modal\" idEnfermedad=\"$row->id\" data-target=\"#modalAgregarEnfermedades\">  <i class=\" fa fa-edit \"></i></button>
                  <button class=\"btn btn-danger btnEliminarEnfermedad\" idEnfermedad=\"$row->id\"><i class=\"fa fa-times\"></i></button></div>";
                            }, 'last')
                            ->toJson();
        }

        $titulos["title"] = lang('enfermedades.title');
        $titulos["subtitle"] = lang('enfermedades.subtitle');

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

    /*
     * Lee paciente
     */

    public function traeEnfermedad() {


        $idEnfermedad = $this->request->getPost("idEnfermedad");
        $datosEnfermedad = $this->enfermedades->find($idEnfermedad);

        echo json_encode($datosEnfermedad);
    }

    /*
     * Guarda o actualiza paciente
     */

    public function guardar() {


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

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

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


            try {


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

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

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

                        echo $error . " ";
                    }

                    return;
                }

                $datosBitacora["descripcion"] = "Se guardo la enfermedad con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;

                $this->bitacora->save($datosBitacora);

                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {


                echo "Error al guardar " . $ex->getMessage();
            }
        } else {


            if ($this->enfermedades->update($datos["idEnfermedad"], $datos) == false) {
                
                $errores = $this->enfermedades->errors();
                foreach ($errores as $field => $error) {

                    echo $error . " ";
                }
                
                return;
               
            } else {

                $datosBitacora["descripcion"] = "Se actualizo el paciente con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;
                $this->bitacora->save($datosBitacora);
                echo "Actualizado Correctamente";

                return;
            }
        }

        return;


    }
    
    
    
     public function delete($id)
    {
        if (!$found = $this->enfermedades->delete($id)) {
            return $this->failNotFound(lang('enfermedades.msg.msg_get_fail'));
        }
        
        $infoEnfermedad = $this->enfermedades->find($id);
        
        $datosBitacora["descripcion"] = "Se elimino la enfermedad que contenia los siguientes datos ". json_encode($infoEnfermedad);
        
        $this->bitacora->save($datosBitacora);
        return $this->respondDeleted($found, lang('enfermedades.msg.msg_delete'));
    }

}

Creamos el archivo enfermedades en la carpeta app/views

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

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

<?= $this->include('enfermedadesModulos\modalCaptura') ?>
<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

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

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

                </button>

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



                                <th>#</th>
                                <th><?= lang('enfermedades.description') ?></th>
                        
                                <th><?= lang('enfermedades.createdAt') ?></th>
                                <th><?= lang('enfermedades.updateAt') ?></th>
                                <th>Acciones </th>


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

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




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

    /**
     * Cargamos la tabla
     */


    function cargaTabla() {



         $('.tablaEnfermedades').DataTable({
         "ajax": "<?= route_to('admin/enfermedades') ?>",
         "deferRender": true,
         "serverSide": true,
         "retrieve": true,
         "processing": true
         
         });
        
    }


    cargaTabla();



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR PACIENTE
     =============================================*/
    $(".tablaEnfermedades").on("click", ".btnEditarEnfermedad", function () {

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


        console.log("idEnfermedad ", idEnfermedad);

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

        $.ajax({

            url: "<?= route_to('admin/enfermedades/traerEnfermedad') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                console.log(respuesta);
                $("#idEnfermedad").val(respuesta["id"]);
                $("#descripcion").val(respuesta["descripcion"]);
            
            }

        })

    })


    /*=============================================
     ELIMINAR PACIENTE
     =============================================*/
    $(".tablaEnfermedades").on("click", ".btnEliminarEnfermedad", function () {

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


 

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

                           $(".tablaEnfermedades").DataTable().destroy();
                            cargaTabla();
                            //tableUser.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })




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

En views Creamos la carpeta enfermedadesModulos y dentro de ella creamos el archivo modalCaptura.php y metemos el siguiente código

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



                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('enfermedades.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="descripcion" id="descripcion" class="form-control <?= session('error.descripcion ') ? 'is-invalid' : '' ?>" value="<?= old('descripcion') ?>" placeholder="<?= lang('enfermedades.description') ?>" 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="btnGuardarEnfermedad"><?= lang('boilerplate.global.save') ?></button>
            </div>
        </div>
    </div>
</div>

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


<script>

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

        console.log("asdasd");
        $(".form-control").val("");

        $("#idEnfermedad").val("0");
        
           $("#btnGuardarEnfermedad").removeAttr("disabled");

    })

    /* 
     * AL hacer click al editar
     */



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


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

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

        $("#idEnfermedad").val(idEnfermedad);
        $("#btnGuardarEnfermedad").removeAttr("disabled");

    })

    /**
     * Guardar paciente
     */

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

        var idEnfermedad = $("#idEnfermedad").val();
        var descripcion = $("#descripcion").val();
      


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


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


        $.ajax({

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


                if (respuesta.match(/Correctamente.*/)) {


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


                    $('.tablaEnfermedades').DataTable().destroy();
                    cargaTabla();
                    $("#btnGuardarEnfermedad").removeAttr("disabled");

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

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

                    $("#btnGuardarEnfermedad").removeAttr("disabled");
                  //  $('#modalAgregarEnfermedad').modal('hide');

                }

            }

        }

        )




    });
</script>


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

En app/languaje/en metemos el archivo de traducción en ingles enfermedades.php con el siguiente código

<?php

$enfermedades["descrption"] = "Description";

$enfermedades["createdAt"] = "Date Creation";
$enfermedades["updateAt"] = "Date Update";
$enfermedades["add"] = "Add disease";
$enfermedades["actions"] = "Actions";
$enfermedades["createEdit"] = "Create  / Edit disease";
$enfermedades["title"] = "Disease";
$enfermedades["subtitle"] = "List of diseases";

$enfermedades["msg_delete"] = "disease has deleted .";
$enfermedades["msg_get_fail"] = "The disease not exist or has deleted.";





return $enfermedades;

En app/languaje/esmetemos el archivo de traducción en español enfermedades.php con el siguiente código

<?php

$enfermedades["description"] = "Descripcion";

$enfermedades["createdAt"] = "Fecha Creación";
$enfermedades["updateAt"] = "Fecha de Modificación";
$enfermedades["add"] = "Agregar Enfermedades";
$enfermedades["actions"] = "Acciones";
$enfermedades["createEdit"] = "Crear  / Editar Enfermedades";
$enfermedades["title"] = "Enfermedades";
$enfermedades["subtitle"] = "Lista de Enfermedades";

$enfermedades["msg_delete"] = "La enfermedad ha sido eliminada .";
$enfermedades["msg_get_fail"] = "La enfermedad no existe o fue eliminada.";





return $enfermedades;

Por ultimo creamos las rutas en app/config/routes.php

    $routes->resource('enfermedades', [
        'filter' => 'permission:enfermedades-permiso',
        'controller' => 'EnfermedadesController',
        'except' => 'show'
    ]);

    $routes->post('enfermedades/guardar', 'EnfermedadesController::guardar');

    $routes->post('enfermedades/traerEnfermedad', 'EnfermedadesController::traeEnfermedad');
Y listo al final quedaría así

CI 4.0 MedicalSoft CRUD Para Medicamentos

Ya creamos el CRUD para pacientes, ahora será necesario crear un CRUD para los medicamentos.

Primero creamos la tabla medicamentos, les dejo la exportación desde phpmyadmin

-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Servidor: 127.0.0.1
-- Tiempo de generación: 06-01-2023 a las 20:37:36
-- 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: `medicalsoft`
--

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

--
-- Estructura de tabla para la tabla `medicamentos`
--

CREATE TABLE `medicamentos` (
  `id` int(11) NOT NULL,
  `descripcion` varchar(256) COLLATE utf8_spanish2_ci NOT NULL,
  `created_at` datetime DEFAULT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;

--
-- Índices para tablas volcadas
--

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

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `medicamentos`
--
ALTER TABLE `medicamentos`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
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 */;

Creamos el archivo MedicamentosModel.php en app/models/

<?php

namespace App\Models;

use CodeIgniter\Model;

class MedicamentosModel extends Model
{
    protected $table      = 'medicamentos';
    protected $primaryKey = 'id';

    protected $useAutoIncrement = true;

    protected $returnType     = 'array';
    protected $useSoftDeletes = true;

    protected $allowedFields = ['id','descripcion', 'created_at','updated_at'];

    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $deletedField  = 'deleted_at';

    protected $validationRules    =  [
        'descripcion'     => 'required|alpha_numeric_space|min_length[3]'
        
    ];
    protected $validationMessages = [];
    protected $skipValidation     = false;
    
   
}

Creamos el archivo MedicamentosController.php en app/controller

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Models\MedicamentosModel;
use \App\Models\BitacoraModel;
use CodeIgniter\API\ResponseTrait;

class MedicamentosController extends BaseController {

    use ResponseTrait;

    protected $bitacora;
    protected $medicamentos;

    public function __construct() {
        $this->medicamentos = new MedicamentosModel();
        $this->bitacora = new BitacoraModel();
        helper('menu');
    }

    public function index() {


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

            /*
              $start = $this->request->getGet('start');
              $length = $this->request->getGet('length');
              $search = $this->request->getGet('search[value]');
              $order = BitacoraModel::ORDERABLE[$this->request->getGet('order[0][column]')];
              $dir = $this->request->getGet('order[0][dir]');
             */

            $datos = $this->medicamentos->select('id,descripcion,created_at,updated_at')->where('deleted_at', null);
            // $resultado = $this->bitacora->findAll();
            // $this->bitacora->getResource()->countAllResults(),
            // $this->bitacora->getResource($search)->countAllResults()
            //      var_dump($datos);


            return \Hermawan\DataTables\DataTable::of($datos)->add('action', function ($row) {
                                return " <div class=\"btn-group\">
                          
                  <button class=\"btn btn-warning btnEditarMedicamento\" data-toggle=\"modal\" idMedicamento=\"$row->id\" data-target=\"#modalAgregarMedicamentos\">  <i class=\" fa fa-edit \"></i></button>
                  <button class=\"btn btn-danger btnEliminarMedicamento\" idMedicamento=\"$row->id\"><i class=\"fa fa-times\"></i></button></div>";
                            }, 'last')
                            ->toJson();
        }

        $titulos["title"] = lang('medicamentos.title');
        $titulos["subtitle"] = lang('medicamentos.subtitle');

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

    /*
     * Lee paciente
     */

    public function traeMedicamento() {


        $idMedicamento = $this->request->getPost("idMedicamento");
        $datosMedicamento = $this->medicamentos->find($idMedicamento);

        echo json_encode($datosMedicamento);
    }

    /*
     * Guarda o actualiza paciente
     */

    public function guardar() {


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

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

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


            try {


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

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

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

                        echo $error . " ";
                    }

                    return;
                }

                $datosBitacora["descripcion"] = "Se guardo la medicamento con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;

                $this->bitacora->save($datosBitacora);

                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {


                echo "Error al guardar " . $ex->getMessage();
            }
        } else {


            if ($this->medicamentos->update($datos["idMedicamento"], $datos) == false) {
                
                $errores = $this->medicamentos->errors();
                foreach ($errores as $field => $error) {

                    echo $error . " ";
                }
                
                return;
               
            } else {

                $datosBitacora["descripcion"] = "Se actualizo el medicamento con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;
                $this->bitacora->save($datosBitacora);
                echo "Actualizado Correctamente";

                return;
            }
        }

        return;


    }
    
    
    
     public function delete($id)
    {
        if (!$found = $this->medicamentos->delete($id)) {
            return $this->failNotFound(lang('medicamentos.msg.msg_get_fail'));
        }
        
        $infoMedicamento = $this->medicamentos->find($id);
        
        $datosBitacora["descripcion"] = "Se elimino la medicamento que contenia los siguientes datos ". json_encode($infoMedicamento);
        
        $this->bitacora->save($datosBitacora);
        return $this->respondDeleted($found, lang('medicamentos.msg.msg_delete'));
    }

}

Creamos el archivo de la vista medicamentos.php en app/views

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

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

<?= $this->include('medicamentosModulos\modalCaptura') ?>
<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

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

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

                </button>

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



                                <th>#</th>
                                <th><?= lang('medicamentos.description') ?></th>
                        
                                <th><?= lang('medicamentos.createdAt') ?></th>
                                <th><?= lang('medicamentos.updateAt') ?></th>
                                <th>Acciones </th>


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

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




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

    /**
     * Cargamos la tabla
     */


    function cargaTabla() {



         $('.tablaMedicamentos').DataTable({
         "ajax": "<?= route_to('admin/medicamentos') ?>",
         "deferRender": true,
         "serverSide": true,
         "retrieve": true,
         "processing": true
         
         });
        
    }


    cargaTabla();



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR PACIENTE
     =============================================*/
    $(".tablaMedicamentos").on("click", ".btnEditarMedicamento", function () {

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


        console.log("idMedicamento ", idMedicamento);

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

        $.ajax({

            url: "<?= route_to('admin/medicamentos/traerMedicamento') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                console.log(respuesta);
                $("#idMedicamento").val(respuesta["id"]);
                $("#descripcion").val(respuesta["descripcion"]);
            
            }

        })

    })


    /*=============================================
     ELIMINAR PACIENTE
     =============================================*/
    $(".tablaMedicamentos").on("click", ".btnEliminarMedicamento", function () {

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


 

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

                           $(".tablaMedicamentos").DataTable().destroy();
                            cargaTabla();
                            //tableUser.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })




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

Creamos la carpeta en views medicamentosModulos y dentro de ella creamos el archivo modelCaptura.php con el siguiente código

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



                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('medicamentos.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="descripcion" id="descripcion" class="form-control <?= session('error.descripcion ') ? 'is-invalid' : '' ?>" value="<?= old('descripcion') ?>" placeholder="<?= lang('medicamentos.description') ?>" 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="btnGuardarMedicamento"><?= lang('boilerplate.global.save') ?></button>
            </div>
        </div>
    </div>
</div>

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


<script>

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

        console.log("asdasd");
        $(".form-control").val("");

        $("#idMedicamento").val("0");
        
           $("#btnGuardarMedicamento").removeAttr("disabled");

    })

    /* 
     * AL hacer click al editar
     */



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


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

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

        $("#idMedicamento").val(idMedicamento);
        $("#btnGuardarMedicamento").removeAttr("disabled");

    })

    /**
     * Guardar paciente
     */

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

        var idMedicamento = $("#idMedicamento").val();
        var descripcion = $("#descripcion").val();
      


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


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


        $.ajax({

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


                if (respuesta.match(/Correctamente.*/)) {


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


                    $('.tablaMedicamentos').DataTable().destroy();
                    cargaTabla();
                    $("#btnGuardarMedicamento").removeAttr("disabled");

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

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

                    $("#btnGuardarMedicamento").removeAttr("disabled");
                  //  $('#modalAgregarMedicamento').modal('hide');

                }

            }

        }

        )




    });
</script>


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

en app/Languaje/en creamos el archivo medicamentos.php con el siguiente código

<?php

$medicamentos["descrption"] = "Description";

$medicamentos["createdAt"] = "Date Creation";
$medicamentos["updateAt"] = "Date Update";
$medicamentos["add"] = "Add medicine";
$medicamentos["actions"] = "Actions";
$medicamentos["createEdit"] = "Create  / Edit medicine";
$medicamentos["title"] = "Medicine";
$medicamentos["subtitle"] = "List of medicines";

$medicamentos["msg_delete"] = "medicine has deleted .";
$medicamentos["msg_get_fail"] = "The medicine not exist or has deleted.";





return $medicamentos;

en app/Languaje/escreamos el archivo medicamentos.php con el siguiente código

<?php

$medicamentos["description"] = "Descripcion";

$medicamentos["createdAt"] = "Fecha Creación";
$medicamentos["updateAt"] = "Fecha de Modificación";
$medicamentos["add"] = "Agregar Medicamentos";
$medicamentos["actions"] = "Acciones";
$medicamentos["createEdit"] = "Crear  / Editar Medicamentos";
$medicamentos["title"] = "Medicamentos";
$medicamentos["subtitle"] = "Lista de Medicamentos";

$medicamentos["msg_delete"] = "El medicamento ha sido eliminada .";
$medicamentos["msg_get_fail"] = "La medicamento no existe o fue eliminada.";





return $medicamentos;

en el archivo app/config/routes.php agregamos dentro del grupo admin la siguiente rutas

    $routes->resource('medicamentos', [
        'filter' => 'permission:medicamentos-permiso',
        'controller' => 'MedicamentosController',
        'except' => 'show'
    ]);

    $routes->post('medicamentos/guardar', 'MedicamentosController::guardar');

    $routes->post('medicamentos/traerMedicamento', 'MedicamentosController::traeMedicamento');
Y listo ya tenemos nuestro CRUD de medicamentos

CI 4.0 MedicalSoft CRUD Para Pacientes

Ahora veremos como crear el CRUD de pacientes en el framework CodeIgniter 4.0 con los siguientes datos, Nombres, Apellidos, DNI, Correo Electrónico, para ello creamos la siguiente tabla.

Leer Mas: CI 4.0 MedicalSoft CRUD Para Pacientes
-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- Servidor: 127.0.0.1
-- Tiempo de generación: 04-01-2023 a las 18:20:08
-- 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: `medicalsoft`
--

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

--
-- Estructura de tabla para la tabla `pacientes`
--

CREATE TABLE `pacientes` (
  `id` int(11) NOT NULL,
  `nombres` varchar(256) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `apellidos` varchar(256) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `dni` varchar(32) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `telefono` varchar(16) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `correoElectronico` varchar(64) COLLATE utf8_spanish2_ci DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `deleted_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;

--
-- Índices para tablas volcadas
--

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

--
-- AUTO_INCREMENT de las tablas volcadas
--

--
-- AUTO_INCREMENT de la tabla `pacientes`
--
ALTER TABLE `pacientes`
  MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
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 */;

Creamos el archivo PacientesModel.php en la carpeta model para el acceso a la tabla de la base de datos

<?php

namespace App\Models;

use CodeIgniter\Model;

class PacientesModel extends Model
{
    protected $table      = 'pacientes';
    protected $primaryKey = 'id';

    protected $useAutoIncrement = true;

    protected $returnType     = 'array';
    protected $useSoftDeletes = true;

    protected $allowedFields = ['id','nombres', 'apellidos','dni','telefono','correoElectronico','created_at','updated_at'];

    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $deletedField  = 'deleted_at';

    protected $validationRules    =  [
        'nombres'     => 'required|alpha_numeric_space|min_length[3]',
        'apellidos'     => 'required|alpha_numeric_space|min_length[5]',
        'correoElectronico'        => 'required|valid_email|is_unique[users.email]'
        
    ];
    protected $validationMessages = [];
    protected $skipValidation     = false;
    
   
}

Creamos el archivo PacientesController.php alli tendremos la funciones para guardar, leer y modificar, creo que no falta decir que va en la carpeta controller

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use \App\Models\PacientesModel;
use \App\Models\BitacoraModel;
use CodeIgniter\API\ResponseTrait;

class PacientesController extends BaseController {

    use ResponseTrait;

    protected $bitacora;
    protected $pacientes;

    public function __construct() {
        $this->pacientes = new PacientesModel();
        $this->bitacora = new BitacoraModel();
        helper('menu');
    }

    public function index() {


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

            /*
              $start = $this->request->getGet('start');
              $length = $this->request->getGet('length');
              $search = $this->request->getGet('search[value]');
              $order = BitacoraModel::ORDERABLE[$this->request->getGet('order[0][column]')];
              $dir = $this->request->getGet('order[0][dir]');
             */

            $datos = $this->pacientes->select('id,nombres,apellidos,dni,telefono,correoElectronico,created_at,updated_at')->where('deleted_at', null);
// $resultado = $this->bitacora->findAll();
// $this->bitacora->getResource()->countAllResults(),
// $this->bitacora->getResource($search)->countAllResults()
//      var_dump($datos);


            return \Hermawan\DataTables\DataTable::of($datos)->add('action', function ($row) {
                                return " <div class=\"btn-group\">
                          
                  <button class=\"btn btn-warning btnEditarPaciente\" data-toggle=\"modal\" idPaciente=\"$row->id\" data-target=\"#modalAgregarPaciente\">  <i class=\" fa fa-edit \"></i></button>
                  <button class=\"btn btn-danger btnEliminarPaciente\" idPaciente=\"$row->id\"><i class=\"fa fa-times\"></i></button></div>";
                            }, 'last')
                            ->toJson();
        }

        $titulos["title"] = lang('patients.title');
        $titulos["subtitle"] = lang('patients.subtitle');
        
       
//$data["data"] = $datos;
        return view('pacientes', $titulos);
    }

    /*
     * Lee paciente
     */

    public function traePaciente() {


        $idPaciente = $this->request->getPost("idPaciente");
        $datosPaciente = $this->pacientes->find($idPaciente);

        echo json_encode($datosPaciente);
    }

    /*
     * Guarda o actualiza paciente
     */

    public function guardar() {


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

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

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


            try {


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

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

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

                        echo $error . " ";
                    }

                    return;
                }

                $datosBitacora["descripcion"] = "Se guardo el paciente con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;

                $this->bitacora->save($datosBitacora);

                echo "Guardado Correctamente";
            } catch (\PHPUnit\Framework\Exception $ex) {


                echo "Error al guardar " . $ex->getMessage();
            }
        } else {


            if ($this->pacientes->update($datos["idPaciente"], $datos) == false) {

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

                    echo $error . " ";
                }

                return;
            } else {

                $datosBitacora["descripcion"] = "Se actualizo el paciente con los siguientes datos: " . json_encode($datos);
                $datosBitacora["usuario"] = $userName;
                $this->bitacora->save($datosBitacora);
                echo "Actualizado Correctamente";

                return;
            }
        }

        return;
    }

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

        $infoPaciente = $this->pacientes->find($id);

        $datosBitacora["descripcion"] = "Se elimino el paciente que contenia los siguientes datos " . json_encode($infoPaciente);

        $this->bitacora->save($datosBitacora);
        return $this->respondDeleted($found, lang('patients.msg.msg_delete'));
    }

    /**
     * Trae en formato JSON los pacientes para el select2
     * @return type
     */
    public function traerPacientesAjax() {
        
        $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
            $pacientes = new PacientesModel();
            $listaPacientes = $pacientes->select('id,nombres,apellidos')
                    ->orderBy('nombres')
                    ->findAll(10);
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record
            $pacientes = new PacientesModel();
            $listaPacientes = $pacientes->select('id,nombres,apellidos')
                    ->where("deleted_at",null)
                    ->like('nombres', $searchTerm)
                    ->orLike('apellidos', $searchTerm)
                    ->orderBy('nombres')
                    ->findAll(10);
        }

        $data = array();
        foreach ($listaPacientes as $paciente) {
            $data[] = array(
                "id" => $paciente['id'],
                "text" => $paciente['nombres'].' '.$paciente['apellidos'],
            );
        }

        $response['data'] = $data;

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

}

En Views creamos el archivo de la vista

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

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

<?= $this->include('pacientesModulos\modalCaptura') ?>
<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="float-right">
            <div class="btn-group">

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

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

                </button>

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



                                <th>#</th>
                                <th><?= lang('patients.names') ?></th>
                                <th><?= lang('patients.lastName') ?></th>
                                <th><?= lang('patients.dni') ?></th>
                                <th><?= lang('patients.phone') ?></th>
                                <th><?= lang('patients.email') ?></th>
                                <th><?= lang('patients.createdAt') ?></th>
                                <th><?= lang('patients.updateAt') ?></th>
                                <th>Acciones </th>


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

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




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

    /**
     * Cargamos la tabla
     */


    function cargaTabla() {



         $('.tablaPacientes').DataTable({
         "ajax": "<?= route_to('admin/pacientes') ?>",
         "deferRender": true,
         "serverSide": true,
         "retrieve": true,
         "processing": true
         
         });
        
    }


    cargaTabla();



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR PACIENTE
     =============================================*/
    $(".tablaPacientes").on("click", ".btnEditarPaciente", function () {

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


        console.log("paciente ", idPaciente);

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

        $.ajax({

            url: "<?= route_to('admin/pacientes/traerPaciente') ?>",
            method: "POST",
            data: datos,
            cache: false,
            contentType: false,
            processData: false,
            dataType: "json",
            success: function (respuesta) {
                console.log(respuesta);
                $("#idPaciente").val(respuesta["id"]);
                $("#nombres").val(respuesta["nombres"]);
                $("#apellidos").val(respuesta["apellidos"]);
                $("#correoElectronico").val(respuesta["correoElectronico"]);
                $("#telefono").val(respuesta["telefono"]);
                $("#dni").val(respuesta["dni"]);

            }

        })

    })


    /*=============================================
     ELIMINAR PACIENTE
     =============================================*/
    $(".tablaPacientes").on("click", ".btnEliminarPaciente", function () {

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


        console.log("eliminar");

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

                           $(".tablaPacientes").DataTable().destroy();
                            cargaTabla();
                            //tableUser.ajax.reload();
                        }).fail((error) => {
                            Toast.fire({
                                icon: 'error',
                                title: error.responseJSON.messages.error,
                            });
                        })
                    }
                })
    })




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

En views creamos la carpeta pacientesModulos y dentro de esa capeta creamos el archivo modalCaptura.php con la siguiente código

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



                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('patients.names') ?></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="nombres" id="nombres" class="form-control <?= session('error.nombres') ? 'is-invalid' : '' ?>" value="<?= old('nombres') ?>" placeholder="<?= lang('patients.names') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>

                    <div class="form-group row">
                        <label for="inputName" class="col-sm-2 col-form-label"><?= lang('patients.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="apellidos"  id="apellidos"class="form-control <?= session('error.apellidos') ? 'is-invalid' : '' ?>" value="<?= old('apellidos') ?>" placeholder="<?= lang('patients.lastName') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>

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

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

                    <div class="form-group row">
                        <label for="correoElectronico" class="col-sm-2 col-form-label"><?= lang('patients.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-envelope"></i></span>
                                </div>
                                <input type="text" name="correoElectronico" id="correoElectronico" class="form-control <?= session('error.correoElectronico') ? 'is-invalid' : '' ?>" value="<?= old('correoElectronico') ?>" placeholder="<?= lang('patients.email') ?>" 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="btnGuardarPaciente"><?= lang('boilerplate.global.save') ?></button>
            </div>
        </div>
    </div>
</div>

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


<script>

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

        console.log("asdasd");
        $(".form-control").val("");

        $("#idPaciente").val("0");
        
           $("#btnGuardarPaciente").removeAttr("disabled");

    })

    /* 
     * AL hacer click al editar
     */



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


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

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

        $("#idPaciente").val(idPaciente);
        $("#btnGuardarPaciente").removeAttr("disabled");

    })

    /**
     * Guardar paciente
     */

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

        var idPaciente = $("#idPaciente").val();
        var nombres = $("#nombres").val();
        var apellidos = $("#apellidos").val();
        var telefono = $("#telefono").val();
        var dni = $("#dni").val();
        var correoElectronico = $("#correoElectronico").val();

        var ajaxPaciente = "ajaxPaciente";


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


        var datos = new FormData();
        datos.append("idPaciente", idPaciente);
        datos.append("nombres", nombres);
        datos.append("apellidos", apellidos);
        datos.append("telefono", telefono);
        datos.append("dni", dni);
        datos.append("correoElectronico", correoElectronico);

        $.ajax({

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


                if (respuesta.match(/Correctamente.*/)) {


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


                    $('.tablaPacientes').DataTable().destroy();
                    cargaTabla();
                    $("#btnGuardarPaciente").removeAttr("disabled");

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

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

                    $("#btnGuardarPaciente").removeAttr("disabled");
                  //  $('#modalAgregarPaciente').modal('hide');

                }

            }

        }

        )




    });
</script>


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

Ahora configuramos las rutas en app/config/routes.php

    $routes->resource('pacientes', [
        'filter' => 'permission:pacientes-permiso',
        'controller' => 'PacientesController',
        'except' => 'show'
    ]);

    $routes->post('pacientes/guardar', 'PacientesController::guardar');

    $routes->post('pacientes/traerPaciente', 'PacientesController::traePaciente');

    $routes->post('pacientes/traerPacientesAjax', 'PacientesController::traerPacientesAjax');

En languaje en la carpeta es creamos el archivo patients con el siguiente codigo

<?php

$patients["names"] = "Firs Name";
$patients["lastName"] = "Last Name";
$patients["dni"] = "CARD ID";
$patients["phone"] = "Phone";
$patients["email"] = "E-Mail";
$patients["createdAt"] = "Date Creation";
$patients["updateAt"] = "Date Update";
$patients["add"] = "Add Patient";
$patients["actions"] = "Actions";
$patients["createEdit"] = "Create  / Edit Patient";
$patients["title"] = "Patient";
$patients["subtitle"] = "List of Patients";

$patients["msg_delete"] = "Patient has deleted .";
$patients["msg_get_fail"] = "The patient not exist or has deleted.";





return $patients;

Y en la carpeta es de español creamos el archivo patients con el siguiente código

<?php

$patients["names"] = "Nombres";
$patients["lastName"] = "Apellidos";
$patients["dni"] = "INE";
$patients["phone"] = "Telefono";
$patients["email"] = "Correo Electronico";
$patients["add"] = "Agregar Paciente";
$patients["updateAt"] = "Fecha Modificacion";
$patients["actions"] = "Acciones";
$patients["createEdit"] = "Crear / Editar Paciente";
$patients["title"] = "Pacientes";
$patients["subtitle"] = "Lista de Pacientes";

$patients["msg_delete"] = "El paciente ha sido eliminado correctamente.";
$patients["msg_get_fail"] = "El paciente no existe o fue eliminado.";




return $patients;
Y listo ya tenemos nuestro CRUD de pacientes

CI 4.0 MedicalSoft Datatable para tabla de Bitacora

Ya vimos como guardar los datos de la bitácora, ahora veremos como usar el datatable con server side para mostrar los datos de guardados en la bitácora

Primero creamos el archivo BitacoraModel.php de la siguiente forma en la carpeta app/model/ aunque a estas altura ya debemos tenerlo

Leer Mas: CI 4.0 MedicalSoft Datatable para tabla de Bitacora
<?php

namespace App\Models;

use CodeIgniter\Model;

class BitacoraModel extends Model
{
    protected $table      = 'bitacora';
    protected $primaryKey = 'id';

    protected $useAutoIncrement = true;

    protected $returnType     = 'array';
    protected $useSoftDeletes = true;

    protected $allowedFields = ['id','descripcion', 'usuario'];

    protected $useTimestamps = true;
    protected $createdField  = 'created_at';
    protected $deletedField  = 'deleted_at';

    protected $validationRules    = [];
    protected $validationMessages = [];
    protected $skipValidation     = false;
    
    
    
    const ORDERABLE = [
        1 => 'id',
        2 => 'descripcion',
        3 => 'usuario',
        4 => 'created_at',
        
    ];

    /**
     * Get resource data.
     *
     * @param string $search
     *
     * @return \CodeIgniter\Database\BaseBuilder
     */
   public function getResource(string $search = '')
    {
        $builder = $this->builder()
            ->select('id, descripcion, usuario, created_at');

        $condition = empty($search)
            ? $builder
            : $builder->groupStart()
                ->like('descripcion', $search)
                ->orLike('Usuario', $search)
                ->orLike('created_at', $search)
            ->groupEnd();

        return $condition->where('deleted_at', null);
    }
}

Para convertir fácilmente el resultado de la consulta SQL del QUERYBUILDER a formato json que usa datatable instalamos en la terminal el siguiente componente de hermawan usando composer

composer require hermawan/codeigniter4-datatables

Creamos el archivo BitacoraController.php con los siguientes, si observamos en la función index vemos que solo con una linea se crea el json

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use \App\Models\BitacoraModel;
use CodeIgniter\API\ResponseTrait;

class BitacoraController extends BaseController {

    use ResponseTrait;

    protected $bitacora;

    public function __construct() {
        $this->bitacora = new BitacoraModel();
        helper('menu');
    }

    public function index() {


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

            /*
            $start = $this->request->getGet('start');
            $length = $this->request->getGet('length');
            $search = $this->request->getGet('search[value]');
            $order = BitacoraModel::ORDERABLE[$this->request->getGet('order[0][column]')];
            $dir = $this->request->getGet('order[0][dir]');
*/

           $datos = $this->bitacora->select('id,descripcion,usuario,created_at');
           // $resultado = $this->bitacora->findAll();
                                   // $this->bitacora->getResource()->countAllResults(),
                                   // $this->bitacora->getResource($search)->countAllResults()
           return \Hermawan\DataTables\DataTable::of($datos)->toJson();
        }
        
        $titulos["title"] = lang('log.title');
        $titulos["subtitle"] = lang('log.subtitle');
        //$data["data"] = $datos;
        return view('bitacora',$titulos);
    }

    public function tabla() {


        if ($this->request->isAJAX()) {
            $start = $this->request->getGet('start');
            $length = $this->request->getGet('length');
            $search = $this->request->getGet('search[value]');
            $order = BitacoraModel::ORDERABLE[$this->request->getGet('order[0][column]')];
            $dir = $this->request->getGet('order[0][dir]');

            return $this->respond(Collection::datatable(
                                    $this->bitacora->getResource($search)->orderBy($order, $dir)->limit($length, $start)->get()->getResultObject(),
                                    $this->bitacora->getResource()->countAllResults(),
                                    $this->bitacora->getResource($search)->countAllResults()
            ));
        }
    }

}

Creamos la vista en app/views/bitacora.php

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

<!-- Section content -->
<?= $this->section('content') ?>
<!-- SELECT2 EXAMPLE -->
<div class="card card-default">
    <div class="card-header">
        <div class="card-tools">
            <div class="btn-group">


            </div>
        </div>
    </div>
    <div class="card-body">
        <div class="row">
            <div class="col-md-12">
                <div class="table-responsive">
                    <table id="tablaBitacora" class="table table-striped table-hover va-middle tablaBitacora">
                        <thead>
                            <tr>
                                <th>#</th>
                                <th><?= lang('log.description') ?></th>
                                <th><?= lang('log.user') ?></th>
                                <th><?= lang('log.date') ?></th>


                            </tr>
                        </thead>
                        <tbody>
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
<!-- /.card -->
<?= $this->endSection() ?>

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

/**
 * Cargamos la tabla
 */
$('.tablaBitacora').DataTable( {
    "ajax": "<?= route_to('admin/bitacora/manage') ?>",
    "deferRender": true,
     "serverSide" : true,
  "retrieve": true,
  "processing": true

} )

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

En las rutas app/config/routes.php agregamos la siguiente linea

    /*
     * RUTA BITACORA
     */

    $routes->resource('bitacora', [
        'filter' => 'permission:bitacora-permiso',
        'controller' => 'BitacoraController',
        'except' => 'show'
    ]);
Al final este es el resultado

Página 4 de 5

Creado con WordPress & Tema de Anders Norén