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

A continuación mostramos como crear el CRUD de almacenes

El CRUD tendrá los siguientes datos

  • Empresa
  • Clave
  • Nombre
  • Inicio de inventario

Primero creamos el archivo de migración App/Database/Migrations/2023-04-28180544_Storages.php

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class Storages extends Migration {

    public function up() {
        // Storages
        $this->forge->addField([
            'id' => ['type' => 'int', 'constraint' => 11, 'unsigned' => true, 'auto_increment' => true],
            'idEmpresa' => ['type' => 'varchar', 'constraint' => 16, 'null' => true],
            'code' => ['type' => 'varchar', 'constraint' => 16, 'null' => true],
            'name' => ['type' => 'varchar', 'constraint' => 256, 'null' => true],
            'type' => ['type' => 'varchar', 'constraint' => 64, 'null' => true],
            'brachoffice' => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'company' => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'costCenter' => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'exist' => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'list' => ['type' => 'int', 'constraint' => 11, 'null' => true],
            'main' => ['type' => 'varchar', 'constraint' => 16, 'null' => true],
            'inicioOperacion' => ['type' => 'date', '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('storages', true);
    }

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

}

Creamos el archivo del modelo App/Models/StoragesModel.php con el siguiente código

<?php

namespace App\Models;

use CodeIgniter\Model;

class StoragesModel extends Model {

    protected $table = 'storages';
    protected $primaryKey = 'id';
    protected $useAutoIncrement = true;
    protected $returnType = 'array';
    protected $useSoftDeletes = true;
    protected $allowedFields = ['id'
        , 'code'
        , 'idEmpresa'
        , 'name'
        , 'type'
        , 'brachoffice'
        , 'company'
        , 'costCenter'
        , 'exist'
        , 'list'
        , 'main'
        , 'inicioOperacion'
        , 'created_at'
        , 'updated_at'
        , 'deleted_at'];
    protected $useTimestamps = true;
    protected $createdField = 'created_at';
    protected $deletedField = 'deleted_at';
    protected $validationRules = [
    ];
    protected $validationMessages = [];
    protected $skipValidation = false;

    public function mdlStorages($empresas) {
        $resultado = $this->db->table('storages a, empresas b')
                ->select('
            a.id
            ,a.code
            ,a.idEmpresa
            ,a.name
            ,a.type
            ,a.brachoffice
            ,a.company
            ,a.costCenter
            ,a.exist
            ,a.list
            ,a.main
            ,a.created_at
            ,a.updated_at
            ,a.deleted_at
            ,a.inicioOperacion
            ,b.nombre as nombreEmpresa ')
                ->where('a.idEmpresa', 'b.id', FALSE)
                ->whereIn('a.idEmpresa', $empresas)
                ->where('a.deleted_at', null);

        return $resultado;
    }

}

Creamos el archivo controlador App/controllers/StorageController.php con el siguiente código

<?php

namespace App\Controllers;

use App\Controllers\BaseController;
use App\Database\Migrations\Branchoffices;
use \App\Models\{
    BranchofficesModel,
    StoragesModel
};
use App\Models\LogModel;
use CodeIgniter\API\ResponseTrait;
use App\Models\EmpresasModel;
use App\Models\UsuariosAlmacenModel;

class StoragesController extends BaseController {

    use ResponseTrait;

    protected $log;
    protected $storages;
    protected $empresa;
    protected $sucursales;
    protected $usuariosPorAlmacen;

    public function __construct() {
        $this->storages = new StoragesModel();
        $this->log = new LogModel();
        $this->empresa = new EmpresasModel();
        $this->sucursales = new BranchofficesModel();
        $this->usuariosPorAlmacen = new UsuariosAlmacenModel();
        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()) {

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

        $fechaActual = fechaMySQLADateHTML5(fechaHoraActual());
        $titulos["fecha"] = $fechaActual;

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

        // $titulos["empresas"] = $this->empresa->select("*")->asArray()->findAll();
        $titulos["sucursales"] = $this->sucursales->select("*")->asArray()->findAll();

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

    /**
     * Read Storages
     */
    public function getStorages() {
        $idStorages = $this->request->getPost("idStorages");
        $datosStorages = $this->storages->find($idStorages);
        echo json_encode($datosStorages);
    }

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

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

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

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

        $response = array();

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

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

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

        $almacenesPorUsuario = array_column($almacenesPorUsuario, "idStorage");
        if (!isset($postData['searchTerm'])) {
            // Fetch record
            $storages = new StoragesModel();
            $listStorages = $storages->select('id,code,name')->where("deleted_at", null)
                    ->whereIn("id", $almacenesPorUsuario)
                    ->where("idEmpresa", $postData["idEmpresa"])
                    ->orderBy('id')
                    ->orderBy('code')
                    ->orderBy('name')
                    ->findAll(10);
        } else {
            $searchTerm = $postData['searchTerm'];

            // Fetch record
            $storages = new StoragesModel();
            $listStorages = $storages->select('id,code,name')
                    ->where("deleted_at", null)
                    ->whereIn("id", $almacenesPorUsuario)
                    ->where("idEmpresa", $postData["idEmpresa"])
                    ->like('name', $searchTerm)
                    ->orLike('id', $searchTerm)
                    ->orLike('code', $searchTerm)
                    ->findAll(10);
        }

        $data = array();
        foreach ($listStorages as $storage) {
            $data[] = array(
                "id" => $storage['id'],
                "text" => $storage['code'] . ' ' . $storage['name'],
            );
        }

        $response['data'] = $data;

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

    public function usuariosPorAlmacen($almacen) {

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



        $usuarios = $this->usuariosPorAlmacen->mdlAlmacenesPorUsuario($almacen, $empresasID);

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

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

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

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

            //ACTUALIZA SI  EXISTE

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

            echo "ok";
        } else {

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

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

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

                    echo $error . " ";
                }

                return;
            }



            echo "ok";
        }
    }

}

Creamos el archivo principal de la vista App/Views/storages.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('modulesStorages/modalCaptureStorages') ?>
<?= $this->include('modulesStorages/usuariosAlmacenModal') ?>

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

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

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

                </button>

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

                                <th>#</th>
                                <th><?= lang('storages.fields.code') ?></th>
                                <th><?= lang('storages.fields.name') ?></th>

                                <th><?= lang('storages.fields.created_at') ?></th>
                                <th><?= lang('storages.fields.updated_at') ?></th>
                                <th><?= lang('storages.fields.deleted_at') ?></th>

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

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

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


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

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

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

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

            {
                'data': 'code'
            },

            {
                'data': 'name'
            },

            {
                '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 btnEditStorages" data-toggle="modal" idStorages="${data.id}" data-target="#modalAddStorages">  <i class=" fa fa-edit"></i></button>
                             <button class="btn btn-danger btn-delete" data-id="${data.id}"><i class="fas fa-trash"></i></button>
                             <button class="btn btn-success btn-users" data-id="${data.id}" data-toggle="modal" data-target="#modalUsuariosAlmacen"><i class="fas fa-users"></i></button>
                         </div>
                         </td>`
                }
            }
        ]
    });



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


        var idStorages = $("#idStorages").val();
        var code = $("#code").val();
        var name = $("#name").val();
        var type = $("#type").val();
        var brachoffice = $("#brachoffice").val();
        var company = $("#company").val();
        var costCenter = $("#costCenter").val();
        var exist = $("#exist").val();
        var list = $("#list").val();
        var main = $("#main").val();
        var idEmpresaStorage = $("#idEmpresaStorage").val();
        var inicioOperacion = $("#inicioInventario").val();

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

        var datos = new FormData();
        datos.append("idStorages", idStorages);
        datos.append("code", code);
        datos.append("name", name);
        datos.append("type", type);
        datos.append("brachoffice", brachoffice);
        datos.append("company", company);
        datos.append("costCenter", costCenter);
        datos.append("exist", exist);
        datos.append("list", list);
        datos.append("main", main);
        datos.append("idEmpresa", idEmpresaStorage);
        datos.append("inicioOperacion", inicioOperacion);


        $.ajax({

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

                    tableStorages.ajax.reload();
                    $("#btnSaveStorages").removeAttr("disabled");


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

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

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


                }

            }

        }

        )

    });



    /**
     * Carga datos actualizar
     */


    /*=============================================
     EDITAR Storages
     =============================================*/
    $(".tableStorages").on("click", ".btnEditStorages", function () {

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

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

        $.ajax({

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

                $("#code").val(respuesta["code"]);
                $("#name").val(respuesta["name"]);
                $("#type").val(respuesta["type"]);
                $("#brachoffice").val(respuesta["brachoffice"]);
                $("#company").val(respuesta["company"]);
                $("#costCenter").val(respuesta["costCenter"]);
                $("#exist").val(respuesta["exist"]);
                $("#list").val(respuesta["list"]);
                $("#main").val(respuesta["main"]);
                $("#idEmpresaStorage").val(respuesta["idEmpresa"]);
                $("#idEmpresaStorage").trigger("change");
                $("#inicioInventario").val(respuesta["inicioOperacion"]);

            }

        })

    })


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

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


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

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

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

Creamos el archivo para la captura de los almacenes, el modal , App/Views/modulesStorages/modalCaptureStorages.php con el siguiente código

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

                    <div class="form-group row">
                        <label for="idEmpresaStorage" 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="idEmpresaStorage" id="idEmpresaStorage" style="width:80%;">

                                    <?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="code" class="col-sm-2 col-form-label"><?= lang('storages.fields.code') ?></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 <?= session('error.code') ? 'is-invalid' : '' ?>" value="<?= old('code') ?>" placeholder="<?= lang('storages.fields.code') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row">
                        <label for="name" class="col-sm-2 col-form-label"><?= lang('storages.fields.name') ?></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="name" id="name" class="form-control <?= session('error.name') ? 'is-invalid' : '' ?>" value="<?= old('name') ?>" placeholder="<?= lang('storages.fields.name') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row" hidden>
                        <label for="type" class="col-sm-2 col-form-label"><?= lang('storages.fields.type') ?></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="type" id="type" class="form-control <?= session('error.type') ? 'is-invalid' : '' ?>" value="<?= old('type') ?>" placeholder="<?= lang('storages.fields.type') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row" hidden>
                        <label for="brachoffice" class="col-sm-2 col-form-label"><?= lang('storages.fields.brachoffice') ?></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 type="text" name="brachoffice" id="brachoffice" class="form-control <?= session('error.brachoffice') ? 'is-invalid' : '' ?>" value="<?= old('brachoffice') ?>" placeholder="<?= lang('storages.fields.brachoffice') ?>" autocomplete="off">
                                    <?php
                                    foreach ($sucursales as $key => $value) {

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


                                    ?>
                                </select>
                            </div>
                        </div>
                    </div>
                    <div class="form-group row" hidden>
                        <label for="company" class="col-sm-2 col-form-label"><?= lang('storages.fields.company') ?></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="company" id="company" class="form-control <?= session('error.company') ? 'is-invalid' : '' ?>" value="<?= old('company') ?>" placeholder="<?= lang('storages.fields.company') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row" hidden>
                        <label for="costCenter" class="col-sm-2 col-form-label"><?= lang('storages.fields.costCenter') ?></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="costCenter" id="costCenter" class="form-control <?= session('error.costCenter') ? 'is-invalid' : '' ?>" value="<?= old('costCenter') ?>" placeholder="<?= lang('storages.fields.costCenter') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row" hidden>
                        <label for="exist" class="col-sm-2 col-form-label"><?= lang('storages.fields.exist') ?></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="exist" id="exist" class="form-control <?= session('error.exist') ? 'is-invalid' : '' ?>" value="<?= old('exist') ?>" placeholder="<?= lang('storages.fields.exist') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row" hidden>
                        <label for="list" class="col-sm-2 col-form-label"><?= lang('storages.fields.list') ?></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="list" id="list" class="form-control <?= session('error.list') ? 'is-invalid' : '' ?>" value="<?= old('list') ?>" placeholder="<?= lang('storages.fields.list') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>
                    <div class="form-group row" hidden>
                        <label for="main" class="col-sm-2 col-form-label"><?= lang('storages.fields.main') ?></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="main" id="main" class="form-control <?= session('error.main') ? 'is-invalid' : '' ?>" value="<?= old('main') ?>" placeholder="<?= lang('storages.fields.main') ?>" autocomplete="off">
                            </div>
                        </div>
                    </div>


                    <div class="form-group row" >
                        <label for="main" class="col-sm-2 col-form-label">Inicio Inventario</label>
                        <div class="col-sm-10">
                            <div class="input-group">
                                <div class="input-group-prepend">
                                    <span class="input-group-text"><i class="fas fa-pencil-alt"></i></span>
                                </div>
                                <input type="date" name="inicioInventario" id="inicioInventario" class="form-control <?= session('error.main') ? 'is-invalid' : '' ?>" value="<?= $fecha ?>" placeholder="Inicio Inventario" 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="btnSaveStorages"><?= lang('boilerplate.global.save') ?></button>
            </div>
        </div>
    </div>
</div>

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


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


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



        var idEmpresa = $("#idEmpresaStorage").val();
        $(".form-control").val("");

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

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

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

    });

    /* 
     * AL hacer click al editar
     */



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


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

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

        $("#idStorages").val(idStorages);
        $("#btnGuardarStorages").removeAttr("disabled");

    });
</script>


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

Para asignarle almacenes a los usuarios creamos el archivo App/Views/modulesStorage/usuariosAlmacenModal.php para la asignación de usuarios por almacén

<!-- Modal Usuarios por tipo nomina -->
<div class="modal fade" id="modalUsuariosAlmacen" tabindex="-1" role="dialog" aria-labelledby="modalUsuariosAlmacen" aria-hidden="true">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Modal Usuarios Por Almacen</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <div class="row">
                    <div class="col-md-12">
                        <div class="table-responsive">
                            <table id="tabla-usuariosAlmacen" class="table table-striped table-hover va-middle tabla-usuariosAlmacen">
                                <thead>
                                    <tr>
                                        <th>Usuario</th>
                                        <th>Acciones</th>
                                    </tr>
                                </thead>
                                <tbody>
                                </tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary btn-sm" data-dismiss="modal"><?= lang('boilerplate.global.close') ?></button>

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



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


<script>
    var tablaUsuarios = $('#tabla-usuariosAlmacen').DataTable({
        processing: true,
        serverSide: true,
        autoWidth: false,
        order: [
            [0, 'asc']
        ],


        ajax: {
            url: `<?= base_url('admin/almacen/usuariosPorAlmacen') ?>/` + "0",
            method: 'GET',
            dataType: "json"

        },
        columnDefs: [{
            orderable: false,
            targets: [1],
            searchable: false,
            targets: [1]

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

            {
                "data": function(data) {

                    if (data.status == "on") {

                        return `<td class="text-right py-0 align-middle">
                         <div class="btn-group btn-group-sm">
                             <button class="btn btn-success btnActivate" status="on"  idEmpresa="${data.idEmpresa}" idStorage="${data.idStorage}"  idUser="${data.id}" idAlmacenUsuario="${data.idAlmacenUsuario}">ON</button>
                             
                         </div>
                         </td>`;


                    } else {

                        return `<td class="text-right py-0 align-middle">
                         <div class="btn-group btn-group-sm">
                             <button class="btn btn-danger btnActivate" status="off" idEmpresa="${data.idEmpresa}"  idStorage="${data.idStorage}"  idUser="${data.id}" idAlmacenUsuario="${data.idAlmacenUsuario}">OFF</button>
                             
                         </div>
                         </td>`;


                    }

                }
            }
        ]
    });



    /**
     * Carga datos actualizar
     */
    $(".tableStorages").on("click", ".btn-users", function() {

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

        console.log("almacen:", almacen);

        tablaUsuarios.ajax.url(`<?= base_url('admin/almacen/usuariosPorAlmacen') ?>/` + almacen).load();


    });


    /**
     * Guarda derecho
     */

    $(".tabla-usuariosAlmacen").on("click", ".btnActivate", function() {


        var statusActual = $(this).attr("status");
        var idAlmacenUsuario = $(this).attr("idAlmacenUsuario");
        var idUsuario = $(this).attr("iduser");
        var idEmpresa = $(this).attr("idEmpresa");
        var idStorage = $(this).attr("idStorage");


        if (statusActual == "on") {

            var status = "off";
        } else {

            var status = "on";

        }



        var datos = new FormData();
        datos.append("idUsuario", idUsuario);
        datos.append("id", idAlmacenUsuario);
        datos.append("idEmpresa", idEmpresa);
        datos.append("idStorage", idStorage);
        datos.append("status", status);


        $.ajax({

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

                if (respuesta == "ok") {

                    tablaUsuarios.ajax.url(`<?= base_url('admin/almacen/usuariosPorAlmacen') ?>/` + idStorage).load();


                } else {

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


                }



            }
        });
    })
</script>


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

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

 <?php

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

$storages["add"] = "Add Storages";
$storages["edit"] = "Edit storages";
$storages["createEdit"] = "Create / Edit";
$storages["title"] = "storages management";
$storages["subtitle"] = "storages list";
$storages["fields"]["code"] = "Code";
$storages["fields"]["name"] = "Name";
$storages["fields"]["type"] = "Type";
$storages["fields"]["brachoffice"] = "Brachoffice";
$storages["fields"]["company"] = "Company";
$storages["fields"]["costCenter"] = "CostCenter";
$storages["fields"]["exist"] = "Exist";
$storages["fields"]["list"] = "List";
$storages["fields"]["main"] = "Main";
$storages["fields"]["created_at"] = "Created_at";
$storages["fields"]["updated_at"] = "Updated_at";
$storages["fields"]["deleted_at"] = "Deleted_at";

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

return $storages;
        

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

<?php

$almacenes["logDescription"] = "El registro en almacenes fue guardado con los siguientes datos:";
$almacenes["logUpdate"] = "El registro en almacenes fue actualizado con los siguientes datos:";
$almacenes["logDeleted"] = "El registro en almacenes fue eliminado con los siguientes datos:";
$almacenes["msg_delete"] = "El Registro en almacenes fue eliminado correctamente:";
$almacenes["add"] = "Agregar almacenes";
$almacenes["edit"] = "Editar almacenes";
$almacenes["createEdit"] = "Crear / Editar";
$almacenes["title"] = "Admon. almacenes";
$almacenes["subtitle"] = "Lista almacenes";

$almacenes["fields"]["code"] = "Clave";
$almacenes["fields"]["name"] = "Nombre";
$almacenes["fields"]["type"] = "Tipo";
$almacenes["fields"]["brachoffice"] = "Sucursal";
$almacenes["fields"]["company"] = "Empresa";
$almacenes["fields"]["costCenter"] = "CCO";
$almacenes["fields"]["exist"] = "Exist";
$almacenes["fields"]["list"] = "Lista";
$almacenes["fields"]["main"] = "Principal";
$almacenes["fields"]["Creado"] = "Created_at";
$almacenes["fields"]["Actualizado"] = "Updated_at";
$almacenes["fields"]["Eliminado"] = "Deleted_at";

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

En App/Config/Routes.php agregamos las siguientes rutas

    $routes->resource('storages', [
        'filter' => 'permission:storages-permission',
        'controller' => 'storagesController',
        'except' => 'show'
    ]);
    $routes->post('storages/save', 'StoragesController::save');
    $routes->post('storages/getStorages', 'StoragesController::getStorages');
    $routes->post('storages/getStoragesAjax', 'StoragesController::getStoragesAjax');

$routes->post('usuariosAlmacen/getUsuariosAlmacen', 'UsuariosAlmacenController::getUsuariosAlmacen');

    $routes->get('almacen/usuariosPorAlmacen/(:any)', 'StoragesController::usuariosPorAlmacen/$1');
    $routes->post('almacen/activarDesactivar', 'StoragesController::activarDesactivar');

Agregamos el menú para acceder al catalogo con los siguientes datos

Agregamos el permiso

Y listo ya tenemos nuestro CRUD de Almacenes

Video demostrativo