mirror of
https://origin.cursor.com/mrdevmx/panels.git
synced 2026-10-09 14:53:18 +00:00
Proveedor y unidad pasan a Select de catálogo (no texto libre). Subtotal, IVA y total se calculan al vuelo. El comprobante usa FileUpload de PrimeVue. Co-authored-by: alberto.martinez <alberto.martinez@mrdev.mx>
643 lines
26 KiB
Vue
643 lines
26 KiB
Vue
<template>
|
|
<div class="list-page">
|
|
<DeskTableBar v-model="tableQ" :placeholder="searchPlaceholder">
|
|
<Chip v-if="tab === 'empresas'" :label="`${listedCompanies.length} empresas`" icon="pi pi-briefcase" />
|
|
<Chip v-if="tab === 'empresas'" :label="`${subCompanies} subempresas`" icon="pi pi-th-large" />
|
|
<Chip v-if="tab === 'nomina'" :label="`${units.length} unidades`" icon="pi pi-percentage" />
|
|
<Chip v-if="tab === 'proveedores'" :label="`${listedSuppliers.length} proveedores`" icon="pi pi-truck" />
|
|
<Chip v-if="tab === 'unidades'" :label="`${listedMeasureUnits.length} unidades`" icon="pi pi-box" />
|
|
<Chip v-if="tab === 'riesgos'" :label="`${listedRisks.length} riesgos`" icon="pi pi-exclamation-triangle" />
|
|
<Chip v-if="tab === 'temas'" :label="`${listedThemes.length} temas`" icon="pi pi-palette" />
|
|
</DeskTableBar>
|
|
<Tabs v-model:value="tab">
|
|
<TabList>
|
|
<Tab value="empresas">Empresas</Tab>
|
|
<Tab value="proveedores">Proveedores</Tab>
|
|
<Tab value="unidades">Unidades</Tab>
|
|
<Tab value="nomina">Nómina</Tab>
|
|
<Tab value="riesgos">Riesgos</Tab>
|
|
<Tab value="temas">Temas de gafete</Tab>
|
|
</TabList>
|
|
<TabPanels>
|
|
<TabPanel value="empresas">
|
|
<Message severity="info" :closable="false">
|
|
Primero la empresa (razón social, RFC, régimen y registro patronal), luego las obras y al final el padrón.
|
|
La cuenta pertenece a <strong>{{ principal?.name || principal?.code || "la empresa principal" }}</strong>.
|
|
Use <strong>Nueva subempresa</strong> en la cinta para dar de alta otra razón o patrón IMSS.
|
|
</Message>
|
|
<DataTable :value="listedCompanies" class="mt-3" data-key="id" paginator :rows="15" empty-message="No hay empresas registradas.">
|
|
<Column field="code" header="Código" />
|
|
<Column field="nombre_comercial" header="Nombre comercial">
|
|
<template #body="{ data }">{{ data.nombre_comercial || data.name }}</template>
|
|
</Column>
|
|
<Column field="razon_social" header="Razón social">
|
|
<template #body="{ data }">{{ data.razon_social || "—" }}</template>
|
|
</Column>
|
|
<Column field="rfc" header="RFC">
|
|
<template #body="{ data }"><span class="identifier">{{ data.rfc || "—" }}</span></template>
|
|
</Column>
|
|
<Column field="registro_patronal" header="Reg. patronal">
|
|
<template #body="{ data }"><span class="identifier">{{ data.registro_patronal || "—" }}</span></template>
|
|
</Column>
|
|
<Column field="regimen_fiscal" header="Régimen">
|
|
<template #body="{ data }">{{ regimenLabel(data.regimen_fiscal) }}</template>
|
|
</Column>
|
|
<Column header="Tipo">
|
|
<template #body="{ data }">
|
|
<Tag :value="data.kind === 'principal' ? 'Principal' : 'Subempresa'" :severity="data.kind === 'principal' ? 'info' : 'secondary'" />
|
|
</template>
|
|
</Column>
|
|
<Column header="Estatus">
|
|
<template #body="{ data }">
|
|
<Tag :value="data.status === 'activo' ? 'Activa' : 'Inactiva'" :severity="data.status === 'activo' ? 'success' : 'secondary'" />
|
|
</template>
|
|
</Column>
|
|
<Column header="" header-style="width: 8rem">
|
|
<template #body="{ data }">
|
|
<Button icon="pi pi-pencil" text rounded size="small" aria-label="Editar" @click="openEdit(data)" />
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
</TabPanel>
|
|
<TabPanel value="nomina">
|
|
<Message severity="info" :closable="false">
|
|
Comisión de préstamos de la empresa. Al prestar se pueden cambiar; lo aceptado se congela en ese préstamo.
|
|
</Message>
|
|
<div class="company-form-grid mt-3" style="max-width: 36rem">
|
|
<FormField label="Comisión activa">
|
|
<ToggleSwitch v-model="payroll.loan_commission_enabled" />
|
|
</FormField>
|
|
<FormField label="Porcentaje">
|
|
<InputNumber v-model="payroll.loan_commission_pct" suffix=" %" :min="0" :max="100" :max-fraction-digits="2" class="w-full" />
|
|
</FormField>
|
|
<FormField label="Tope préstamo chico (entregado)">
|
|
<InputNumber v-model="payroll.loan_small_max" mode="currency" currency="MXN" locale="es-MX" class="w-full" />
|
|
</FormField>
|
|
<div class="span-2">
|
|
<Button label="Guardar nómina" icon="pi pi-check" :loading="savingPayroll" @click="savePayroll" />
|
|
</div>
|
|
</div>
|
|
<h3 class="mt-4">Unidades de destajo</h3>
|
|
<p class="muted">Solo para destajo de nómina. Las unidades de gastos y presupuesto están en <strong>Unidades</strong>.</p>
|
|
<DataTable :value="units" class="mt-2" empty-message="Sin unidades">
|
|
<Column field="code" header="Código" />
|
|
<Column field="label" header="Nombre" />
|
|
</DataTable>
|
|
<div class="company-form-grid mt-3" style="max-width: 36rem">
|
|
<FormField label="Código">
|
|
<InputText v-model="unitForm.code" class="w-full" placeholder="ml" />
|
|
</FormField>
|
|
<FormField label="Nombre">
|
|
<InputText v-model="unitForm.label" class="w-full" placeholder="Metro lineal" />
|
|
</FormField>
|
|
<div>
|
|
<Button label="Agregar unidad" icon="pi pi-plus" outlined @click="addUnit" />
|
|
</div>
|
|
</div>
|
|
</TabPanel>
|
|
<TabPanel value="proveedores">
|
|
<Message severity="info" :closable="false">
|
|
Catálogo de proveedores para capturar gastos. En Gastos solo se selecciona; aquí se guardan nombre, RFC y datos generales.
|
|
</Message>
|
|
<DataTable :value="listedSuppliers" class="mt-3" data-key="id" paginator :rows="15" empty-message="No hay proveedores. Use Nuevo proveedor.">
|
|
<Column field="name" header="Nombre" />
|
|
<Column field="legal_name" header="Razón social">
|
|
<template #body="{ data }">{{ data.legal_name || "—" }}</template>
|
|
</Column>
|
|
<Column field="rfc" header="RFC">
|
|
<template #body="{ data }"><span class="identifier">{{ data.rfc || "—" }}</span></template>
|
|
</Column>
|
|
<Column field="phone" header="Teléfono">
|
|
<template #body="{ data }">{{ data.phone || "—" }}</template>
|
|
</Column>
|
|
<Column field="email" header="Correo">
|
|
<template #body="{ data }">{{ data.email || "—" }}</template>
|
|
</Column>
|
|
<Column header="" header-style="width: 5rem">
|
|
<template #body="{ data }">
|
|
<Button icon="pi pi-pencil" text rounded size="small" aria-label="Editar" @click="openSupplier(data)" />
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
</TabPanel>
|
|
<TabPanel value="unidades">
|
|
<Message severity="info" :closable="false">
|
|
Unidades de medida para gastos (pieza, m², bulto…). Distintas de las unidades de destajo de nómina.
|
|
</Message>
|
|
<DataTable :value="listedMeasureUnits" class="mt-3" data-key="code" paginator :rows="15" empty-message="Sin unidades">
|
|
<Column field="code" header="Código" />
|
|
<Column field="label" header="Nombre" />
|
|
</DataTable>
|
|
<div class="company-form-grid mt-3" style="max-width: 36rem">
|
|
<FormField label="Código">
|
|
<InputText v-model="measureUnitForm.code" class="w-full" placeholder="bulto" />
|
|
</FormField>
|
|
<FormField label="Nombre">
|
|
<InputText v-model="measureUnitForm.label" class="w-full" placeholder="Bulto" />
|
|
</FormField>
|
|
<div>
|
|
<Button label="Agregar unidad" icon="pi pi-plus" outlined @click="addMeasureUnit" />
|
|
</div>
|
|
</div>
|
|
</TabPanel>
|
|
<TabPanel value="riesgos">
|
|
<DataTable :value="listedRisks" paginator :rows="15">
|
|
<Column field="code" header="Barra gafete" />
|
|
<Column field="label" header="Color" />
|
|
<Column header="Nivel">
|
|
<template #body="{ data }">
|
|
<Tag :value="riskLevelLabel(data.code)" :severity="riskLevelSeverity(data.code)" />
|
|
</template>
|
|
</Column>
|
|
<Column header="Muestra">
|
|
<template #body="{ data }">
|
|
<Tag :value="data.color" :severity="riskSeverity(data.code)" />
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
</TabPanel>
|
|
<TabPanel value="temas">
|
|
<DataTable :value="listedThemes" paginator :rows="15">
|
|
<Column field="id" header="ID" />
|
|
<Column field="name" header="Nombre" />
|
|
<Column header="Layout">
|
|
<template #body="{ data }">
|
|
<Chip :label="data.layout" icon="pi pi-id-card" />
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
</TabPanel>
|
|
</TabPanels>
|
|
</Tabs>
|
|
|
|
<Dialog
|
|
v-model:visible="showForm"
|
|
:header="editing ? 'Editar empresa' : 'Nueva subempresa'"
|
|
modal
|
|
class="company-form-dialog"
|
|
:style="{ width: 'min(820px, 96vw)' }"
|
|
>
|
|
<Tabs v-if="editing" v-model:value="formTab" class="mb-3">
|
|
<TabList>
|
|
<Tab value="datos">Datos</Tab>
|
|
<Tab value="docs">Expediente</Tab>
|
|
</TabList>
|
|
</Tabs>
|
|
<Message v-if="formTab === 'datos'" severity="secondary" :closable="false" class="mb-3">
|
|
Datos fiscales y patronales de la empresa. El nombre corto se usa en listados; la razón social y el RFC quedan para IMSS y facturación.
|
|
</Message>
|
|
<div v-show="formTab === 'datos'" class="company-form-grid">
|
|
<FormField label="Nombre comercial" required>
|
|
<InputText v-model="form.nombre_comercial" class="w-full" placeholder="Constructora Maya" />
|
|
</FormField>
|
|
<FormField label="Razón social" required>
|
|
<InputText v-model="form.razon_social" class="w-full" placeholder="Constructora Maya S.A. de C.V." />
|
|
</FormField>
|
|
<FormField label="Código {{ editing ? '' : '(opcional)' }}">
|
|
<InputText v-model="form.code" class="w-full identifier-input" placeholder="Se genera del nombre si lo deja vacío" maxlength="16" />
|
|
</FormField>
|
|
<FormField label="RFC">
|
|
<InputText v-model="form.rfc" class="w-full identifier-input" maxlength="13" placeholder="XAXX010101000" />
|
|
</FormField>
|
|
<FormField label="Régimen fiscal">
|
|
<Select
|
|
v-model="form.regimen_fiscal"
|
|
:options="regimenOpts"
|
|
option-label="label"
|
|
option-value="value"
|
|
placeholder="Seleccione régimen"
|
|
show-clear
|
|
class="w-full"
|
|
/>
|
|
</FormField>
|
|
<FormField label="Registro patronal IMSS">
|
|
<InputText v-model="form.registro_patronal" class="w-full identifier-input" placeholder="Número patronal" />
|
|
</FormField>
|
|
<FormField label="Clase de riesgo IMSS">
|
|
<Select
|
|
v-model="form.clase_riesgo"
|
|
:options="claseRiesgoOpts"
|
|
option-label="label"
|
|
option-value="value"
|
|
placeholder="I a V"
|
|
show-clear
|
|
class="w-full"
|
|
/>
|
|
</FormField>
|
|
<FormField label="Giro / actividad">
|
|
<InputText v-model="form.giro" class="w-full" placeholder="Construcción, servicios…" />
|
|
</FormField>
|
|
<FormField label="Domicilio fiscal" class="span-2">
|
|
<InputText v-model="form.domicilio_fiscal" class="w-full" placeholder="Calle, número, colonia" />
|
|
</FormField>
|
|
<FormField label="C.P.">
|
|
<InputText v-model="form.codigo_postal" class="w-full" maxlength="5" placeholder="77500" />
|
|
</FormField>
|
|
<FormField label="Ciudad">
|
|
<InputText v-model="form.ciudad" class="w-full" />
|
|
</FormField>
|
|
<FormField label="Estado">
|
|
<InputText v-model="form.estado" class="w-full" />
|
|
</FormField>
|
|
<FormField label="Teléfono">
|
|
<InputText v-model="form.telefono" class="w-full" />
|
|
</FormField>
|
|
<FormField label="Correo">
|
|
<InputText v-model="form.email" class="w-full no-caps" />
|
|
</FormField>
|
|
<FormField label="Representante legal" class="span-2">
|
|
<InputText v-model="form.representante_legal" class="w-full" />
|
|
</FormField>
|
|
<FormField v-if="editing && editing.kind !== 'principal'" label="Estatus">
|
|
<Select v-model="form.status" :options="statusOpts" option-label="label" option-value="value" class="w-full" />
|
|
</FormField>
|
|
</div>
|
|
<CompanyDocuments
|
|
v-if="editing && formTab === 'docs'"
|
|
:company-id="editing.id"
|
|
:documents="companyDocs.documents"
|
|
:checklist="companyDocs.checklist"
|
|
@changed="loadCompanyDocs"
|
|
/>
|
|
<Message v-if="formError" severity="error" class="mt-3" :closable="false">{{ formError }}</Message>
|
|
<template #footer>
|
|
<Button label="Cerrar" severity="secondary" text @click="showForm = false" />
|
|
<Button v-if="formTab === 'datos'" :label="editing ? 'Guardar' : 'Crear'" icon="pi pi-check" :loading="saving" @click="save" />
|
|
</template>
|
|
</Dialog>
|
|
|
|
<SupplierFormDialog v-model="showSupplierForm" :supplier="editingSupplier" @saved="onSupplierSaved" />
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import FormField from "~/components/padron/FormField.vue";
|
|
import CompanyDocuments from "~/components/CompanyDocuments.vue";
|
|
import SupplierFormDialog, { type Supplier } from "~/components/SupplierFormDialog.vue";
|
|
import type { CompanyOption } from "~/types/padron";
|
|
|
|
const emptyForm = () => ({
|
|
nombre_comercial: "",
|
|
razon_social: "",
|
|
code: "",
|
|
status: "activo",
|
|
registro_patronal: "",
|
|
rfc: "",
|
|
regimen_fiscal: "",
|
|
clase_riesgo: "",
|
|
domicilio_fiscal: "",
|
|
codigo_postal: "",
|
|
ciudad: "",
|
|
estado: "",
|
|
telefono: "",
|
|
email: "",
|
|
representante_legal: "",
|
|
giro: "",
|
|
});
|
|
|
|
const { riskSeverity, riskLevelLabel, riskLevelSeverity } = useUi();
|
|
const desk = useDesktop();
|
|
const { api } = useApi();
|
|
const toast = useToast();
|
|
const route = useRoute();
|
|
const tab = ref("empresas");
|
|
const risks = ref<Record<string, string>[]>([]);
|
|
const themes = ref<Record<string, string>[]>([]);
|
|
const companies = ref<CompanyOption[]>([]);
|
|
const payroll = reactive({
|
|
loan_commission_enabled: true,
|
|
loan_commission_pct: 10,
|
|
loan_small_max: 500,
|
|
});
|
|
const units = ref<{ code: string; label: string }[]>([]);
|
|
const unitForm = reactive({ code: "", label: "" });
|
|
const suppliers = ref<Supplier[]>([]);
|
|
const measureUnits = ref<{ code: string; label: string }[]>([]);
|
|
const measureUnitForm = reactive({ code: "", label: "" });
|
|
const showSupplierForm = ref(false);
|
|
const editingSupplier = ref<Supplier | null>(null);
|
|
const savingPayroll = ref(false);
|
|
const showForm = ref(false);
|
|
const saving = ref(false);
|
|
const formError = ref("");
|
|
const editing = ref<CompanyOption | null>(null);
|
|
const formTab = ref("datos");
|
|
const companyDocs = reactive<{ documents: Array<{ id: number; type_code: string; original_name: string; uploaded_at: string; is_current: number; mime?: string }>; checklist: Array<{ code: string; label: string; required: boolean; present: boolean; category?: string }> }>({
|
|
documents: [],
|
|
checklist: [],
|
|
});
|
|
const form = reactive(emptyForm());
|
|
const statusOpts = [
|
|
{ label: "Activa", value: "activo" },
|
|
{ label: "Inactiva", value: "inactivo" },
|
|
];
|
|
const regimenOpts = [
|
|
{ value: "601", label: "601 · General de Ley Personas Morales" },
|
|
{ value: "603", label: "603 · Personas Morales con Fines no Lucrativos" },
|
|
{ value: "605", label: "605 · Sueldos y Salarios" },
|
|
{ value: "606", label: "606 · Arrendamiento" },
|
|
{ value: "612", label: "612 · Actividades Empresariales y Profesionales" },
|
|
{ value: "621", label: "621 · Incorporación Fiscal" },
|
|
{ value: "625", label: "625 · Plataformas Tecnológicas" },
|
|
{ value: "626", label: "626 · Régimen Simplificado de Confianza" },
|
|
];
|
|
const claseRiesgoOpts = [
|
|
{ value: "I", label: "Clase I" },
|
|
{ value: "II", label: "Clase II" },
|
|
{ value: "III", label: "Clase III" },
|
|
{ value: "IV", label: "Clase IV" },
|
|
{ value: "V", label: "Clase V" },
|
|
];
|
|
const principal = computed(() => companies.value.find((company) => company.kind === "principal"));
|
|
const { tableQ, matches } = useTableSearch();
|
|
const searchPlaceholder = computed(() =>
|
|
tab.value === "riesgos"
|
|
? "Buscar riesgo o color"
|
|
: tab.value === "temas"
|
|
? "Buscar tema"
|
|
: tab.value === "nomina"
|
|
? "Nómina y destajo"
|
|
: tab.value === "proveedores"
|
|
? "Buscar proveedor o RFC"
|
|
: tab.value === "unidades"
|
|
? "Buscar unidad"
|
|
: "Buscar empresa, RFC o código",
|
|
);
|
|
const listedCompanies = computed(() =>
|
|
companies.value.filter((company) =>
|
|
matches(
|
|
company.code,
|
|
company.name,
|
|
company.nombre_comercial,
|
|
company.razon_social,
|
|
company.rfc,
|
|
company.registro_patronal,
|
|
company.regimen_fiscal,
|
|
company.kind,
|
|
company.parent_name,
|
|
company.parent_code,
|
|
company.status,
|
|
),
|
|
),
|
|
);
|
|
const subCompanies = computed(() => listedCompanies.value.filter((company) => company.kind !== "principal").length);
|
|
const listedRisks = computed(() => risks.value.filter((risk) => matches(risk.code, risk.label, risk.color)));
|
|
const listedThemes = computed(() => themes.value.filter((theme) => matches(theme.id, theme.name, theme.layout)));
|
|
const listedSuppliers = computed(() =>
|
|
suppliers.value.filter((s) => matches(s.name, s.legal_name, s.rfc, s.email, s.phone, s.city)),
|
|
);
|
|
const listedMeasureUnits = computed(() =>
|
|
measureUnits.value.filter((u) => matches(u.code, u.label)),
|
|
);
|
|
|
|
function regimenLabel(code?: string | null) {
|
|
if (!code) return "—";
|
|
return regimenOpts.find((item) => item.value === code)?.label || code;
|
|
}
|
|
|
|
function resetForm() {
|
|
Object.assign(form, emptyForm());
|
|
}
|
|
|
|
async function load() {
|
|
const [c, pay] = await Promise.all([
|
|
api<{
|
|
risks: Record<string, string>[];
|
|
themes: Record<string, string>[];
|
|
companies: CompanyOption[];
|
|
}>("/v1/catalogs"),
|
|
api<{
|
|
settings: { loan_commission_enabled: number; loan_commission_pct: number; loan_small_max: number };
|
|
units: { code: string; label: string }[];
|
|
}>("/v1/payroll/settings"),
|
|
]);
|
|
risks.value = c.risks;
|
|
themes.value = c.themes;
|
|
companies.value = c.companies || [];
|
|
payroll.loan_commission_enabled = Boolean(pay.settings.loan_commission_enabled);
|
|
payroll.loan_commission_pct = Number(pay.settings.loan_commission_pct);
|
|
payroll.loan_small_max = Number(pay.settings.loan_small_max);
|
|
units.value = pay.units || [];
|
|
try {
|
|
const sup = await api<{ suppliers?: Supplier[]; data?: { suppliers?: Supplier[] } }>("/v1/suppliers");
|
|
suppliers.value = sup.suppliers ?? sup.data?.suppliers ?? [];
|
|
} catch {
|
|
suppliers.value = [];
|
|
}
|
|
try {
|
|
const mu = await api<{ units?: { code: string; label: string }[]; data?: { units?: { code: string; label: string }[] } }>("/v1/measure-units");
|
|
measureUnits.value = mu.units ?? mu.data?.units ?? [];
|
|
} catch {
|
|
measureUnits.value = [];
|
|
}
|
|
syncChrome();
|
|
}
|
|
|
|
async function savePayroll() {
|
|
savingPayroll.value = true;
|
|
try {
|
|
await api("/v1/payroll/settings", { method: "PATCH", json: payroll });
|
|
toast.add({ severity: "success", summary: "Nómina actualizada", life: 1800 });
|
|
await load();
|
|
} finally {
|
|
savingPayroll.value = false;
|
|
}
|
|
}
|
|
|
|
async function addUnit() {
|
|
if (!unitForm.code.trim()) return;
|
|
await api("/v1/destajo/units", { method: "POST", json: { code: unitForm.code, label: unitForm.label } });
|
|
unitForm.code = "";
|
|
unitForm.label = "";
|
|
toast.add({ severity: "success", summary: "Unidad agregada", life: 1600 });
|
|
await load();
|
|
}
|
|
|
|
async function addMeasureUnit() {
|
|
if (!measureUnitForm.code.trim()) return;
|
|
await api("/v1/measure-units", { method: "POST", json: { code: measureUnitForm.code, label: measureUnitForm.label } });
|
|
measureUnitForm.code = "";
|
|
measureUnitForm.label = "";
|
|
toast.add({ severity: "success", summary: "Unidad de medida agregada", life: 1600 });
|
|
await load();
|
|
}
|
|
|
|
function openNewSupplier() {
|
|
editingSupplier.value = null;
|
|
showSupplierForm.value = true;
|
|
}
|
|
|
|
function openSupplier(row: Supplier) {
|
|
editingSupplier.value = row;
|
|
showSupplierForm.value = true;
|
|
}
|
|
|
|
async function onSupplierSaved() {
|
|
toast.add({ severity: "success", summary: "Proveedor guardado", life: 1600 });
|
|
await load();
|
|
}
|
|
|
|
function openNew() {
|
|
editing.value = null;
|
|
formTab.value = "datos";
|
|
resetForm();
|
|
formError.value = "";
|
|
showForm.value = true;
|
|
}
|
|
|
|
async function loadCompanyDocs() {
|
|
if (!editing.value) {
|
|
companyDocs.documents = [];
|
|
companyDocs.checklist = [];
|
|
return;
|
|
}
|
|
const detail = await api<{
|
|
documents: typeof companyDocs.documents;
|
|
checklist: typeof companyDocs.checklist;
|
|
}>(`/v1/companies/${editing.value.id}`);
|
|
companyDocs.documents = detail.documents || [];
|
|
companyDocs.checklist = detail.checklist || [];
|
|
}
|
|
|
|
async function openEdit(company: CompanyOption) {
|
|
editing.value = company;
|
|
resetForm();
|
|
form.nombre_comercial = company.nombre_comercial || company.name || "";
|
|
form.razon_social = company.razon_social || company.name || "";
|
|
form.code = company.code;
|
|
form.status = company.status;
|
|
form.registro_patronal = company.registro_patronal || "";
|
|
form.rfc = company.rfc || "";
|
|
form.regimen_fiscal = company.regimen_fiscal || "";
|
|
form.clase_riesgo = company.clase_riesgo || "";
|
|
form.domicilio_fiscal = company.domicilio_fiscal || "";
|
|
form.codigo_postal = company.codigo_postal || "";
|
|
form.ciudad = company.ciudad || "";
|
|
form.estado = company.estado || "";
|
|
form.telefono = company.telefono || "";
|
|
form.email = company.email || "";
|
|
form.representante_legal = company.representante_legal || "";
|
|
form.giro = company.giro || "";
|
|
formError.value = "";
|
|
formTab.value = "datos";
|
|
showForm.value = true;
|
|
await loadCompanyDocs();
|
|
}
|
|
|
|
function payload() {
|
|
return {
|
|
name: form.nombre_comercial || form.razon_social,
|
|
nombre_comercial: form.nombre_comercial,
|
|
razon_social: form.razon_social,
|
|
code: form.code || undefined,
|
|
registro_patronal: form.registro_patronal,
|
|
rfc: form.rfc,
|
|
regimen_fiscal: form.regimen_fiscal,
|
|
clase_riesgo: form.clase_riesgo,
|
|
domicilio_fiscal: form.domicilio_fiscal,
|
|
codigo_postal: form.codigo_postal,
|
|
ciudad: form.ciudad,
|
|
estado: form.estado,
|
|
telefono: form.telefono,
|
|
email: form.email,
|
|
representante_legal: form.representante_legal,
|
|
giro: form.giro,
|
|
};
|
|
}
|
|
|
|
async function save() {
|
|
if (!form.nombre_comercial.trim() && !form.razon_social.trim()) {
|
|
formError.value = "Indique nombre comercial o razón social";
|
|
return;
|
|
}
|
|
saving.value = true;
|
|
formError.value = "";
|
|
try {
|
|
if (editing.value) {
|
|
await api(`/v1/companies/${editing.value.id}`, {
|
|
method: "PATCH",
|
|
json: {
|
|
...payload(),
|
|
status: editing.value.kind === "principal" ? "activo" : form.status,
|
|
},
|
|
});
|
|
toast.add({ severity: "success", summary: "Empresa actualizada", life: 2200 });
|
|
} else {
|
|
await api("/v1/companies", { method: "POST", json: payload() });
|
|
toast.add({ severity: "success", summary: "Subempresa creada", life: 2200 });
|
|
}
|
|
showForm.value = false;
|
|
await load();
|
|
} catch (error) {
|
|
const detail = error as { data?: { error?: string } };
|
|
formError.value = detail.data?.error || (error instanceof Error ? error.message : "No se pudo guardar");
|
|
} finally {
|
|
saving.value = false;
|
|
}
|
|
}
|
|
|
|
function syncChrome() {
|
|
desk.configure({
|
|
tab: "Configuración",
|
|
treeTitle: "Catálogos",
|
|
showTree: true,
|
|
showDetail: false,
|
|
treeKey: tab.value,
|
|
onTreeSelect: (key) => {
|
|
if (["empresas", "proveedores", "unidades", "riesgos", "temas", "nomina"].includes(key)) tab.value = key;
|
|
},
|
|
tree: [
|
|
{ key: "empresas", label: "Empresas", icon: "pi pi-briefcase", data: { count: companies.value.length } },
|
|
{ key: "proveedores", label: "Proveedores", icon: "pi pi-truck", data: { count: suppliers.value.length } },
|
|
{ key: "unidades", label: "Unidades", icon: "pi pi-box", data: { count: measureUnits.value.length } },
|
|
{ key: "nomina", label: "Nómina", icon: "pi pi-wallet" },
|
|
{ key: "riesgos", label: "Riesgos", icon: "pi pi-exclamation-triangle", data: { count: risks.value.length } },
|
|
{ key: "temas", label: "Temas de gafete", icon: "pi pi-palette", data: { count: themes.value.length } },
|
|
],
|
|
ribbon: [
|
|
{
|
|
key: "acciones",
|
|
label: "Acciones",
|
|
items: [
|
|
{ key: "alta", label: "Nueva subempresa", icon: "pi pi-plus", disabled: tab.value !== "empresas", command: openNew },
|
|
{ key: "proveedor", label: "Nuevo proveedor", icon: "pi pi-plus", disabled: tab.value !== "proveedores", command: openNewSupplier },
|
|
],
|
|
},
|
|
{
|
|
key: "vista",
|
|
label: "Vista",
|
|
items: [{ key: "refresh", label: "Actualizar", icon: "pi pi-refresh", command: load }],
|
|
},
|
|
],
|
|
});
|
|
}
|
|
onMounted(load);
|
|
watch(
|
|
() => String(route.query.tab || ""),
|
|
(key) => {
|
|
if (["empresas", "proveedores", "unidades", "riesgos", "temas", "nomina"].includes(key)) tab.value = key;
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
watch(tab, (v) => {
|
|
desk.treeKey.value = v;
|
|
syncChrome();
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
.company-form-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 14px 12px;
|
|
}
|
|
.company-form-grid :deep(.form-field.span-2),
|
|
.company-form-grid :deep(.span-2) {
|
|
grid-column: 1 / -1;
|
|
}
|
|
@media (max-width: 700px) {
|
|
.company-form-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|