first commit

This commit is contained in:
2025-03-05 20:43:35 -06:00
commit aa938a3cab
47 changed files with 4388 additions and 0 deletions

View File

@ -0,0 +1,269 @@
<?php
namespace Koneko\VuexyStoreManager\Livewire\Company;
use Illuminate\Support\Facades\DB;
use Koneko\SatCatalogs\Models\Colonia;
use Koneko\SatCatalogs\Models\Estado;
use Koneko\SatCatalogs\Models\Localidad;
use Koneko\SatCatalogs\Models\Municipio;
use Koneko\SatCatalogs\Models\Pais;
use Koneko\SatCatalogs\Models\RegimenFiscal;
use Koneko\VuexyStoreManager\Models\Store;
use Livewire\Component;
class CompanyIndex extends Component
{
public $btnSubmitText;
public $mode;
public $storeId;
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,
$status,
$show_on_website,
$enable_ecommerce;
public $manager_id_options = [],
$c_regimen_fiscal_options = [],
$c_pais_options = [],
$c_estado_options = [],
$c_localidad_options = [],
$c_municipio_options = [],
$c_colonia_options = [];
protected $listeners = [
'editStore' => 'loadStore',
'confirmDeletionStore' => 'loadStoreForDeletion',
];
public function mount(String $mode = 'create', Store $store = null)
{
$this->mode = $mode;
$this->loadData($store);
$this->loadOptions();
}
private function loadData(Store $store)
{
switch($this->mode){
case 'create':
$this->btnSubmitText = 'Crear sucursal';
$this->c_pais = 'MEX';
$this->status = true;
break;
case 'edit':
$this->btnSubmitText = 'Guardar cambios';
break;
case 'delete':
$this->btnSubmitText = 'Eliminar sucursal';
break;
}
if($store){
$this->storeId = $store->id;
$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->serie_ingresos = $store->serie_ingresos;
$this->serie_egresos = $store->serie_egresos;
$this->serie_pagos = $store->serie_pagos;
$this->c_codigo_postal = $store->c_codigo_postal;
$this->c_pais = $store->c_pais;
$this->c_estado = $store->c_estado;
$this->c_localidad = $store->c_localidad;
$this->c_municipio = $store->c_municipio;
$this->c_colonia = $store->c_colonia;
$this->direccion = $store->direccion;
$this->num_ext = $store->num_ext;
$this->num_int = $store->num_int;
$this->email = $store->email;
$this->tel = $store->tel;
$this->tel2 = $store->tel2;
$this->lat = $store->lat;
$this->lng = $store->lng;
$this->status = $store->status;
$this->show_on_website = $store->show_on_website;
$this->enable_ecommerce = $store->enable_ecommerce;
}
}
private function loadOptions()
{
$this->manager_id_options = DB::table('users')
->select('id', DB::raw("CONCAT(CONCAT_WS(' ', name, last_name), ' - ', email) as full_name"))
//->where('is_user', 1)
->orderBy('full_name')
->pluck('full_name', 'id');
$this->c_regimen_fiscal_options = RegimenFiscal::selectList();
$this->c_pais_options = Pais::selectList();
if($this->mode !== 'create'){
$this->c_estado_options = ['' => 'Seleccione el estado'] + Estado::selectList($this->c_pais)->toArray();
$this->c_localidad_options = ['' => 'Seleccione la localidad'] + Localidad::selectList($this->c_estado)->toArray();
$this->c_municipio_options = ['' => 'Seleccione el municipio'] + Municipio::selectList($this->c_estado, $this->c_municipio)->toArray();
$this->c_colonia_options = ['' => 'Seleccione la colonia'] + Colonia::selectList($this->c_codigo_postal, $this->c_colonia)->toArray();
}
}
public function onSubmit()
{
if ($this->mode === 'delete') {
return $this->delete();
}
return $this->save();
}
private function save()
{
$validatedData = $this->validate([
'code' => 'required|string|max:16',
'name' => 'required|string|max:96',
'description' => 'nullable|string|max:1024',
'manager_id' => 'nullable|exists:users,id',
'rfc' => 'nullable|string|max:13',
'nombre_fiscal' => 'nullable|string|max:255',
'c_regimen_fiscal' => 'nullable|integer',
'domicilio_fiscal' => 'nullable|integer',
'c_pais' => 'nullable|string|max:3',
'c_estado' => 'nullable|string|max:3',
'c_municipio' => 'nullable|integer',
'c_localidad' => 'nullable|integer',
'c_codigo_postal' => 'nullable|integer',
'c_colonia' => 'nullable|integer',
'direccion' => 'nullable|string|max:255',
'num_ext' => 'nullable|string|max:50',
'num_int' => 'nullable|string|max:50',
'lat' => 'nullable|numeric',
'lng' => 'nullable|numeric',
'email' => 'nullable|email|max:96',
'tel' => 'nullable|string|max:15',
'tel2' => 'nullable|string|max:15',
'status' => 'nullable|boolean',
'show_on_website' => 'nullable|boolean',
'enable_ecommerce' => 'nullable|boolean',
]);
try {
$store = Store::updateOrCreate(
[ 'id' => $this->storeId ], // Si $this->storeId es null, creará un nuevo registro
[
'code' => $validatedData['code'],
'name' => $validatedData['name'],
'description' => $validatedData['description'] ?? null,
'manager_id' => $validatedData['manager_id'] ?? null,
'rfc' => $validatedData['rfc'] ?? null,
'nombre_fiscal' => $validatedData['nombre_fiscal'] ?? null,
'c_regimen_fiscal' => $validatedData['c_regimen_fiscal'] ?? null,
'domicilio_fiscal' => $validatedData['domicilio_fiscal'] ?? null,
'c_pais' => $validatedData['c_pais'] ?? null,
'c_estado' => $validatedData['c_estado'] ?? null,
'c_municipio' => $validatedData['c_municipio'] ?? null,
'c_localidad' => $validatedData['c_localidad'] ?? null,
'c_codigo_postal' => $validatedData['c_codigo_postal'] ?? null,
'c_colonia' => $validatedData['c_colonia'] ?? null,
'direccion' => $validatedData['direccion'] ?? null,
'num_ext' => $validatedData['num_ext'] ?? null,
'num_int' => $validatedData['num_int'] ?? null,
'lat' => $validatedData['lat'] ?? null,
'lng' => $validatedData['lng'] ?? null,
'email' => $validatedData['email'] ?? null,
'tel' => $validatedData['tel'] ?? null,
'tel2' => $validatedData['tel2'] ?? null,
'show_on_website' => (bool) $validatedData['show_on_website'],
'enable_ecommerce' => (bool) $validatedData['enable_ecommerce'],
'status' => (bool) $validatedData['status'],
]
);
session()->flash('success', 'Sucursal guardada correctamente.');
return redirect()->route('admin.store-manager.stores.index');
} catch (QueryException $e) {
// Manejar un error específico de SQL/DB, por ejemplo duplicados, FK violation, etc.
// O podrías capturar \Exception para cualquier error genérico
session()->flash('error', 'Ocurrió un error al guardar la sucursal.');
// Opcionalmente: loguear el error para depuración:
\Log::error($e->getMessage());
// Si no haces return, el método continuará, podrías redirigir o quedarte en la misma página
return;
}
}
public function delete()
{
if ($this->storeId) {
try {
Store::find($this->storeId)->delete();
session()->flash('warning', 'Sucursal eliminada correctamente.');
return redirect()->route('admin.store-manager.stores.index');
} catch (QueryException $e) {
// Manejar un error específico de SQL/DB, por ejemplo duplicados, FK violation, etc.
// O podrías capturar \Exception para cualquier error genérico
session()->flash('error', 'Ocurrió un error al eliminar la sucursal.');
// Opcionalmente: loguear el error para depuración:
\Log::error($e->getMessage());
// Si no haces return, el método continuará, podrías redirigir o quedarte en la misma página
return;
}
}
}
public function render()
{
return view('vuexy-store-manager::livewire.company.index');
}
}

190
Livewire/Store/PostForm.php Normal file
View File

@ -0,0 +1,190 @@
<?php
namespace Koneko\VuexyStoreManager\Livewire\Stores;
use Illuminate\Support\Facades\DB;
use Koneko\VuexyWarehouse\Models\Warehouse;
use Livewire\Component;
class StoreForm extends Component
{
public $form_title;
public $mode = 'create';
public $warehouseId,
$store_id,
$workcenter_id,
$code,
$name,
$description,
$is_active = true,
$is_default,
$confirm_delete;
public $store_options = [],
$workcenter_options = [];
protected $listeners = [
'editWarehouse' => 'loadWarehouse',
'confirmDeleteWarehouse' => 'loadWarehouseForDeletion',
];
public function mount()
{
$this->loadOptions();
$this->resetForm();
}
private function loadOptions()
{
$this->store_options = DB::table('stores')
->select('id', 'name')
->orderBy('name')
->pluck('name', 'id');
$this->workcenter_options = DB::table('store_work_centers')
->select('id', 'name')
->orderBy('name')
->pluck('name', 'id');
}
public function loadWarehouse($id)
{
$warehouse = Warehouse::find($id);
if ($warehouse) {
$this->fill($warehouse->only(['id', 'store_id', 'workcenter_id', 'code', 'name', 'description', 'is_active', 'is_default']));
$this->form_title = "Editar: $warehouse->name";
$this->mode = 'edit';
$this->dispatch('on-edit-warehouse-modal');
}
}
public function loadWarehouseForDeletion($id)
{
$warehouse = Warehouse::find($id);
if ($warehouse) {
$this->fill($warehouse->only(['id', 'store_id', 'workcenter_id', 'code', 'name', 'description', 'is_active', 'is_default']));
$this->form_title = "Eliminar: $warehouse->name";
$this->mode = 'delete';
$this->dispatch('on-delete-warehouse-modal');
}
}
public function editWarehouse($id)
{
$warehouse = Warehouse::find($id);
if ($warehouse) {
$this->form_title = 'Editar: ' . $warehouse->name;
$this->mode = 'edit';
$this->warehouseId = $warehouse->id;
$this->store_id = $warehouse->store_id;
$this->workcenter_id = $warehouse->workcenter_id;
$this->code = $warehouse->code;
$this->name = $warehouse->name;
$this->description = $warehouse->description;
$this->is_active = $warehouse->is_active;
$this->is_default = $warehouse->is_default;
$this->dispatch('on-edit-warehouse-modal');
}
}
public function confirmDeleteWarehouse($id)
{
$warehouse = Warehouse::find($id);
if ($warehouse) {
$this->form_title = 'Eliminar: ' . $warehouse->name;
$this->mode = 'delete';
$this->warehouseId = $warehouse->id;
$this->store_id = $warehouse->store_id;
$this->workcenter_id = $warehouse->workcenter_id;
$this->code = $warehouse->code;
$this->name = $warehouse->name;
$this->description = $warehouse->description;
$this->is_active = $warehouse->is_active;
$this->is_default = $warehouse->is_default;
$this->dispatch('on-delete-warehouse-modal');
}
}
public function onSubmit()
{
if ($this->mode === 'delete') {
return $this->delete();
}
return $this->save();
}
private function save()
{
try {
$validatedData = $this->validate([
'store_id' => 'required',
'code' => 'required|string|max:16',
'name' => 'required|string|max:96',
'description' => 'nullable|string|max:1024',
]);
} catch (\Illuminate\Validation\ValidationException $e) {
$this->dispatch('on-failed-validation-warehouse-modal');
$this->dispatch('warehouse-message', ['type' => 'danger', 'message' => 'Error en la validación']);
throw $e;
}
Warehouse::updateOrCreate(
['id' => $this->warehouseId],
[
'store_id' => $validatedData['store_id'],
'workcenter_id' => $this->workcenter_id,
'code' => $validatedData['code'],
'name' => $validatedData['name'],
'description' => $validatedData['description'] ?? null,
'is_active' => (bool) $this->is_active,
'is_default' => (bool) $this->is_default,
]
);
$this->dispatch('warehouse-message', ['type' => 'success', 'message' => 'Almacén guardado correctamente']);
$this->dispatch('reload-warehouse-table');
$this->dispatch('close-warehouse-modal');
$this->resetForm();
}
public function delete()
{
if ($this->warehouseId) {
Warehouse::find($this->warehouseId)->delete();
$this->dispatch('warehouse-message', ['type' => 'warning', 'message' => 'Almacén eliminado']);
$this->dispatch('reload-warehouse-table');
$this->dispatch('close-warehouse-modal');
$this->resetForm();
}
}
public function resetForm()
{
$this->reset(['warehouseId', 'store_id', 'workcenter_id', 'code', 'name', 'description', 'is_default', 'confirm_delete']);
$this->form_title = 'Agregar almacén';
$this->mode = 'create';
$this->is_active = true;
}
public function render()
{
return view('vuexy-warehouse::livewire.stores.form');
}
}

View File

@ -0,0 +1,306 @@
<?php
namespace Koneko\VuexyStoreManager\Livewire\Stores;
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\VuexyStoreManager\Models\Store;
/**
* Class StoreForm
*
* 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 StoreForm 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';
}
}

View File

@ -0,0 +1,230 @@
<?php
namespace Koneko\VuexyStoreManager\Livewire\Stores;
use Koneko\VuexyAdmin\Livewire\Table\AbstractIndexComponent;
use Koneko\VuexyStoreManager\Models\Store;
/**
* Listado de Tiendas, extiende de la clase base AbstractIndexComponent
* para reutilizar la lógica de configuración y renderizado de tablas.
*/
class StoreIndex extends AbstractIndexComponent
{
/**
* Almacena rutas útiles para la funcionalidad de edición o eliminación.
* (En tu caso, lo llenas en mount())
*/
public $routes = [];
/**
* Método que define la clase o instancia del modelo a usar en este Index.
*
* @return string
*/
protected function model(): string
{
return Store::class;
}
/**
* Retorna las columnas (header) de la tabla.
*
* @return array
*/
protected function columns(): array
{
return [
'action' => 'Acciones',
'code' => 'Código',
'name' => 'Nombre de la tienda',
'description' => 'Descripción',
'manager_name' => 'Gerente',
'pais' => 'País',
'estado' => 'Estado',
'localidad' => 'Localidad',
'municipio' => 'Municipio',
'codigo_postal' => 'Código Postal',
'colonia' => 'Colonia',
'direccion' => 'Dirección',
'lat' => 'Latitud',
'lng' => 'Longitud',
'email' => 'Correo de la tienda',
'tel' => 'Teléfono',
'tel2' => 'Teléfono Alternativo',
'rfc' => 'RFC',
'nombre_fiscal' => 'Nombre Fiscal',
'regimen_fiscal' => 'Régimen Fiscal',
'domicilio_fiscal' => 'Domicilio Fiscal',
'show_on_website' => 'Visible en Sitio Web',
'enable_ecommerce' => 'eCommerce',
'status' => 'Estatus',
'created_at' => 'Creada',
'updated_at' => 'Modificada',
];
}
/**
* Retorna el formato (formatter) para cada columna (similar a 'bt_datatable.format').
*
* @return array
*/
protected function format(): array
{
return [
'action' => [
'formatter' => 'storeActionFormatter',
'onlyFormatter' => true,
],
'code' => [
'formatter' => [
'name' => 'dynamicBadgeFormatter',
'params' => ['color' => 'secondary'],
],
'align' => 'center',
'switchable' => false,
],
'name' => [
'switchable' => false,
],
'description' => [
'visible' => false,
],
'codigo_postal' => [
'align' => 'center',
'visible' => false,
],
'manager_name' => [
'formatter' => 'managerFormatter',
'visible' => true,
],
'pais' => [
'align' => 'center',
],
'estado' => [
'formatter' => 'textNowrapFormatter',
'align' => 'center',
],
'localidad' => [
'formatter' => 'textNowrapFormatter',
'visible' => false,
],
'municipio' => [
'formatter' => 'textNowrapFormatter',
'visible' => false,
],
// la segunda definición de 'codigo_postal' la omites, pues ya está arriba
'colonia' => [
'formatter' => 'textNowrapFormatter',
'visible' => false,
],
'direccion' => [
'formatter' => 'direccionFormatter',
'visible' => false,
],
'lat' => [
'align' => 'center',
'visible' => false,
],
'lng' => [
'align' => 'center',
'visible' => false,
],
'email' => [
'visible' => true,
'formatter' => 'emailFormatter',
],
'tel' => [
'formatter' => 'telFormatter',
],
'tel2' => [
'formatter' => 'telFormatter',
'visible' => false,
],
'rfc' => [
'align' => 'center',
'visible' => false,
],
'nombre_fiscal' => [
'formatter' => 'textNowrapFormatter',
'visible' => false,
],
'regimen_fiscal' => [
'visible' => false,
],
'domicilio_fiscal' => [
'align' => 'center',
'visible' => false,
],
'show_on_website' => [
'formatter' => 'dynamicBooleanFormatter',
'align' => 'center',
],
'enable_ecommerce' => [
'formatter' => 'dynamicBooleanFormatter',
'align' => 'center',
],
'status' => [
'formatter' => [
'name' => 'dynamicBooleanFormatter',
'params' => ['tag' => 'activo'],
],
'align' => 'center',
],
'created_at' => [
'formatter' => 'textNowrapFormatter',
'align' => 'center',
'visible' => false,
],
'updated_at' => [
'formatter' => 'textNowrapFormatter',
'align' => 'center',
'visible' => false,
],
];
}
/**
* Sobrescribe la config base de la tabla para inyectar
* tus valores (similar a 'bt_datatable').
*
* @return array
*/
protected function bootstraptableConfig(): array
{
// Llamamos al padre y reemplazamos/ajustamos lo que necesitemos.
return array_merge(parent::bootstraptableConfig(), [
'sortName' => 'code',
'exportFileName' => 'Tiendas',
'showFullscreen' => false,
'showPaginationSwitch'=> false,
'showRefresh' => false,
'pagination' => false,
]);
}
/**
* Montamos el componente (ajustando rutas o algo adicional),
* y llamamos al parent::mount() para que se configure la tabla.
*/
public function mount(): void
{
parent::mount();
// Definimos las rutas específicas de este componente
$this->routes = [
'admin.store-manager.stores.edit' => route('admin.store-manager.stores.edit', ['store' => ':id']),
'admin.store-manager.stores.delete' => route('admin.store-manager.stores.delete', ['store' => ':id']),
];
}
/**
* Retorna la vista a renderizar por este componente.
*
* @return string
*/
protected function viewPath(): string
{
return 'vuexy-store-manager::livewire.stores.index';
}
}

View File

@ -0,0 +1,211 @@
<?php
namespace Koneko\VuexyStoreManager\Livewire\WorkCenters;
use Koneko\VuexyAdmin\Livewire\Table\AbstractIndexComponent;
use Koneko\VuexyStoreManager\Models\StoreWorkCenter;
use Koneko\VuexyStoreManager\Services\StoreCatalogService;
/**
* Index para Centros de Trabajo, extendiendo la clase base AbstractIndexComponent.
*/
class WorkCenterIndex extends AbstractIndexComponent
{
/**
* Usado para filtrar (opcional) o cargar catálogos en la vista.
*/
public $store_id;
/**
* Opciones de tiendas (por ejemplo, para un select de filtrado).
*/
public $storeOptions = [];
/**
* Montamos (inicializamos) el componente y llamamos al mount() del padre
* para configurar la tabla y setear $tagName, $singularName, $formId, etc.
*/
public function mount(): void
{
parent::mount();
// Ahora cargamos las opciones de tienda, usando el servicio necesario.
$storeCatalogService = app(StoreCatalogService::class);
$this->storeOptions = $storeCatalogService->searchCatalog('stores', '', ['limit' => -1]);
}
/**
* Indica la clase (o instancia) de tu modelo.
*
* @return string
*/
protected function model(): string
{
// Retornamos la clase del modelo
return StoreWorkCenter::class;
}
/**
* Retorna las columnas (header) de tu tabla.
*
* @return array
*/
protected function columns(): array
{
return [
'action' => 'Acciones',
'stores_code' => 'Código de Tienda',
'stores_name' => 'Nombre de la Tienda',
'code' => 'Código del Centro',
'name' => 'Nombre del Centro',
'description' => 'Descripción',
'manager_name' => 'Gerente',
'tel' => 'Teléfono',
'tel2' => 'Teléfono Alternativo',
'codigo_postal' => 'Código Postal',
'pais' => 'País',
'estado' => 'Estado',
'localidad' => 'Localidad',
'municipio' => 'Municipio',
'colonia' => 'Colonia',
'direccion' => 'Dirección',
'lat' => 'Latitud',
'lng' => 'Longitud',
'status' => 'Estatus',
'created_at' => 'Creado',
'updated_at' => 'Actualizado',
];
}
/**
* Retorna el formato (formatter) de las columnas.
*
* @return array
*/
protected function format(): array
{
return [
'action' => [
'formatter' => 'workCenterActionFormatter',
'onlyFormatter' => true,
],
'stores_code' => [
'formatter' => [
'name' => 'dynamicBadgeFormatter',
'params' => ['color' => 'secondary'],
],
'align' => 'center',
],
'stores_name' => [
'visible' => false,
],
'code' => [
'formatter' => [
'name' => 'dynamicBadgeFormatter',
'params' => ['color' => 'secondary'],
],
'align' => 'center',
'switchable' => false,
],
'name' => [
'switchable' => false,
],
'description' => [
'visible' => false,
],
'manager_name' => [
'formatter' => 'managerFormatter',
],
'tel' => [
'formatter' => 'telFormatter',
'align' => 'center',
],
'tel2' => [
'formatter' => 'telFormatter',
'align' => 'center',
'visible' => false,
],
'pais' => [
'align' => 'center',
'visible' => false,
],
'estado' => [
'formatter' => 'textNowrapFormatter',
'visible' => false,
],
'localidad' => [
'formatter' => 'textNowrapFormatter',
'visible' => false,
],
'municipio' => [
'formatter' => 'textNowrapFormatter',
'visible' => false,
],
'codigo_postal' => [
'align' => 'center',
'visible' => false,
],
'colonia' => [
'formatter' => 'textNowrapFormatter',
'visible' => false,
],
'direccion' => [
'formatter' => 'direccionFormatter',
'visible' => false,
],
'lat' => [
'align' => 'center',
'visible' => false,
],
'lng' => [
'align' => 'center',
'visible' => false,
],
'status' => [
'formatter' => [
'name' => 'dynamicBooleanFormatter',
'params' => ['tag' => 'activo'],
],
'align' => 'center',
],
'created_at' => [
'formatter' => 'textNowrapFormatter',
'align' => 'center',
'visible' => false,
],
'updated_at' => [
'formatter' => 'textNowrapFormatter',
'align' => 'center',
'visible' => false,
],
];
}
/**
* Sobrescribe la config base para adaptarla al caso de los Centros de Trabajo.
*
* @return array
*/
protected function bootstraptableConfig(): array
{
// Llamamos al padre y luego ajustamos lo que necesitemos.
return array_merge(parent::bootstraptableConfig(), [
'sortName' => 'code',
'exportFileName' => 'Centros de Trabajo',
'showFullscreen' => false,
'showPaginationSwitch'=> false,
'showRefresh' => false,
'pagination' => false,
]);
}
/**
* Retorna la vista que se usará para renderizar este componente.
*
* @return string
*/
protected function viewPath(): string
{
return 'vuexy-store-manager::livewire.work-center.index';
}
}

View File

@ -0,0 +1,202 @@
<?php
namespace Koneko\VuexyStoreManager\Livewire\Workcenters;
use Illuminate\Validation\Rule;
use Koneko\VuexyAdmin\Livewire\Form\AbstractFormOffCanvasComponent;
use Koneko\VuexyContacts\Services\ContactCatalogService;
use Koneko\VuexyStoreManager\Models\StoreWorkCenter;
use Koneko\VuexyStoreManager\Services\StoreCatalogService;
/**
* Class WorkCenterOffcanvasForm
*
* Componente Livewire para gestionar centros de trabajo.
* Extiende las funcionalidades generales de AbstractFormOffCanvasComponent.
*
* @package Koneko\VuexyStoreManager\Livewire\Workcenters
*/
class WorkCenterOffcanvasForm extends AbstractFormOffCanvasComponent
{
/**
* Campos específicos del centro de trabajo.
*/
public $store_id, $code, $name, $description, $manager_id, $tel, $tel2, $lat, $lng, $status;
/**
* Listas de opciones para selects en el formulario.
*/
public $store_options = [],
$manager_options = [];
/**
* Eventos de escucha de Livewire.
*
* @var array
*/
protected $listeners = [
'editWorkCenter' => 'loadFormModel',
'confirmDeletionWorkCenter' => 'loadFormModelForDeletion',
];
/**
* Definición de tipos de datos que se deben castear.
*
* @var array
*/
protected $casts = [
//
'status' => 'boolean',
];
/**
* Devuelve el modelo relacionado con el formulario.
*
* @return string
*/
protected function model(): string
{
return StoreWorkCenter::class;
}
/**
* Define los campos del formulario.
*
* @return array<string, mixed>
*/
protected function fields(): array
{
return (new StoreWorkCenter())->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 'code';
}
/**
* Define reglas de validación dinámicas basadas en el modo actual.
*
* @param string $mode El modo actual del formulario ('create', 'edit', 'delete').
* @return array
*/
protected function dynamicRules(string $mode): array
{
switch ($mode) {
case 'create':
case 'edit':
return [
'store_id' => ['required', 'integer', 'exists:stores,id'],
'code' => [
'required', 'string', 'max:16',
'regex:/^[a-zA-Z0-9_-]+$/', // Solo alfanuméricos, guiones y guiones bajos
Rule::unique('store_work_centers', 'code')->ignore($this->id),
],
'name' => [
'required', 'string', 'max:96',
'regex:/^[a-zA-ZáéíóúÁÉÍÓÚñÑ0-9\s-]+$/', // Solo letras, números y espacios
Rule::unique('store_work_centers')
->where(fn ($query) => $query->where('store_id', $this->store_id))
->ignore($this->id),
],
'description' => ['nullable', 'string', 'max:1024'],
'manager_id' => ['nullable', 'integer', 'exists:users,id'],
'tel' => ['nullable', 'regex:/^\+?[0-9()\s-]+$/', 'max:20'], // Permitir formatos internacionales
'tel2' => ['nullable', 'regex:/^\+?[0-9()\s-]+$/', 'max:20'],
'lat' => ['nullable', 'numeric', 'between:-90,90'],
'lng' => ['nullable', 'numeric', 'between:-180,180'],
'status' => ['nullable', 'boolean']
];
case 'delete':
return [
'confirmDeletion' => 'accepted', // Confirma que el usuario acepta eliminar
];
default:
return [];
}
}
// ===================== VALIDACIONES =====================
/**
* Get custom attributes for validator errors.
*
* @return array<string, string>
*/
protected function attributes(): array
{
return [
'store_id' => 'negocio',
'code' => 'código del centro de trabajo',
'name' => 'nombre del centro de trabajo',
'tel' => 'teléfono',
'tel2' => 'teléfono alternativo',
'lat' => 'latitud',
'lng' => 'longitud',
'status' => 'estado',
];
}
/**
* Get the error messages for the defined validation rules.
*
* @return array<string, string>
*/
protected function messages(): array
{
return [
'store_id.required' => 'El centro de trabajo debe estar asociado a un negocio.',
'code.required' => 'El código del centro de trabajo es obligatorio.',
'code.unique' => 'Este código ya está en uso por otro centro de trabajo.',
'name.required' => 'El nombre del centro de trabajo es obligatorio.',
'name.unique' => 'Ya existe un centro de trabajo con este nombre en este negocio.',
'name.regex' => 'El nombre solo puede contener letras, números, espacios y guiones.',
];
}
/**
* 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-store-manager::livewire.work-center.form-offcanvas';
}
}