commit 06dbf8e2a7325f33c3bc78c03334f28181d1690b Author: Arturo Corro Date: Wed Mar 5 20:44:45 2025 -0600 first commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8f0de65 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[docker-compose.yml] +indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7333620 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,38 @@ +* text=auto eol=lf + +*.blade.php diff=html +*.css diff=css +*.html diff=html +*.md diff=markdown +*.php diff=php + +/.github export-ignore +CHANGELOG.md export-ignore +.styleci.yml export-ignore + +# Ignorar archivos de configuración y herramientas de desarrollo +.editorconfig export-ignore +.prettierrc.json export-ignore +.prettierignore export-ignore +.eslintrc.json export-ignore + +# Ignorar node_modules y dependencias locales +node_modules/ export-ignore +vendor/ export-ignore + +# Ignorar archivos de build +npm-debug.log export-ignore + +# Ignorar carpetas de logs y caché +storage/logs/ export-ignore +storage/framework/ export-ignore + +# Ignorar carpetas de compilación de frontend +public/build/ export-ignore +dist/ export-ignore + +# Ignorar archivos de CI/CD +.github/ export-ignore +.gitlab-ci.yml export-ignore +.vscode/ export-ignore +.idea/ export-ignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d07bec2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +/node_modules +/vendor +/.vscode +/.nova +/.fleet +/.phpactor.json +/.phpunit.cache +/.phpunit.result.cache +/.zed +/.idea diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..5d3dfee --- /dev/null +++ b/.prettierignore @@ -0,0 +1,16 @@ +# Dependencias de Composer y Node.js +/vendor/ +/node_modules/ + +# Caché y logs +/storage/ +*.log +*.cache + +# Archivos del sistema +.DS_Store +Thumbs.db + +# Configuración de editores +.idea/ +.vscode/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..5f11c9c --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,29 @@ +{ + "arrowParens": "avoid", + "bracketSpacing": true, + "bracketSameLine": true, + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxSingleQuote": true, + "printWidth": 120, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": true, + "singleQuote": true, + "tabWidth": 4, + "trailingComma": "none", + "useTabs": false, + "endOfLine": "lf", + "embeddedLanguageFormatting": "auto", + "overrides": [ + { + "files": [ + "resources/assets/**/*.js" + ], + "options": { + "semi": false + } + } + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..93a240e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,39 @@ +# 📜 CHANGELOG - Laravel Vuexy Warehouse + +Este documento sigue el formato [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). + +## [0.1.0] - ALPHA - 2024-03-05 + +### ✨ Added (Agregado) +- 🚀 Primera versión alpha de la librería. +- 🔹 Implementación inicial de [funcionalidad clave 1]. +- 🔹 Integración con [dependencia o servicio principal]. +- 🔹 Soporte para [Laravel/Vuexy Admin, si aplica]. + +### 🛠 Changed (Modificado) +- 🔄 Optimización de [código o estructura interna]. + +### 🐛 Fixed (Correcciones) +- 🐞 Correcciones iniciales en [migraciones, modelos, servicios, etc.]. + +--- + +## 📅 Próximos Cambios Planeados +- 📊 **Mejoras en [feature futuro]**. +- 🏪 **Compatibilidad con [Laravel 11, Vuexy, etc.]**. +- 📍 **Integración con [API o funcionalidad esperada]**. + +--- + +**📌 Nota:** Esta es una versión **ALPHA**, aún en desarrollo. + +--- + +## 🔄 Sincronización de Cambios +Este `CHANGELOG.md` se actualiza primero en nuestro repositorio principal en **[Tea - Koneko Git](https://git.koneko.mx/koneko/laravel-vuexy-warehouse)** y luego se refleja en GitHub. +Los cambios recientes pueden verse antes en **Tea** que en **GitHub** debido a la sincronización automática. + +--- + +📅 Última actualización: **2024-03-05**. + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b2ac5f9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +## 🔐 Acceso al Repositorio Privado + +Nuestro servidor Git en **Tea** tiene un registro cerrado. Para contribuir: + +1. Abre un **Issue** en [GitHub](https://github.com/koneko-mx/laravel-vuexy-warehouse/issues) indicando tu interés en contribuir. +2. Alternativamente, envía un correo a **contacto@koneko.mx** solicitando acceso. +3. Una vez aprobado, recibirás una invitación para registrarte y clonar el repositorio. + +Si solo necesitas acceso de lectura, puedes clonar la versión pública en **GitHub**. diff --git a/Http/Controllers/InventoryItemController.php b/Http/Controllers/InventoryItemController.php new file mode 100644 index 0000000..8d0ef2d --- /dev/null +++ b/Http/Controllers/InventoryItemController.php @@ -0,0 +1,65 @@ +ajax()) { + $bootstrapTableIndexConfig = [ + 'table' => 'warehouses', + 'columns' => [ + 'warehouses.id', + 'stores.code AS store_code', + 'stores.name AS store_name', + 'store_work_centers.code AS work_center_code', + 'store_work_centers.name AS work_center_name', + 'warehouses.code', + 'warehouses.name', + 'warehouses.description', + DB::raw("CONCAT_WS(' ', users.name, users.last_name) AS manager_name"), + 'users.email AS manager_email', + 'warehouses.tel', + 'warehouses.tel2', + 'sat_pais.descripcion AS pais', + 'sat_estado.nombre_del_estado AS estado', + 'sat_localidad.descripcion AS localidad', + 'sat_municipio.descripcion AS municipio', + 'sat_colonia.nombre_del_asentamiento AS colonia', + DB::raw("CONCAT_WS(' ', COALESCE(stores.direccion, ''), COALESCE(stores.num_ext, ''), IF(stores.num_int IS NOT NULL, CONCAT('Int ', stores.num_int), '')) AS direccion"), + 'warehouses.priority', + 'warehouses.status', + 'warehouses.created_at', + 'warehouses.updated_at', + ], + 'joins' => [ + [ + 'table' => 'stores', + 'first' => 'warehouses.store_id', + 'second' => 'stores.id', + 'type' => 'join', + ], + [ + 'table' => 'store_work_centers', + 'first' => 'warehouses.work_center_id', + 'second' => 'store_work_centers.id', + 'type' => 'leftJoin', + ], + [ + 'table' => 'users', + 'first' => 'warehouses.manager_id', + 'second' => 'users.id', + 'type' => 'leftJoin', + ], + [ + 'table' => 'sat_pais', + 'first' => 'stores.c_pais', + 'second' => 'sat_pais.c_pais', + 'type' => 'leftJoin', + ], + [ + 'table' => 'sat_estado', + 'first' => 'stores.c_estado', + 'second' => 'sat_estado.c_estado', + 'and' => [ + 'stores.c_pais = sat_estado.c_pais', + ], + 'type' => 'leftJoin', + ], + [ + 'table' => 'sat_localidad', + 'first' => 'stores.c_localidad', + 'second' => 'sat_localidad.c_localidad', + 'and' => [ + 'stores.c_estado = sat_localidad.c_estado', + ], + 'type' => 'leftJoin', + ], + [ + 'table' => 'sat_municipio', + 'first' => 'stores.c_municipio', + 'second' => 'sat_municipio.c_municipio', + 'and' => [ + 'stores.c_estado = sat_municipio.c_estado', + ], + 'type' => 'leftJoin', + ], + [ + 'table' => 'sat_colonia', + 'first' => 'stores.c_colonia', + 'second' => 'sat_colonia.c_colonia', + 'and' => [ + 'stores.c_codigo_postal = sat_colonia.c_codigo_postal', + ], + 'type' => 'leftJoin', + ], + ], + 'filters' => [ + 'search' => [ + 'warehouses.code', + 'warehouses.name', + 'stores.name', + 'store_work_centers.name', + ], + ], + 'sort_column' => 'warehouses.name', // Columna por defecto para ordenamiento + 'default_sort_order' => 'asc', // Orden por defecto + ]; + + return (new GenericQueryBuilder($request, $bootstrapTableIndexConfig))->getJson(); + } + + return view('vuexy-warehouse::warehouses.index'); + } + + /** + * Display the specified resource. + */ + public function show(Warehouse $warehouse) + { + // + } + +} diff --git a/Http/Controllers/WarehouseMovementController.php b/Http/Controllers/WarehouseMovementController.php new file mode 100644 index 0000000..242bc95 --- /dev/null +++ b/Http/Controllers/WarehouseMovementController.php @@ -0,0 +1,66 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.inventory-items.inventory-items-index'); + } + +} diff --git a/Livewire/InventoryMovements/InventoryMovementsIndex.php b/Livewire/InventoryMovements/InventoryMovementsIndex.php new file mode 100644 index 0000000..a3d4a3e --- /dev/null +++ b/Livewire/InventoryMovements/InventoryMovementsIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.inventory-movements.inventory-movements-index'); + } + +} diff --git a/Livewire/InventoryStock/InventoryStockIndex.php b/Livewire/InventoryStock/InventoryStockIndex.php new file mode 100644 index 0000000..dab768a --- /dev/null +++ b/Livewire/InventoryStock/InventoryStockIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.inventory-stock.inventory-stock-index'); + } + +} diff --git a/Livewire/Materials/MaterialsIndex.php b/Livewire/Materials/MaterialsIndex.php new file mode 100644 index 0000000..519bd56 --- /dev/null +++ b/Livewire/Materials/MaterialsIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.materials.materials-index'); + } + +} diff --git a/Livewire/ProductCatalogs/ProductCatalogsIndex.php b/Livewire/ProductCatalogs/ProductCatalogsIndex.php new file mode 100644 index 0000000..1642d11 --- /dev/null +++ b/Livewire/ProductCatalogs/ProductCatalogsIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.product-catalogs.product-catalogs-index'); + } + +} diff --git a/Livewire/ProductCategories/ProductCategoriesIndex.php b/Livewire/ProductCategories/ProductCategoriesIndex.php new file mode 100644 index 0000000..d87de06 --- /dev/null +++ b/Livewire/ProductCategories/ProductCategoriesIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.product-categories.product-categories-index'); + } + +} diff --git a/Livewire/ProductReceipts/ProductReceiptsIndex.php b/Livewire/ProductReceipts/ProductReceiptsIndex.php new file mode 100644 index 0000000..1c2000e --- /dev/null +++ b/Livewire/ProductReceipts/ProductReceiptsIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.product-receipts.product-receipts-index'); + } + +} diff --git a/Livewire/Products/ProductsIndex.php b/Livewire/Products/ProductsIndex.php new file mode 100644 index 0000000..5356aa2 --- /dev/null +++ b/Livewire/Products/ProductsIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.products.products-index'); + } + +} diff --git a/Livewire/PurchaseOrders/PurchaseOrdersIndex.php b/Livewire/PurchaseOrders/PurchaseOrdersIndex.php new file mode 100644 index 0000000..c44de8f --- /dev/null +++ b/Livewire/PurchaseOrders/PurchaseOrdersIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.purchase-orders.purchase-orders-index'); + } + +} diff --git a/Livewire/WarehouseMovements/WarehouseMovementsIndex.php b/Livewire/WarehouseMovements/WarehouseMovementsIndex.php new file mode 100644 index 0000000..018a218 --- /dev/null +++ b/Livewire/WarehouseMovements/WarehouseMovementsIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.warehouse-movements.warehouse-movements-index'); + } + +} diff --git a/Livewire/WarehouseTransfers/WarehouseTransfersIndex.php b/Livewire/WarehouseTransfers/WarehouseTransfersIndex.php new file mode 100644 index 0000000..a4adea6 --- /dev/null +++ b/Livewire/WarehouseTransfers/WarehouseTransfersIndex.php @@ -0,0 +1,109 @@ +tipo_persona_options = [ + User::TIPO_RFC_FISICA => User::$tipoRfcList[User::TIPO_RFC_FISICA], + User::TIPO_RFC_MORAL => User::$tipoRfcList[User::TIPO_RFC_MORAL], + User::TIPO_RFC_PUBLICO => User::$tipoRfcList[User::TIPO_RFC_PUBLICO], + ]; + + $this->estado_options = DB::table('sat_estado') + ->select('sat_estado.c_estado', 'sat_estado.nombre_del_estado') + ->join('sat_codigo_postal', 'sat_estado.c_estado', '=', 'sat_codigo_postal.c_estado') + ->join('users', 'users.domicilio_fiscal', '=', 'sat_codigo_postal.c_codigo_postal') + ->distinct() + ->orderBy('sat_estado.c_estado') + ->pluck('sat_estado.nombre_del_estado', 'sat_estado.c_estado'); + + + $this->status_options = $this->status_list = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + //$this->status = User::STATUS_ENABLED; + $this->status_list_class = User::$statusListClass; + + $this->statuses = [ + User::STATUS_ENABLED => ['title' => 'Activo', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_ENABLED]], + User::STATUS_DISABLED => ['title' => 'Deshabilitado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_DISABLED]], + User::STATUS_REMOVED => ['title' => 'Eliminado', 'class' => 'badge bg-label-' . User::$statusListClass[User::STATUS_REMOVED]], + ]; + + $roles = Role::whereNotIn('name', ['Patient', 'Doctor'])->get(); + + $this->roles_html_select = ""; + + $this->status_options = [ + User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED], + User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED], + ]; + + $this->uso_cfdi_options = UsoCfdi::selectList(); + $this->regimen_fiscal_options = RegimenFiscal::selectList(); + } + + + + + public function render() + { + return view('vuexy-warehouse::livewire.warehouse-transfers.warehouse-transfers-index'); + } + +} diff --git a/Livewire/Warehouses/WarehouseIndex.php b/Livewire/Warehouses/WarehouseIndex.php new file mode 100644 index 0000000..91391b4 --- /dev/null +++ b/Livewire/Warehouses/WarehouseIndex.php @@ -0,0 +1,221 @@ + 'Acciones', + 'store_code' => 'Código de Tienda', + 'store_name' => 'Nombre de la Tienda', + 'work_center_code' => 'Código del Centro de Trabajo', + 'work_center_name' => 'Nombre del Centro de Trabajo', + 'code' => 'Código de Almacén', + 'name' => 'Nombre del Almacén', + 'description' => 'Descripción', + 'manager_name' => 'Encargado', + 'pais' => 'País', + 'estado' => 'Estado', + 'localidad' => 'Localidad', + 'municipio' => 'Municipio', + 'codigo_postal' => 'Código Postal', + 'colonia' => 'Colonia', + 'direccion' => 'Dirección', + 'tel' => 'Teléfono', + 'tel2' => 'Teléfono Alternativo', + 'priority' => 'Prioridad', + 'status' => 'Estatus', + 'created_at' => 'Fecha de Creación', + 'updated_at' => 'Última Actualización', + ]; + } + + /** + * Define los formatos de cada columna (se inyectará en $bt_datatable['format']). + * + * @return array + */ + protected function format(): array + { + return [ + 'action' => [ + 'formatter' => 'warehouseActionFormatter', + 'onlyFormatter' => true, + ], + 'store_code' => [ + 'formatter' => [ + 'name' => 'dynamicBadgeFormatter', + 'params' => ['color' => 'secondary'] + ], + 'align' => 'center', + 'visible' => false, + ], + 'work_center_code' => [ + 'formatter' => [ + 'name' => 'dynamicBadgeFormatter', + 'params' => ['color' => 'secondary'] + ], + 'align' => 'center', + 'visible' => false, + ], + 'work_center_name' => [ + 'visible' => false, + ], + 'code' => [ + 'formatter' => [ + 'name' => 'dynamicBadgeFormatter', + 'params' => ['color' => 'secondary'], + ], + 'align' => 'center', + 'switchable' => false, + ], + 'name' => [ + 'switchable' => false, + ], + 'description' => [ + 'visible' => false, + ], + 'manager_name' => [ + 'formatter' => 'managerFormatter', + ], + 'tel' => [ + 'formatter' => 'telFormatter', + 'align' => 'center', + ], + 'tel2' => [ + 'formatter' => 'telFormatter', + 'align' => 'center', + 'visible' => false, + ], + 'pais' => [ + 'align' => 'center', + 'visible' => false, + ], + 'estado' => [ + 'formatter' => 'textNowrapFormatter', + 'visible' => false, + ], + 'localidad' => [ + 'formatter' => 'textNowrapFormatter', + 'visible' => false, + ], + 'municipio' => [ + 'formatter' => 'textNowrapFormatter', + 'visible' => false, + ], + 'codigo_postal' => [ + 'align' => 'center', + 'visible' => false, + ], + 'colonia' => [ + 'formatter' => 'textNowrapFormatter', + 'visible' => false, + ], + 'direccion' => [ + 'formatter' => 'direccionFormatter', + 'visible' => false, + ], + 'priority' => [ + 'formatter' => 'numberFormatter', + 'align' => 'center', + ], + 'status' => [ + 'formatter' => [ + 'name' => 'dynamicBooleanFormatter', + 'params' => ['tag' => 'activo'] + ], + 'align' => 'center', + ], + 'created_at' => [ + 'formatter' => 'textNowrapFormatter', + 'align' => 'center', + 'visible' => false, + ], + 'updated_at' => [ + 'formatter' => 'textNowrapFormatter', + 'align' => 'center', + 'visible' => false, + ], + ]; + } + + /** + * Retorna la configuración base (común) para la tabla Bootstrap Table. + * + * @return array + */ + protected function bootstraptableConfig(): array + { + return [ + 'sortName' => 'code', + 'exportFileName' => 'Almacenes', + 'showFullscreen' => false, + 'showPaginationSwitch' => false, + 'showRefresh' => false, + 'pagination' => false, + ]; + } + + /** + * Retorna la ruta de la vista Blade. + * + * @return string + */ + protected function viewPath(): string + { + // La vista que ya tienes creada para WarehouseIndex + return 'vuexy-warehouse::livewire.warehouses.index'; + } + + /** + * Métodos que necesites sobreescribir o extender. + */ + public function mount(): void + { + parent::mount(); + + // Cargar opciones de tienda, por ejemplo: + $storeCatalogService = app(StoreCatalogService::class); + $this->storeOptions = $storeCatalogService->searchCatalog('stores', '', ['limit' => -1]); + } + + /** + * Puedes agregar lógica de filtrado específica para warehouses, + * si lo requieres, sobrescribiendo el método applyFilters del padre. + */ + protected function applyFilters($criteria = []) + { + $query = parent::applyFilters($criteria); + + // Ejemplo de aplicar filtro por store_id + if ($this->store_id) { + $query->where('store_id', $this->store_id); + } + + return $query; + } +} diff --git a/Livewire/Warehouses/WarehouseOffcanvasForm.php b/Livewire/Warehouses/WarehouseOffcanvasForm.php new file mode 100644 index 0000000..d4156cc --- /dev/null +++ b/Livewire/Warehouses/WarehouseOffcanvasForm.php @@ -0,0 +1,219 @@ + 'loadFormModel', + 'confirmDeletionWarehouse' => 'loadFormModelForDeletion', + ]; + + /** + * Definición de tipos de datos que se deben castear. + * + * @var array + */ + protected $casts = [ + 'status' => 'boolean', + ]; + + /** + * Define el modelo Eloquent asociado con el formulario. + * + * @return string + */ + protected function model(): string + { + return Warehouse::class; + } + + /** + * Define los campos del formulario. + * + * @return array + */ + protected function fields(): array + { + return (new Warehouse())->getFillable(); + } + + /** + * Valores por defecto para el formulario. + * + * @return array + */ + protected function defaults(): array + { + return [ + 'priority' => 0, + 'status' => true, + ]; + } + + /** + * Campo que se debe enfocar cuando se abra el formulario. + * + * @return string + */ + protected function focusOnOpen(): string + { + return 'code'; + } + + /** + * Define reglas de validación dinámicas basadas en el modo actual. + * + * @param string $mode El modo actual del formulario ('create', 'edit', 'delete'). + * @return array + */ + protected function dynamicRules(string $mode): array + { + switch ($mode) { + case 'create': + case 'edit': + return [ + 'store_id' => ['required', 'integer', 'exists:stores,id'], + 'work_center_id' => ['nullable', 'integer', 'exists:store_work_centers,id'], + 'code' => ['required', 'string', 'max:16', Rule::unique('warehouses', 'code')->ignore($this->id)], + 'name' => ['required', 'string', 'max:96'], + 'description' => ['nullable', 'string', 'max:1024'], + 'manager_id' => ['nullable', 'integer', 'exists:users,id'], + 'tel' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'tel2' => ['nullable', 'regex:/^[0-9+\-\s]+$/', 'max:20'], + 'priority' => ['nullable', 'numeric', 'between:0,99'], + 'status' => ['nullable', 'boolean'], + ]; + + case 'delete': + return [ + 'confirmDeletion' => 'accepted', // Asegura que el usuario confirme la eliminación + ]; + + default: + return []; + } + } + + // ===================== VALIDACIONES ===================== + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + protected function attributes(): array + { + return [ + 'code' => 'código de almacén', + 'name' => 'nombre del almacén', + ]; + } + + /** + * Get the error messages for the defined validation rules. + * + * @return array + */ + protected function messages(): array + { + return [ + 'store_id.required' => 'El almacén debe estar asociado a un negocio.', + 'code.required' => 'El código del almacén es obligatorio.', + 'code.unique' => 'Este código ya está en uso por otro almacén.', + 'name.required' => 'El nombre del almacén es obligatorio.', + ]; + } + + /** + * Carga el formulario con datos del almacén y actualiza las opciones dinámicas. + * + * @param int $id + */ + public function loadFormModel($id): void + { + parent::loadFormModel($id); + + $this->work_center_options = $this->store_id + ? DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray() + : []; + } + + /** + * Carga el formulario para eliminar un almacén, actualizando las opciones necesarias. + * + * @param int $id + */ + public function loadFormModelForDeletion($id): void + { + parent::loadFormModelForDeletion($id); + + $this->work_center_options = DB::table('store_work_centers') + ->where('store_id', $this->store_id) + ->pluck('name', 'id') + ->toArray(); + } + + /** + * Define las opciones de los selectores desplegables. + * + * @return array + */ + protected function options(): array + { + $storeCatalogService = app(StoreCatalogService::class); + $contactCatalogService = app(ContactCatalogService::class); + + return [ + 'store_options' => $storeCatalogService->searchCatalog('stores', '', ['limit' => -1]), + 'manager_options' => $contactCatalogService->searchCatalog('users', '', ['limit' => -1]), + ]; + } + + /** + * Ruta de la vista asociada con este formulario. + * + * @return string + */ + protected function viewPath(): string + { + return 'vuexy-warehouse::livewire.warehouses.form-offcanvas'; + } +} diff --git a/Models/Currency.php b/Models/Currency.php new file mode 100644 index 0000000..96dcc6e --- /dev/null +++ b/Models/Currency.php @@ -0,0 +1,19 @@ + 'decimal:6', + 'status' => 'boolean' + ]; +} diff --git a/Models/FixedAsset.php b/Models/FixedAsset.php new file mode 100644 index 0000000..446a134 --- /dev/null +++ b/Models/FixedAsset.php @@ -0,0 +1,20 @@ + 'date', + 'value' => 'decimal:2', + 'status' => 'boolean' + ]; +} diff --git a/Models/InventoryMovement.php b/Models/InventoryMovement.php new file mode 100644 index 0000000..e0c4cf8 --- /dev/null +++ b/Models/InventoryMovement.php @@ -0,0 +1,84 @@ + 'decimal:6', + 'cost' => 'decimal:2', + 'cost_before' => 'decimal:2', + 'cost_after' => 'decimal:2', + 'cost_type' => 'string', + ]; + + /** + * Relación con el producto. + */ + public function product(): BelongsTo + { + return $this->belongsTo(Product::class, 'product_id'); + } + + /** + * Relación con la sucursal. + */ + public function store(): BelongsTo + { + return $this->belongsTo(Store::class, 'store_id'); + } + + /** + * Relación con el almacén. + */ + public function warehouse(): BelongsTo + { + return $this->belongsTo(Warehouse::class, 'warehouse_id'); + } + + /** + * Relación con el usuario que creó el movimiento. + */ + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * Relación polimórfica para vincular diferentes transacciones. + */ + public function transactionable(): MorphTo + { + return $this->morphTo(); + } +} diff --git a/Models/InventoryStockLevel.php b/Models/InventoryStockLevel.php new file mode 100644 index 0000000..56a5939 --- /dev/null +++ b/Models/InventoryStockLevel.php @@ -0,0 +1,62 @@ + 'decimal:6', + 'pos_stock' => 'decimal:6', + 'ecommerce_stock' => 'decimal:6', + 'purchase_reserved_stock' => 'decimal:6', + 'asset_stock' => 'decimal:6', + 'last_cost' => 'decimal:2', + 'average_cost' => 'decimal:2', + 'total_last_cost' => 'decimal:2', + 'total_average_cost' => 'decimal:2', + 'total_identified_cost' => 'decimal:2', + 'costing_method' => 'integer', + ]; + + /** + * Verificar que el stock en áreas no supere el stock total + */ + public static function boot() + { + parent::boot(); + + static::saving(function ($inventory) { + $totalReserved = $inventory->pos_stock + $inventory->ecommerce_stock + $inventory->purchase_reserved_stock + $inventory->asset_stock; + if ($totalReserved > $inventory->quantity) { + throw new \Exception("Error: El stock reservado no puede ser mayor al stock total."); + } + }); + } + + public function product() + { + return $this->belongsTo(Product::class, 'product_id'); + } + + public function store(): BelongsTo + { + return $this->belongsTo(Store::class, 'store_id'); + } + + public function warehouse() + { + return $this->belongsTo(Warehouse::class, 'warehouse_id'); + } +} diff --git a/Models/LotNumber.php b/Models/LotNumber.php new file mode 100644 index 0000000..b697e3b --- /dev/null +++ b/Models/LotNumber.php @@ -0,0 +1,62 @@ + 'date', + 'expiry_date' => 'date', + 'initial_quantity' => 'decimal:6', + 'remaining_quantity' => 'decimal:6', + 'cost' => 'decimal:2', + ]; + + /** + * Relación con el producto. + */ + public function product(): BelongsTo + { + return $this->belongsTo(Product::class, 'product_id'); + } + + /** + * Relación con la sucursal. + */ + public function store(): BelongsTo + { + return $this->belongsTo(Store::class, 'store_id'); + } + + /** + * Relación con el almacén. + */ + public function warehouse(): BelongsTo + { + return $this->belongsTo(Warehouse::class, 'warehouse_id'); + } +} diff --git a/Models/Product.php b/Models/Product.php new file mode 100644 index 0000000..7a312d4 --- /dev/null +++ b/Models/Product.php @@ -0,0 +1,134 @@ + 'boolean', + 'available_in_ecommerce' => 'boolean', + 'available_in_purchases' => 'boolean', + 'available_in_maanufacturing' => 'boolean', + 'available_in_quality' => 'boolean', + 'available_in_assets' => 'boolean', + 'costo' => 'decimal:2', + 'traslados' => 'decimal:6', + 'retenciones' => 'decimal:6', + 'minimum_unit' => 'decimal:6', + 'minimum_stock' => 'decimal:6', + 'maximum_stock' => 'decimal:6', + 'data_lot_enable' => 'boolean', + 'data_lot_require' => 'boolean', + 'data_series_enable' => 'boolean', + 'data_series_require' => 'boolean', + 'data_expiration_enable' => 'boolean', + 'data_expiration_require' => 'boolean', + 'data_warranty_enable' => 'boolean', + 'data_best_before_enable' => 'boolean', + 'data_best_before_require' => 'boolean', + 'data_observations_enable' => 'boolean', + 'affects_inventory' => 'boolean', + 'alert_minimum_stock' => 'boolean', + 'alert_maximum_stock' => 'boolean', + ]; + + /** + * Relación con la categoría del producto. + */ + public function category(): BelongsTo + { + return $this->belongsTo(ProductCategory::class, 'category_id'); + } + + /** + * Relación con la moneda. + */ + public function currency(): BelongsTo + { + return $this->belongsTo(Currency::class, 'c_moneda', 'c_currency'); + } + + /** + * Relación con la clave unidad SAT. + */ + public function claveUnidad(): BelongsTo + { + return $this->belongsTo(ClaveUnidad::class, 'c_clave_unidad', 'c_clave_unidad'); + } + + /** + * Relación con la clave de producto/servicio SAT. + */ + public function claveProdServ(): BelongsTo + { + return $this->belongsTo(ClaveProdServ::class, 'c_clave_prod_serv', 'c_clave_prod_serv'); + } + + /** + * Relación con el usuario que creó el producto. + */ + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } +} diff --git a/Models/ProductCategory.php b/Models/ProductCategory.php new file mode 100644 index 0000000..70cc7e7 --- /dev/null +++ b/Models/ProductCategory.php @@ -0,0 +1,68 @@ + 'boolean', + 'show_in_purchases' => 'boolean', + 'show_in_ecommerce' => 'boolean', + 'show_in_manufacturing' => 'boolean', + 'show_in_quality' => 'boolean', + 'show_in_assets' => 'boolean', + 'order' => 'integer', + ]; + + /** + * Relación con la categoría padre. + */ + public function parent(): BelongsTo + { + return $this->belongsTo(ProductCategory::class, 'parent_id'); + } + + /** + * Relación con categorías hijas. + */ + public function children(): HasMany + { + return $this->hasMany(ProductCategory::class, 'parent_id'); + } + + /** + * Relación con productos. + */ + public function products(): HasMany + { + return $this->hasMany(Product::class, 'category_id'); + } +} diff --git a/Models/ProductProperty.php b/Models/ProductProperty.php new file mode 100644 index 0000000..9b57fea --- /dev/null +++ b/Models/ProductProperty.php @@ -0,0 +1,29 @@ +hasMany(ProductPropertyValue::class, 'property_id'); + } +} diff --git a/Models/ProductPropertyValue.php b/Models/ProductPropertyValue.php new file mode 100644 index 0000000..b7174aa --- /dev/null +++ b/Models/ProductPropertyValue.php @@ -0,0 +1,39 @@ +belongsTo(Product::class, 'product_id'); + } + + /** + * Relación con la propiedad del producto. + */ + public function property(): BelongsTo + { + return $this->belongsTo(ProductProperty::class, 'property_id'); + } +} diff --git a/Models/Warehouse.php b/Models/Warehouse.php new file mode 100644 index 0000000..74c7014 --- /dev/null +++ b/Models/Warehouse.php @@ -0,0 +1,94 @@ + 'integer', + 'status' => 'boolean', + ]; + + /** + * Nombre de la etiqueta para generar Componentes + * + * @var string + */ + public $tagName = 'Warehouse'; + + /** + * Nombre de la columna que contiee el nombre del registro + * + * @var string + */ + public $columnNameLabel = 'name'; + + /** + * Nombre singular del registro. + * + * @var string + */ + public $singularName = 'almacén'; + + /** + * Nombre plural del registro. + * + * @var string + */ + public $pluralName = 'almacenes'; + + /** + * Relación con la sucursal a la que pertenece el almacén. + */ + public function store(): BelongsTo + { + return $this->belongsTo(Store::class, 'store_id'); + } + + /** + * Relación con el usuario que gestiona el centro de trabajo. + */ + public function manager(): BelongsTo + { + return $this->belongsTo(User::class, 'manager_id'); + } + + /** + * Relación con la sucursal a la que pertenece el almacén. + */ + public function workcenter(): BelongsTo + { + return $this->belongsTo(StoreWorkCenter::class, 'work_center_id'); + } + + /** + * Relación con los movimientos de inventario del almacén. + */ + public function movements(): HasMany + { + return $this->hasMany(WarehouseMovement::class, 'warehouse_id'); + } +} diff --git a/Models/WarehouseMovement.php b/Models/WarehouseMovement.php new file mode 100644 index 0000000..a83e513 --- /dev/null +++ b/Models/WarehouseMovement.php @@ -0,0 +1,80 @@ + 'decimal:6', + 'cost' => 'decimal:2', + 'status' => 'integer', + ]; + + /** + * Relación con la sucursal donde ocurrió el movimiento. + */ + public function store(): BelongsTo + { + return $this->belongsTo(Store::class, 'store_id'); + } + + /** + * Relación con el almacén de origen del movimiento. + */ + public function warehouse(): BelongsTo + { + return $this->belongsTo(Warehouse::class, 'warehouse_id'); + } + + /** + * Relación con el almacén de destino (si aplica). + */ + public function toWarehouse(): BelongsTo + { + return $this->belongsTo(Warehouse::class, 'to_warehouse_id'); + } + + /** + * Relación con el usuario que creó el movimiento. + */ + public function createdBy(): BelongsTo + { + return $this->belongsTo(User::class, 'created_by'); + } + + /** + * Relación con el usuario que aprobó el movimiento (si aplica). + */ + public function approvedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'approved_by'); + } +} diff --git a/Providers/VuexyWarehouseServiceProvider.php b/Providers/VuexyWarehouseServiceProvider.php new file mode 100644 index 0000000..d9876f9 --- /dev/null +++ b/Providers/VuexyWarehouseServiceProvider.php @@ -0,0 +1,63 @@ +loadRoutesFrom(__DIR__.'/../routes/admin.php'); + + + // Cargar vistas del paquete + $this->loadViewsFrom(__DIR__.'/../resources/views', 'vuexy-warehouse'); + + + // Register the migrations + $this->loadMigrationsFrom(__DIR__ . '/../database/migrations'); + + + // Registrar Livewire Components + $components = [ + 'warehouse-index' => WarehouseIndex::class, + 'warehouse-offcanvas-form' => WarehouseOffcanvasForm::class, + 'product-index' => ProductIndex::class, + 'product-form' => ProductForm::class, + 'product-property-index' => ProductPropertyIndex::class, + 'product-property-offcanvas-form' => ProductPropertyOffcanvasForm::class, + 'product-category-index' => ProductCategoryIndex::class, + 'product-receipts-index' => ProductReceiptsIndex::class, + 'product-receipts-form' => ProductReceiptsForm::class, + 'inventory-index' => InventoryIndex::class, + 'inventory-movements-index' => InventoryMovementsIndex::class, + 'inventory-movements-form' => InventoryMovementsForm::class, + 'warehouse-transfer-index' => WarehouseTransferIndex::class, + 'warehouse-transfer-form' => WarehouseTransferForm::class, + ]; + + foreach ($components as $alias => $component) { + Livewire::component($alias, $component); + } + + + // Registrar auditoría en usuarios + //User::observe(AuditableObserver::class); + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..aaafd8e --- /dev/null +++ b/README.md @@ -0,0 +1,133 @@ +# 🎨 Laravel Vuexy Warehouse - Vuexy Admin + +

+ Koneko Soluciones Tecnológicas Logo +

+

+ Sitio Web + Latest Stable Version + License + Servidor Git + Build Status + Issues +

+ +--- + +## 📌 Descripción + +**Laravel Vuexy Warehouse** es un módulo diseñado para **Laravel Vuexy Admin**, proporcionando [breve descripción de la funcionalidad]. + +### ✨ Características: +- 🔹 Integración completa con Vuexy Admin. +- 🔹 Funcionalidad clave 1. +- 🔹 Funcionalidad clave 2. + +--- + +## 📦 Instalación + +Instalar vía **Composer**: + +```bash +composer require koneko/laravel-vuexy-warehouse +``` + +Publicar archivos de configuración y migraciones (si aplica): + +```bash +php artisan vendor:publish --tag=laravel-vuexy-warehouse-config +php artisan migrate +``` + +--- + +## 🚀 Uso básico + +```php +use Koneko\NombreLibreria\Models\Model; + +$model = Model::create([ + 'campo1' => 'Valor', + 'campo2' => 'Otro valor', +]); +``` + +--- + +## 📚 Configuración adicional + +Si necesitas personalizar la configuración del módulo, publica el archivo de configuración: + +```bash +php artisan vendor:publish --tag=laravel-vuexy-warehouse-config +``` + +Esto generará `config/nombre_libreria.php`, donde puedes modificar valores predeterminados. + +--- + +## 🛠 Dependencias + +Este paquete requiere las siguientes dependencias: +- Laravel 11 +- `koneko/laravel-vuexy-warehouse` +- Dependencias específicas de la librería + +--- + +## 📦 Publicación de Assets y Configuraciones + +Para publicar configuraciones y seeders: + +```bash +php artisan vendor:publish --tag=laravel-vuexy-warehouse-config +php artisan vendor:publish --tag=laravel-vuexy-warehouse-seeders +php artisan migrate --seed +``` + +Para publicar imágenes del tema: + +```bash +php artisan vendor:publish --tag=laravel-vuexy-warehouse-images +``` + +--- + +## 🛠 Pruebas + +Ejecuta los tests con: + +```bash +php artisan test +``` + +--- + +## 🌍 Repositorio Principal y Sincronización + +Este repositorio es una **copia sincronizada** del repositorio principal alojado en **[Tea - Koneko Git](https://git.koneko.mx/koneko/laravel-vuexy-warehouse)**. + +### 🔄 Sincronización con GitHub +- **Repositorio Principal:** [git.koneko.mx](https://git.koneko.mx/koneko/laravel-vuexy-warehouse) +- **Repositorio en GitHub:** [github.com/koneko/laravel-vuexy-warehouse](https://github.com/koneko/laravel-vuexy-warehouse) +- **Los cambios pueden reflejarse primero en Tea antes de GitHub.** + +### 🤝 Contribuciones +Si deseas contribuir: +1. Puedes abrir un **Issue** en [GitHub Issues](https://github.com/koneko/laravel-vuexy-warehouse/issues). +2. Para Pull Requests, **preferimos contribuciones en Tea**. Contacta a `admin@koneko.mx` para solicitar acceso. + +⚠️ **Nota:** Algunos cambios pueden tardar en reflejarse en GitHub, ya que este repositorio se actualiza automáticamente desde Tea. + +--- + +## 🏅 Licencia + +Este paquete es de código abierto bajo la licencia [MIT](LICENSE). + +--- + +

+ Hecho con ❤️ por Koneko Soluciones Tecnológicas +

diff --git a/Services/WarehouseCatalogService.php b/Services/WarehouseCatalogService.php new file mode 100644 index 0000000..e6075a2 --- /dev/null +++ b/Services/WarehouseCatalogService.php @@ -0,0 +1,98 @@ + [ + 'table' => 'warehouses', + 'key' => 'id', + 'value' => "CONCAT(code, ' - ', name) as item", + 'search_columns' => ['code', 'name'], + 'order_by' => 'name', + 'limit' => 15, + ], + 'store_warehouses' => [ + 'table' => 'warehouses', + 'key' => 'id', + 'value' => "CONCAT(code, ' - ', name) as item", + 'search_columns' => ['code', 'name'], + 'extra_conditions' => ['store_id'], + 'order_by' => 'name', + 'limit' => 15, + ], + 'product_categories' => [ + 'table' => 'product_categories', + 'key' => 'id', + 'value' => "CONCAT(name, ' - ', slug) as item", + 'search_columns' => ['name', 'slug'], + 'order_by' => 'name', + 'limit' => 20, + ], + 'products' => [ + 'table' => 'products', + 'key' => 'id', + 'value' => "CONCAT(no_identificacion, ' - ', descripcion) as item", + 'search_columns' => ['no_identificacion', 'descripcion'], + 'order_by' => 'descripcion', + 'limit' => 20, + ], + 'inventory_levels' => [ + 'table' => 'inventory_stock_levels', + 'key' => 'id', + 'value' => "CONCAT(product_id, ' - ', quantity) as item", + 'search_columns' => ['product_id', 'quantity'], + 'extra_conditions' => ['warehouse_id'], + 'order_by' => 'quantity', + 'limit' => 10, + ], + ]; + + /** + * Busca registros en los catálogos del módulo de almacenes. + * + * @param string $catalog + * @param string|null $searchTerm + * @param array $options + * @return array + */ + public function searchCatalog(string $catalog, ?string $searchTerm = '', array $options = []): array + { + if (!isset($this->catalogs[$catalog])) { + return []; + } + + $config = $this->catalogs[$catalog]; + $query = DB::table($config['table']); + + // Selección de columnas + $query->selectRaw("{$config['key']}, {$config['value']}"); + + // Aplicar condiciones extra + if (!empty($config['extra_conditions'])) { + foreach ($config['extra_conditions'] as $column => $value) { + if (isset($options[$column])) { + $query->where($column, $options[$column]); + } + } + } + + // Búsqueda + if ($searchTerm) { + $query->where(function ($subQ) use ($config, $searchTerm) { + foreach ($config['search_columns'] as $column) { + $subQ->orWhere($column, 'LIKE', "%{$searchTerm}%"); + } + }); + } + + // Orden y límite + $query->orderBy($config['order_by'] ?? $config['key'], 'asc'); + $query->limit($options['limit'] ?? $config['limit'] ?? 20); + + return $query->pluck('item', $config['key'])->toArray(); + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f753145 --- /dev/null +++ b/composer.json @@ -0,0 +1,36 @@ +{ + "name": "koneko/laravel-vuexy-warehouse", + "description": "Laravel Vuexy Warehouse, un modulo administrativo de almacenes optimizado para México.", + "keywords": ["laravel", "koneko", "framework", "vuexy", "warehouse", "admin", "mexico"], + "type": "library", + "license": "MIT", + "require": { + "php": "^8.2", + "koneko/laravel-vuexy-store-manager": "@dev", + "laravel/framework": "^11.31" + }, + "autoload": { + "psr-4": { + "Koneko\\VuexyWarehouse\\": "" + } + }, + "extra": { + "laravel": { + "providers": [ + "Koneko\\VuexyWarehouse\\Providers\\VuexyWarehouseServiceProvider" + ] + } + }, + "authors": [ + { + "name": "Arturo Corro Pacheco", + "email": "arturo@koneko.mx" + } + ], + "support": { + "source": "https://github.com/koneko-mx/laravel-vuexy-warehouse", + "issues": "https://github.com/koneko-mx/laravel-vuexy-warehouse/issues" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/database/migrations/2024_12_16_091520_create_product_categories_table.php b/database/migrations/2024_12_16_091520_create_product_categories_table.php new file mode 100644 index 0000000..42d8e1c --- /dev/null +++ b/database/migrations/2024_12_16_091520_create_product_categories_table.php @@ -0,0 +1,84 @@ +mediumIncrements('id'); + + $table->unsignedMediumInteger('parent_id')->nullable()->index(); + $table->string('parent_slug')->nullable()->index(); + + $table->string('name')->index(); + $table->string('slug')->nullable()->index(); + $table->string('icon')->nullable(); + + $table->mediumText('description')->nullable(); + + $table->boolean('show_in_pos')->index(); + $table->boolean('show_in_purchases')->index(); + $table->boolean('show_in_ecommerce')->index(); + $table->boolean('show_in_manufacturing')->index(); + $table->boolean('show_in_quality')->index(); + $table->boolean('show_in_assets')->index(); + + $table->unsignedTinyInteger('priority')->nullable()->index(); + + // Aditoria + $table->timestamps(); + + // Index + $table->unique(['parent_id', 'slug']); + + // Relaciones + //$table->foreign('parent_id')->references('id')->on('product_categories')->onUpdate('restrict')->onDelete('cascade'); + }); + + DB::unprepared("CREATE TRIGGER before_delete_category + BEFORE DELETE ON product_categories + FOR EACH ROW + BEGIN + -- Verificar si la categoría tiene hijos + IF (SELECT COUNT(*) FROM product_categories WHERE parent_id = OLD.id) > 0 THEN + -- Generar un error para evitar la eliminación + SIGNAL SQLSTATE '45000' + SET MESSAGE_TEXT = 'No se puede eliminar la categoría porque tiene categorías hijas.'; + END IF; + END"); + + + Schema::create('category_properties', function (Blueprint $table) { + $table->mediumIncrements('id'); + + $table->unsignedMediumInteger('category_id')->index(); + $table->unsignedSmallInteger('property_id')->index(); + + $table->boolean('is_required'); // Si es obligatorio en esta categoría + $table->boolean('is_filterable'); // Si se puede usar en filtros + + $table->timestamps(); + + // Relaciones + $table->foreign('category_id')->references('id')->on('product_categories')->onUpdate('restrict')->onDelete('cascade'); + //$table->foreign('property_id')->references('id')->on('product_properties')->onUpdate('restrict')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('product_categories'); + DB::unprepared("DROP TRIGGER IF EXISTS before_delete_category"); + Schema::dropIfExists('category_properties'); + } +}; diff --git a/database/migrations/2024_12_16_092473_create_products_tables.php b/database/migrations/2024_12_16_092473_create_products_tables.php new file mode 100644 index 0000000..5843cc1 --- /dev/null +++ b/database/migrations/2024_12_16_092473_create_products_tables.php @@ -0,0 +1,149 @@ +mediumIncrements('id'); + $table->unsignedTinyInteger('type')->index(); + + $table->unsignedMediumInteger('category_id')->index(); // product_categories.id + + $table->string('descripcion')->fulltext(); + $table->mediumText('descripcion_completa')->nullable()->index(); + $table->string('no_identificacion', 40)->nullable()->unique(); + $table->string('slug')->nullable()->unique(); + + $table->boolean('available_in_pos')->index(); + $table->boolean('available_in_purchases')->index(); + $table->boolean('available_in_ecommerce')->index(); + $table->boolean('available_in_maanufacturing')->index(); + $table->boolean('available_in_quality')->index(); + $table->boolean('available_in_assets')->index(); + + $table->string('c_clave_unidad', 3)->nullable()->index(); // sat_clave_unidad. + $table->unsignedInteger('c_clave_prod_serv')->nullable()->index(); // sat_clave_prod_serv. + $table->unsignedBigInteger('ean_code')->nullable()->unique(); + + $table->decimal('costo', 9, 2)->unsigned()->nullable(); + $table->char('c_moneda', 3)->charset('ascii')->collation('ascii_general_ci')->nullable()->index(); + $table->unsignedTinyInteger('c_objeto_imp')->nullable()->index(); // sat_objeto_imp. + $table->json('impuestos')->nullable(); + $table->decimal('traslados', 9, 6)->unsigned()->nullable(); + $table->decimal('retenciones', 9, 6)->unsigned()->nullable(); + + $table->unsignedTinyInteger('data_lot_enable')->nullable()->index(); + $table->unsignedTinyInteger('data_lot_require')->nullable()->index(); + $table->unsignedTinyInteger('data_series_enable')->nullable()->index(); + $table->unsignedTinyInteger('data_series_require')->nullable()->index(); + $table->unsignedTinyInteger('data_expiration_enable')->nullable()->index(); + $table->unsignedTinyInteger('data_expiration_require')->nullable()->index(); + $table->unsignedTinyInteger('data_warranty_enable')->nullable()->index(); + $table->unsignedMediumInteger('warranty')->nullable()->index(); + $table->unsignedTinyInteger('data_best_before_enable')->nullable()->index(); + $table->unsignedTinyInteger('data_best_before_require')->nullable()->index(); + $table->unsignedTinyInteger('data_observations_enable')->nullable()->index(); + + $table->decimal('minimum_unit', 7, 6)->unsigned()->nullable(); // Decimales soportados en la Unidad de Medida + $table->unsignedTinyInteger('affects_inventory')->nullable()->index(); + + $table->unsignedTinyInteger('status')->index(); + + // Auditoría + $table->unsignedMediumInteger('created_by')->nullable()->index(); // users.id + + $table->timestamps(); + + // Relaciones + $table->foreign('category_id')->references('id')->on('product_categories')->onUpdate('restrict')->onDelete('restrict'); + $table->foreign('c_moneda')->references('c_moneda')->on('sat_moneda')->onUpdate('restrict')->onDelete('restrict'); + $table->foreign('c_clave_unidad')->references('c_clave_unidad')->on('sat_clave_unidad')->onUpdate('restrict')->onDelete('restrict'); + $table->foreign('c_clave_prod_serv')->references('c_clave_prod_serv')->on('sat_clave_prod_serv')->onUpdate('restrict')->onDelete('restrict'); + $table->foreign('created_by')->references('id')->on('users')->onUpdate('restrict')->onDelete('restrict'); + }); + + Schema::create('product_properties', function (Blueprint $table) { + $table->smallIncrements('id'); + + $table->string('name')->index(); // Ej: "Marca", "Potencia", "Color" + $table->enum('type', ['text', 'number', 'boolean', 'select'])->default('text'); + + $table->timestamps(); + }); + + Schema::create('product_store_prices', function (Blueprint $table) { + $table->mediumIncrements('id'); + + $table->unsignedMediumInteger('product_id')->index(); + $table->unsignedSmallInteger('store_id')->index(); + + $table->decimal('price', 9, 2)->unsigned()->default(0); + $table->char('currency', 3)->charset('ascii')->collation('ascii_general_ci'); + + $table->boolean('is_discounted')->index(); + $table->decimal('discount_price', 9, 2)->unsigned()->nullable(); + $table->date('discount_start')->nullable(); + $table->date('discount_end')->nullable(); + + // Auditoría + $table->timestamps(); + + // Relaciones + $table->foreign('product_id')->references('id')->on('products')->onUpdate('restrict')->onDelete('cascade'); + $table->foreign('store_id')->references('id')->on('stores')->onUpdate('restrict')->onDelete('cascade'); + }); + + Schema::create('product_property', function (Blueprint $table) { + $table->mediumIncrements('id'); + + $table->unsignedMediumInteger('product_id')->index(); + $table->unsignedSmallInteger('property_id')->index(); + + $table->string('value')->index(); // Valor de la propiedad, ej: 'Intel', 'DDR4', '500W' + + // Auditoria + $table->timestamps(); + + // Relaciones + $table->foreign('product_id')->references('id')->on('products')->onUpdate('restrict')->onDelete('cascade'); + $table->foreign('property_id')->references('id')->on('product_properties')->onUpdate('restrict')->onDelete('cascade'); + }); + + Schema::create('product_property_values', function (Blueprint $table) { + $table->mediumIncrements('id'); + + $table->unsignedMediumInteger('product_id')->index(); + $table->unsignedSmallInteger('property_id')->index(); + + $table->string('value_text')->nullable()->index(); + $table->decimal('value_number', 9, 2)->nullable(); + $table->boolean('value_boolean')->nullable(); + + $table->timestamps(); + + // Relaciones + $table->foreign('product_id')->references('id')->on('products')->onUpdate('restrict')->onDelete('cascade'); + $table->foreign('property_id')->references('id')->on('product_properties')->onUpdate('restrict')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('products'); + Schema::dropIfExists('product_properties'); + Schema::dropIfExists('product_store_prices'); + Schema::dropIfExists('product_property'); + Schema::dropIfExists('product_property_values'); + } +}; diff --git a/database/migrations/2024_12_16_111823_create_warehouse_tables.php b/database/migrations/2024_12_16_111823_create_warehouse_tables.php new file mode 100644 index 0000000..0d84ae3 --- /dev/null +++ b/database/migrations/2024_12_16_111823_create_warehouse_tables.php @@ -0,0 +1,193 @@ +smallIncrements('id'); + + $table->unsignedSmallInteger('store_id')->index(); // Relación con sucursal + $table->unsignedSmallInteger('work_center_id')->nullable()->index(); + + $table->string('code', 16)->unique(); + $table->string('name', 96)->index(); + + $table->mediumText('description')->nullable(); + + $table->unsignedMediumInteger('manager_id')->nullable()->index(); // sat_codigo_postal. + + $table->string('tel')->nullable(); + $table->string('tel2')->nullable(); + + $table->unsignedTinyInteger('priority')->nullable(); + + $table->boolean('status')->default(true)->index(); + + $table->timestamps(); + + // Indices + $table->unique(['store_id', 'name']); + + $table->foreign('store_id')->references('id')->on('stores')->onDelete('cascade'); + $table->foreign('work_center_id')->references('id')->on('store_work_centers')->onDelete('cascade'); + $table->foreign('manager_id')->references('id')->on('users')->onUpdate('restrict')->onDelete('restrict'); + }); + + Schema::create('warehouse_movements', function (Blueprint $table) { + $table->mediumIncrements('id'); + + $table->unsignedSmallInteger('store_id')->index(); + $table->unsignedSmallInteger('warehouse_id')->index(); // Almacén involucrado + + // Tipo de ajuste (ajuste de inventario o traspaso) + $table->unsignedTinyInteger('movement_type')->index(); // Tipo de movimiento: ajuste o traspaso + $table->unsignedMediumInteger('movement_id')->nullable(); // UID específico por sucursal + + $table->unsignedMediumInteger('created_by')->index(); // Usuario que registró el movimiento + $table->unsignedMediumInteger('approved_by')->nullable()->index(); // Usuario que autorizó el movimiento + + // Cantidad ajustada + $table->decimal('adjusted_quantity', 13, 6); // Cantidad ajustada para el ajuste o transferencia + + // Costo asociado + $table->decimal('cost', 9, 2)->unsigned()->default(0); // Costo de la operación + + // Notas sobre el ajuste + $table->mediumText('notes')->nullable(); // Notas adicionales + + // Campos para el traspaso + $table->unsignedSmallInteger('to_warehouse_id')->nullable()->index(); // Almacén de destino (solo para traspasos) + + $table->unsignedTinyInteger('status')->index(); // Estatus de la orden ('pending', 'approved', 'received', 'cancelled') + + $table->softDeletes(); + $table->timestamps(); + + + // Claves foráneas + $table->foreign('store_id')->references('id')->on('stores')->onDelete('restrict'); + $table->foreign('warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); + $table->foreign('to_warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); + $table->foreign('created_by')->references('id')->on('users')->onDelete('restrict'); + $table->foreign('approved_by')->references('id')->on('users')->onDelete('restrict'); + + }); + + Schema::create('lot_numbers', function (Blueprint $table) { + $table->mediumIncrements('id'); + + $table->unsignedMediumInteger('product_id')->index(); + $table->unsignedSmallInteger('store_id')->index(); + $table->unsignedSmallInteger('warehouse_id')->index(); + + $table->string('lot_number', 50)->unique()->comment('Número único del lote'); + $table->date('production_date')->nullable(); + $table->date('expiry_date')->nullable(); + $table->decimal('initial_quantity', 13, 6)->unsigned()->default(0); + $table->decimal('remaining_quantity', 13, 6)->unsigned()->default(0); + $table->decimal('cost', 9, 2)->unsigned()->default(0); + + $table->timestamps(); + + $table->foreign('product_id')->references('id')->on('products')->onDelete('restrict'); + $table->foreign('store_id')->references('id')->on('stores')->onDelete('restrict'); + $table->foreign('warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); + }); + + Schema::create('inventory_stock_levels', function (Blueprint $table) { + $table->mediumIncrements('id'); + + $table->unsignedMediumInteger('product_id')->index(); + $table->unsignedSmallInteger('store_id')->nullable()->index(); + $table->unsignedSmallInteger('warehouse_id')->index(); + + $table->decimal('quantity', 13, 6)->unsigned()->comment('Stock total disponible en el almacén'); + + // Stock separado por área + $table->decimal('pos_stock', 13, 6)->unsigned()->default(0)->comment('Stock destinado a ventas en POS'); + $table->decimal('ecommerce_stock', 13, 6)->unsigned()->default(0)->comment('Stock destinado a eCommerce'); + $table->decimal('purchase_reserved_stock', 13, 6)->unsigned()->default(0)->comment('Stock reservado para órdenes de compra'); + $table->decimal('asset_stock', 13, 6)->unsigned()->default(0)->comment('Stock reservado para uso interno'); + + // Alertas de Stock mínimo y máximo + $table->unsignedTinyInteger('alert_minimum_stock')->nullable()->index(); + $table->decimal('minimum_stock', 13, 6)->unsigned()->nullable(); + $table->unsignedTinyInteger('alert_maximum_stock')->nullable()->index(); + $table->decimal('maximum_stock', 13, 6)->unsigned()->nullable(); + + // Costos asociados + $table->decimal('last_cost', 9, 2)->unsigned()->default(0); // Último costo registrado + $table->decimal('average_cost', 9, 2)->unsigned()->default(0); // Costo promedio ponderado + $table->decimal('total_last_cost', 11, 2)->unsigned()->default(0); // Costo total último costo registrado + $table->decimal('total_average_cost', 11, 2)->unsigned()->default(0); // Costo total promedio ponderado + $table->decimal('total_identified_cost', 11, 2)->unsigned()->default(0); // Costo total identificado + + $table->unsignedTinyInteger('costing_method')->index(); // Método de costeo: 'average', 'last', 'identified' + + $table->timestamps(); + + $table->unique(['warehouse_id', 'product_id']); + + $table->foreign('product_id')->references('id')->on('products')->onDelete('restrict'); + $table->foreign('store_id')->references('id')->on('stores')->onDelete('restrict'); + $table->foreign('warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); + }); + + Schema::create('inventory_movements', function (Blueprint $table) { + $table->integerIncrements('id'); + + $table->unsignedMediumInteger('product_id')->index(); + $table->unsignedSmallInteger('store_id')->index(); + $table->unsignedSmallInteger('warehouse_id')->index(); + + $table->enum('movement_type', ['in', 'out'])->index(); // Tipo de movimiento (entrada/salida) + + $table->decimal('quantity', 13, 6)->unsigned(); + + $table->decimal('cost', 9, 2)->unsigned(); // Costo asociado a la transacción + $table->decimal('cost_before', 9, 2)->unsigned()->nullable(); + $table->decimal('cost_after', 9, 2)->unsigned()->nullable(); + $table->enum('cost_type', ['average', 'last', 'specific'])->default('last'); + + $table->text('notes')->nullable(); // Notas sobre el movimiento + + // Define el campo para la relación polimórfica manualmente + $table->unsignedMediumInteger('transactionable_id')->index(); + $table->string('transactionable_type')->index(); + + $table->unsignedMediumInteger('created_by')->index(); // Usuario que registró el movimiento + + $table->timestamps(); + + // Agrega el índice con un nombre específico para evitar el problema de longitud + $table->index(['transactionable_type', 'transactionable_id'], 'inventory_movements_transactionable_index'); + + $table->foreign('product_id')->references('id')->on('products')->onDelete('restrict'); + $table->foreign('store_id')->references('id')->on('stores')->onDelete('restrict'); + $table->foreign('warehouse_id')->references('id')->on('warehouses')->onDelete('restrict'); + $table->foreign('created_by')->references('id')->on('users')->onDelete('restrict'); + }); + + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('warehouses'); + Schema::dropIfExists('warehouse_movements'); + Schema::dropIfExists('lot_numbers'); + Schema::dropIfExists('inventory_stock_levels'); + Schema::dropIfExists('inventory_movements'); + } +}; diff --git a/resources/assets/js/bootstrap-table/warehouseFormatters.js b/resources/assets/js/bootstrap-table/warehouseFormatters.js new file mode 100644 index 0000000..d3df94f --- /dev/null +++ b/resources/assets/js/bootstrap-table/warehouseFormatters.js @@ -0,0 +1,17 @@ +import {routes} from '../../../../../laravel-vuexy-admin/resources/assets/js/bootstrap-table/globalConfig.js'; + +export const warehouseActionFormatter = (value, row, index) => { + if (!row.id) return ''; + + return ` + `.trim(); +}; + + diff --git a/resources/views/inventory-items/index.blade.php b/resources/views/inventory-items/index.blade.php new file mode 100644 index 0000000..32837b4 --- /dev/null +++ b/resources/views/inventory-items/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Almacenes') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('warehouse-index') +@endsection diff --git a/resources/views/inventory-movements/index.blade.php b/resources/views/inventory-movements/index.blade.php new file mode 100644 index 0000000..a5a73db --- /dev/null +++ b/resources/views/inventory-movements/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Movimientos de almacenes') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('inventory-movements-index') +@endsection diff --git a/resources/views/inventory-stock/index.blade.php b/resources/views/inventory-stock/index.blade.php new file mode 100644 index 0000000..58af6ba --- /dev/null +++ b/resources/views/inventory-stock/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Stock de inventario') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('inventory-stock-index') +@endsection diff --git a/resources/views/livewire/inventory-items/inventory-items-index.blade.php b/resources/views/livewire/inventory-items/inventory-items-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/inventory-items/inventory-items-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/inventory-movements/inventory-movements-index.blade.php b/resources/views/livewire/inventory-movements/inventory-movements-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/inventory-movements/inventory-movements-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/inventory-stock/inventory-stock-index.blade.php b/resources/views/livewire/inventory-stock/inventory-stock-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/inventory-stock/inventory-stock-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/materials/materials-index.blade.php b/resources/views/livewire/materials/materials-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/materials/materials-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/product-catalogs/product-catalogs-index.blade.php b/resources/views/livewire/product-catalogs/product-catalogs-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/product-catalogs/product-catalogs-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/product-categories/product-categories-index.blade.php b/resources/views/livewire/product-categories/product-categories-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/product-categories/product-categories-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/product-receipts/product-receipts-index.blade.php b/resources/views/livewire/product-receipts/product-receipts-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/product-receipts/product-receipts-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/products/products-index.blade.php b/resources/views/livewire/products/products-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/products/products-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/warehouse-movements/warehouse-movements-index.blade.php b/resources/views/livewire/warehouse-movements/warehouse-movements-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/warehouse-movements/warehouse-movements-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/warehouse-transfers/warehouse-transfers-index.blade.php b/resources/views/livewire/warehouse-transfers/warehouse-transfers-index.blade.php new file mode 100644 index 0000000..59be0f4 --- /dev/null +++ b/resources/views/livewire/warehouse-transfers/warehouse-transfers-index.blade.php @@ -0,0 +1,516 @@ +
+
+
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/warehouses/form-offcanvas.blade.php b/resources/views/livewire/warehouses/form-offcanvas.blade.php new file mode 100644 index 0000000..627969a --- /dev/null +++ b/resources/views/livewire/warehouses/form-offcanvas.blade.php @@ -0,0 +1,132 @@ +
+ + + + + + {{-- Selección de Sucursal --}} + + + + {{-- Identificación y Configuración --}} +
+ +
+ {{-- Código y Prioridad --}} +
+
+ + +
+ {{-- Detalles del Almacén --}} + + + {{-- Información de Contacto --}} +
+
+ + +
+ {{-- Configuración Adicional --}} +
+ +
+
+
+ +@push('page-script') + +@endpush diff --git a/resources/views/livewire/warehouses/index.blade.php b/resources/views/livewire/warehouses/index.blade.php new file mode 100644 index 0000000..c0c1a3f --- /dev/null +++ b/resources/views/livewire/warehouses/index.blade.php @@ -0,0 +1,12 @@ + + +
+ +
+ @if(count($storeOptions) > 1) +
+ +
+ @endif +
+
diff --git a/resources/views/materials/index.blade.php b/resources/views/materials/index.blade.php new file mode 100644 index 0000000..5732f46 --- /dev/null +++ b/resources/views/materials/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Materiales') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('materials-index') +@endsection diff --git a/resources/views/product-categories/index.blade.php b/resources/views/product-categories/index.blade.php new file mode 100644 index 0000000..af35cd5 --- /dev/null +++ b/resources/views/product-categories/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Categorias de productos') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('product-categories-index') +@endsection diff --git a/resources/views/product-receipts/index.blade.php b/resources/views/product-receipts/index.blade.php new file mode 100644 index 0000000..68acef7 --- /dev/null +++ b/resources/views/product-receipts/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Recepción de productos') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('product-receipts-index') +@endsection diff --git a/resources/views/products/index.blade.php b/resources/views/products/index.blade.php new file mode 100644 index 0000000..1583322 --- /dev/null +++ b/resources/views/products/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Productos') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('products-index') +@endsection diff --git a/resources/views/warehouse-movements/index.blade.php b/resources/views/warehouse-movements/index.blade.php new file mode 100644 index 0000000..78cb4eb --- /dev/null +++ b/resources/views/warehouse-movements/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Mantenimientos de almacenes') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('warehouse-movements-index') +@endsection diff --git a/resources/views/warehouse-transfers/index.blade.php b/resources/views/warehouse-transfers/index.blade.php new file mode 100644 index 0000000..3085ddf --- /dev/null +++ b/resources/views/warehouse-transfers/index.blade.php @@ -0,0 +1,21 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Transferencias de Almacenes') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('warehouse-transfers-index') +@endsection diff --git a/resources/views/warehouses/index.blade.php b/resources/views/warehouses/index.blade.php new file mode 100644 index 0000000..df8f65b --- /dev/null +++ b/resources/views/warehouses/index.blade.php @@ -0,0 +1,24 @@ +@extends('vuexy-admin::layouts.vuexy.layoutMaster') + +@section('title', 'Almacenes') + +@section('vendor-style') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/bootstrap-table/bootstrap-table.scss', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/fonts/bootstrap-icons.scss', + ]) +@endsection + +@push('page-script') + @vite([ + 'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/bootstrap-table/bootstrapTableManager.js', + 'vendor/koneko/laravel-vuexy-admin/resources/assets/js/forms/formConvasHelper.js', + ]) +@endpush + +@section('content') + @livewire('warehouse-index') + @livewire('warehouse-offcanvas-form') +@endsection diff --git a/routes/admin.php b/routes/admin.php new file mode 100644 index 0000000..3c1c421 --- /dev/null +++ b/routes/admin.php @@ -0,0 +1,109 @@ +name('admin.inventory.')->middleware(['web', 'auth', 'admin'])->group(function () { + Route::controller(ProductCategorieController::class)->prefix('categorias')->name('product-categories.')->group(function () { + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'categorie')->name('store'); // Guardar + Route::get('{categorie}', 'show')->name('show'); // Ver + Route::get('{categorie}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{categorie}', 'update')->name('update'); // Actualizar + Route::delete('{categorie}', 'destroy')->name('destroy'); // Eliminar + }); + + Route::controller(ProductCatalogController::class)->prefix('catalogos')->name('product-catalogs.')->group(function () { + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'catalog')->name('store'); // Guardar + Route::get('{catalog}', 'show')->name('show'); // Ver + Route::get('{catalog}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{catalog}', 'update')->name('update'); // Actualizar + Route::delete('{catalog}', 'destroy')->name('destroy'); // Eliminar + }); + + Route::controller(ProductController::class)->prefix('productos')->name('products.')->group(function () { + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'product')->name('store'); // Guardar + Route::get('{product}', 'show')->name('show'); // Ver + Route::get('{product}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{product}', 'update')->name('update'); // Actualizar + Route::delete('{product}', 'destroy')->name('destroy'); // Eliminar + }); +}); + +Route::prefix('admin/inventario-y-logistica')->name('admin.inventory.')->middleware(['web', 'auth', 'admin'])->group(function () { + Route::controller(ProductReceiptController::class)->prefix('recepcion-de-productos')->name('reception.')->group(function () {; + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'reception')->name('store'); // Guardar + Route::get('{reception}', 'show')->name('show'); // Ver + Route::get('{reception}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{reception}', 'update')->name('update'); // Actualizar + Route::delete('{reception}', 'destroy')->name('destroy'); // Eliminar + }); + + Route::controller(MaterialController::class)->prefix('gestion-de-insumos')->name('materials.')->group(function () {; + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'materials')->name('store'); // Guardar + Route::get('{materials}', 'show')->name('show'); // Ver + Route::get('{materials}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{materials}', 'update')->name('update'); // Actualizar + Route::delete('{materials}', 'destroy')->name('destroy'); // Eliminar + }); + + Route::controller(WarehouseController::class)->prefix('almacenes')->name('warehouse.')->group(function () {; + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'warehouse')->name('store'); // Guardar + Route::get('{warehouse}', 'show')->name('show'); // Ver + Route::get('{warehouse}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{warehouse}', 'update')->name('update'); // Actualizar + Route::delete('{warehouse}', 'destroy')->name('destroy'); // Eliminar + }); + + Route::controller(InventoryStockController::class)->prefix('stock-de-inventario')->name('stock.')->group(function () {; + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'stock')->name('store'); // Guardar + Route::get('{stock}', 'show')->name('show'); // Ver + Route::get('{stock}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{stock}', 'update')->name('update'); // Actualizar + Route::delete('{stock}', 'destroy')->name('destroy'); // Eliminar + }); + + Route::controller(InventoryMovementController::class)->prefix('movimientos-de-inventario')->name('movements.')->group(function () {; + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'movements')->name('store'); // Guardar + Route::get('{movements}', 'show')->name('show'); // Ver + Route::get('{movements}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{movements}', 'update')->name('update'); // Actualizar + Route::delete('{movements}', 'destroy')->name('destroy'); // Eliminar + }); + + Route::controller(WarehouseTransferController::class)->prefix('transferencias-entre-almacenes')->name('transfers.')->group(function () {; + Route::get('/', 'index')->name('index'); // Listar + Route::get('create', 'create')->name('create'); // Formulario de creación + Route::post('/', 'transfers')->name('store'); // Guardar + Route::get('{transfers}', 'show')->name('show'); // Ver + Route::get('{transfers}/edit', 'edit')->name('edit'); // Formulario de edición + Route::put('{transfers}', 'update')->name('update'); // Actualizar + Route::delete('{transfers}', 'destroy')->name('destroy'); // Eliminar + }); + +});