first commit
This commit is contained in:
commit
ac40d0f399
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@ -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
|
38
.gitattributes
vendored
Normal file
38
.gitattributes
vendored
Normal file
@ -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
|
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/node_modules
|
||||||
|
/vendor
|
||||||
|
/.vscode
|
||||||
|
/.nova
|
||||||
|
/.fleet
|
||||||
|
/.phpactor.json
|
||||||
|
/.phpunit.cache
|
||||||
|
/.phpunit.result.cache
|
||||||
|
/.zed
|
||||||
|
/.idea
|
16
.prettierignore
Normal file
16
.prettierignore
Normal file
@ -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/
|
29
.prettierrc.json
Normal file
29
.prettierrc.json
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
39
CHANGELOG.md
Normal file
39
CHANGELOG.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# 📜 CHANGELOG - Laravel Vuexy Contacts
|
||||||
|
|
||||||
|
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-contacts)** 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**.
|
||||||
|
|
9
CONTRIBUTING.md
Normal file
9
CONTRIBUTING.md
Normal file
@ -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-contacts/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**.
|
181
Http/Controllers/ContactController.php
Normal file
181
Http/Controllers/ContactController.php
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Koneko\VuexyAdmin\Queries\GenericQueryBuilder;
|
||||||
|
|
||||||
|
class ContactController extends Controller
|
||||||
|
{
|
||||||
|
public function index(Request $request)
|
||||||
|
{
|
||||||
|
if ($request->ajax()) {
|
||||||
|
$bootstrapTableIndexConfig = [
|
||||||
|
'table' => 'users',
|
||||||
|
'columns' => [
|
||||||
|
'users.id',
|
||||||
|
'users.code',
|
||||||
|
DB::raw("CONCAT_WS(' ', users.name, users.last_name) AS full_name"),
|
||||||
|
'users.email',
|
||||||
|
DB::raw("CONCAT_WS(' ', parent.name, parent.last_name) AS parent_name"),
|
||||||
|
'parent.email AS parent_email',
|
||||||
|
DB::raw("CONCAT_WS(' ', agent.name, agent.last_name) AS agent_name"),
|
||||||
|
'agent.email AS agent_email',
|
||||||
|
'users.company',
|
||||||
|
'users.birth_date',
|
||||||
|
'users.hire_date',
|
||||||
|
'users.curp',
|
||||||
|
'users.nss',
|
||||||
|
'users.job_title',
|
||||||
|
'users.rfc',
|
||||||
|
'users.nombre_fiscal',
|
||||||
|
'users.tipo_persona',
|
||||||
|
'users.c_regimen_fiscal',
|
||||||
|
'sat_regimen_fiscal.descripcion AS regimen_fiscal',
|
||||||
|
'users.domicilio_fiscal',
|
||||||
|
'users.c_uso_cfdi',
|
||||||
|
'sat_uso_cfdi.descripcion AS uso_cfdi',
|
||||||
|
'sat_estado.nombre_del_estado AS estado',
|
||||||
|
'sat_municipio.descripcion AS municipio',
|
||||||
|
'sat_localidad.descripcion AS localidad',
|
||||||
|
'users.profile_photo_path',
|
||||||
|
'users.is_partner',
|
||||||
|
'users.is_employee',
|
||||||
|
'users.is_prospect',
|
||||||
|
'users.is_customer',
|
||||||
|
'users.is_provider',
|
||||||
|
'users.is_user',
|
||||||
|
'users.status',
|
||||||
|
DB::raw("CONCAT_WS(' ', created.name, created.last_name) AS creator"),
|
||||||
|
'created.email AS creator_email',
|
||||||
|
'users.created_at',
|
||||||
|
'users.updated_at',
|
||||||
|
],
|
||||||
|
'joins' => [
|
||||||
|
[
|
||||||
|
'table' => 'users as parent',
|
||||||
|
'first' => 'users.parent_id',
|
||||||
|
'second' => 'parent.id',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'table' => 'users as agent',
|
||||||
|
'first' => 'users.agent_id',
|
||||||
|
'second' => 'agent.id',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'table' => 'users as created',
|
||||||
|
'first' => 'users.created_by',
|
||||||
|
'second' => 'created.id',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'table' => 'sat_codigo_postal',
|
||||||
|
'first' => 'users.domicilio_fiscal',
|
||||||
|
'second' => 'sat_codigo_postal.c_codigo_postal',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'table' => 'sat_estado',
|
||||||
|
'first' => 'sat_codigo_postal.c_estado',
|
||||||
|
'second' => 'sat_estado.c_estado',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
'and' => [
|
||||||
|
'sat_estado.c_pais = "MEX"',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'table' => 'sat_localidad',
|
||||||
|
'first' => 'sat_codigo_postal.c_localidad',
|
||||||
|
'second' => 'sat_localidad.c_localidad',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
'and' => [
|
||||||
|
'sat_codigo_postal.c_estado = sat_localidad.c_estado',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'table' => 'sat_municipio',
|
||||||
|
'first' => 'sat_codigo_postal.c_municipio',
|
||||||
|
'second' => 'sat_municipio.c_municipio',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
'and' => [
|
||||||
|
'sat_codigo_postal.c_estado = sat_municipio.c_estado',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'table' => 'sat_regimen_fiscal',
|
||||||
|
'first' => 'users.c_regimen_fiscal',
|
||||||
|
'second' => 'sat_regimen_fiscal.c_regimen_fiscal',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'table' => 'sat_uso_cfdi',
|
||||||
|
'first' => 'users.c_uso_cfdi',
|
||||||
|
'second' => 'sat_uso_cfdi.c_uso_cfdi',
|
||||||
|
'type' => 'leftJoin',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'filters' => [
|
||||||
|
'search' => ['users.name', 'users.email', 'users.code', 'parent.name', 'created.name'],
|
||||||
|
],
|
||||||
|
'sort_column' => 'users.name',
|
||||||
|
'default_sort_order' => 'asc',
|
||||||
|
];
|
||||||
|
|
||||||
|
return (new GenericQueryBuilder($request, $bootstrapTableIndexConfig))->getJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
$pageConfigs = ['contentLayout' => 'wide'];
|
||||||
|
|
||||||
|
$breadcrumbs = [
|
||||||
|
['route' => 'admin.home', 'name' => "Inicio"],
|
||||||
|
['name' => "Contactos", 'active' => true]
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('vuexy-contacts::contacts.index', compact('breadcrumbs', 'pageConfigs'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the crud for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
return view('vuexy-contacts::contacts.crud')
|
||||||
|
->with('mode', 'create')
|
||||||
|
->with('contact', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(User $contact)
|
||||||
|
{
|
||||||
|
return view('vuexy-contacts::contacts.show', compact('contact'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the crud for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(User $contact)
|
||||||
|
{
|
||||||
|
//$contact = User::findOrFail($id);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return view('vuexy-contacts::contacts.crud', compact('contact'))->with('mode', 'edit');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the crud for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function delete(User $contact)
|
||||||
|
{
|
||||||
|
return view('vuexy-contacts::contact.crud', compact('contact'))->with('mode', 'delete');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
65
Http/Controllers/CustomerController.php
Normal file
65
Http/Controllers/CustomerController.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class CustomerController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('vuexy-contacts::customers.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
65
Http/Controllers/EmployeeController.php
Normal file
65
Http/Controllers/EmployeeController.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class EmployeeController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('vuexy-contacts::employees.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
65
Http/Controllers/SupplierController.php
Normal file
65
Http/Controllers/SupplierController.php
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SupplierController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Display a listing of the resource.
|
||||||
|
*/
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return view('vuexy-contacts::suppliers.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for creating a new resource.
|
||||||
|
*/
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a newly created resource in storage.
|
||||||
|
*/
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the specified resource.
|
||||||
|
*/
|
||||||
|
public function show(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the form for editing the specified resource.
|
||||||
|
*/
|
||||||
|
public function edit(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the specified resource in storage.
|
||||||
|
*/
|
||||||
|
public function update(Request $request, string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the specified resource from storage.
|
||||||
|
*/
|
||||||
|
public function destroy(string $id)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
9
LICENSE
Normal file
9
LICENSE
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 koneko
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
306
Livewire/Contacts/ContactForm.php
Normal file
306
Livewire/Contacts/ContactForm.php
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Livewire\Contacts;
|
||||||
|
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
|
use Koneko\VuexyAdmin\Livewire\Form\AbstractFormComponent;
|
||||||
|
use Koneko\SatCatalogs\Models\{Colonia, Estado, Localidad, Municipio, Pais, RegimenFiscal};
|
||||||
|
use Koneko\VuexyContacts\Models\Store;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ContactForm
|
||||||
|
*
|
||||||
|
* Componente Livewire para manejar el formulario CRUD de sucursales en el sistema ERP.
|
||||||
|
* Implementa la creación, edición y eliminación de sucursales con validaciones dinámicas.
|
||||||
|
*/
|
||||||
|
class ContactForm extends AbstractFormComponent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Campos específicos del formulario.
|
||||||
|
*/
|
||||||
|
public $code, $name, $description, $manager_id, $rfc, $nombre_fiscal, $c_regimen_fiscal,
|
||||||
|
$domicilio_fiscal, $serie_ingresos, $serie_egresos, $serie_pagos, $c_codigo_postal,
|
||||||
|
$c_pais, $c_estado, $c_localidad, $c_municipio, $c_colonia, $direccion, $num_ext,
|
||||||
|
$num_int, $email, $tel, $tel2, $lat, $lng, $show_on_website, $enable_ecommerce, $status;
|
||||||
|
|
||||||
|
|
||||||
|
public $confirmDeletion = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listas de opciones para selects en el formulario.
|
||||||
|
*/
|
||||||
|
public $manager_id_options = [],
|
||||||
|
$c_regimen_fiscal_options = [],
|
||||||
|
$c_pais_options = [],
|
||||||
|
$c_estado_options = [],
|
||||||
|
$c_localidad_options = [],
|
||||||
|
$c_municipio_options = [],
|
||||||
|
$c_colonia_options = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Montar el formulario e inicializar datos específicos.
|
||||||
|
*
|
||||||
|
* @param string $mode Modo del formulario: create, edit, delete.
|
||||||
|
* @param Store|null $store El modelo Store si está en modo edición o eliminación.
|
||||||
|
*/
|
||||||
|
public function mount(string $mode = 'create', mixed $store = null): void
|
||||||
|
{
|
||||||
|
parent::mount($mode, $store->id ?? null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cargar opciones de formularios según el modo actual.
|
||||||
|
*
|
||||||
|
* @param string $mode
|
||||||
|
*/
|
||||||
|
private function loadOptions(string $mode): void
|
||||||
|
{
|
||||||
|
$this->manager_id_options = User::getUsersListWithInactive($this->manager_id, ['type' => 'user', 'status' => 1]);
|
||||||
|
$this->c_regimen_fiscal_options = RegimenFiscal::selectList();
|
||||||
|
$this->c_pais_options = Pais::selectList();
|
||||||
|
$this->c_estado_options = Estado::selectList($this->c_pais)->toArray();
|
||||||
|
|
||||||
|
if ($mode !== 'create') {
|
||||||
|
$this->c_localidad_options = Localidad::selectList($this->c_estado)->toArray();
|
||||||
|
$this->c_municipio_options = Municipio::selectList($this->c_estado, $this->c_municipio)->toArray();
|
||||||
|
$this->c_colonia_options = Colonia::selectList($this->c_codigo_postal, $this->c_colonia)->toArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== MÉTODOS OBLIGATORIOS =====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el modelo Eloquent asociado.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function model(): string
|
||||||
|
{
|
||||||
|
return Store::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reglas de validación dinámicas según el modo actual.
|
||||||
|
*
|
||||||
|
* @param string $mode
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function dynamicRules(string $mode): array
|
||||||
|
{
|
||||||
|
switch ($mode) {
|
||||||
|
case 'create':
|
||||||
|
case 'edit':
|
||||||
|
return [
|
||||||
|
'code' => [
|
||||||
|
'required', 'string', 'alpha_num', 'max:16',
|
||||||
|
Rule::unique('stores', 'code')->ignore($this->id)
|
||||||
|
],
|
||||||
|
'name' => 'required|string|max:96',
|
||||||
|
'description' => 'nullable|string|max:1024',
|
||||||
|
'manager_id' => 'nullable|exists:users,id',
|
||||||
|
|
||||||
|
// Información fiscal
|
||||||
|
'rfc' => ['nullable', 'string', 'regex:/^([A-ZÑ&]{3,4})(\d{6})([A-Z\d]{3})$/i', 'max:13'],
|
||||||
|
'nombre_fiscal' => 'nullable|string|max:255',
|
||||||
|
'c_regimen_fiscal' => 'nullable|exists:sat_regimen_fiscal,c_regimen_fiscal',
|
||||||
|
'domicilio_fiscal' => 'nullable|exists:sat_codigo_postal,c_codigo_postal',
|
||||||
|
|
||||||
|
// Ubicación
|
||||||
|
'c_pais' => 'nullable|exists:sat_pais,c_pais|string|size:3',
|
||||||
|
'c_estado' => 'nullable|exists:sat_estado,c_estado|string|min:2|max:3',
|
||||||
|
'c_municipio' => 'nullable|exists:sat_municipio,c_municipio|integer',
|
||||||
|
'c_localidad' => 'nullable|integer',
|
||||||
|
'c_codigo_postal' => 'nullable|exists:sat_codigo_postal,c_codigo_postal|integer',
|
||||||
|
'c_colonia' => 'nullable|exists:sat_colonia,c_colonia|integer',
|
||||||
|
'direccion' => 'nullable|string|max:255',
|
||||||
|
'num_ext' => 'nullable|string|max:50',
|
||||||
|
'num_int' => 'nullable|string|max:50',
|
||||||
|
'lat' => 'nullable|numeric|between:-90,90',
|
||||||
|
'lng' => 'nullable|numeric|between:-180,180',
|
||||||
|
|
||||||
|
// Contacto
|
||||||
|
'email' => ['nullable', 'email', 'required_if:enable_ecommerce,true'],
|
||||||
|
'tel' => ['nullable', 'regex:/^[0-9\s\-\+\(\)]+$/', 'max:15'],
|
||||||
|
'tel2' => ['nullable', 'regex:/^[0-9\s\-\+\(\)]+$/', 'max:15'],
|
||||||
|
|
||||||
|
// Configuración web y estado
|
||||||
|
'show_on_website' => 'nullable|boolean',
|
||||||
|
'enable_ecommerce' => 'nullable|boolean',
|
||||||
|
'status' => 'nullable|boolean',
|
||||||
|
];
|
||||||
|
|
||||||
|
case 'delete':
|
||||||
|
return [
|
||||||
|
'confirmDeletion' => 'accepted', // Asegura que el usuario confirme la eliminación
|
||||||
|
];
|
||||||
|
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inicializa los datos del formulario en función del modo.
|
||||||
|
*
|
||||||
|
* @param Store|null $store
|
||||||
|
* @param string $mode
|
||||||
|
*/
|
||||||
|
protected function initializeFormData(mixed $store, string $mode): void
|
||||||
|
{
|
||||||
|
if ($store) {
|
||||||
|
$this->code = $store->code;
|
||||||
|
$this->name = $store->name;
|
||||||
|
$this->description = $store->description;
|
||||||
|
$this->manager_id = $store->manager_id;
|
||||||
|
$this->rfc = $store->rfc;
|
||||||
|
$this->nombre_fiscal = $store->nombre_fiscal;
|
||||||
|
$this->c_regimen_fiscal = $store->c_regimen_fiscal;
|
||||||
|
$this->domicilio_fiscal = $store->domicilio_fiscal;
|
||||||
|
$this->c_pais = $store->c_pais;
|
||||||
|
$this->c_estado = $store->c_estado;
|
||||||
|
$this->c_municipio = $store->c_municipio;
|
||||||
|
$this->c_localidad = $store->c_localidad;
|
||||||
|
$this->c_codigo_postal = $store->c_codigo_postal;
|
||||||
|
$this->c_colonia = $store->c_colonia;
|
||||||
|
$this->direccion = $store->direccion;
|
||||||
|
$this->num_ext = $store->num_ext;
|
||||||
|
$this->num_int = $store->num_int;
|
||||||
|
$this->lat = $store->lat;
|
||||||
|
$this->lng = $store->lng;
|
||||||
|
$this->email = $store->email;
|
||||||
|
$this->tel = $store->tel;
|
||||||
|
$this->tel2 = $store->tel2;
|
||||||
|
$this->show_on_website = (bool) $store->show_on_website;
|
||||||
|
$this->enable_ecommerce = (bool) $store->enable_ecommerce;
|
||||||
|
$this->status = (bool) $store->status;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$this->c_pais = 'MEX';
|
||||||
|
$this->status = true;
|
||||||
|
$this->show_on_website = false;
|
||||||
|
$this->enable_ecommerce = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->loadOptions($mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepara los datos validados para su almacenamiento.
|
||||||
|
*
|
||||||
|
* @param array $validatedData
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function prepareData(array $validatedData): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code' => $validatedData['code'],
|
||||||
|
'name' => $validatedData['name'],
|
||||||
|
'description' => strip_tags($validatedData['description']),
|
||||||
|
'manager_id' => $validatedData['manager_id'],
|
||||||
|
'rfc' => $validatedData['rfc'],
|
||||||
|
'nombre_fiscal' => $validatedData['nombre_fiscal'],
|
||||||
|
'c_regimen_fiscal' => $validatedData['c_regimen_fiscal'],
|
||||||
|
'domicilio_fiscal' => $validatedData['domicilio_fiscal'],
|
||||||
|
'c_codigo_postal' => $validatedData['c_codigo_postal'],
|
||||||
|
'c_pais' => $validatedData['c_pais'],
|
||||||
|
'c_estado' => $validatedData['c_estado'],
|
||||||
|
'c_localidad' => $validatedData['c_localidad'],
|
||||||
|
'c_municipio' => $validatedData['c_municipio'],
|
||||||
|
'c_colonia' => $validatedData['c_colonia'],
|
||||||
|
'direccion' => $validatedData['direccion'],
|
||||||
|
'num_ext' => $validatedData['num_ext'],
|
||||||
|
'num_int' => $validatedData['num_int'],
|
||||||
|
'email' => $validatedData['email'],
|
||||||
|
'tel' => $validatedData['tel'],
|
||||||
|
'tel2' => $validatedData['tel2'],
|
||||||
|
'lat' => $validatedData['lat'],
|
||||||
|
'lng' => $validatedData['lng'],
|
||||||
|
'status' => $validatedData['status'],
|
||||||
|
'show_on_website' => $validatedData['show_on_website'],
|
||||||
|
'enable_ecommerce' => $validatedData['enable_ecommerce'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definición de los contenedores de notificación.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function targetNotifies(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
"index" => "#bt-stores .notification-container",
|
||||||
|
"form" => "#store-form .notification-container",
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ruta de vista asociada al formulario.
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\View
|
||||||
|
*/
|
||||||
|
protected function viewPath(): string
|
||||||
|
{
|
||||||
|
return 'vuexy-store-manager::livewire.stores.form';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== VALIDACIONES =====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get custom attributes for validator errors.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function attributes(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code' => 'código de sucursal',
|
||||||
|
'name' => 'nombre de la sucursal',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error messages for the defined validation rules.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'code.required' => 'El código de la sucursal es obligatorio.',
|
||||||
|
'code.unique' => 'Este código ya está en uso por otra sucursal.',
|
||||||
|
'name.required' => 'El nombre de la sucursal es obligatorio.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== PREPARACIÓN DE DATOS =====================
|
||||||
|
|
||||||
|
// ===================== NOTIFICACIONES Y EVENTOS =====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Definición de los eventos del componente.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function dispatches(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'on-failed-validation' => 'on-failed-validation-store',
|
||||||
|
'on-hydrate' => 'on-hydrate-store-modal',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================== REDIRECCIÓN =====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define la ruta de redirección tras guardar o eliminar.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function getRedirectRoute(): string
|
||||||
|
{
|
||||||
|
return 'admin.store-manager.stores.index';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
236
Livewire/Contacts/ContactIndex.php
Normal file
236
Livewire/Contacts/ContactIndex.php
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Livewire\Contacts;
|
||||||
|
|
||||||
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
|
use Koneko\VuexyAdmin\Livewire\Table\AbstractIndexComponent;
|
||||||
|
|
||||||
|
class ContactIndex extends AbstractIndexComponent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Almacena rutas útiles para la funcionalidad de edición o eliminación.
|
||||||
|
*/
|
||||||
|
public $routes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método que define la clase o instancia del modelo a usar en este Index.
|
||||||
|
*/
|
||||||
|
protected function model(): string
|
||||||
|
{
|
||||||
|
return User::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna las columnas (header) de la tabla.
|
||||||
|
*/
|
||||||
|
protected function columns(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'action' => 'Acciones',
|
||||||
|
'code' => 'Código personal',
|
||||||
|
'full_name' => 'Nombre Completo',
|
||||||
|
'email' => 'Correo Electrónico',
|
||||||
|
'parent_name' => 'Responsable',
|
||||||
|
'parent_email' => 'Correo Responsable',
|
||||||
|
'company' => 'Empresa',
|
||||||
|
'birth_date' => 'Fecha de Nacimiento',
|
||||||
|
'hire_date' => 'Fecha de Contratación',
|
||||||
|
'curp' => 'CURP',
|
||||||
|
'nss' => 'NSS',
|
||||||
|
'job_title' => 'Puesto',
|
||||||
|
'rfc' => 'RFC',
|
||||||
|
'nombre_fiscal' => 'Nombre Fiscal',
|
||||||
|
'tipo_persona' => 'Tipo de Persona',
|
||||||
|
'c_regimen_fiscal'=> 'Régimen Fiscal',
|
||||||
|
'domicilio_fiscal'=> 'Domicilio Fiscal',
|
||||||
|
'c_uso_cfdi' => 'Clave Uso CFDI',
|
||||||
|
'uso_cfdi' => 'Uso CFDI',
|
||||||
|
'profile_photo_path' => 'Foto de Perfil',
|
||||||
|
'is_partner' => 'Socio',
|
||||||
|
'is_employee' => 'Empleado',
|
||||||
|
'is_prospect' => 'Prospecto',
|
||||||
|
'is_customer' => 'Cliente',
|
||||||
|
'is_provider' => 'Proveedor',
|
||||||
|
'is_user' => 'Usuario',
|
||||||
|
'status' => 'Estatus',
|
||||||
|
'creator' => 'Creado Por',
|
||||||
|
'creator_email' => 'Correo Creador',
|
||||||
|
'created_at' => 'Fecha de Creación',
|
||||||
|
'updated_at' => 'Última Modificación',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna el formato (formatter) para cada columna.
|
||||||
|
*/
|
||||||
|
protected function format(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'action' => [
|
||||||
|
'formatter' => 'contactActionFormatter',
|
||||||
|
'onlyFormatter' => true,
|
||||||
|
],
|
||||||
|
'code' => [
|
||||||
|
'formatter' => [
|
||||||
|
'name' => 'dynamicBadgeFormatter',
|
||||||
|
'params' => ['color' => 'secondary'],
|
||||||
|
],
|
||||||
|
'align' => 'center',
|
||||||
|
'switchable' => false,
|
||||||
|
],
|
||||||
|
'full_name' => [
|
||||||
|
'formatter' => 'contactProfileFormatter',
|
||||||
|
],
|
||||||
|
'email' => [
|
||||||
|
'formatter' => 'emailFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'parent_name' => [
|
||||||
|
'formatter' => 'contactParentFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'agent_name' => [
|
||||||
|
'formatter' => 'agentFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'company' => [
|
||||||
|
'formatter' => 'textNowrapFormatter',
|
||||||
|
],
|
||||||
|
'curp' => [
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'nss' => [
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'job_title' => [
|
||||||
|
'formatter' => 'textNowrapFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'rfc' => [
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'nombre_fiscal' => [
|
||||||
|
'formatter' => 'textNowrapFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'domicilio_fiscal' => [
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'c_uso_cfdi' => [
|
||||||
|
'formatter' => 'usoCfdiFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'tipo_persona' => [
|
||||||
|
'formatter' => 'dynamicBadgeFormatter',
|
||||||
|
'align' => 'center',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'c_regimen_fiscal' => [
|
||||||
|
'formatter' => 'regimenFiscalFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'birth_date' => [
|
||||||
|
'align' => 'center',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'hire_date' => [
|
||||||
|
'align' => 'center',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'estado' => [
|
||||||
|
'formatter' => 'textNowrapFormatter',
|
||||||
|
],
|
||||||
|
'municipio' => [
|
||||||
|
'formatter' => 'textNowrapFormatter',
|
||||||
|
],
|
||||||
|
'localidad' => [
|
||||||
|
'formatter' => 'textNowrapFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'is_partner' => [
|
||||||
|
'formatter' => [
|
||||||
|
'name' => 'dynamicBooleanFormatter',
|
||||||
|
'params' => ['tag' => 'checkSI'],
|
||||||
|
],
|
||||||
|
'align' => 'center',
|
||||||
|
],
|
||||||
|
'is_employee' => [
|
||||||
|
'formatter' => [
|
||||||
|
'name' => 'dynamicBooleanFormatter',
|
||||||
|
'params' => ['tag' => 'checkSI'],
|
||||||
|
],
|
||||||
|
'align' => 'center',
|
||||||
|
],
|
||||||
|
'is_prospect' => [
|
||||||
|
'formatter' => [
|
||||||
|
'name' => 'dynamicBooleanFormatter',
|
||||||
|
'params' => ['tag' => 'checkSI'],
|
||||||
|
],
|
||||||
|
'align' => 'center',
|
||||||
|
],
|
||||||
|
'is_customer' => [
|
||||||
|
'formatter' => [
|
||||||
|
'name' => 'dynamicBooleanFormatter',
|
||||||
|
'params' => ['tag' => 'checkSI'],
|
||||||
|
],
|
||||||
|
'align' => 'center',
|
||||||
|
],
|
||||||
|
'is_provider' => [
|
||||||
|
'formatter' => [
|
||||||
|
'name' => 'dynamicBooleanFormatter',
|
||||||
|
'params' => ['tag' => 'checkSI'],
|
||||||
|
],
|
||||||
|
'align' => 'center',
|
||||||
|
],
|
||||||
|
'is_user' => [
|
||||||
|
'formatter' => [
|
||||||
|
'name' => 'dynamicBooleanFormatter',
|
||||||
|
'params' => ['tag' => 'checkSI'],
|
||||||
|
],
|
||||||
|
'align' => 'center',
|
||||||
|
],
|
||||||
|
'status' => [
|
||||||
|
'formatter' => 'statusIntBadgeBgFormatter',
|
||||||
|
'align' => 'center',
|
||||||
|
],
|
||||||
|
'creator' => [
|
||||||
|
'formatter' => 'creatorFormatter',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'created_at' => [
|
||||||
|
'formatter' => 'textNowrapFormatter',
|
||||||
|
'align' => 'center',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
'updated_at' => [
|
||||||
|
'formatter' => 'textNowrapFormatter',
|
||||||
|
'align' => 'center',
|
||||||
|
'visible' => false,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Montamos el componente y llamamos al parent::mount() para configurar la tabla.
|
||||||
|
*/
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
parent::mount();
|
||||||
|
|
||||||
|
// Definimos las rutas específicas de este componente
|
||||||
|
$this->routes = [
|
||||||
|
'admin.user.show' => route('admin.core.users.show', ['user' => ':id']),
|
||||||
|
'admin.contact.show' => route('admin.contacts.contacts.show', ['contact' => ':id']),
|
||||||
|
'admin.contact.edit' => route('admin.contacts.contacts.edit', ['contact' => ':id']),
|
||||||
|
'admin.contact.delete' => route('admin.contacts.contacts.delete', ['contact' => ':id']),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retorna la vista a renderizar por este componente.
|
||||||
|
*/
|
||||||
|
protected function viewPath(): string
|
||||||
|
{
|
||||||
|
return 'vuexy-contacts::livewire.contacts.index';
|
||||||
|
}
|
||||||
|
}
|
233
Livewire/Contacts/ContactOffCanvasForm.php
Normal file
233
Livewire/Contacts/ContactOffCanvasForm.php
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Livewire\Contacts;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Validation\Rule;
|
||||||
|
use Koneko\VuexyAdmin\Livewire\Form\AbstractFormOffCanvasComponent;
|
||||||
|
use Koneko\VuexyContacts\Services\ContactCatalogService;
|
||||||
|
use Koneko\VuexyStoreManager\Services\StoreCatalogService;
|
||||||
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ContactOffCanvasForm
|
||||||
|
*
|
||||||
|
* Componente Livewire para gestionar almacenes.
|
||||||
|
* Extiende la clase AbstractFormOffCanvasComponent e implementa validaciones dinámicas,
|
||||||
|
* manejo de formularios, eventos y actualizaciones en tiempo real.
|
||||||
|
*
|
||||||
|
* @package Koneko\VuexyContacts\Livewire\Contacts
|
||||||
|
*/
|
||||||
|
class ContactOffCanvasForm extends AbstractFormOffCanvasComponent
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Propiedades del formulario relacionadas con el almacén.
|
||||||
|
*/
|
||||||
|
public $code,
|
||||||
|
$parent_id,
|
||||||
|
$name,
|
||||||
|
$last_name,
|
||||||
|
$email,
|
||||||
|
$company,
|
||||||
|
$rfc,
|
||||||
|
$nombre_fiscal,
|
||||||
|
$tipo_persona,
|
||||||
|
$c_regimen_fiscal,
|
||||||
|
$domicilio_fiscal,
|
||||||
|
$is_partner,
|
||||||
|
$is_employee,
|
||||||
|
$is_prospect,
|
||||||
|
$is_customer,
|
||||||
|
$is_provider,
|
||||||
|
$status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listas de opciones para selects en el formulario.
|
||||||
|
*/
|
||||||
|
public $store_options = [],
|
||||||
|
$work_center_options = [],
|
||||||
|
$manager_options = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eventos de escucha de Livewire.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $listeners = [
|
||||||
|
'editContacts' => 'loadFormModel',
|
||||||
|
'confirmDeletionContacts' => '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 User::class;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define los campos del formulario.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
protected function fields(): array
|
||||||
|
{
|
||||||
|
return (new User())->getFillable();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valores por defecto para el formulario.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function defaults(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
//'status' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Campo que se debe enfocar cuando se abra el formulario.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function focusOnOpen(): string
|
||||||
|
{
|
||||||
|
return 'name';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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('contact', '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<string, string>
|
||||||
|
*/
|
||||||
|
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<string, string>
|
||||||
|
*/
|
||||||
|
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-contacts::livewire.contacts.offcanvas-form';
|
||||||
|
}
|
||||||
|
}
|
283
Livewire/Contacts/ContactShow.php
Normal file
283
Livewire/Contacts/ContactShow.php
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Livewire\Contacts;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Catalog\DropdownList;
|
||||||
|
use Koneko\SatCatalogs\Models\UsoCfdi;
|
||||||
|
use Koneko\SatCatalogs\Models\RegimenFiscal;
|
||||||
|
use Intervention\Image\ImageManager;
|
||||||
|
use Livewire\WithFileUploads;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
class ContactShow extends Component
|
||||||
|
{
|
||||||
|
use WithFileUploads;
|
||||||
|
|
||||||
|
public $image;
|
||||||
|
|
||||||
|
public User $user;
|
||||||
|
public $status_options, $pricelists_options;
|
||||||
|
public $regimen_fiscal_options, $uso_cfdi_options;
|
||||||
|
public $userId,
|
||||||
|
$name,
|
||||||
|
$cargo,
|
||||||
|
$profile_photo,
|
||||||
|
$profile_photo_path,
|
||||||
|
$email,
|
||||||
|
$password,
|
||||||
|
$password_confirmation,
|
||||||
|
$tipo_persona,
|
||||||
|
$rfc,
|
||||||
|
$nombre_fiscal,
|
||||||
|
$c_regimen_fiscal,
|
||||||
|
$domicilio_fiscal,
|
||||||
|
$c_uso_cfdi,
|
||||||
|
$pricelist_id,
|
||||||
|
$enable_credit,
|
||||||
|
$credit_days,
|
||||||
|
$credit_limit,
|
||||||
|
$is_prospect,
|
||||||
|
$is_customer,
|
||||||
|
$is_provider,
|
||||||
|
$is_user,
|
||||||
|
$status;
|
||||||
|
public $deleteUserImage;
|
||||||
|
public $cuentaUsuarioAlert,
|
||||||
|
$accesosAlert,
|
||||||
|
$facturacionElectronicaAlert;
|
||||||
|
|
||||||
|
// Reglas de validación para la cuenta de usuario
|
||||||
|
protected $rulesUser = [
|
||||||
|
'tipo_persona' => 'nullable|integer',
|
||||||
|
'name' => 'required|string|min:3|max:255',
|
||||||
|
'cargo' => 'nullable|string|min:3|max:255',
|
||||||
|
'is_prospect' => 'nullable|boolean',
|
||||||
|
'is_customer' => 'nullable|boolean',
|
||||||
|
'is_provider' => 'nullable|boolean',
|
||||||
|
'is_user' => 'nullable|boolean',
|
||||||
|
'pricelist_id' => 'nullable|integer',
|
||||||
|
'enable_credit' => 'nullable|boolean',
|
||||||
|
'credit_days' => 'nullable|integer',
|
||||||
|
'credit_limit' => 'nullable|numeric|min:0|max:9999999.99|regex:/^\d{1,7}(\.\d{1,2})?$/',
|
||||||
|
'image' => 'nullable|mimes:jpg,png|image|max:20480', // 20MB Max
|
||||||
|
];
|
||||||
|
|
||||||
|
// Reglas de validación para los campos fiscales
|
||||||
|
protected $rulesFacturacion = [
|
||||||
|
'rfc' => 'nullable|string|max:13',
|
||||||
|
'domicilio_fiscal' => [
|
||||||
|
'nullable',
|
||||||
|
'regex:/^[0-9]{5}$/',
|
||||||
|
'exists:sat_codigo_postal,c_codigo_postal'
|
||||||
|
],
|
||||||
|
'nombre_fiscal' => 'nullable|string|max:255',
|
||||||
|
'c_regimen_fiscal' => 'nullable|integer',
|
||||||
|
'c_uso_cfdi' => 'nullable|string',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function mount($userId)
|
||||||
|
{
|
||||||
|
$this->user = User::findOrFail($userId);
|
||||||
|
|
||||||
|
$this->reloadUserData();
|
||||||
|
|
||||||
|
$this->pricelists_options = DropdownList::selectList(DropdownList::POS_PRICELIST);
|
||||||
|
|
||||||
|
$this->status_options = [
|
||||||
|
User::STATUS_ENABLED => User::$statusList[User::STATUS_ENABLED],
|
||||||
|
User::STATUS_DISABLED => User::$statusList[User::STATUS_DISABLED],
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->regimen_fiscal_options = RegimenFiscal::selectList();
|
||||||
|
$this->uso_cfdi_options = UsoCfdi::selectList();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function reloadUserData()
|
||||||
|
{
|
||||||
|
$this->tipo_persona = $this->user->tipo_persona;
|
||||||
|
$this->name = $this->user->name;
|
||||||
|
$this->cargo = $this->user->cargo;
|
||||||
|
$this->is_prospect = $this->user->is_prospect? true : false;
|
||||||
|
$this->is_customer = $this->user->is_customer? true : false;
|
||||||
|
$this->is_provider = $this->user->is_provider? true : false;
|
||||||
|
$this->is_user = $this->user->is_user? true : false;
|
||||||
|
$this->pricelist_id = $this->user->pricelist_id;
|
||||||
|
$this->enable_credit = $this->user->enable_credit? true : false;
|
||||||
|
$this->credit_days = $this->user->credit_days;
|
||||||
|
$this->credit_limit = $this->user->credit_limit;
|
||||||
|
$this->profile_photo = $this->user->profile_photo_url;
|
||||||
|
$this->profile_photo_path = $this->user->profile_photo_path;
|
||||||
|
$this->image = null;
|
||||||
|
$this->deleteUserImage = false;
|
||||||
|
|
||||||
|
$this->status = $this->user->status;
|
||||||
|
$this->email = $this->user->email;
|
||||||
|
$this->password = null;
|
||||||
|
$this->password_confirmation = null;
|
||||||
|
|
||||||
|
$this->rfc = $this->user->rfc;
|
||||||
|
$this->domicilio_fiscal = $this->user->domicilio_fiscal;
|
||||||
|
$this->nombre_fiscal = $this->user->nombre_fiscal;
|
||||||
|
$this->c_regimen_fiscal = $this->user->c_regimen_fiscal;
|
||||||
|
$this->c_uso_cfdi = $this->user->c_uso_cfdi;
|
||||||
|
|
||||||
|
$this->cuentaUsuarioAlert = null;
|
||||||
|
$this->accesosAlert = null;
|
||||||
|
$this->facturacionElectronicaAlert = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function saveCuentaUsuario()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Validar Información de usuario
|
||||||
|
$validatedData = $this->validate($this->rulesUser);
|
||||||
|
|
||||||
|
$validatedData['name'] = trim($validatedData['name']);
|
||||||
|
$validatedData['cargo'] = $validatedData['cargo']? trim($validatedData['cargo']): null;
|
||||||
|
$validatedData['is_prospect'] = $validatedData['is_prospect'] ? 1 : 0;
|
||||||
|
$validatedData['is_customer'] = $validatedData['is_customer'] ? 1 : 0;
|
||||||
|
$validatedData['is_provider'] = $validatedData['is_provider'] ? 1 : 0;
|
||||||
|
$validatedData['is_user'] = $validatedData['is_user'] ? 1 : 0;
|
||||||
|
$validatedData['pricelist_id'] = $validatedData['pricelist_id'] ?: null;
|
||||||
|
$validatedData['enable_credit'] = $validatedData['enable_credit'] ? 1 : 0;
|
||||||
|
$validatedData['credit_days'] = $validatedData['credit_days'] ?: null;
|
||||||
|
$validatedData['credit_limit'] = $validatedData['credit_limit'] ?: null;
|
||||||
|
|
||||||
|
if($this->tipo_persona == User::TIPO_RFC_PUBLICO){
|
||||||
|
$validatedData['cargo'] = null;
|
||||||
|
$validatedData['is_prospect'] = null;
|
||||||
|
$validatedData['is_provider'] = null;
|
||||||
|
$validatedData['is_user'] = null;
|
||||||
|
$validatedData['enable_credit'] = null;
|
||||||
|
$validatedData['credit_days'] = null;
|
||||||
|
$validatedData['credit_limit'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->user->is_prospect && !$this->user->is_customer){
|
||||||
|
$validatedData['pricelist_id'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!$this->user->is_customer){
|
||||||
|
$validatedData['enable_credit'] = null;
|
||||||
|
$validatedData['credit_days'] = null;
|
||||||
|
$validatedData['credit_limit'] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->user->update($validatedData);
|
||||||
|
|
||||||
|
|
||||||
|
if($this->deleteUserImage && $this->user->profile_photo_path){
|
||||||
|
$this->user->deleteProfilePhoto();
|
||||||
|
|
||||||
|
// Reiniciar variables después de la eliminación
|
||||||
|
$this->deleteUserImage = false;
|
||||||
|
$this->profile_photo_path = null;
|
||||||
|
$this->profile_photo = $this->user->profile_photo_url;
|
||||||
|
|
||||||
|
}else if ($this->image) {
|
||||||
|
$image = ImageManager::imagick()->read($this->image->getRealPath());
|
||||||
|
$image = $image->scale(520, 520);
|
||||||
|
|
||||||
|
$imageName = $this->image->hashName(); // Genera un nombre único
|
||||||
|
|
||||||
|
$image->save(storage_path('app/public/profile-photos/' . $imageName));
|
||||||
|
|
||||||
|
$this->user->deleteProfilePhoto();
|
||||||
|
|
||||||
|
$this->profile_photo_path = $this->user->profile_photo_path = 'profile-photos/' . $imageName;
|
||||||
|
$this->profile_photo = $this->user->profile_photo_url;
|
||||||
|
$this->user->save();
|
||||||
|
|
||||||
|
unlink($this->image->getRealPath());
|
||||||
|
|
||||||
|
$this->reset('image');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Puedes también devolver un mensaje de éxito si lo deseas
|
||||||
|
$this->setAlert('Se guardó los cambios exitosamente.', 'cuentaUsuarioAlert');
|
||||||
|
|
||||||
|
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||||
|
// Si hay errores de validación, los puedes capturar y manejar aquí
|
||||||
|
$this->setAlert('Ocurrieron errores en la validación: ' . $e->validator->errors()->first(), 'cuentaUsuarioAlert', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveAccesos()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$validatedData = $this->validate([
|
||||||
|
'status' => 'integer',
|
||||||
|
'email' => ['required', 'email', 'unique:users,email,' . $this->user->id],
|
||||||
|
'password' => ['nullable', 'string', 'min:6', 'max:32', 'confirmed'], // La regla 'confirmed' valida que ambas contraseñas coincidan
|
||||||
|
], [
|
||||||
|
'email.required' => 'El correo electrónico es obligatorio.',
|
||||||
|
'email.email' => 'Debes ingresar un correo electrónico válido.',
|
||||||
|
'email.unique' => 'Este correo ya está en uso.',
|
||||||
|
'password.min' => 'La contraseña debe tener al menos 5 caracteres.',
|
||||||
|
'password.max' => 'La contraseña no puede tener más de 32 caracteres.',
|
||||||
|
'password.confirmed' => 'Las contraseñas no coinciden.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Si la validación es exitosa, continuar con el procesamiento
|
||||||
|
$validatedData['email'] = trim($this->email);
|
||||||
|
|
||||||
|
if ($this->password)
|
||||||
|
$validatedData['password'] = bcrypt($this->password);
|
||||||
|
|
||||||
|
else
|
||||||
|
unset($validatedData['password']);
|
||||||
|
|
||||||
|
$this->user->update($validatedData);
|
||||||
|
|
||||||
|
$this->password = null;
|
||||||
|
$this->password_confirmation = null;
|
||||||
|
|
||||||
|
// Puedes también devolver un mensaje de éxito si lo deseas
|
||||||
|
$this->setAlert('Se guardó los cambios exitosamente.', 'accesosAlert');
|
||||||
|
|
||||||
|
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||||
|
// Si hay errores de validación, los puedes capturar y manejar aquí
|
||||||
|
$this->setAlert('Ocurrieron errores en la validación: ' . $e->validator->errors()->first(), 'accesosAlert', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveFacturacionElectronica()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Validar Información fiscal
|
||||||
|
$validatedData = $this->validate($this->rulesFacturacion);
|
||||||
|
|
||||||
|
$validatedData['rfc'] = strtoupper(trim($validatedData['rfc'])) ?: null;
|
||||||
|
$validatedData['domicilio_fiscal'] = $validatedData['domicilio_fiscal'] ?: null;
|
||||||
|
$validatedData['nombre_fiscal'] = strtoupper(trim($validatedData['nombre_fiscal'])) ?: null;
|
||||||
|
$validatedData['c_regimen_fiscal'] = $validatedData['c_regimen_fiscal'] ?: null;
|
||||||
|
$validatedData['c_uso_cfdi'] = $validatedData['c_uso_cfdi'] ?: null;
|
||||||
|
|
||||||
|
$this->user->update($validatedData);
|
||||||
|
|
||||||
|
// Puedes también devolver un mensaje de éxito si lo deseas
|
||||||
|
$this->setAlert('Se guardó los cambios exitosamente.', 'facturacionElectronicaAlert');
|
||||||
|
|
||||||
|
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||||
|
// Si hay errores de validación, los puedes capturar y manejar aquí
|
||||||
|
$this->setAlert('Ocurrieron errores en la validación: ' . $e->validator->errors()->first(), 'facturacionElectronicaAlert', 'danger');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function setAlert($message, $alertName, $type = 'success')
|
||||||
|
{
|
||||||
|
$this->$alertName = [
|
||||||
|
'message' => $message,
|
||||||
|
'type' => $type
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return view('livewire.admin.crm.contact-view');
|
||||||
|
}
|
||||||
|
}
|
16
Models/ContactUser.php
Normal file
16
Models/ContactUser.php
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Models;
|
||||||
|
|
||||||
|
use Koneko\VuexyAdmin\Models\User;
|
||||||
|
use Koneko\VuexyContacts\Traits\HasContactsAttributes;
|
||||||
|
|
||||||
|
class ContactUser extends User
|
||||||
|
{
|
||||||
|
use HasContactsAttributes;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
...parent::FILLABLE, // Hereda los fillables de User
|
||||||
|
'curp', 'rfc', 'company', 'birth_date', 'note',
|
||||||
|
];
|
||||||
|
}
|
102
Models/ContactableAddress.php
Normal file
102
Models/ContactableAddress.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class ContactableAddress extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'contactable_addresses';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'contactable_id',
|
||||||
|
'contactable_type',
|
||||||
|
'type',
|
||||||
|
'c_pais',
|
||||||
|
'c_codigo_postal',
|
||||||
|
'c_estado',
|
||||||
|
'c_localidad',
|
||||||
|
'c_municipio',
|
||||||
|
'c_colonia',
|
||||||
|
'direccion',
|
||||||
|
'num_ext',
|
||||||
|
'num_int',
|
||||||
|
'referencia',
|
||||||
|
'lat',
|
||||||
|
'lng',
|
||||||
|
'preference_level',
|
||||||
|
'notes'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'lat' => 'decimal:6',
|
||||||
|
'lng' => 'decimal:6',
|
||||||
|
'preference_level' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relación polimórfica con cualquier modelo (User, Empresa, etc.)
|
||||||
|
*/
|
||||||
|
public function contactable()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el país de la dirección basado en el catálogo SAT
|
||||||
|
*/
|
||||||
|
public function country()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(SatPais::class, 'c_pais', 'c_pais');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el estado basado en el catálogo SAT
|
||||||
|
*/
|
||||||
|
public function state()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(SatEstado::class, ['c_estado', 'c_pais'], ['c_estado', 'c_pais']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene el municipio basado en el catálogo SAT
|
||||||
|
*/
|
||||||
|
public function municipality()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(SatMunicipio::class, ['c_municipio', 'c_estado'], ['c_municipio', 'c_estado']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene la localidad basada en el catálogo SAT
|
||||||
|
*/
|
||||||
|
public function locality()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(SatLocalidad::class, ['c_localidad', 'c_estado'], ['c_localidad', 'c_estado']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene la colonia basada en el catálogo SAT
|
||||||
|
*/
|
||||||
|
public function neighborhood()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(SatColonia::class, ['c_colonia', 'c_codigo_postal'], ['c_colonia', 'c_codigo_postal']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define si la dirección es preferida
|
||||||
|
*/
|
||||||
|
public function isPreferred()
|
||||||
|
{
|
||||||
|
return $this->preference_level === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve la dirección en formato legible
|
||||||
|
*/
|
||||||
|
public function getFullAddressAttribute()
|
||||||
|
{
|
||||||
|
return "{$this->direccion}, {$this->num_ext}" .
|
||||||
|
($this->num_int ? " Int. {$this->num_int}" : '') .
|
||||||
|
", {$this->neighborhood->nombre ?? ''}, {$this->municipality->nombre ?? ''}, {$this->state->nombre ?? ''}, {$this->country->nombre ?? ''}";
|
||||||
|
}
|
||||||
|
}
|
56
Models/ContactableItem.PHP
Normal file
56
Models/ContactableItem.PHP
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyAdmin\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Koneko\VuexyAdmin\Services\ContactableItemService;
|
||||||
|
|
||||||
|
class ContactableItem extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'contactable_items';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'contactable_id',
|
||||||
|
'contactable_type',
|
||||||
|
'type',
|
||||||
|
'data_contact',
|
||||||
|
'preference_level',
|
||||||
|
'notes'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'preference_level' => 'integer',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relación polimórfica con cualquier modelo (User, Empresa, etc.)
|
||||||
|
*/
|
||||||
|
public function contactable()
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve la información del tipo de contacto desde `settings`
|
||||||
|
*/
|
||||||
|
public function getTypeInfoAttribute()
|
||||||
|
{
|
||||||
|
return app(ContactableItemService::class)->getTypeById($this->type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el nombre del tipo de contacto
|
||||||
|
*/
|
||||||
|
public function getTypeTextAttribute()
|
||||||
|
{
|
||||||
|
return $this->type_info['label'] ?? 'Desconocido';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Devuelve el icono del tipo de contacto
|
||||||
|
*/
|
||||||
|
public function getTypeIconAttribute()
|
||||||
|
{
|
||||||
|
return $this->type_info['icon'] ?? 'ti-help-circle';
|
||||||
|
}
|
||||||
|
}
|
53
Providers/VuexyContactsServiceProvider.php
Normal file
53
Providers/VuexyContactsServiceProvider.php
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Livewire\Livewire;
|
||||||
|
use Koneko\VuexyContacts\Livewire\Contacts\{ContactIndex,ContactShow,ContactForm,ContactOffCanvasForm};
|
||||||
|
use OwenIt\Auditing\AuditableObserver;
|
||||||
|
|
||||||
|
class VuexyContactsServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Register any application services.
|
||||||
|
*/
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap any application services.
|
||||||
|
*/
|
||||||
|
public function boot(): void
|
||||||
|
{
|
||||||
|
// Register the module's routes
|
||||||
|
$this->loadRoutesFrom(__DIR__.'/../routes/admin.php');
|
||||||
|
|
||||||
|
|
||||||
|
// Cargar vistas del paquete
|
||||||
|
$this->loadViewsFrom(__DIR__.'/../resources/views', 'vuexy-contacts');
|
||||||
|
|
||||||
|
|
||||||
|
// Register the migrations
|
||||||
|
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
|
||||||
|
|
||||||
|
|
||||||
|
// Registrar Livewire Components
|
||||||
|
$components = [
|
||||||
|
'contact-index' => ContactIndex::class,
|
||||||
|
'contact-show' => ContactShow::class,
|
||||||
|
'contact-form' => ContactForm::class,
|
||||||
|
'contact-offcanvas-form' => ContactOffCanvasForm::class,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($components as $alias => $component) {
|
||||||
|
Livewire::component($alias, $component);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Registrar auditoría en usuarios
|
||||||
|
//User::observe(AuditableObserver::class);
|
||||||
|
}
|
||||||
|
}
|
133
README.md
Normal file
133
README.md
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
# 🎨 Laravel Vuexy Contacts - Vuexy Admin
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://koneko.mx" target="_blank"> <img src="https://git.koneko.mx/Koneko-ST/koneko-st/raw/branch/main/logo-images/horizontal-05.png" width="400" alt="Koneko Soluciones Tecnológicas Logo"> </a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://koneko.mx"><img src="https://img.shields.io/badge/Website-koneko.mx-blue" alt="Sitio Web"></a>
|
||||||
|
<a href="https://packagist.org/packages/koneko/laravel-vuexy-contacts"><img src="https://img.shields.io/packagist/v/koneko/laravel-vuexy-contacts" alt="Latest Stable Version"></a>
|
||||||
|
<a href="https://packagist.org/packages/koneko/laravel-vuexy-contacts"><img src="https://img.shields.io/packagist/l/koneko/laravel-vuexy-contacts" alt="License"></a>
|
||||||
|
<a href="https://git.koneko.mx/koneko"><img src="https://img.shields.io/badge/Git%20Server-Koneko%20Git-orange" alt="Servidor Git"></a>
|
||||||
|
<a href="https://github.com/koneko-mx/laravel-vuexy-contacts/actions/workflows/tests.yml"><img src="https://github.com/koneko-mx/laravel-vuexy-contacts/actions/workflows/tests.yml/badge.svg" alt="Build Status"></a>
|
||||||
|
<a href="https://github.com/koneko-mx/laravel-vuexy-contacts/issues"><img src="https://img.shields.io/github/issues/koneko/laravel-vuexy-contacts" alt="Issues"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 Descripción
|
||||||
|
|
||||||
|
**Laravel Vuexy Contacts** 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-contacts
|
||||||
|
```
|
||||||
|
|
||||||
|
Publicar archivos de configuración y migraciones (si aplica):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan vendor:publish --tag=laravel-vuexy-contacts-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-contacts-config
|
||||||
|
```
|
||||||
|
|
||||||
|
Esto generará `config/laravel-vuexy-contacts.php`, donde puedes modificar valores predeterminados.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Dependencias
|
||||||
|
|
||||||
|
Este paquete requiere las siguientes dependencias:
|
||||||
|
- Laravel 11
|
||||||
|
- `koneko/laravel-vuexy-contacts`
|
||||||
|
- 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-contacts-config
|
||||||
|
php artisan vendor:publish --tag=laravel-vuexy-contacts-seeders
|
||||||
|
php artisan migrate --seed
|
||||||
|
```
|
||||||
|
|
||||||
|
Para publicar imágenes del tema:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
php artisan vendor:publish --tag=laravel-vuexy-contacts-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-contacts)**.
|
||||||
|
|
||||||
|
### 🔄 Sincronización con GitHub
|
||||||
|
- **Repositorio Principal:** [git.koneko.mx](https://git.koneko.mx/koneko/laravel-vuexy-contacts)
|
||||||
|
- **Repositorio en GitHub:** [github.com/koneko/laravel-vuexy-contacts](https://github.com/koneko/laravel-vuexy-contacts)
|
||||||
|
- **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-contacts/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).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Hecho con ❤️ por <a href="https://koneko.mx">Koneko Soluciones Tecnológicas</a>
|
||||||
|
</p>
|
212
Services/ConstanciaFiscalService.php
Normal file
212
Services/ConstanciaFiscalService.php
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Services;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Spatie\PdfToText\Pdf;
|
||||||
|
use Koneko\SatCatalogs\Models\{Estado,Localidad,Municipio,Colonia,RegimenFiscal};
|
||||||
|
|
||||||
|
class ConstanciaFiscalService
|
||||||
|
{
|
||||||
|
protected $data = [];
|
||||||
|
protected $actividades = [];
|
||||||
|
protected $regimenes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procesa el PDF de la Constancia de Situación Fiscal y extrae la información relevante.
|
||||||
|
*
|
||||||
|
* @param UploadedFile $file
|
||||||
|
* @return array
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function extractData(UploadedFile $file): array
|
||||||
|
{
|
||||||
|
if ($file->getClientOriginalExtension() !== 'pdf') {
|
||||||
|
throw new Exception('El archivo debe ser un PDF.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$text = Pdf::getText($file->getRealPath());
|
||||||
|
$text_array = array_values(array_filter(explode("\n", $text), 'trim')); // Limpia líneas vacías
|
||||||
|
|
||||||
|
// **Estructura base con NULL en todos los campos**
|
||||||
|
$this->data = array_fill_keys([
|
||||||
|
"rfc", "curp", "name", "last_name", "second_last_name", "nombre_comercial", "telefono",
|
||||||
|
"fecha_inicio_operaciones", "estatus_padron", "fecha_ultimo_cambio_estado",
|
||||||
|
"c_codigo_postal", "c_estado", "estado", "c_localidad", "localidad",
|
||||||
|
"c_municipio", "municipio", "c_colonia", "colonia",
|
||||||
|
"vialidad", "entre_calle", "num_ext", "num_int", "regimenes"
|
||||||
|
], null);
|
||||||
|
|
||||||
|
$this->data['regimenes'] = []; // Siempre inicializar como array
|
||||||
|
|
||||||
|
$this->procesarDatosInline($text_array);
|
||||||
|
|
||||||
|
$this->procesarRegimenes($text_array);
|
||||||
|
|
||||||
|
if (empty($this->data['rfc'])) {
|
||||||
|
throw new Exception('No se encontró RFC en el documento.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procesa los datos generales hasta encontrar "Regímenes:".
|
||||||
|
*/
|
||||||
|
protected function procesarDatosInline(array $text_array)
|
||||||
|
{
|
||||||
|
foreach ($text_array as $index => $linea) {
|
||||||
|
$linea = trim($linea);
|
||||||
|
|
||||||
|
if ($linea === "Regímenes:") {
|
||||||
|
return; // Detener la iteración
|
||||||
|
}
|
||||||
|
|
||||||
|
// RFC, CURP, Nombre y Apellidos
|
||||||
|
if (str_contains($linea, "RFC:")) {
|
||||||
|
$this->data['rfc'] = trim($text_array[$index + 1]);
|
||||||
|
|
||||||
|
} elseif (str_contains($linea, "CURP:")) {
|
||||||
|
$this->data['curp'] = trim($text_array[$index + 1]);
|
||||||
|
|
||||||
|
} elseif (str_contains($linea, "Nombre (s):")) {
|
||||||
|
$this->data['name'] = trim($text_array[$index + 1]);
|
||||||
|
|
||||||
|
} elseif (str_contains($linea, "Primer Apellido:")) {
|
||||||
|
$this->data['last_name'] = trim($text_array[$index + 1]);
|
||||||
|
|
||||||
|
} elseif (str_contains($linea, "Segundo Apellido:")) {
|
||||||
|
$this->data['second_last_name'] = trim($text_array[$index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fechas y estatus
|
||||||
|
elseif (str_contains($linea, "Fecha inicio de operaciones:")) {
|
||||||
|
$this->data['fecha_inicio_operaciones'] = trim($text_array[$index + 1]);
|
||||||
|
|
||||||
|
} elseif (str_contains($linea, "Estatus en el padrón:")) {
|
||||||
|
$this->data['estatus_padron'] = trim($text_array[$index + 1]);
|
||||||
|
|
||||||
|
} elseif (str_contains($linea, "Fecha de último cambio de estado:")) {
|
||||||
|
$this->data['fecha_ultimo_cambio_estado'] = trim($text_array[$index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nombre Comercial
|
||||||
|
elseif (str_contains($linea, "Nombre Comercial:")) {
|
||||||
|
$this->data['nombre_comercial'] = trim($text_array[$index + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Teléfono (Dos Líneas)**
|
||||||
|
elseif (str_contains($linea, "Tel. Fijo Lada:")) {
|
||||||
|
$lada = trim(str_replace("Tel. Fijo Lada:", "", $linea));
|
||||||
|
|
||||||
|
if (isset($text_array[$index + 1]) && str_contains($text_array[$index + 1], "Número:")) {
|
||||||
|
$numero = trim(str_replace("Número:", "", $text_array[$index + 1]));
|
||||||
|
$this->data['telefono'] = $lada . " " . $numero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Código Postal**
|
||||||
|
elseif (str_contains($linea, "Código Postal:")) {
|
||||||
|
$this->data['c_codigo_postal'] = trim(str_replace("Código Postal:", "", $linea));
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Estado**
|
||||||
|
elseif (str_contains($linea, "Nombre de la Entidad Federativa:")) {
|
||||||
|
$estado_nombre = trim(str_replace("Nombre de la Entidad Federativa:", "", $linea));
|
||||||
|
$estado = Estado::where('nombre_del_estado', 'like', "%{$estado_nombre}%")->first();
|
||||||
|
|
||||||
|
$this->data['c_estado'] = $estado->c_estado ?? null;
|
||||||
|
$this->data['estado'] = $estado_nombre;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Municipio**
|
||||||
|
elseif (str_contains($linea, "Nombre del Municipio o Demarcación Territorial:")) {
|
||||||
|
$municipio_nombre = trim(str_replace("Nombre del Municipio o Demarcación Territorial:", "", $linea));
|
||||||
|
$municipio = Municipio::where('descripcion', 'like', "%{$municipio_nombre}%")->first();
|
||||||
|
|
||||||
|
$this->data['c_municipio'] = $municipio->c_municipio ?? null;
|
||||||
|
$this->data['municipio'] = $municipio_nombre;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Colonia**
|
||||||
|
elseif (str_contains($linea, "Nombre de la Colonia:")) {
|
||||||
|
$colonia_nombre = trim(str_replace("Nombre de la Colonia:", "", $linea));
|
||||||
|
$colonia = Colonia::where('nombre_del_asentamiento', 'like', "%{$colonia_nombre}%")->first();
|
||||||
|
|
||||||
|
$this->data['c_colonia'] = $colonia->c_colonia ?? null;
|
||||||
|
$this->data['colonia'] = $colonia_nombre;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Localidad** (Nueva implementación)
|
||||||
|
elseif (str_contains($linea, "Nombre de la Localidad:")) {
|
||||||
|
$localidad_nombre = trim(str_replace("Nombre de la Localidad:", "", $linea));
|
||||||
|
$localidad = Localidad::where('descripcion', 'like', "%{$localidad_nombre}%")->first();
|
||||||
|
|
||||||
|
$this->data['c_localidad'] = $localidad->c_localidad ?? null;
|
||||||
|
$this->data['localidad'] = $localidad_nombre;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Entre Calle**
|
||||||
|
elseif (str_contains($linea, "Entre Calle:")) {
|
||||||
|
$this->data['entre_calle'] = trim(str_replace("Entre Calle:", "", $linea));
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Dirección**
|
||||||
|
elseif (str_contains($linea, "Nombre de Vialidad:")) {
|
||||||
|
$this->data['vialidad'] = trim(str_replace("Nombre de Vialidad:", "", $linea));
|
||||||
|
|
||||||
|
} elseif (str_contains($linea, "Número Exterior:")) {
|
||||||
|
$this->data['num_ext'] = trim(str_replace("Número Exterior:", "", $linea));
|
||||||
|
|
||||||
|
} elseif (str_contains($linea, "Número Interior:") && !str_contains($text_array[$index + 1], "Nombre de la Colonia:")) {
|
||||||
|
$this->data['num_int'] = trim(str_replace("Número Interior:", "", $linea));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procesa los regímenes fiscales hasta encontrar "Obligaciones:".
|
||||||
|
*/
|
||||||
|
protected function procesarRegimenes(array $text_array)
|
||||||
|
{
|
||||||
|
$procesando = false;
|
||||||
|
|
||||||
|
foreach ($text_array as $index => $linea) {
|
||||||
|
if (trim($linea) === "Regímenes:") {
|
||||||
|
$procesando = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trim($linea) === "Obligaciones:") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($procesando) {
|
||||||
|
// **Filtrar líneas no relevantes**
|
||||||
|
if (in_array($linea, ["Régimen", "Fecha Inicio", "Fecha Fin", "Obligaciones:"])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// **Si la línea es una fecha, asociarla al régimen anterior**
|
||||||
|
if (preg_match('/\d{2}\/\d{2}\/\d{4}/', $linea)) {
|
||||||
|
$ultimo_regimen = &$this->data['regimenes'][count($this->data['regimenes']) - 1];
|
||||||
|
if (isset($ultimo_regimen)) {
|
||||||
|
$ultimo_regimen['fecha_inicio'] = trim($linea);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($linea)) {
|
||||||
|
$regimenFiscal = RegimenFiscal::where('descripcion', 'like', "%{$linea}%")->first();
|
||||||
|
$this->data['regimenes'][] = [
|
||||||
|
'regimen_fiscal' => trim($linea),
|
||||||
|
'c_regimen_fiscal' => $regimenFiscal->c_regimen_fiscal ?? null,
|
||||||
|
'fecha_inicio' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
101
Services/ContactCatalogService.php
Normal file
101
Services/ContactCatalogService.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Services;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class ContactCatalogService
|
||||||
|
{
|
||||||
|
protected $catalogs = [
|
||||||
|
'users' => [
|
||||||
|
'table' => 'users',
|
||||||
|
'key' => 'id',
|
||||||
|
'value' => "CONCAT(name, ' ', COALESCE(last_name, ''), ' - ', email) as item",
|
||||||
|
'search_columns' => ['name', 'last_name', 'email', 'rfc'],
|
||||||
|
'order_by' => 'name',
|
||||||
|
'limit' => 20,
|
||||||
|
],
|
||||||
|
'contacts' => [
|
||||||
|
'table' => 'users',
|
||||||
|
'key' => 'id',
|
||||||
|
'value' => "CONCAT(name, ' ', COALESCE(last_name, '')) as item",
|
||||||
|
'search_columns' => ['name', 'last_name', 'rfc', 'company'],
|
||||||
|
'extra_conditions' => ['is_customer' => true],
|
||||||
|
'order_by' => 'name',
|
||||||
|
'limit' => 20,
|
||||||
|
],
|
||||||
|
'providers' => [
|
||||||
|
'table' => 'users',
|
||||||
|
'key' => 'id',
|
||||||
|
'value' => "CONCAT(name, ' ', COALESCE(last_name, '')) as item",
|
||||||
|
'search_columns' => ['name', 'last_name', 'rfc', 'company'],
|
||||||
|
'extra_conditions' => ['is_provider' => true],
|
||||||
|
'order_by' => 'name',
|
||||||
|
'limit' => 20,
|
||||||
|
],
|
||||||
|
'addresses' => [
|
||||||
|
'table' => 'contactable_addresses',
|
||||||
|
'key' => 'id',
|
||||||
|
'value' => "CONCAT(direccion, ' #', num_ext, ' ', COALESCE(num_int, ''), ', ', c_codigo_postal) as item",
|
||||||
|
'search_columns' => ['direccion', 'num_ext', 'c_codigo_postal'],
|
||||||
|
'extra_conditions' => ['contactable_id', 'contactable_type'],
|
||||||
|
'order_by' => 'direccion',
|
||||||
|
'limit' => 10,
|
||||||
|
],
|
||||||
|
'emails' => [
|
||||||
|
'table' => 'contactable_items',
|
||||||
|
'key' => 'id',
|
||||||
|
'value' => 'data_contact as item',
|
||||||
|
'search_columns' => ['data_contact'],
|
||||||
|
'extra_conditions' => ['contactable_id', 'contactable_type', 'type' => 1], // 1 = email
|
||||||
|
'order_by' => 'data_contact',
|
||||||
|
'limit' => 10,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Método para obtener datos de catálogos con filtrado flexible.
|
||||||
|
*
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
}
|
48
Services/ContactableItemService.php
Normal file
48
Services/ContactableItemService.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Services;
|
||||||
|
|
||||||
|
use Koneko\VuexyAdmin\Models\Setting;
|
||||||
|
|
||||||
|
class ContactableItemService
|
||||||
|
{
|
||||||
|
protected $settingsKey = 'contactable_item_types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene la lista de tipos de medios de contacto desde settings.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getContactableTypes(): array
|
||||||
|
{
|
||||||
|
$setting = Setting::where('key', $this->settingsKey)->first();
|
||||||
|
|
||||||
|
return $setting ? json_decode($setting->value, true) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guarda una lista de tipos de medios de contacto.
|
||||||
|
*
|
||||||
|
* @param array $types
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function saveContactableTypes(array $types): bool
|
||||||
|
{
|
||||||
|
return Setting::updateOrCreate(
|
||||||
|
['key' => $this->settingsKey],
|
||||||
|
['value' => json_encode($types)]
|
||||||
|
) ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtiene un tipo de contacto específico por ID.
|
||||||
|
*
|
||||||
|
* @param int $id
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function getTypeById(int $id): ?array
|
||||||
|
{
|
||||||
|
$types = $this->getContactableTypes();
|
||||||
|
return collect($types)->firstWhere('id', $id);
|
||||||
|
}
|
||||||
|
}
|
159
Services/FacturaXmlService.php
Normal file
159
Services/FacturaXmlService.php
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Services;
|
||||||
|
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use SimpleXMLElement;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class FacturaXmlService
|
||||||
|
{
|
||||||
|
protected $xml;
|
||||||
|
protected $data = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Procesa un archivo XML subido y extrae los datos.
|
||||||
|
*
|
||||||
|
* @param UploadedFile $file
|
||||||
|
* @return array
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function processUploadedFile(UploadedFile $file): array
|
||||||
|
{
|
||||||
|
if (!$file->isValid()) {
|
||||||
|
throw new Exception("El archivo no es válido o está corrupto.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validamos que sea XML
|
||||||
|
if ($file->getClientOriginalExtension() !== 'xml') {
|
||||||
|
throw new Exception("Solo se permiten archivos XML.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cargar XML desde el contenido del archivo
|
||||||
|
$xmlContent = file_get_contents($file->getRealPath());
|
||||||
|
$this->loadXml($xmlContent);
|
||||||
|
|
||||||
|
// Extraer datos del XML
|
||||||
|
return $this->extractData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Carga el XML y lo convierte en un objeto SimpleXMLElement
|
||||||
|
*
|
||||||
|
* @param string $xmlContent
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function loadXml(string $xmlContent): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->xml = new SimpleXMLElement($xmlContent);
|
||||||
|
|
||||||
|
// Registrar espacios de nombres
|
||||||
|
$this->xml->registerXPathNamespace('cfdi', 'http://www.sat.gob.mx/cfd/4');
|
||||||
|
$this->xml->registerXPathNamespace('tfd', 'http://www.sat.gob.mx/TimbreFiscalDigital');
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new Exception('Error al cargar el XML: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extrae los datos clave de la factura XML
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function extractData(): array
|
||||||
|
{
|
||||||
|
if (!$this->xml) {
|
||||||
|
throw new Exception('No se ha cargado un XML válido.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->data['comprobante'] = $this->extractComprobante();
|
||||||
|
$this->data['emisor'] = $this->extractEmisor();
|
||||||
|
$this->data['receptor'] = $this->extractReceptor();
|
||||||
|
$this->data['conceptos'] = $this->extractConceptos();
|
||||||
|
$this->data['impuestos'] = $this->extractImpuestos();
|
||||||
|
$this->data['timbreFiscal'] = $this->extractTimbreFiscal();
|
||||||
|
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractComprobante(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'fecha' => (string) $this->xml['Fecha'] ?? null,
|
||||||
|
'total' => (float) $this->xml['Total'] ?? 0.0,
|
||||||
|
'moneda' => (string) $this->xml['Moneda'] ?? 'MXN',
|
||||||
|
'tipoDeComprobante' => (string) $this->xml['TipoDeComprobante'] ?? null,
|
||||||
|
'metodoPago' => (string) $this->xml['MetodoPago'] ?? null,
|
||||||
|
'lugarExpedicion' => (string) $this->xml['LugarExpedicion'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractEmisor(): array
|
||||||
|
{
|
||||||
|
$emisor = $this->xml->xpath('//cfdi:Emisor')[0] ?? null;
|
||||||
|
if (!$emisor) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'rfc' => (string) $emisor['Rfc'] ?? null,
|
||||||
|
'nombre' => (string) $emisor['Nombre'] ?? null,
|
||||||
|
'regimenFiscal' => (string) $emisor['RegimenFiscal'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractReceptor(): array
|
||||||
|
{
|
||||||
|
$receptor = $this->xml->xpath('//cfdi:Receptor')[0] ?? null;
|
||||||
|
if (!$receptor) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'rfc' => (string) $receptor['Rfc'] ?? null,
|
||||||
|
'nombre' => (string) $receptor['Nombre'] ?? null,
|
||||||
|
'usoCFDI' => (string) $receptor['UsoCFDI'] ?? null,
|
||||||
|
'regimenFiscal' => (string) $receptor['RegimenFiscalReceptor'] ?? null,
|
||||||
|
'domicilioFiscal' => (string) $receptor['DomicilioFiscalReceptor'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractConceptos(): array
|
||||||
|
{
|
||||||
|
$conceptos = [];
|
||||||
|
foreach ($this->xml->xpath('//cfdi:Concepto') as $concepto) {
|
||||||
|
$conceptos[] = [
|
||||||
|
'descripcion' => (string) $concepto['Descripcion'] ?? null,
|
||||||
|
'cantidad' => (float) $concepto['Cantidad'] ?? 1.0,
|
||||||
|
'valorUnitario' => (float) $concepto['ValorUnitario'] ?? 0.0,
|
||||||
|
'importe' => (float) $concepto['Importe'] ?? 0.0,
|
||||||
|
'claveProdServ' => (string) $concepto['ClaveProdServ'] ?? null,
|
||||||
|
'claveUnidad' => (string) $concepto['ClaveUnidad'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return $conceptos;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractImpuestos(): array
|
||||||
|
{
|
||||||
|
$impuestos = $this->xml->xpath('//cfdi:Impuestos')[0] ?? null;
|
||||||
|
if (!$impuestos) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'totalImpuestosTrasladados' => (float) $impuestos['TotalImpuestosTrasladados'] ?? 0.0,
|
||||||
|
'totalImpuestosRetenidos' => (float) $impuestos['TotalImpuestosRetenidos'] ?? 0.0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function extractTimbreFiscal(): array
|
||||||
|
{
|
||||||
|
$timbre = $this->xml->xpath('//tfd:TimbreFiscalDigital')[0] ?? null;
|
||||||
|
if (!$timbre) return [];
|
||||||
|
|
||||||
|
return [
|
||||||
|
'uuid' => (string) $timbre['UUID'] ?? null,
|
||||||
|
'fechaTimbrado' => (string) $timbre['FechaTimbrado'] ?? null,
|
||||||
|
'selloCFD' => (string) $timbre['SelloCFD'] ?? null,
|
||||||
|
'selloSAT' => (string) $timbre['SelloSAT'] ?? null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
77
Traits/HasContactsAttributes.php
Normal file
77
Traits/HasContactsAttributes.php
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Koneko\VuexyContacts\Traits;
|
||||||
|
|
||||||
|
trait HasContactsAttributes
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Tipos de RFC disponibles.
|
||||||
|
*/
|
||||||
|
public const TIPO_RFC_INVALID = 0;
|
||||||
|
public const TIPO_RFC_FISICA = 1;
|
||||||
|
public const TIPO_RFC_MORAL = 2;
|
||||||
|
public const TIPO_RFC_PUBLICO = 9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lista de nombres para cada tipo de RFC.
|
||||||
|
*/
|
||||||
|
public static array $tipoRfcList = [
|
||||||
|
self::TIPO_RFC_INVALID => 'RFC inválido',
|
||||||
|
self::TIPO_RFC_FISICA => 'Persona física',
|
||||||
|
self::TIPO_RFC_MORAL => 'Persona moral',
|
||||||
|
self::TIPO_RFC_PUBLICO => 'Público en general',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Agrega atributos específicos al `$fillable` del modelo sin modificarlo directamente.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getFillable()
|
||||||
|
{
|
||||||
|
return array_merge(parent::getFillable(), [
|
||||||
|
'code',
|
||||||
|
'parent_id',
|
||||||
|
'agent_id',
|
||||||
|
'company',
|
||||||
|
'birth_date',
|
||||||
|
'hire_date',
|
||||||
|
'curp',
|
||||||
|
'nss',
|
||||||
|
'job_title',
|
||||||
|
'rfc',
|
||||||
|
'nombre_fiscal',
|
||||||
|
'tipo_persona',
|
||||||
|
'c_regimen_fiscal',
|
||||||
|
'domicilio_fiscal',
|
||||||
|
'c_uso_cfdi',
|
||||||
|
'note',
|
||||||
|
'is_partner',
|
||||||
|
'is_employee',
|
||||||
|
'is_prospect',
|
||||||
|
'is_customer',
|
||||||
|
'is_provider',
|
||||||
|
'is_user',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detecta el tipo de RFC con base en el formato de caracteres.
|
||||||
|
*/
|
||||||
|
public function getTipoRfcAttribute(): int
|
||||||
|
{
|
||||||
|
$rfc = strtoupper(trim($this->attributes['rfc'] ?? ''));
|
||||||
|
|
||||||
|
if ($rfc === 'XAXX010101000') {
|
||||||
|
return self::TIPO_RFC_PUBLICO;
|
||||||
|
}
|
||||||
|
|
||||||
|
$longitud = strlen($rfc);
|
||||||
|
|
||||||
|
if (!in_array($longitud, [12, 13])) {
|
||||||
|
return self::TIPO_RFC_INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ($longitud === 13) ? self::TIPO_RFC_FISICA : self::TIPO_RFC_MORAL;
|
||||||
|
}
|
||||||
|
}
|
38
composer.json
Normal file
38
composer.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "koneko/laravel-vuexy-contacts",
|
||||||
|
"description": "Laravel Vuexy Contacts, un modulo de administracion de contactos optimizado para México.",
|
||||||
|
"keywords": ["laravel", "koneko", "framework", "vuexy", "contacts", "admin", "mexico"],
|
||||||
|
"type": "library",
|
||||||
|
"license": "MIT",
|
||||||
|
"require": {
|
||||||
|
"php": "^8.2",
|
||||||
|
"koneko/laravel-sat-catalogs": "@dev",
|
||||||
|
"koneko/laravel-vuexy-admin": "@dev",
|
||||||
|
"laravel/framework": "^11.31",
|
||||||
|
"spatie/pdf-to-text": "^1.54"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Koneko\\VuexyContacts\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Koneko\\VuexyContacts\\Providers\\VuexyContactsServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Arturo Corro Pacheco",
|
||||||
|
"email": "arturo@koneko.mx"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/koneko-mx/laravel-vuexy-contacts",
|
||||||
|
"issues": "https://github.com/koneko-mx/laravel-vuexy-contacts/issues"
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('code', 50)->unique()->nullable()->comment('Código único del contacto')->after('id');
|
||||||
|
$table->unsignedMediumInteger('parent_id')->nullable()->index()->after('code');
|
||||||
|
$table->unsignedMediumInteger('agent_id')->nullable()->index()->after('parent_id');
|
||||||
|
$table->string('company', 100)->nullable()->comment('Nombre de la empresa')->index()->after('email_verified_at');
|
||||||
|
$table->date('birth_date')->nullable()->comment('Fecha de nacimiento')->after('company');
|
||||||
|
$table->date('hire_date')->nullable()->comment('Fecha de contratación')->after('birth_date');
|
||||||
|
$table->string('curp', 50)->nullable()->comment('Clave Única de Registro de Población (CURP)')->index()->after('hire_date');
|
||||||
|
$table->string('nss', 11)->nullable()->comment('Número de seguridad social')->index()->after('curp');
|
||||||
|
$table->string('job_title', 100)->nullable()->comment('Cargo del contacto en la empresa')->after('nss');
|
||||||
|
$table->string('rfc', 13)->unique()->nullable()->index()->after('job_title');
|
||||||
|
$table->string('nombre_fiscal')->nullable()->index()->after('rfc');
|
||||||
|
$table->unsignedTinyInteger('tipo_persona')->nullable()->index()->after('nombre_fiscal');
|
||||||
|
$table->unsignedSmallInteger('c_regimen_fiscal')->nullable()->index()->after('tipo_persona');
|
||||||
|
$table->unsignedMediumInteger('domicilio_fiscal')->nullable()->index()->after('c_regimen_fiscal');
|
||||||
|
$table->char('c_uso_cfdi', 4)->charset('ascii')->collation('ascii_general_ci')->nullable()->index()->after('domicilio_fiscal');
|
||||||
|
$table->mediumText('note')->nullable()->after('c_uso_cfdi');
|
||||||
|
|
||||||
|
$table->unsignedTinyInteger('is_partner')->nullable()->index()->after('profile_photo_path');
|
||||||
|
$table->unsignedTinyInteger('is_employee')->nullable()->index()->after('is_partner');
|
||||||
|
$table->unsignedTinyInteger('is_prospect')->nullable()->index()->after('is_employee');
|
||||||
|
$table->unsignedTinyInteger('is_customer')->nullable()->index()->after('is_prospect');
|
||||||
|
$table->unsignedTinyInteger('is_provider')->nullable()->index()->after('is_customer');
|
||||||
|
$table->unsignedTinyInteger('is_user')->nullable()->index()->after('is_provider');
|
||||||
|
|
||||||
|
$table->text('notes')->nullable()->after('is_user');
|
||||||
|
|
||||||
|
$table->foreign('parent_id')
|
||||||
|
->references('id')
|
||||||
|
->on('users')
|
||||||
|
->onUpdate('restrict')
|
||||||
|
->onDelete('restrict');
|
||||||
|
|
||||||
|
$table->foreign('agent_id')
|
||||||
|
->references('id')
|
||||||
|
->on('users')
|
||||||
|
->onUpdate('restrict')
|
||||||
|
->onDelete('restrict');
|
||||||
|
|
||||||
|
$table->foreign('c_regimen_fiscal')
|
||||||
|
->references('c_regimen_fiscal')
|
||||||
|
->on('sat_regimen_fiscal')
|
||||||
|
->onUpdate('restrict')
|
||||||
|
->onDelete('restrict');
|
||||||
|
|
||||||
|
$table->foreign('domicilio_fiscal')
|
||||||
|
->references('c_codigo_postal')
|
||||||
|
->on('sat_codigo_postal')
|
||||||
|
->onUpdate('restrict')
|
||||||
|
->onDelete('restrict');
|
||||||
|
|
||||||
|
$table->foreign('c_uso_cfdi')
|
||||||
|
->references('c_uso_cfdi')
|
||||||
|
->on('sat_uso_cfdi')
|
||||||
|
->onUpdate('restrict')
|
||||||
|
->onDelete('restrict');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropForeign(['created_by', 'c_regimen_fiscal', 'domicilio_fiscal', 'c_uso_cfdi']);
|
||||||
|
$table->dropColumn([
|
||||||
|
'c_regimen_fiscal', 'domicilio_fiscal', 'c_uso_cfdi',
|
||||||
|
'code', 'company', 'birth_date', 'hire_date',
|
||||||
|
'curp', 'nss', 'job_title', 'rfc', 'nombre_fiscal', 'tipo_persona',
|
||||||
|
'is_partner', 'is_employee', 'is_prospect', 'is_customer', 'is_provider', 'is_user'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('contactable_addresses', function (Blueprint $table) {
|
||||||
|
$table->mediumIncrements('id');
|
||||||
|
|
||||||
|
// Relación optimizada (sin morphs por rendimiento)
|
||||||
|
$table->unsignedMediumInteger('contactable_id')->index();
|
||||||
|
$table->string('contactable_type')->index();
|
||||||
|
|
||||||
|
// Tipo de dirección
|
||||||
|
$table->unsignedTinyInteger('type')->index();
|
||||||
|
|
||||||
|
// Ubicación basada en el SAT
|
||||||
|
$table->char('c_pais', 3)->charset('ascii')->collation('ascii_general_ci')->nullable()->index();
|
||||||
|
$table->unsignedMediumInteger('c_codigo_postal')->nullable()->index();
|
||||||
|
$table->char('c_estado', 3)->charset('ascii')->collation('ascii_general_ci')->nullable()->index();
|
||||||
|
$table->unsignedTinyInteger('c_localidad')->nullable()->index();
|
||||||
|
$table->unsignedSmallInteger('c_municipio')->nullable()->index();
|
||||||
|
$table->unsignedMediumInteger('c_colonia')->nullable()->index();
|
||||||
|
|
||||||
|
// Datos de la dirección
|
||||||
|
$table->string('direccion')->nullable();
|
||||||
|
$table->string('num_ext')->nullable();
|
||||||
|
$table->string('num_int')->nullable();
|
||||||
|
$table->string('referencia')->nullable();
|
||||||
|
$table->decimal('lat', 9, 6)->nullable();
|
||||||
|
$table->decimal('lng', 9, 6)->nullable();
|
||||||
|
|
||||||
|
// Preferencia
|
||||||
|
$table->unsignedTinyInteger('preference_level')->nullable();
|
||||||
|
|
||||||
|
// Notas o comentarios
|
||||||
|
$table->text('notes')->nullable(); // Nuevo campo para comentarios
|
||||||
|
|
||||||
|
// Autoría
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Índices
|
||||||
|
$table->index(['contactable_type', 'contactable_id']);
|
||||||
|
$table->index(['contactable_type', 'contactable_id', 'type']);
|
||||||
|
$table->index(['c_municipio', 'c_estado']);
|
||||||
|
|
||||||
|
// Relaciones con catálogos SAT
|
||||||
|
$table->foreign('c_pais')->references('c_pais')->on('sat_pais')->onUpdate('restrict')->onDelete('restrict');
|
||||||
|
$table->foreign('c_codigo_postal')->references('c_codigo_postal')->on('sat_codigo_postal')->onUpdate('restrict')->onDelete('restrict');
|
||||||
|
$table->foreign(['c_estado', 'c_pais'])->references(['c_estado', 'c_pais'])->on('sat_estado')->onUpdate('restrict')->onDelete('restrict');
|
||||||
|
$table->foreign(['c_municipio', 'c_estado'])->references(['c_municipio', 'c_estado'])->on('sat_municipio')->onUpdate('restrict')->onDelete('restrict');
|
||||||
|
$table->foreign(['c_localidad', 'c_estado'])->references(['c_localidad', 'c_estado'])->on('sat_localidad')->onUpdate('restrict')->onDelete('restrict');
|
||||||
|
$table->foreign(['c_colonia', 'c_codigo_postal'])->references(['c_colonia', 'c_codigo_postal'])->on('sat_colonia')->onUpdate('restrict')->onDelete('restrict');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create('contactable_items', function (Blueprint $table) {
|
||||||
|
$table->mediumIncrements('id');
|
||||||
|
|
||||||
|
// Relación optimizada
|
||||||
|
$table->unsignedMediumInteger('contactable_id')->index();
|
||||||
|
$table->string('contactable_type')->index();
|
||||||
|
|
||||||
|
// Tipo de medio de contacto (ej. 1=Email, 2=Teléfono, 3=WhatsApp)
|
||||||
|
$table->unsignedTinyInteger('type')->index();
|
||||||
|
|
||||||
|
// Dato de contacto (ej. email o teléfono)
|
||||||
|
$table->string('data_contact')->index();
|
||||||
|
|
||||||
|
// Preferencia
|
||||||
|
$table->unsignedTinyInteger('preference_level')->nullable();
|
||||||
|
|
||||||
|
// Notas o comentarios
|
||||||
|
$table->text('notes')->nullable(); // Nuevo campo para comentarios
|
||||||
|
|
||||||
|
// Autoría
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Índices
|
||||||
|
$table->index(['contactable_type', 'contactable_id']);
|
||||||
|
$table->index(['contactable_type', 'contactable_id', 'type']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('contactable_addresses');
|
||||||
|
Schema::dropIfExists('contactable_items');
|
||||||
|
}
|
||||||
|
};
|
519
resources/assets/js/addresses/AddressFormHandler.js
Normal file
519
resources/assets/js/addresses/AddressFormHandler.js
Normal file
@ -0,0 +1,519 @@
|
|||||||
|
export default class AddressFormHandler {
|
||||||
|
constructor(formSelectors, ajaxRoutes, livewireInstance, csrfToken) {
|
||||||
|
if (!ajaxRoutes && !ajaxRoutes.estado) {
|
||||||
|
throw new Error("ajax AddressFormHandler routes not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.formSelectors = formSelectors;
|
||||||
|
this.ajaxRoutes = ajaxRoutes;
|
||||||
|
this.livewireInstance = livewireInstance; // Livewire Instance
|
||||||
|
this.csrfToken = csrfToken;
|
||||||
|
this.placeholders = {
|
||||||
|
pais: 'Selecciona el país',
|
||||||
|
estado: 'Selecciona el estado',
|
||||||
|
localidad: 'Selecciona la localidad',
|
||||||
|
municipio: 'Selecciona el municipio',
|
||||||
|
colonia: 'Selecciona la colonia',
|
||||||
|
codigo_postal: 'Ingresa el código postal',
|
||||||
|
}
|
||||||
|
|
||||||
|
this.synchronizedZipCode = false;
|
||||||
|
|
||||||
|
this.initializeSelects();
|
||||||
|
this.initializeZipCodeInput();
|
||||||
|
this.loadDefaultValuesFromLivewire();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
initializeSelects() {
|
||||||
|
const { c_codigo_postal, c_pais, c_estado, c_localidad, c_municipio, c_colonia } = this.formSelectors;
|
||||||
|
|
||||||
|
// País
|
||||||
|
$(c_pais)
|
||||||
|
.select2({
|
||||||
|
language: "es",
|
||||||
|
placeholder: this.placeholders.pais,
|
||||||
|
allowClear: true,
|
||||||
|
width: "100%"
|
||||||
|
})
|
||||||
|
.on('select2:select', (e) => this.onCountrySelect(e))
|
||||||
|
.on('select2:clear', () => {
|
||||||
|
this.resetFieldWithDependencies('c_pais');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Estado
|
||||||
|
$(c_estado)
|
||||||
|
.select2({
|
||||||
|
language: "es",
|
||||||
|
placeholder: this.placeholders.estado,
|
||||||
|
allowClear: true,
|
||||||
|
width: "100%"
|
||||||
|
})
|
||||||
|
.on('select2:select', (e) => this.onStateSelect(e))
|
||||||
|
.on('select2:clear', () => {
|
||||||
|
this.resetFieldWithDependencies('c_estado');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Localidad
|
||||||
|
$(c_localidad)
|
||||||
|
.select2({
|
||||||
|
language: "es",
|
||||||
|
placeholder: this.placeholders.localidad,
|
||||||
|
allowClear: true,
|
||||||
|
width: "100%"
|
||||||
|
})
|
||||||
|
.on('select2:select', (e) => this.onLocalitySelect(e))
|
||||||
|
.on('select2:clear', () => {
|
||||||
|
this.resetFieldWithDependencies('c_localidad');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Municipio
|
||||||
|
$(c_municipio)
|
||||||
|
.select2({
|
||||||
|
ajax: {
|
||||||
|
url: this.ajaxRoutes['municipio'],
|
||||||
|
type: "post",
|
||||||
|
delay: 250,
|
||||||
|
dataType: 'json',
|
||||||
|
data: (params) => ({
|
||||||
|
_token: this.csrfToken,
|
||||||
|
select2Mode: true,
|
||||||
|
searchTerm: params.term,
|
||||||
|
c_estado: $(c_estado).val(),
|
||||||
|
}),
|
||||||
|
processResults: (response) => ({
|
||||||
|
results: response
|
||||||
|
}),
|
||||||
|
cache: true
|
||||||
|
},
|
||||||
|
language: "es",
|
||||||
|
placeholder: this.placeholders.municipio,
|
||||||
|
allowClear: true,
|
||||||
|
width: "100%"
|
||||||
|
})
|
||||||
|
.on('select2:select', (e) => this.onMunicipalitySelect(e))
|
||||||
|
.on('select2:clear', () => {
|
||||||
|
this.resetFieldWithDependencies('c_municipio');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Colonia
|
||||||
|
$(c_colonia)
|
||||||
|
.select2({
|
||||||
|
ajax: {
|
||||||
|
url: this.ajaxRoutes['colonia'],
|
||||||
|
type: "post",
|
||||||
|
delay: 250,
|
||||||
|
dataType: 'json',
|
||||||
|
data: (params) => ({
|
||||||
|
_token: this.csrfToken,
|
||||||
|
select2Mode: true,
|
||||||
|
searchTerm: params.term,
|
||||||
|
c_codigo_postal: $(c_codigo_postal).val(),
|
||||||
|
c_estado: $(c_estado).val(),
|
||||||
|
c_municipio: $(c_municipio).val(),
|
||||||
|
}),
|
||||||
|
processResults: (response) => ({
|
||||||
|
results: response
|
||||||
|
}),
|
||||||
|
cache: true
|
||||||
|
},
|
||||||
|
language: "es",
|
||||||
|
placeholder: this.placeholders.colonia,
|
||||||
|
allowClear: true,
|
||||||
|
width: "100%"
|
||||||
|
})
|
||||||
|
.on('select2:select', (e) => this.onColoniaSelect(e))
|
||||||
|
.on('select2:clear', () => {
|
||||||
|
this.livewireInstance.set('c_colonia', null, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeZipCodeInput() {
|
||||||
|
const { c_codigo_postal } = this.formSelectors;
|
||||||
|
|
||||||
|
let lastPostalCode = ''; // Para evitar solicitudes duplicadas
|
||||||
|
let debounceTimeout;
|
||||||
|
|
||||||
|
$(c_codigo_postal).on('input', () => {
|
||||||
|
const postalCode = $(c_codigo_postal).val();
|
||||||
|
|
||||||
|
clearTimeout(debounceTimeout); // Cancelar el temporizador anterior
|
||||||
|
|
||||||
|
this.synchronizedZipCode = false;
|
||||||
|
|
||||||
|
debounceTimeout = setTimeout(() => {
|
||||||
|
if (postalCode.length === 5 && postalCode !== lastPostalCode) {
|
||||||
|
lastPostalCode = postalCode;
|
||||||
|
|
||||||
|
this.fetchZipCode(postalCode);
|
||||||
|
}
|
||||||
|
}, 300); // Ejecutar después de 300 ms de inactividad
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchZipCode(postalCode) {
|
||||||
|
try {
|
||||||
|
const response = await this.makeAjaxPost(this.ajaxRoutes.codigo_postal, {
|
||||||
|
_token: this.csrfToken,
|
||||||
|
searchTerm: postalCode,
|
||||||
|
firstRow: true,
|
||||||
|
rawMode: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Realizar solicitud AJAX para obtener datos del código postal
|
||||||
|
if (response.c_codigo_postal) {
|
||||||
|
// Limpiamos Interface
|
||||||
|
this.resetFieldWithDependencies('c_codigo_postal', response.c_codigo_postal);
|
||||||
|
|
||||||
|
// Cargamos Estados
|
||||||
|
this.toggleSelector('c_estado', response.c_estado);
|
||||||
|
|
||||||
|
// Cargar localidades
|
||||||
|
this.fillSelectOptions('c_localidad', {[response.c_localidad]: response.localidad }, response.c_localidad);
|
||||||
|
|
||||||
|
// Cargar municipios
|
||||||
|
this.fillSelectOptions('c_municipio', {[response.c_municipio]: response.municipio}, response.c_municipio);
|
||||||
|
|
||||||
|
// Abrir select de colonia
|
||||||
|
this.openSelect2('c_colonia');
|
||||||
|
|
||||||
|
// Marcar como sincronizado el código postal
|
||||||
|
this.synchronizedZipCode = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
const { notification } = this.formSelectors;
|
||||||
|
|
||||||
|
// Emitir una notificación simple desde JavaScript
|
||||||
|
window.livewireNotification.emitNotification({
|
||||||
|
message: `Código postal ${postalCode} no encontrado.`,
|
||||||
|
type: 'warning',
|
||||||
|
target: notification,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al obtener datos del código postal:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onCountrySelect(event) {
|
||||||
|
const countryCode = event.params.data.id;
|
||||||
|
|
||||||
|
// Limpiamos Interface
|
||||||
|
this.resetFieldWithDependencies('c_pais', countryCode);
|
||||||
|
|
||||||
|
// Cargamos Interface de estados
|
||||||
|
this.fetchStates(countryCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchStates(countryCode) {
|
||||||
|
try {
|
||||||
|
const response = await this.makeAjaxPost(this.ajaxRoutes.estado, {
|
||||||
|
_token: this.csrfToken,
|
||||||
|
c_pais: countryCode
|
||||||
|
});
|
||||||
|
|
||||||
|
// Realizamos solicitud AJAX para obtener datos de los estados
|
||||||
|
if (response) {
|
||||||
|
const { c_codigo_postal } = this.formSelectors;
|
||||||
|
|
||||||
|
// Cargar los estados
|
||||||
|
this.fillSelectOptions('c_estado', response);
|
||||||
|
|
||||||
|
// Ocultar y resetear los campos si no es México
|
||||||
|
if (countryCode === 'MEX') {
|
||||||
|
$('.if_local_address_show').show();
|
||||||
|
|
||||||
|
// Colocar el foco en el código postal
|
||||||
|
$(c_codigo_postal).focus();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$('.if_local_address_show').hide();
|
||||||
|
|
||||||
|
// Abrir select de estado
|
||||||
|
this.openSelect2('c_estado');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar los estados:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onStateSelect(event) {
|
||||||
|
const { c_pais, direccion } = this.formSelectors;
|
||||||
|
const stateId = event.params.data.id;
|
||||||
|
|
||||||
|
// Limpiar Interface
|
||||||
|
this.resetFieldWithDependencies('c_estado', stateId);
|
||||||
|
|
||||||
|
// Si es México
|
||||||
|
if($(c_pais).val() == 'MEX'){
|
||||||
|
// Cargar localidades
|
||||||
|
this.fetchLocalities(stateId);
|
||||||
|
|
||||||
|
}else{
|
||||||
|
// Colocar el foco en la dirección
|
||||||
|
$(direccion).focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchLocalities(stateId) {
|
||||||
|
try {
|
||||||
|
const response = await this.makeAjaxPost(this.ajaxRoutes.localidad, {
|
||||||
|
_token: this.csrfToken,
|
||||||
|
c_estado: stateId,
|
||||||
|
limit: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
const { c_localidad } = this.formSelectors;
|
||||||
|
|
||||||
|
// Cargar localidades
|
||||||
|
this.fillSelectOptions('c_localidad', response, c_localidad);
|
||||||
|
|
||||||
|
// Abrir select de localidad
|
||||||
|
this.openSelect2('c_localidad');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar las localidades:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onLocalitySelect(event) {
|
||||||
|
const locationId = event.params.data.id;
|
||||||
|
|
||||||
|
// Limpiar Interface
|
||||||
|
this.resetFieldWithDependencies('c_localidad', locationId);
|
||||||
|
|
||||||
|
// Abrir select de municipio
|
||||||
|
this.openSelect2('c_municipio');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onMunicipalitySelect(event) {
|
||||||
|
const municipalityId = event.params.data.id;
|
||||||
|
|
||||||
|
// Limpiar Interface
|
||||||
|
this.resetFieldWithDependencies('c_municipio', municipalityId);
|
||||||
|
|
||||||
|
// Habilitamos colonias
|
||||||
|
this.toggleSelector('c_colonia');
|
||||||
|
|
||||||
|
// Abrir select colonia
|
||||||
|
this.openSelect2('c_colonia');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onColoniaSelect(event) {
|
||||||
|
const coloniaId = event.params.data.id;
|
||||||
|
|
||||||
|
// Cargar colonia
|
||||||
|
this.fillSelectOptions('c_colonia', {[coloniaId]: event.params.data.text}, coloniaId);
|
||||||
|
|
||||||
|
// Actualizar código postal si no está sincronizado
|
||||||
|
if(!this.synchronizedZipCode){
|
||||||
|
this.fetchZipCodeByData(this.livewireInstance.c_estado, this.livewireInstance.c_municipio, coloniaId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchZipCodeByData(stateId, municipalityId, coloniaId) {
|
||||||
|
try {
|
||||||
|
const response = await this.makeAjaxPost(this.ajaxRoutes.colonia, {
|
||||||
|
_token: this.csrfToken,
|
||||||
|
c_estado: stateId,
|
||||||
|
c_municipio: municipalityId,
|
||||||
|
c_colonia: coloniaId,
|
||||||
|
firstRow: true,
|
||||||
|
rawMode: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
const { c_codigo_postal, direccion } = this.formSelectors;
|
||||||
|
|
||||||
|
// Actualizar código postal si no está sincronizado
|
||||||
|
if($(c_codigo_postal).val() !== response.c_codigo_postal){
|
||||||
|
$(c_codigo_postal).val(response.c_codigo_postal);
|
||||||
|
|
||||||
|
// Actualizar en Livewire
|
||||||
|
this.livewireInstance.set('c_codigo_postal', response.c_codigo_postal, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marcar como sincronizado el código postal
|
||||||
|
this.synchronizedZipCode = true;
|
||||||
|
|
||||||
|
// Abrir select colonia
|
||||||
|
$(direccion).focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error al cargar las localidades:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fillSelectOptions(selector, data, selected) {
|
||||||
|
const placeholder = this.placeholders[selector] || 'Selecciona una opción';
|
||||||
|
const $selector = $(this.formSelectors[selector]);
|
||||||
|
|
||||||
|
// Actualizar las opciones directamente en Livewire
|
||||||
|
this.livewireInstance.set(`${selector}_options`, data, false);
|
||||||
|
|
||||||
|
// Limpiar y agregar las nuevas opciones al selector
|
||||||
|
$selector.empty().append(new Option(placeholder, '', true, true)); // Agregar el placeholder
|
||||||
|
|
||||||
|
// Agregar opciones obtenidas por AJAX
|
||||||
|
if (typeof data === 'object' && Object.keys(data).length > 0) {
|
||||||
|
$.each(data, (id, value) => {
|
||||||
|
$selector.append(new Option(value, id, false, id == selected));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar el valor seleccionado si corresponde
|
||||||
|
if (selected) {
|
||||||
|
this.livewireInstance.set(selector, selected, false);
|
||||||
|
|
||||||
|
$selector.val(selected).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Habilitar el select
|
||||||
|
$selector.prop('disabled', false).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSelector(selector, selected = null) {
|
||||||
|
const $selector = $(this.formSelectors[selector]);
|
||||||
|
|
||||||
|
if(typeof selected === 'number' || typeof selected === 'string'){
|
||||||
|
this.livewireInstance.set(selector, selected, false);
|
||||||
|
|
||||||
|
$selector
|
||||||
|
.val(selected)
|
||||||
|
.trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
$selector.prop('disabled', !(selected || $selector.val())).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
resetFieldWithDependencies(field, value = null) {
|
||||||
|
const dependencies = {
|
||||||
|
c_codigo_postal: ['c_localidad', 'c_municipio', 'c_colonia'],
|
||||||
|
c_pais: ['c_codigo_postal', 'c_estado', 'c_localidad', 'c_municipio', 'c_colonia'],
|
||||||
|
c_estado: ['c_codigo_postal', 'c_localidad', 'c_municipio', 'c_colonia'],
|
||||||
|
c_localidad: ['c_codigo_postal', 'c_municipio', 'c_colonia'],
|
||||||
|
c_municipio: ['c_codigo_postal', 'c_colonia']
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetFields = (fields) => {
|
||||||
|
fields.forEach((key) => {
|
||||||
|
const field = this.formSelectors[key]; // Obtener el selector por clave
|
||||||
|
const placeholder = this.placeholders[key] || 'Selecciona una opción';
|
||||||
|
const $field = $(field);
|
||||||
|
|
||||||
|
// Limpiar valor en Livewire
|
||||||
|
if (this.livewireInstance[key] !== null) {
|
||||||
|
this.livewireInstance.set(key, null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($field.is('select')) {
|
||||||
|
// Resetear select
|
||||||
|
$field.empty()
|
||||||
|
.append(new Option(placeholder, '', true, true))
|
||||||
|
.prop('disabled', true)
|
||||||
|
.trigger('change.select2'); // Actualizar select2
|
||||||
|
|
||||||
|
} else if ($field.is('input[type="text"]') || $field.is('input[type="number"]')) {
|
||||||
|
// Resetear input de texto o número
|
||||||
|
$field.val('');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
console.warn(`El campo ${field} no es un input ni un select válido.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Limpieza de campos dependientes
|
||||||
|
if (dependencies[field]) {
|
||||||
|
resetFields(dependencies[field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actualizar el valor del campo principal en Livewire
|
||||||
|
this.livewireInstance.set(field, value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
openSelect2(selector) {
|
||||||
|
const $selector = $(this.formSelectors[selector]);
|
||||||
|
|
||||||
|
$selector.prop('disabled', false).trigger('change.select2');
|
||||||
|
|
||||||
|
setTimeout(() => $selector.select2('open'), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
async makeAjaxPost(url, data) {
|
||||||
|
try {
|
||||||
|
const response = await $.post(url, {
|
||||||
|
...data,
|
||||||
|
_token: this.csrfToken
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error al realizar la solicitud AJAX a ${url}:`, error);
|
||||||
|
|
||||||
|
window.livewireNotification.emitNotification({
|
||||||
|
message: `Error al intentar realizar la solicitud.`,
|
||||||
|
target: this.formSelectors.notification,
|
||||||
|
type: 'danger',
|
||||||
|
});
|
||||||
|
|
||||||
|
return null; // Devuelve null en caso de error para evitar llamadas infinitas
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDefaultValuesFromLivewire() {
|
||||||
|
const { c_pais, c_estado, c_localidad, c_municipio, c_colonia, c_codigo_postal } = this.livewireInstance;
|
||||||
|
const { c_pais: paisSelector, c_estado: estadoSelector, c_localidad: localidadSelector, c_municipio: municipioSelector, c_colonia: coloniaSelector, c_codigo_postal: codigoPostalSelector } = this.formSelectors;
|
||||||
|
|
||||||
|
// Cargar país por defecto
|
||||||
|
if (c_pais && $(paisSelector).val() !== c_pais) {
|
||||||
|
$(paisSelector).val(c_pais).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cargar estados por defecto si están disponibles
|
||||||
|
if (c_estado && $(estadoSelector).val() !== c_estado) {
|
||||||
|
$(estadoSelector).val(c_estado).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si el país es México, mostrar campos de dirección
|
||||||
|
if (c_pais === 'MEX') {
|
||||||
|
$('.if_local_address_show').show();
|
||||||
|
|
||||||
|
if (c_localidad && $(localidadSelector).val() !== c_localidad) {
|
||||||
|
$(localidadSelector).val(c_localidad).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c_municipio && $(municipioSelector).val() !== c_municipio) {
|
||||||
|
$(municipioSelector).val(c_municipio).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c_colonia && $(coloniaSelector).val() !== c_colonia) {
|
||||||
|
$(coloniaSelector).val(c_colonia).trigger('change.select2');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c_codigo_postal && $(codigoPostalSelector).val() !== c_codigo_postal) {
|
||||||
|
$(codigoPostalSelector).val(c_codigo_postal);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$('.if_local_address_show').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
window.AddressFormHandler = AddressFormHandler;
|
93
resources/assets/js/bootstrap-table/contactsFormatters.js
Normal file
93
resources/assets/js/bootstrap-table/contactsFormatters.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
import {routes} from '../../../../../laravel-vuexy-admin/resources/assets/js/bootstrap-table/globalConfig.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const contactActionFormatter = (value, row, index) => {
|
||||||
|
if (!row.id) return '';
|
||||||
|
|
||||||
|
const showUrl = routes['admin.contact.show'].replace(':id', row.id);
|
||||||
|
const editUrl = routes['admin.contact.edit'].replace(':id', row.id);
|
||||||
|
const deleteUrl = routes['admin.contact.delete'].replace(':id', row.id);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<a href="${editUrl}" title="Editar" class="icon-button">
|
||||||
|
<i class="ti ti-edit"></i>
|
||||||
|
</a>
|
||||||
|
<a href="${deleteUrl}" title="Eliminar" class="icon-button">
|
||||||
|
<i class="ti ti-trash"></i>
|
||||||
|
</a>
|
||||||
|
<a href="${showUrl}" title="Ver" class="icon-button">
|
||||||
|
<i class="ti ti-eye"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
`.trim();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const agentFormatter = (value, row, index) => {
|
||||||
|
if (!row.agent_name) return '';
|
||||||
|
|
||||||
|
const email = row.agent_email || 'Sin correo';
|
||||||
|
const userUrl = routes['admin.user.show'].replace(':id', row.id);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<a href="${userUrl}" class="font-medium text-slate-600 hover:underline block text-wrap">${row.agent_name}</a>
|
||||||
|
<small class="text-muted">${email}</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const contactParentFormatter = (value, row, index) => {
|
||||||
|
if (!row.parent_name) return '';
|
||||||
|
|
||||||
|
const email = row.parent_email || 'Sin correo';
|
||||||
|
const showUrl = routes['admin.contact.show'].replace(':id', row.id);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<a href="${showUrl}" class="font-medium text-slate-600 hover:underline block text-wrap">${row.parent_name}</a>
|
||||||
|
<small class="text-muted">${email}</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const emailFormatter = (value, row, index) => {
|
||||||
|
if (!value) return '';
|
||||||
|
return `
|
||||||
|
<a href="mailto:${value}" class="flex items-center space-x-2 text-blue-600 hover:underline">
|
||||||
|
<i class="fa-solid fa-envelope"></i>
|
||||||
|
<span>${value}</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const telFormatter = (value, row, index) => {
|
||||||
|
if (!value) return '';
|
||||||
|
return `
|
||||||
|
<a href="tel:${value}" class="flex items-center space-x-2 text-green-600 hover:underline">
|
||||||
|
<i class="fa-solid fa-phone"></i>
|
||||||
|
<span class="whitespace-nowrap">${value}</span>
|
||||||
|
</a>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const direccionFormatter = (value, row, index) => {
|
||||||
|
let direccion = row.direccion ? row.direccion.trim() : '';
|
||||||
|
let numExt = row.num_ext ? ` #${row.num_ext}` : '';
|
||||||
|
let numInt = row.num_int ? `, Int. ${row.num_int}` : '';
|
||||||
|
|
||||||
|
let fullAddress = `${direccion}${numExt}${numInt}`.trim();
|
||||||
|
|
||||||
|
return fullAddress ? `<span class="whitespace-nowrap">${fullAddress}</span>` : '<span class="text-muted">Sin dirección</span>';
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
29
resources/views/components/card/address.blade.php
Normal file
29
resources/views/components/card/address.blade.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
@props([
|
||||||
|
'uid' => uniqid(),
|
||||||
|
'paisOptions' => [],
|
||||||
|
'estadoOptions' => [],
|
||||||
|
'localidadOptions' => [],
|
||||||
|
'municipioOptions' => [],
|
||||||
|
'coloniaOptions' => [],
|
||||||
|
])
|
||||||
|
|
||||||
|
<x-vuexy-admin::card.basic title="Dirección">
|
||||||
|
<div class="address-notification"></div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<x-vuexy-admin::form.select :uid="$uid" model="c_pais" label="País" parentClass="col-8" :options="$paisOptions" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uid" model="c_codigo_postal" label="Código Postal" max="5" align="center" parentClass="col-4 if_local_address_show" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<x-vuexy-admin::form.select :uid="$uid" model="c_estado" label="Estado" :options="$estadoOptions" />
|
||||||
|
<x-vuexy-admin::form.select :uid="$uid" model="c_localidad" label="Localidad" :options="$localidadOptions" parentClass="if_local_address_show" />
|
||||||
|
<x-vuexy-admin::form.select :uid="$uid" model="c_municipio" label="Municipio" :options="$municipioOptions" parentClass="if_local_address_show" />
|
||||||
|
<x-vuexy-admin::form.select :uid="$uid" model="c_colonia" label="Colonia" :options="$coloniaOptions" parentClass="if_local_address_show" />
|
||||||
|
|
||||||
|
<x-vuexy-admin::form.input :uid="$uid" model="direccion" label="Dirección" />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<x-vuexy-admin::form.input :uid="$uid" model="num_ext" label="Número exterior" parentClass="col-6" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uid" model="num_int" label="Número interior" parentClass="col-6" />
|
||||||
|
</div>
|
||||||
|
</x-vuexy-admin::card.basic>
|
15
resources/views/components/card/location.blade.php
Normal file
15
resources/views/components/card/location.blade.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@props([
|
||||||
|
'uid' => uniqid(),
|
||||||
|
'mapId' => 'geo_map',
|
||||||
|
'mapHeight' => '400px',
|
||||||
|
'searchPlaceholder' => 'Buscar ubicación',
|
||||||
|
])
|
||||||
|
|
||||||
|
<x-vuexy-admin::card.basic title="Ubicación">
|
||||||
|
<x-vuexy-admin::form.textarea :uid="$uid" name="location_search" label="Dirección de búsqueda" :placeholder="$searchPlaceholder" rows="2" button-icon="ti ti-map-pin-search" onClickButton="clearCoordinates()" />
|
||||||
|
<div class="row">
|
||||||
|
<x-vuexy-admin::form.input :uid="$uid" model="lat" label="Latitud" type="number" step="0.000001" max="90" min="-90" parentClass="col-6" align="center" size="small" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uid" model="lng" label="Longitud" type="number" step="0.000001" max="180" min="-180" parentClass="col-6" align="center" size="small" />
|
||||||
|
</div>
|
||||||
|
<div style="height: {{ $mapHeight }}; z-index: 1;" id="locationMap_{{ $uid }}"></div>
|
||||||
|
</x-vuexy-admin::card.basic>
|
28
resources/views/contacts/crud.blade.php
Normal file
28
resources/views/contacts/crud.blade.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
|
||||||
|
|
||||||
|
@section('title', $contacto->name)
|
||||||
|
|
||||||
|
@section('vendor-style')
|
||||||
|
@vite([
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatables.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/select2/select2.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/dropzone/dropzone.scss',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@livewire('contact-form', ['userId' => $contacto->id])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('vendor-script')
|
||||||
|
@vite([
|
||||||
|
'resources/assets/admin/vendor/libs/moment/moment.js',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatable-lang-es.js',
|
||||||
|
'resources/assets/admin/vendor/libs/dropzone/dropzone.js',
|
||||||
|
'resources/assets/admin/vendor/libs/select2/select2.js',
|
||||||
|
'resources/assets/admin/vendor/libs/jquery-validation/jquery.validate.js',
|
||||||
|
])
|
||||||
|
@endsection
|
31
resources/views/contacts/index.blade.php
Normal file
31
resources/views/contacts/index.blade.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
|
||||||
|
|
||||||
|
@section('title', 'Contactos')
|
||||||
|
|
||||||
|
@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',
|
||||||
|
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/dropzone/dropzone.scss',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('vendor-script')
|
||||||
|
@vite([
|
||||||
|
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js',
|
||||||
|
])
|
||||||
|
@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',
|
||||||
|
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/dropzone/dropzone.js',
|
||||||
|
])
|
||||||
|
@endpush
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
@livewire('contact-index')
|
||||||
|
@livewire('contact-offcanvas-form')
|
||||||
|
@endsection
|
28
resources/views/contacts/show.blade.php
Normal file
28
resources/views/contacts/show.blade.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
|
||||||
|
|
||||||
|
@section('title', $contact->name)
|
||||||
|
|
||||||
|
@section('vendor-style')
|
||||||
|
@vite([
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatables.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/select2/select2.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/dropzone/dropzone.scss',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('vendor-script')
|
||||||
|
@vite([
|
||||||
|
'resources/assets/admin/vendor/libs/moment/moment.js',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatable-lang-es.js',
|
||||||
|
'resources/assets/admin/vendor/libs/dropzone/dropzone.js',
|
||||||
|
'resources/assets/admin/vendor/libs/select2/select2.js',
|
||||||
|
'resources/assets/admin/vendor/libs/jquery-validation/jquery.validate.js',
|
||||||
|
])
|
||||||
|
@endsection
|
28
resources/views/employees/index.blade.php
Normal file
28
resources/views/employees/index.blade.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
|
||||||
|
|
||||||
|
@section('title', 'Empleados')
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
@section('vendor-script')
|
||||||
|
@vite([
|
||||||
|
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js',
|
||||||
|
])
|
||||||
|
@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('employee-index')
|
||||||
|
@endsection
|
30
resources/views/employees/show.blade.php
Normal file
30
resources/views/employees/show.blade.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
|
||||||
|
|
||||||
|
@section('title', $contacto->name)
|
||||||
|
|
||||||
|
@section('vendor-style')
|
||||||
|
@vite([
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatables.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-responsive-bs5/responsive.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-buttons-bs5/buttons.bootstrap5.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/select2/select2.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/dropzone/dropzone.scss',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<section class="crm-contacts-show">
|
||||||
|
@livewire('admin.crm.contact-view', ['userId' => $contacto->id])
|
||||||
|
</section>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('vendor-script')
|
||||||
|
@vite([
|
||||||
|
'resources/assets/admin/vendor/libs/moment/moment.js',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatable-lang-es.js',
|
||||||
|
'resources/assets/admin/vendor/libs/dropzone/dropzone.js',
|
||||||
|
'resources/assets/admin/vendor/libs/select2/select2.js',
|
||||||
|
'resources/assets/admin/vendor/libs/jquery-validation/jquery.validate.js',
|
||||||
|
])
|
||||||
|
@endsection
|
586
resources/views/livewire/contacts/contacts-index.blade copy.php
Normal file
586
resources/views/livewire/contacts/contacts-index.blade copy.php
Normal file
@ -0,0 +1,586 @@
|
|||||||
|
<section id="crm-contacts-index">
|
||||||
|
<div class="crm-contacts-index alert-errors"></div>
|
||||||
|
|
||||||
|
<div wire:ignore>
|
||||||
|
<div class="query-filters" id="toolbar">
|
||||||
|
<div class="d-flex flex-wrap">
|
||||||
|
<div class="pt-1 pr-2 pb-1" style="min-width: 175px; max-width: 225px">
|
||||||
|
<button data-bs-toggle='offcanvas' data-bs-target='#offcanvasUser' class="btn btn-primary waves-effect waves-light">Agregar contacto</button>
|
||||||
|
</div>
|
||||||
|
<div class="accordion mr-3 mb-3" id="accordionParentFiltrado">
|
||||||
|
<div class="card accordion-item">
|
||||||
|
<h2 class="accordion-header" id="headingFiltrado">
|
||||||
|
<button type="button" class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#accordionFiltrado" aria-expanded="false" aria-controls="accordionFiltrado">
|
||||||
|
Filtrado
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="accordionFiltrado" class="accordion-collapse collapse" aria-labelledby="headingFiltrado" data-bs-parent="#accordionParentFiltrado">
|
||||||
|
<div class="accordion-body">
|
||||||
|
<div class="d-flex flex-wrap">
|
||||||
|
|
||||||
|
<div class="pr-2 pl-4">
|
||||||
|
<div class="form-check form-check-secondary mb-0">
|
||||||
|
<input class="form-check-input" type="checkbox" id="filter_is_prospect" checked="checked">
|
||||||
|
<label class="form-check-label" for="filter_is_prospect">Es prospecto</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-secondary mb-0">
|
||||||
|
<input class="form-check-input" type="checkbox" id="filter_is_prospect" checked="checked">
|
||||||
|
<label class="form-check-label" for="filter_is_customer">Es cliente</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-secondary m-0">
|
||||||
|
<input class="form-check-input" type="checkbox" id="filter_is_provider" checked="checked">
|
||||||
|
<label class="form-check-label" for="filter_is_provider">Es proveedor</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pr-2 pl-4">
|
||||||
|
<div class="form-check form-check-secondary m-0">
|
||||||
|
<input class="form-check-input" type="checkbox" id="filter_is_user">
|
||||||
|
<label class="form-check-label" for="filter_is_user">Es usuario</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-2 pt-1">
|
||||||
|
<button class="btn btn-label-secondary waves-effect py-3 btn-refresh" disabled><i class="fa-solid fa-rotate"></i></button>
|
||||||
|
</div>
|
||||||
|
<div class="pr-2" style="width: 60px;">
|
||||||
|
<a href="javascript:void(0)" class="clear-filters">Limpiar Filtrado</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table id="bt-contacts"></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
@push('page-script')
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const store_route = '{{ route('admin.system.users.store') }}',
|
||||||
|
route_show = '{{ route('admin.contacts.show', ['contact' => '~contact~']) }}',
|
||||||
|
route_destroy = '{{ route('admin.contacts.destroy', ['contact' => '~contact~']) }}',
|
||||||
|
statusList = {!! json_encode($status_list) !!},
|
||||||
|
statusIntCatalogCss = {!! json_encode($status_list_class) !!};
|
||||||
|
|
||||||
|
var btt_height;
|
||||||
|
|
||||||
|
|
||||||
|
// BootstrapTable Petición AJAX
|
||||||
|
function ajaxRequest(params) {
|
||||||
|
let url = '{{ url()->current() }}' +
|
||||||
|
'?' +
|
||||||
|
$.param(params.data) +
|
||||||
|
'&' +
|
||||||
|
$('.query-filters :input').serialize();
|
||||||
|
|
||||||
|
$.get(url).then(function (res) {
|
||||||
|
params.success(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// BootstrapTable Formatter
|
||||||
|
function userAvatarFormatter(value, row, index) {
|
||||||
|
if(row.id){
|
||||||
|
let show_href = route_show.replace('~contacto~', row.id);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'<div class="d-flex justify-content-start align-items-center user-name">',
|
||||||
|
'<div class="avatar-wrapper">',
|
||||||
|
'<div class="avatar avatar-sm me-4">' +
|
||||||
|
'<img src="' + row.profile_photo_url + '" alt="Avatar" class="rounded-circle">' +
|
||||||
|
'</div>',
|
||||||
|
'</div>',
|
||||||
|
'<div class="d-flex flex-column">',
|
||||||
|
'<a href="' + show_href + '" class="text-heading text-truncate"><span class="fw-medium">' + row.name + '</span></a>',
|
||||||
|
'<small>' + row.email + '</small>',
|
||||||
|
'</div>',
|
||||||
|
'</div>',
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function uidFormatter(value, row, index) {
|
||||||
|
if(row.id){
|
||||||
|
let show_href = route_show.replace('~contacto~', row.id);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'<div class="d-flex align-items-center justify-content-center">',
|
||||||
|
'<a href="' + show_href + '" class="whitespace-nowrap" title="Ver Contacto"> ' + row.id + '</a>',
|
||||||
|
@can('crm.contacts.update')
|
||||||
|
'<a href="javascript:;" class="btn btn-icon btn-text-secondary waves-effect waves-light rounded-pill dropdown-toggle hide-arrow" data-bs-toggle="dropdown"><i class="ti ti-dots-vertical ti-md"></i></a>',
|
||||||
|
'<div class="dropdown-menu dropdown-menu-end m-0">',
|
||||||
|
'<a href="javascript:deleteRow(' + row.id + ');" class="dropdown-item delete-record">Eliminar</a>',
|
||||||
|
'</div>',
|
||||||
|
@endcan
|
||||||
|
'</div>'
|
||||||
|
].join('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BootstrapTable Init
|
||||||
|
function initTable(table) {
|
||||||
|
$(table)
|
||||||
|
.bootstrapTable('destroy')
|
||||||
|
.bootstrapTable({
|
||||||
|
height: btt_height,
|
||||||
|
locale: 'es-MX',
|
||||||
|
ajax: "ajaxRequest",
|
||||||
|
toolbar: "#toolbar",
|
||||||
|
search: true,
|
||||||
|
showColumns: true,
|
||||||
|
showColumnsToggleAll: true,
|
||||||
|
showExport: true,
|
||||||
|
showFullscreen: true,
|
||||||
|
showPaginationSwitch: true,
|
||||||
|
showRefresh: true,
|
||||||
|
showToggle: true,
|
||||||
|
clickToSelect: true,
|
||||||
|
minimumCountColumns: 4,
|
||||||
|
fixedColumns: true,
|
||||||
|
fixedNumber: 1,
|
||||||
|
idField: "id",
|
||||||
|
pagination: true,
|
||||||
|
pageList: [10, 25, 50, 100, 500],
|
||||||
|
sidePagination: "server",
|
||||||
|
exportTypes: ['csv', 'txt', 'excel'],
|
||||||
|
exportOptions: {
|
||||||
|
fileName: 'Contactos',
|
||||||
|
},
|
||||||
|
sortName: 'users.id',
|
||||||
|
sortOrder: 'desc',
|
||||||
|
mobileResponsive: true,
|
||||||
|
cookie: true,
|
||||||
|
resizable: true,
|
||||||
|
cookieIdTable:"crm-contacts-index",
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
field: 'id',
|
||||||
|
title: 'UID',
|
||||||
|
align: 'center',
|
||||||
|
sortable: true,
|
||||||
|
sortName: 'users.id',
|
||||||
|
switchable: false,
|
||||||
|
formatter: uidFormatter
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
title: 'Nombre',
|
||||||
|
formatter: userAvatarFormatter,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'email',
|
||||||
|
title: 'Correo electrónico',
|
||||||
|
sortable: true,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'tipo_persona',
|
||||||
|
title: 'Tipo persona',
|
||||||
|
visible: false,
|
||||||
|
sortable: true,
|
||||||
|
formatter: window.btFormatter.tipoPersona,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'rfc',
|
||||||
|
title: 'RFC',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'nombre_fiscal',
|
||||||
|
title: 'Nombre fiscal',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'c_regimen_fiscal',
|
||||||
|
title: 'Regimen fiscal',
|
||||||
|
sortable: true,
|
||||||
|
visible: false,
|
||||||
|
formatter: window.btFormatter.regimenFiscal,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'domicilio_fiscal',
|
||||||
|
title: 'Domicilio fiscal',
|
||||||
|
sortable: true,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'estado',
|
||||||
|
title: 'Estado',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'municipio',
|
||||||
|
title: 'Municipio',
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'localidad',
|
||||||
|
title: 'Localidad',
|
||||||
|
sortable: true,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'c_uso_cfdi',
|
||||||
|
title: 'Uso de CFDI',
|
||||||
|
sortable: true,
|
||||||
|
visible: false,
|
||||||
|
formatter: window.btFormatter.usoCfdi,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'cargo',
|
||||||
|
title: 'Cargo',
|
||||||
|
visible: false,
|
||||||
|
sortable: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'is_prospect',
|
||||||
|
title: 'Es prospecto',
|
||||||
|
align: 'center',
|
||||||
|
visible: false,
|
||||||
|
sortable: true,
|
||||||
|
formatter: window.btFormatter.check,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'is_customer',
|
||||||
|
title: 'Es cliente',
|
||||||
|
align: 'center',
|
||||||
|
visible: false,
|
||||||
|
sortable: true,
|
||||||
|
formatter: window.btFormatter.check,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'is_provider',
|
||||||
|
title: 'Es proveedor',
|
||||||
|
align: 'center',
|
||||||
|
visible: false,
|
||||||
|
sortable: true,
|
||||||
|
formatter: window.btFormatter.check,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'is_user',
|
||||||
|
title: 'Es usuario',
|
||||||
|
align: 'center',
|
||||||
|
visible: false,
|
||||||
|
sortable: true,
|
||||||
|
formatter: window.btFormatter.check,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
field: 'status',
|
||||||
|
title: 'Estado',
|
||||||
|
align: 'center',
|
||||||
|
sortable: true,
|
||||||
|
formatter: window.btFormatter.status,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'created_at',
|
||||||
|
title: 'Creado ',
|
||||||
|
align: 'center',
|
||||||
|
sortable: true,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'created_by_name',
|
||||||
|
title: 'Creado por',
|
||||||
|
align: 'center',
|
||||||
|
sortable: true,
|
||||||
|
visible: false,
|
||||||
|
sortName: 'created_by_name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'updated_at',
|
||||||
|
title: 'Modificado ',
|
||||||
|
align: 'center',
|
||||||
|
sortable: true,
|
||||||
|
visible: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSections() {
|
||||||
|
const isProspect = $('#is_prospect').is(':checked');
|
||||||
|
const isCustomer = $('#is_customer').is(':checked');
|
||||||
|
const isProvider = $('#is_provider').is(':checked');
|
||||||
|
const isUser = $('#is_user').is(':checked');
|
||||||
|
|
||||||
|
$('.div-sat').toggle(isCustomer || isProvider);
|
||||||
|
$('.div-sat-customer').toggle(isCustomer);
|
||||||
|
$('.div-user-auth').toggle(isCustomer || isUser);
|
||||||
|
$('.div-roles').toggle(isUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCheckboxes(status) {
|
||||||
|
const isDisabled = status == 1;
|
||||||
|
$('#is_prospect, #is_customer, #is_provider, #is_user').prop('disabled', isDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
load_js_form = () => {
|
||||||
|
$('#userForm .select2')
|
||||||
|
.each(function() {
|
||||||
|
var $this = $(this)
|
||||||
|
|
||||||
|
$this.wrap('<div class="position-relative"></div>')
|
||||||
|
|
||||||
|
$this.select2({
|
||||||
|
dropdownAutoWidth: true,
|
||||||
|
width: '100%',
|
||||||
|
dropdownParent: $this.parent()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#pdf-dropzone")
|
||||||
|
.dropzone({
|
||||||
|
url: '{{ route('admin.contacts.extraer-datos-pdf-constancia') }}',
|
||||||
|
paramName: "file",
|
||||||
|
maxFiles: 1,
|
||||||
|
acceptedFiles: '.pdf',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
success: function(file, response) {
|
||||||
|
if($('#name').val().trim() == '')
|
||||||
|
@this.set('name', response.nombre_fiscal, false);
|
||||||
|
|
||||||
|
@this.set('rfc', response.rfc, false);
|
||||||
|
@this.set('nombre_fiscal', response.nombre_fiscal, false);
|
||||||
|
|
||||||
|
if($('#email').val().trim() == '' && response.email)
|
||||||
|
@this.set('email', response.email, false);
|
||||||
|
|
||||||
|
if(response.c_regimen_fiscal)
|
||||||
|
@this.set('c_regimen_fiscal', response.c_regimen_fiscal, false);
|
||||||
|
|
||||||
|
@this.set('domicilio_fiscal', response.domicilio_fiscal, false);
|
||||||
|
|
||||||
|
$('.pdf-dropzone-div').slideUp(200);
|
||||||
|
},
|
||||||
|
error: function(file, response) {
|
||||||
|
$('.pdf-dropzone-div .error-message').html('<label class="error">' + response + '</label>');
|
||||||
|
|
||||||
|
this.removeAllFiles(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Evento para los checkboxes
|
||||||
|
$('#is_prospect, #is_customer, #is_provider, #is_user').on('change', toggleSections);
|
||||||
|
|
||||||
|
// Evento para el select de estado
|
||||||
|
$('.div-status').on('change', 'select[name="status"]', function() {
|
||||||
|
toggleCheckboxes($(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Previo de imagenes
|
||||||
|
document.getElementById("photo").addEventListener('change', updatePreviewImage);
|
||||||
|
|
||||||
|
|
||||||
|
// Reset form
|
||||||
|
$("#userForm")
|
||||||
|
.on('reset', function(){
|
||||||
|
var form = $("#userForm");
|
||||||
|
|
||||||
|
form.validate().resetForm();
|
||||||
|
|
||||||
|
setTimeout(function(){
|
||||||
|
$('#roles').trigger('change');
|
||||||
|
|
||||||
|
toggleSections();
|
||||||
|
}, 250)
|
||||||
|
|
||||||
|
$('#user-image').prop("src", "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7");
|
||||||
|
|
||||||
|
$('#userForm .alert-errors').html('');
|
||||||
|
|
||||||
|
$('.pdf-dropzone-div').show();
|
||||||
|
|
||||||
|
$("#pdf-dropzone").removeAllFiles(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$("#userForm")
|
||||||
|
.validate({
|
||||||
|
errorClass: 'error',
|
||||||
|
highlight: function(element, errorClass, validClass) {
|
||||||
|
// Agrega la clase de error a la fila (contenedor del campo)
|
||||||
|
$(element).closest('.mb-3').addClass('has-error');
|
||||||
|
},
|
||||||
|
unhighlight: function(element, errorClass, validClass) {
|
||||||
|
// Elimina la clase de error de la fila (contenedor del campo)
|
||||||
|
$(element).closest('.mb-3').removeClass('has-error');
|
||||||
|
},
|
||||||
|
errorPlacement: function(error, element) {
|
||||||
|
// Controla dónde se colocan los mensajes de error
|
||||||
|
error.appendTo(element.closest('.mb-3').find('.error-message'));
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
name: {
|
||||||
|
required: true,
|
||||||
|
minlength: 5
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
required: true,
|
||||||
|
email: true
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
required: function(element) {
|
||||||
|
return !$("#userForm input[name=id]").val() && ($('#is_user').is(':checked') || $('#is_customer').is(':checked'));
|
||||||
|
},
|
||||||
|
minlength: 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
messages: {
|
||||||
|
name: {
|
||||||
|
required: "Por favor ingrese su nombre completo",
|
||||||
|
minlength: "El nombre completo debe tener al menos 8 caracteres"
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
required: "Por favor ingrese su correo electrónico",
|
||||||
|
email: "El valor no es una dirección de correo válida"
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
required: "La contraseña es obligatoria para nuevos usuarios",
|
||||||
|
minlength: "La contraseña debe tener al menos 6 caracteres"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
submitHandler: function(form, event) {
|
||||||
|
// Evita que el formulario se envíe automáticamente
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var form = $("#userForm")[0],
|
||||||
|
data = new FormData(form);
|
||||||
|
|
||||||
|
$('#userForm :input').prop('disabled', true);
|
||||||
|
$('#userForm .alert-errors').html('');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: store_route,
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
|
||||||
|
},
|
||||||
|
data: data,
|
||||||
|
contentType: false,
|
||||||
|
processData: false,
|
||||||
|
cache: false,
|
||||||
|
timeout: 3000,
|
||||||
|
success: function(data) {
|
||||||
|
$('#userForm :input').prop('disabled', false);
|
||||||
|
|
||||||
|
if (data.errors) {
|
||||||
|
$('#userForm .alert-errors').html('<div class="alert alert-danger alert-dismissible fade show" role="alert">' +
|
||||||
|
'<div class="alert-body">' + data.errors + '</div>' +
|
||||||
|
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
|
||||||
|
'</div>');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let $usersIndexAlert = $('.crm-contacts-index.alert-errors');
|
||||||
|
|
||||||
|
$usersIndexAlert.html('<div class="alert alert-success alert-dismissible fade show" role="alert">' +
|
||||||
|
'<div class="alert-body">' +
|
||||||
|
'<p class="mb-0"><strong>' + data.success + '</strong></p>' +
|
||||||
|
'</div>' +
|
||||||
|
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
|
||||||
|
'</div>');
|
||||||
|
|
||||||
|
$('#userForm button[type=reset]').trigger('click');
|
||||||
|
$('#toolbar .clear-filters').trigger('click');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(e) {
|
||||||
|
$('#userForm :input').prop('disabled', false);
|
||||||
|
|
||||||
|
$('#userForm .alert-errors').html('<div class="alert alert-danger alert-dismissible fade show" role="alert">' +
|
||||||
|
'<div class="alert-body">' + e.responseJSON.message + '</div>' +
|
||||||
|
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
|
||||||
|
'</div>');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Inicializar el estado al cargar la página
|
||||||
|
toggleSections();
|
||||||
|
toggleCheckboxes($('select[name="status"]').val());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previo de imagen de perfil
|
||||||
|
updatePreviewImage = (event) => {
|
||||||
|
var file = event.target.files[0],
|
||||||
|
reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = event => {
|
||||||
|
document.getElementById('user-image').setAttribute('src', event.target.result);
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
$(document).ready(function() {
|
||||||
|
var $table = $('#bt-contacts'),
|
||||||
|
$btnRefresh = $('#toolbar .btn-refresh'),
|
||||||
|
$clearFilters = $('a.clear-filters');
|
||||||
|
|
||||||
|
var btt_rest_height = 220,
|
||||||
|
btt_min_height = 600;
|
||||||
|
|
||||||
|
btt_height = (window.innerHeight - btt_rest_height) < btt_min_height?
|
||||||
|
btt_height:
|
||||||
|
window.innerHeight - btt_rest_height;
|
||||||
|
|
||||||
|
var offcanvasElement = document.getElementById('offcanvasUser'),
|
||||||
|
offcanvasUser = new bootstrap.Offcanvas(offcanvasElement);
|
||||||
|
|
||||||
|
const refreshButton = document.querySelector('.btn-refresh');
|
||||||
|
const inputs = document.querySelectorAll('#toolbar input, #toolbar select');
|
||||||
|
|
||||||
|
inputs.forEach(input => {
|
||||||
|
input.addEventListener('change', () => {
|
||||||
|
refreshButton.disabled = false;
|
||||||
|
refreshButton.classList.remove('btn-label-secondary');
|
||||||
|
refreshButton.classList.add('btn-label-success');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Button Refresh
|
||||||
|
$btnRefresh
|
||||||
|
.on('click', () => {
|
||||||
|
$table.bootstrapTable('refresh');
|
||||||
|
refreshButton.disabled = true;
|
||||||
|
refreshButton.classList.remove('btn-label-success');
|
||||||
|
refreshButton.classList.add('btn-label-secondary');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Button clear filters
|
||||||
|
$clearFilters
|
||||||
|
.on('click', () => {
|
||||||
|
$table.bootstrapTable('resetSearch', ''); // Inicializa la búsqueda con cadena vacía
|
||||||
|
refreshButton.disabled = true;
|
||||||
|
refreshButton.classList.remove('btn-label-success');
|
||||||
|
refreshButton.classList.add('btn-label-secondary');
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
initTable('#bt-contacts'); // Una vez que todos los scripts estén cargados, inicializa Bootstrap Table
|
||||||
|
|
||||||
|
|
||||||
|
load_js_form();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
165
resources/views/livewire/contacts/form.blade.php
Normal file
165
resources/views/livewire/contacts/form.blade.php
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
<div>
|
||||||
|
<x-vuexy-admin::form id="{{ $formId }}" :mode="$mode" wireSubmit="onSubmit" actionPosition="both">
|
||||||
|
<x-slot name="actions">
|
||||||
|
<x-vuexy-admin::button.offcanvas-buttons :mode="$mode" :tagName="$tagName" />
|
||||||
|
</x-slot>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
{{-- Identificación --}}
|
||||||
|
<x-vuexy-admin::card.basic title="Identificación">
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="code" label="Identificador único" icon="ti ti-tag" placeholder="UID code" autofocus autocomplete="off" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="name" label="Nombre de la sucursal" autocomplete="organization" />
|
||||||
|
<x-vuexy-admin::form.textarea :uid="$uniqueId" model="description" label="Descripción" placeholder="Descripción de la sucursal" :autosize=true />
|
||||||
|
</x-vuexy-admin::card.basic>
|
||||||
|
|
||||||
|
{{-- Series de facturación --}}
|
||||||
|
<x-vuexy-admin::card.basic title="Series de facturación">
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="serie_ingresos" label="Serie para Ingresos" inline=true :labelCol=6 :inputCol=6 maxlength="5" autocomplete="off" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="serie_egresos" label="Serie para Egresos" inline=true :labelCol=6 :inputCol=6 maxlength="5" autocomplete="off" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="serie_pagos" label="Serie para Pagos" inline=true :labelCol=6 :inputCol=6 maxlength="5" autocomplete="off" />
|
||||||
|
</x-vuexy-admin::card.basic>
|
||||||
|
|
||||||
|
{{-- Configuraciones --}}
|
||||||
|
<x-vuexy-admin::card.basic title="Configuraciones">
|
||||||
|
<x-vuexy-admin::form.checkbox uid="random" model="status" label="Habilitar sucursal" switch="true" />
|
||||||
|
<x-vuexy-admin::form.checkbox uid="random" model="show_on_website" label="Mostrar en sitio web" switch="true" />
|
||||||
|
<x-vuexy-admin::form.checkbox uid="random" model="enable_ecommerce" label="eCommerce habilitado en sitio Web" switch="true" />
|
||||||
|
</x-vuexy-admin::card.basic>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
{{-- Información de contacto --}}
|
||||||
|
<x-vuexy-admin::card.basic title="Información de contacto">
|
||||||
|
<x-vuexy-admin::form.input type="tel" :uid="$uniqueId" model="tel" label="Teléfono" icon="ti ti-phone" phoneMode="national" />
|
||||||
|
<x-vuexy-admin::form.input type="tel" :uid="$uniqueId" model="tel2" label="Teléfono alternativo" icon="ti ti-phone" phoneMode="both" />
|
||||||
|
<x-vuexy-admin::form.input type="email" :uid="$uniqueId" model="email" label="Correo electrónico" icon="ti ti-mail" autocomplete="email" inputmode="email" />
|
||||||
|
<x-vuexy-admin::form.select :uid="$uniqueId" model="manager_id" label="Gerente" :options="$manager_id_options" placeholder="Selecciona el gerente" />
|
||||||
|
</x-vuexy-admin::card.basic>
|
||||||
|
|
||||||
|
{{-- Información fiscal --}}
|
||||||
|
<x-vuexy-admin::card.basic title="Información fiscal">
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="rfc" label="RFC" autocomplete="off" pattern="^[A-Z&Ñ]{3,4}[0-9]{6}[A-Z0-9]{3}$" maxlength="13" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="nombre_fiscal" label="Nombre fiscal" autocomplete="organization" />
|
||||||
|
<x-vuexy-admin::form.select :uid="$uniqueId" model="c_regimen_fiscal" label="Régimen fiscal" :options="$c_regimen_fiscal_options" placeholder="Selecciona el régimen fiscal" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="domicilio_fiscal" label="Domicilio fiscal" autocomplete="address-line1" maxlength="100" />
|
||||||
|
</x-vuexy-admin::card.basic>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-4">
|
||||||
|
{{-- Dirección --}}
|
||||||
|
<x-vuexy-contacts::card.address :uid="$uniqueId" :paisOptions="$c_pais_options" :estadoOptions="$c_estado_options" :localidadOptions="$c_localidad_options" :municipioOptions="$c_municipio_options" :coloniaOptions="$c_colonia_options"/>
|
||||||
|
|
||||||
|
{{-- Ubicación --}}
|
||||||
|
<x-vuexy-contacts::card.location :uid="$uniqueId" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</x-vuexy-admin::form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@push('page-script')
|
||||||
|
<script>
|
||||||
|
const initializeStoreForm = (mode) => {
|
||||||
|
const initializeContactInformation = () => {
|
||||||
|
let $manager_id = $("#manager_id_{{ $uniqueId }}");
|
||||||
|
|
||||||
|
$manager_id
|
||||||
|
.select2({
|
||||||
|
language: "es",
|
||||||
|
placeholder: "Selecciona el gerente",
|
||||||
|
allowClear: true,
|
||||||
|
width: "100%"
|
||||||
|
})
|
||||||
|
.on('select2:select select2:clear', function (e) {
|
||||||
|
@this.manager_id = e.params?.data?.id || null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeFiscalInformation = () => {
|
||||||
|
let $c_regimen_fiscal = $("#c_regimen_fiscal_{{ $uniqueId }}");
|
||||||
|
|
||||||
|
$c_regimen_fiscal
|
||||||
|
.select2({
|
||||||
|
language: "es",
|
||||||
|
placeholder: "Selecciona el regimen fiscal",
|
||||||
|
allowClear: true,
|
||||||
|
width: "100%"
|
||||||
|
})
|
||||||
|
.on('select2:select select2:clear', function (e) {
|
||||||
|
@this.c_regimen_fiscal = e.params?.data?.id || null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeLocationIQ = () => {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeAddressFormHandler = () => {
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
|
|
||||||
|
// Definición de selectores AddressFormHandler
|
||||||
|
formSelectors = {
|
||||||
|
c_pais: '#c_pais_{{ $uniqueId }}',
|
||||||
|
c_estado: '#c_estado_{{ $uniqueId }}',
|
||||||
|
c_localidad: '#c_localidad_{{ $uniqueId }}',
|
||||||
|
c_municipio: '#c_municipio_{{ $uniqueId }}',
|
||||||
|
c_colonia: '#c_colonia_{{ $uniqueId }}',
|
||||||
|
c_codigo_postal: '#c_codigo_postal_{{ $uniqueId }}',
|
||||||
|
direccion: '#direccion_{{ $uniqueId }}',
|
||||||
|
notification: '#{{ $formId }} .address-notification'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Definición de rutas AJAX Componente AddressFormHandler
|
||||||
|
const ajaxRoutes = {
|
||||||
|
codigo_postal: "{{ route('admin.core.sat.get.ajax', 'codigo_postal') }}",
|
||||||
|
localidad: "{{ route('admin.core.sat.get.ajax', 'localidad') }}",
|
||||||
|
estado: "{{ route('admin.core.sat.get.ajax', 'estado') }}",
|
||||||
|
municipio: "{{ route('admin.core.sat.get.ajax', 'municipio') }}",
|
||||||
|
colonia: "{{ route('admin.core.sat.get.ajax', 'colonia') }}"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inicializamos el handler de la información de la dirección
|
||||||
|
new AddressFormHandler(formSelectors, ajaxRoutes, @this, csrfToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
const initializeLocationCard = (mode) => {
|
||||||
|
const locationInputs = {
|
||||||
|
search: '#location_search_{{ $uniqueId }}',
|
||||||
|
btnSearch: '#btn_search_{{ $uniqueId }}',
|
||||||
|
lat: '#lat_{{ $uniqueId }}',
|
||||||
|
lng: '#lng_{{ $uniqueId }}',
|
||||||
|
btnClear: '#{{ $formId }} .btn-clear-coords',
|
||||||
|
mapId: 'locationMap_{{ $uniqueId }}',
|
||||||
|
}
|
||||||
|
|
||||||
|
leafletMap = LeafletMapHelper.initializeMap(locationInputs, mode, @this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Inicializamos Tarjeta de Información de contacto
|
||||||
|
initializeContactInformation();
|
||||||
|
|
||||||
|
// Inicializamos Tarjeta de Información fiscal
|
||||||
|
initializeFiscalInformation();
|
||||||
|
|
||||||
|
// Inicializamos Tarjeta de Dirección
|
||||||
|
initializeAddressFormHandler();
|
||||||
|
|
||||||
|
// Inicializamos Tarjeta de Ubicación
|
||||||
|
initializeLocationCard(mode);
|
||||||
|
|
||||||
|
// Deshabilitamos el formulario si estamos eliminando
|
||||||
|
if (mode === 'delete') {
|
||||||
|
window.disableStoreForm('#{{ $formId }}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evento para inicializar el formulario
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
document.addEventListener('on-failed-validation-store', (event) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
initializeStoreForm('{{ $mode }}');
|
||||||
|
}, 10);
|
||||||
|
});
|
||||||
|
|
||||||
|
initializeStoreForm('{{ $mode }}');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
@endpush
|
57
resources/views/livewire/contacts/index.blade.php
Normal file
57
resources/views/livewire/contacts/index.blade.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<x-vuexy-admin::table.bootstrap.manager :tagName="$tagName" :datatableConfig="$bt_datatable" :routes="$routes">
|
||||||
|
<x-slot name="tools">
|
||||||
|
<div class="mb-4 pr-2">
|
||||||
|
<x-vuexy-admin::button.index-off-canvas :label="$singularName" :tagName="$tagName" />
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
<x-slot name="postTools">
|
||||||
|
<div class="mb-4 pr-2">
|
||||||
|
<x-vuexy-admin::file.dropzone id="user_doc_file" model="user_doc_file" message="Crear nuevo usuario" note="XML CFDI o PDF CSF" size="xs" />
|
||||||
|
</div>
|
||||||
|
</x-slot>
|
||||||
|
</x-vuexy-admin::table.bootstrap.manager>
|
||||||
|
|
||||||
|
@push('page-script')
|
||||||
|
<script>
|
||||||
|
// Evento para inicializar el formulario cuando se carga la página
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
let dropzone = new Dropzone("#dropzone_user_doc_file", {
|
||||||
|
url: "#",
|
||||||
|
autoProcessQueue: false,
|
||||||
|
acceptedFiles: ".pdf,.xml",
|
||||||
|
maxFiles: 1,
|
||||||
|
addRemoveLinks: true,
|
||||||
|
dictDefaultMessage: "Arrastra aquí tu PDF de Constancia de Situación Fiscal",
|
||||||
|
init: function () {
|
||||||
|
this.on("addedfile", function (file) {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = function () {
|
||||||
|
let input = document.querySelector("#user_doc_file");
|
||||||
|
let dataTransfer = new DataTransfer();
|
||||||
|
|
||||||
|
dataTransfer.items.add(new File([file], file.name, { type: file.type }));
|
||||||
|
|
||||||
|
// Asignamos solo un archivo, no un FileList
|
||||||
|
//input.files = dataTransfer.files;
|
||||||
|
|
||||||
|
// Livewire solo recibe archivos en forma de input, no como FileList
|
||||||
|
@this.upload('doc_file', dataTransfer.files[0],
|
||||||
|
(uploadedFile) => {
|
||||||
|
@this.call('processDocument');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error("Error al subir PDF:", error);
|
||||||
|
},
|
||||||
|
(progressEvent) => {
|
||||||
|
console.log("Progreso de subida:", progressEvent);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@endpush
|
94
resources/views/livewire/contacts/offcanvas-form.blade.php
Normal file
94
resources/views/livewire/contacts/offcanvas-form.blade.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<div>
|
||||||
|
<x-vuexy-admin::offcanvas.basic :id="$offcanvasId" :tag-name="$tagName">
|
||||||
|
{{-- Dropzone Constancia de Situación Fiscal --}}
|
||||||
|
<x-vuexy-admin::file.dropzone :uid="$uniqueId" model="doc_file" message="CSF PDF o CFDI XML" note="Arrastra aquí un PDF de Constancia de Situación Fiscal o XML de CFDI para cargar los datos al fomulario" />
|
||||||
|
|
||||||
|
<x-vuexy-admin::form :uid="$uniqueId" :id="$formId" :mode="$mode" wireSubmit="onSubmit" actionPosition="both">
|
||||||
|
<x-slot name="actions">
|
||||||
|
<x-vuexy-admin::button.offcanvas-buttons :mode="$mode" :tagName="$tagName" />
|
||||||
|
</x-slot>
|
||||||
|
|
||||||
|
{{-- Selección de Sucursal --}}
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="name" label="Nombre(s)" />
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="last_name" label="Apellidos" />
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<x-vuexy-admin::form.input :uid="$uniqueId" model="code" label="Código de usuario" icon="ti ti-tag" parent-class="col-md-8" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Teléfonos y Correos --}}
|
||||||
|
<x-vuexy-admin::form.input type="email" :uid="$uniqueId" model="email" label="Correo electrónico" icon="ti ti-mail" autocomplete="email" inputmode="email" />
|
||||||
|
<x-vuexy-admin::form.input type="tel" :uid="$uniqueId" model="tel" label="Teléfono" icon="ti ti-phone" phoneMode="both" />
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<x-vuexy-admin::form.textarea :uid="$uniqueId" model="notes" label="Notas / Observaciones" />
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
{{-- Estado del Centro de Trabajo --}}
|
||||||
|
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_partner" label="Es socio" switch />
|
||||||
|
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_employee" label="Es empleado" switch />
|
||||||
|
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_prospect" label="Es prospecto" switch />
|
||||||
|
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_customer" label="Es cliente" switch />
|
||||||
|
<x-vuexy-admin::form.checkbox :uid="$uniqueId" model="is_provider" label="Es proveedor" switch />
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
</x-vuexy-admin::form>
|
||||||
|
</x-vuexy-admin::offcanvas.basic>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@push('page-script')
|
||||||
|
<script>
|
||||||
|
// Evento para inicializar el formulario cuando se carga la página
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const initializeUserForm = () => {
|
||||||
|
let dropzone = new Dropzone("#dropzone_doc_file_{{ $uniqueId }}", {
|
||||||
|
url: "#",
|
||||||
|
autoProcessQueue: false,
|
||||||
|
acceptedFiles: ".pdf,.xml",
|
||||||
|
maxFiles: 1,
|
||||||
|
addRemoveLinks: true,
|
||||||
|
dictDefaultMessage: "Arrastra aquí tu PDF de Constancia de Situación Fiscal",
|
||||||
|
init: function () {
|
||||||
|
this.on("addedfile", function (file) {
|
||||||
|
let reader = new FileReader();
|
||||||
|
reader.onload = function () {
|
||||||
|
let input = document.querySelector("#doc_file_{{ $uniqueId }}");
|
||||||
|
let dataTransfer = new DataTransfer();
|
||||||
|
|
||||||
|
dataTransfer.items.add(new File([file], file.name, { type: file.type }));
|
||||||
|
|
||||||
|
// Asignamos solo un archivo, no un FileList
|
||||||
|
//input.files = dataTransfer.files;
|
||||||
|
|
||||||
|
// Livewire solo recibe archivos en forma de input, no como FileList
|
||||||
|
@this.upload('doc_file', dataTransfer.files[0],
|
||||||
|
(uploadedFile) => {
|
||||||
|
@this.call('processDocument');
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error("Error al subir PDF:", error);
|
||||||
|
},
|
||||||
|
(progressEvent) => {
|
||||||
|
console.log("Progreso de subida:", progressEvent);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var myOffcanvas = document.getElementById('{{ $offcanvasId }}');
|
||||||
|
myOffcanvas.addEventListener('show.bs.offcanvas', function () {
|
||||||
|
initializeUserForm();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
@endpush
|
1685
resources/views/livewire/contacts/show.blade.php
Normal file
1685
resources/views/livewire/contacts/show.blade.php
Normal file
File diff suppressed because it is too large
Load Diff
28
resources/views/suppliers/index.blade.php
Normal file
28
resources/views/suppliers/index.blade.php
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
|
||||||
|
|
||||||
|
@section('title', 'Proveedores')
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
|
@section('vendor-script')
|
||||||
|
@vite([
|
||||||
|
'vendor/koneko/laravel-vuexy-admin/resources/assets/vendor/libs/select2/select2.js',
|
||||||
|
])
|
||||||
|
@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('supplier-index')
|
||||||
|
@endsection
|
27
resources/views/suppliers/show.blade.php
Normal file
27
resources/views/suppliers/show.blade.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
@extends('vuexy-admin::layouts.vuexy.layoutMaster')
|
||||||
|
|
||||||
|
@section('title', $contacto->name)
|
||||||
|
|
||||||
|
@section('vendor-style')
|
||||||
|
@vite([
|
||||||
|
'resources/assets/admin/vendor/libs/select2/select2.scss',
|
||||||
|
'resources/assets/admin/vendor/libs/dropzone/dropzone.scss',
|
||||||
|
])
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<section class="crm-contacts-show">
|
||||||
|
@livewire('admin.crm.contact-view', ['userId' => $contacto->id])
|
||||||
|
</section>
|
||||||
|
@endsection
|
||||||
|
|
||||||
|
@section('vendor-script')
|
||||||
|
@vite([
|
||||||
|
'resources/assets/admin/vendor/libs/moment/moment.js',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatables-bootstrap5.js',
|
||||||
|
'resources/assets/admin/vendor/libs/datatables-bs5/datatable-lang-es.js',
|
||||||
|
'resources/assets/admin/vendor/libs/dropzone/dropzone.js',
|
||||||
|
'resources/assets/admin/vendor/libs/select2/select2.js',
|
||||||
|
'resources/assets/admin/vendor/libs/jquery-validation/jquery.validate.js',
|
||||||
|
])
|
||||||
|
@endsection
|
59
routes/admin.php
Normal file
59
routes/admin.php
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
use Koneko\VuexyContacts\Http\Controllers\ContactController;
|
||||||
|
use Koneko\VuexyContacts\Http\Controllers\SupplierController;
|
||||||
|
use Koneko\VuexyContacts\Http\Controllers\CustomerController;
|
||||||
|
use Koneko\VuexyContacts\Http\Controllers\EmployeeController;
|
||||||
|
|
||||||
|
|
||||||
|
// Grupo raíz para admin con middleware y prefijos comunes
|
||||||
|
Route::prefix('admin')->name('admin.contacts.')->middleware(['web', 'auth', 'admin'])->group(function () {
|
||||||
|
// Contactos
|
||||||
|
Route::controller(ContactController::class)->prefix('contactos/contactos')->name('contacts.')->group(function () {
|
||||||
|
Route::get('/', 'index')->name('index');
|
||||||
|
Route::get('create', 'create')->name('create');
|
||||||
|
Route::get('{contact}', 'show')->name('show');
|
||||||
|
Route::get('{contact}/delete', 'delete')->name('delete');
|
||||||
|
Route::get('{contact}/edit', 'edit')->name('edit');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Contactos
|
||||||
|
Route::controller(ContactController::class)->prefix('contactos')->group(function () {
|
||||||
|
Route::post('extraer-datos-pdf-constancia', 'extraerDataConstancia')->name('extraer-datos-pdf-constancia');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Proveedores
|
||||||
|
Route::controller(SupplierController::class)->prefix('inventario-y-logistica/proveedores')->name('suppliers.')->group(function () {
|
||||||
|
Route::get('/', 'index')->name('index'); // Listar
|
||||||
|
Route::get('create', 'create')->name('create'); // Formulario de creación
|
||||||
|
Route::post('proveedores', 'store')->name('store'); // Guardar
|
||||||
|
Route::get('{supplier}', 'show')->name('show'); // Ver
|
||||||
|
Route::get('{supplier}/edit', 'edit')->name('edit'); // Formulario de edición
|
||||||
|
Route::put('{supplier}', 'update')->name('update'); // Actualizar
|
||||||
|
Route::delete('{supplier}', 'destroy')->name('destroy'); // Eliminar
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clientes
|
||||||
|
Route::controller(CustomerController::class)->prefix('ventas/clientes')->name('customers.')->group(function () {
|
||||||
|
Route::get('/', 'index')->name('index'); // Listar
|
||||||
|
Route::get('create', 'create')->name('create'); // Formulario de creación
|
||||||
|
Route::post('clientes', 'store')->name('store'); // Guardar
|
||||||
|
Route::get('{customer}', 'show')->name('show'); // Ver
|
||||||
|
Route::get('{customer}/edit', 'edit')->name('edit'); // Formulario de edición
|
||||||
|
Route::put('{customer}', 'update')->name('update'); // Actualizar
|
||||||
|
Route::delete('{customer}', 'destroy')->name('destroy'); // Eliminar
|
||||||
|
});
|
||||||
|
|
||||||
|
// Empleados
|
||||||
|
Route::controller(EmployeeController::class)->prefix('rrhh/empleados')->name('employees.')->group(function () {
|
||||||
|
Route::get('/', 'index')->name('index'); // Listar
|
||||||
|
Route::get('create', 'create')->name('create'); // Formulario de creación
|
||||||
|
Route::post('empleados', 'store')->name('store'); // Guardar
|
||||||
|
Route::get('{employee}', 'show')->name('show'); // Ver
|
||||||
|
Route::get('{employee}/edit', 'edit')->name('edit'); // Formulario de edición
|
||||||
|
Route::put('{employee}', 'update')->name('update'); // Actualizar
|
||||||
|
Route::delete('{employee}', 'destroy')->name('destroy'); // Eliminar
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user