Licencias de software
This commit is contained in:
parent
1989bbff89
commit
5144073481
@ -43,14 +43,104 @@ export async function verifyResources(
|
||||
return ResourceResponseSchema.parse(await response.json()).resource;
|
||||
}
|
||||
const initial = await read(global);
|
||||
assert.equal(initial.items.length, 18);
|
||||
assert.equal(initial.items.filter((item) => item.values.kind === "template").length, 5);
|
||||
assert.equal(initial.items.length, 26);
|
||||
assert.equal(initial.items.filter((item) => item.values.kind === "template").length, 6);
|
||||
assert.ok(initial.items.every((item) => item.builtin && item.id[14] === "7"));
|
||||
assert.ok(
|
||||
initial.items.every((item) =>
|
||||
item.bindings.every((binding) => !binding.required && binding.id[14] === "7")
|
||||
)
|
||||
);
|
||||
const licenseTemplate = initial.items.find(
|
||||
(item) => item.builtinKey === "template:software_license"
|
||||
);
|
||||
assert.ok(licenseTemplate && licenseTemplate.values.kind === "template");
|
||||
const licenseFieldId = (key: string) => {
|
||||
const field = initial.items.find((item) => item.builtinKey === `field:${key}`);
|
||||
assert.ok(field && field.values.kind === "field");
|
||||
return field.id;
|
||||
};
|
||||
const licenseInput = {
|
||||
name: "Microsoft 365 Business Standard",
|
||||
templateId: licenseTemplate.id,
|
||||
active: true,
|
||||
notes: "",
|
||||
characteristics: {
|
||||
[licenseFieldId("software_product")]: "Microsoft 365",
|
||||
[licenseFieldId("software_edition")]: "Business Standard",
|
||||
[licenseFieldId("license_model")]: "Suscripción",
|
||||
[licenseFieldId("license_metric")]: "Por usuario",
|
||||
[licenseFieldId("license_quantity")]: 25,
|
||||
[licenseFieldId("expires")]: "2027-10-07",
|
||||
[licenseFieldId("renews")]: "2027-09-30",
|
||||
[licenseFieldId("auto_renew")]: false,
|
||||
},
|
||||
};
|
||||
const license = await resource(inventory, licenseInput);
|
||||
assert.equal(license.companyId, companyId);
|
||||
assert.equal(license.specification.templateName, "Licencia de software");
|
||||
assert.equal(license.values.characteristics[licenseFieldId("auto_renew")], false);
|
||||
assert.equal(license.id[14], "7");
|
||||
const licenseUpdated = await resource(`${inventory}/${license.id}/update`, {
|
||||
...licenseInput,
|
||||
characteristics: { ...licenseInput.characteristics, [licenseFieldId("license_quantity")]: 30 },
|
||||
});
|
||||
assert.equal(licenseUpdated.history.length, 2);
|
||||
assert.equal(
|
||||
licenseUpdated.history[0]?.values.characteristics[licenseFieldId("license_quantity")],
|
||||
25
|
||||
);
|
||||
await error(
|
||||
inventory,
|
||||
{
|
||||
values: {
|
||||
...licenseInput,
|
||||
name: "Fecha inválida",
|
||||
characteristics: { [licenseFieldId("renews")]: "2027-02-30" },
|
||||
},
|
||||
},
|
||||
"INVALID_CHARACTERISTICS"
|
||||
);
|
||||
await error(
|
||||
inventory,
|
||||
{
|
||||
values: {
|
||||
...licenseInput,
|
||||
name: "Modalidad inválida",
|
||||
characteristics: { [licenseFieldId("license_model")]: "Desconocida" },
|
||||
},
|
||||
},
|
||||
"INVALID_CHARACTERISTICS"
|
||||
);
|
||||
const minimal = await resource(inventory, {
|
||||
...licenseInput,
|
||||
name: "ESET empresa",
|
||||
characteristics: {},
|
||||
});
|
||||
assert.deepEqual(minimal.values.characteristics, {});
|
||||
await error(
|
||||
`/resources/${otherId}/${license.id}/update`,
|
||||
{ values: licenseInput },
|
||||
"RESOURCE_NOT_FOUND",
|
||||
404
|
||||
);
|
||||
assert.equal(
|
||||
(await request(`${company}/${licenseTemplate.id}/status`, { active: false })).status,
|
||||
200
|
||||
);
|
||||
await error(
|
||||
inventory,
|
||||
{ values: { ...licenseInput, name: "No disponible" } },
|
||||
"TEMPLATE_UNAVAILABLE"
|
||||
);
|
||||
await resource(`${inventory}/${license.id}/update`, {
|
||||
...licenseUpdated.values,
|
||||
notes: "Conservada",
|
||||
});
|
||||
assert.equal(
|
||||
(await request(`${company}/${licenseTemplate.id}/status`, { active: null })).status,
|
||||
200
|
||||
);
|
||||
const assetsResponse = await request("/asset-catalog/global");
|
||||
const assets = AssetCatalogResponseSchema.parse(await assetsResponse.json());
|
||||
assert.ok(initial.items.every((item) => !assets.items.some((asset) => asset.id === item.id)));
|
||||
|
||||
@ -49,7 +49,7 @@ Buzones independientes corporativos o externos, cuenta informática opcional, pe
|
||||
|
||||
## Recursos con campos y plantillas: base implementada
|
||||
|
||||
Catálogo independiente de activos, 13 campos y cinco plantillas de serie, adiciones globales y por compañía, disponibilidad local y recursos con especificación capturada e historial. Los buzones y recursos básicos anteriores están migrados, conservando UUID, historial y relaciones. La gestión anterior se ha retirado; sus tablas se mantienen como proyecciones para permisos y configuraciones existentes. Consultar [Recursos, campos y plantillas](recursos-campos-y-plantillas.md).
|
||||
Catálogo independiente de activos, 20 campos y seis plantillas de serie, adiciones globales y por compañía, disponibilidad local y recursos con especificación capturada e historial. Los buzones y recursos básicos anteriores están migrados, conservando UUID, historial y relaciones. La gestión anterior se ha retirado; sus tablas se mantienen como proyecciones para permisos y configuraciones existentes. Consultar [Recursos, campos y plantillas](recursos-campos-y-plantillas.md).
|
||||
|
||||
## Impresoras por perfil: implementado
|
||||
|
||||
@ -87,7 +87,7 @@ Varios métodos por activo, catálogo global ampliable con herramientas de serie
|
||||
|
||||
1. Vinculación explícita entre un acceso remoto y su VPN previa, si resulta necesaria.
|
||||
|
||||
Este orden se concreta antes de cada entrega. Las licencias y su relación con cuentas se explorarán posteriormente.
|
||||
Este orden se concreta antes de cada entrega. El registro de licencias como recursos está implementado; sus asignaciones y relaciones con cuentas, personas, equipos y aplicaciones quedan pendientes.
|
||||
|
||||
## Inventario: implementado
|
||||
|
||||
@ -103,9 +103,13 @@ Tickets por compañía y consulta global con búsqueda/filtros, referencia corta
|
||||
|
||||
Consulta global y por compañía, técnico, tiempos, diagnóstico, trabajo realizado, resultado y elementos afectados. Varias intervenciones por ticket o actuaciones internas sin ticket. Pasos opcionales, procedimientos versionados, comprobaciones obligatorias al finalizar, historial, idempotencia y control de concurrencia. Consultar [Intervenciones y plantillas](intervenciones-y-plantillas.md).
|
||||
|
||||
## Licencias de software: registro implementado
|
||||
|
||||
Plantilla global protegida, siete campos nuevos y cuatro reutilizados del catálogo de recursos. Registro por compañía, características opcionales, modalidades, criterio de licenciamiento, cantidad contratada, caducidad, renovación e historial. Modales con ayuda específica. Pendientes las asignaciones, capacidad disponible, vínculos con aplicaciones y avisos. Consultar [Recursos, campos y plantillas](recursos-campos-y-plantillas.md).
|
||||
|
||||
## Servicios y documentación: pendiente
|
||||
|
||||
Software, licencias, Microsoft 365, ERP, antivirus, cloud, dominios, DNS, certificados, contratos y documentación.
|
||||
Gestión de asignaciones de licencias, Microsoft 365, ERP, antivirus, cloud, dominios, DNS, certificados, contratos y documentación.
|
||||
Las credenciales se resolverán con una solución específica; no almacenarlas como texto libre.
|
||||
|
||||
## Decisiones pendientes
|
||||
|
||||
@ -20,7 +20,7 @@ Cada compañía puede activar o desactivar una definición global para su ámbit
|
||||
|
||||
## Datos de serie
|
||||
|
||||
Se incluyen 13 campos y cinco plantillas:
|
||||
Se incluyen 20 campos y seis plantillas:
|
||||
|
||||
| Plantilla | Campos |
|
||||
| --- | --- |
|
||||
@ -28,6 +28,7 @@ Se incluyen 13 campos y cinco plantillas:
|
||||
| Carpeta compartida | Ruta UNC, servidor como referencia documental y descripción |
|
||||
| Aplicación | Proveedor, URL y descripción |
|
||||
| Certificado digital | Titular, emisor, caducidad, huella digital y finalidad |
|
||||
| Licencia de software | Producto, edición o plan, proveedor, modalidad, criterio de licenciamiento, cantidad contratada, referencia, caducidad, fecha de renovación, renovación automática y descripción |
|
||||
| Recurso de acceso | Referencia; contexto estructurado de tipo de acceso, sistema y entorno |
|
||||
|
||||
Las características de serie son opcionales salvo la dirección válida exigida al registrar un buzón. Los datos se alimentan con claves de serie estables: reiniciar no duplica definiciones, cambia UUID ni reactiva elementos desactivados. No se crean recursos, personas o compañías de ejemplo.
|
||||
@ -77,3 +78,13 @@ POP/IMAP pertenece a la configuración de correo por perfil; letras de unidad pe
|
||||
Las pruebas con MariaDB de test comprueban UUID y revisiones originales, repetición idempotente, edición canónica y actualización de proyecciones, aislamiento por compañía y rechazo de escrituras antiguas. Se mantienen las pruebas de múltiples correos, permisos, necesidades, cobertura y perfiles. Las pruebas de dominio cubren correo inválido o ausente, normalización, cuenta opcional, cambio auditado y tipo fijo.
|
||||
|
||||
La comprobación en navegador usa los datos existentes de desarrollo, sin crear registros ficticios: ficha, historial, personas vinculadas, edición modal y selectores.
|
||||
|
||||
## Licencias de software: registro implementado
|
||||
|
||||
Una licencia es un Resource de la compañía creado con la plantilla de serie Licencia de software. Se registra en Recursos → Nuevo recurso, seleccionando esa plantilla; no constituye un activo ni una instalación de aplicación. Ejemplos: Microsoft 365 Business Standard, ESET Endpoint contratado para la empresa o licencia perpetua de ERP.
|
||||
|
||||
Nombre, plantilla y estado siguen el contrato común. Todas las características de esta plantilla son opcionales. Modalidad ofrece Suscripción, Perpetua, OEM y Otra; criterio ofrece Por usuario, Por dispositivo, Por servidor, Concurrente, Corporativa y Otro. Cantidad contratada es una característica numérica documental; no representa plazas libres ni impone aún reglas de asignación. Caducidad y fecha de renovación se registran independientemente. Renovación automática distingue sí, no y sin datos. No almacenar claves de activación.
|
||||
|
||||
Se reutilizan proveedor, referencia, caducidad y descripción del catálogo de recursos. Los siete campos nuevos y la plantilla se incorporan de forma idempotente al arrancar, sin cambiar identidades, fichas existentes ni disponibilidad configurada. La plantilla de serie está protegida y se puede copiar para personalizar; se puede desactivar globalmente o para una compañía. La ayuda específica aparece al final del modal, antes de Guardar y Cancelar.
|
||||
|
||||
La ficha captura su especificación y conserva revisiones como cualquier recurso. Registrar una licencia no crea asignaciones, vínculos con aplicaciones/cuentas/equipos, avisos de vencimiento ni cálculos de capacidad. Estas relaciones y avisos quedan pendientes de una entrega posterior.
|
||||
|
||||
@ -2,7 +2,7 @@ import type { ResourceCatalog } from "./resource-catalog.aggregate";
|
||||
import type { ResourceDefinitionSpec, ResourceFieldType } from "./resource-definition";
|
||||
import { ResourceDefinitionValues } from "./value-objects/resource-definition-values";
|
||||
|
||||
const FIELDS: readonly [string, string, ResourceFieldType][] = [
|
||||
const FIELDS: readonly [string, string, ResourceFieldType, (readonly string[])?][] = [
|
||||
["email", "Dirección de correo", "text"],
|
||||
["provider", "Proveedor", "text"],
|
||||
["context", "Contexto", "text"],
|
||||
@ -16,6 +16,18 @@ const FIELDS: readonly [string, string, ResourceFieldType][] = [
|
||||
["fingerprint", "Huella digital", "text"],
|
||||
["purpose", "Finalidad", "text"],
|
||||
["reference", "Referencia", "text"],
|
||||
["software_product", "Producto de software", "text"],
|
||||
["software_edition", "Edición o plan", "text"],
|
||||
["license_model", "Modalidad de licencia", "choice", ["Suscripción", "Perpetua", "OEM", "Otra"]],
|
||||
[
|
||||
"license_metric",
|
||||
"Criterio de licenciamiento",
|
||||
"choice",
|
||||
["Por usuario", "Por dispositivo", "Por servidor", "Concurrente", "Corporativa", "Otro"],
|
||||
],
|
||||
["license_quantity", "Cantidad contratada", "number"],
|
||||
["renews", "Fecha de renovación", "date"],
|
||||
["auto_renew", "Renovación automática", "boolean"],
|
||||
];
|
||||
const TEMPLATES: readonly [string, string, readonly string[]][] = [
|
||||
["mailbox", "Buzón de correo", ["email", "provider", "context"]],
|
||||
@ -23,6 +35,23 @@ const TEMPLATES: readonly [string, string, readonly string[]][] = [
|
||||
["shared_folder", "Carpeta compartida", ["unc", "server", "description"]],
|
||||
["application", "Aplicación", ["provider", "url", "description"]],
|
||||
["certificate", "Certificado digital", ["holder", "issuer", "expires", "fingerprint", "purpose"]],
|
||||
[
|
||||
"software_license",
|
||||
"Licencia de software",
|
||||
[
|
||||
"software_product",
|
||||
"software_edition",
|
||||
"provider",
|
||||
"license_model",
|
||||
"license_metric",
|
||||
"license_quantity",
|
||||
"reference",
|
||||
"expires",
|
||||
"renews",
|
||||
"auto_renew",
|
||||
"description",
|
||||
],
|
||||
],
|
||||
];
|
||||
/** Solo alimenta definiciones; no crea recursos ni migra registros anteriores. */
|
||||
export function seedResourceCatalog(catalog: ResourceCatalog) {
|
||||
@ -38,8 +67,15 @@ export function seedResourceCatalog(catalog: ResourceCatalog) {
|
||||
}
|
||||
current = result.data;
|
||||
};
|
||||
for (const [key, name, type] of FIELDS) {
|
||||
add(`field:${key}`, { kind: "field", name, description: "", type, unit: "", choices: [] });
|
||||
for (const [key, name, type, choices] of FIELDS) {
|
||||
add(`field:${key}`, {
|
||||
kind: "field",
|
||||
name,
|
||||
description: "",
|
||||
type,
|
||||
unit: "",
|
||||
choices: [...(choices ?? [])],
|
||||
});
|
||||
}
|
||||
for (const [key, name, fields] of TEMPLATES) {
|
||||
const references = fields.map((field) => {
|
||||
|
||||
@ -24,7 +24,7 @@ const field = {
|
||||
} as const;
|
||||
test("catálogo de recursos: serie idempotente, UUID v7, características opcionales y protección", () => {
|
||||
const catalog = seedResourceCatalog(ResourceCatalog.create());
|
||||
assert.equal(catalog.definitions.length, 18);
|
||||
assert.equal(catalog.definitions.length, 26);
|
||||
assert.equal(seedResourceCatalog(catalog), catalog);
|
||||
assert.ok(catalog.definitions.every((item) => item.id.toPrimitive()[14] === "7"));
|
||||
const template = catalog.definitions.find((item) => item.values.data.kind === "template");
|
||||
@ -110,3 +110,62 @@ test("ámbitos, tipos, listas, campos externos y disponibilidad por compañía",
|
||||
assert.ok(reset.isSuccess);
|
||||
assert.equal(reset.data.active(stock, Maybe.some(company)), true);
|
||||
});
|
||||
|
||||
test("licencias: ampliar un catálogo previo conserva identidades y disponibilidad", () => {
|
||||
const seeded = seedResourceCatalog(ResourceCatalog.create());
|
||||
const originalKeys = new Set([
|
||||
"field:email",
|
||||
"field:provider",
|
||||
"field:context",
|
||||
"field:unc",
|
||||
"field:server",
|
||||
"field:description",
|
||||
"field:url",
|
||||
"field:holder",
|
||||
"field:issuer",
|
||||
"field:expires",
|
||||
"field:fingerprint",
|
||||
"field:purpose",
|
||||
"field:reference",
|
||||
"template:mailbox",
|
||||
"template:access",
|
||||
"template:shared_folder",
|
||||
"template:application",
|
||||
"template:certificate",
|
||||
]);
|
||||
const previous = ResourceCatalog.rehydrate(
|
||||
{
|
||||
definitions: seeded.definitions.filter((item) =>
|
||||
originalKeys.has(item.stockKey.getOrUndefined() ?? "")
|
||||
),
|
||||
availability: [],
|
||||
},
|
||||
seeded.id
|
||||
);
|
||||
const original = previous.definitions[0];
|
||||
assert.ok(original);
|
||||
const disabled = previous.setActive(Maybe.none(), original.id, false);
|
||||
assert.ok(disabled.isSuccess);
|
||||
const upgraded = seedResourceCatalog(disabled.data);
|
||||
assert.equal(upgraded.definitions.length, 26);
|
||||
for (const existing of disabled.data.definitions) {
|
||||
assert.equal(
|
||||
upgraded.definitions.find((item) => item.id.equals(existing.id)),
|
||||
existing
|
||||
);
|
||||
}
|
||||
const license = upgraded.definitions.find(
|
||||
(item) => item.stockKey.getOrUndefined() === "template:software_license"
|
||||
);
|
||||
assert.ok(license && license.values.data.kind === "template");
|
||||
assert.ok(license.values.data.fields.every((binding) => !binding.required));
|
||||
const protectedTemplate = license.update(values({ ...license.values.data, name: "Otra" }));
|
||||
assert.ok(!protectedTemplate.isSuccess);
|
||||
assert.equal(protectedTemplate.error.code, "BUILTIN_PROTECTED");
|
||||
const company = UniqueID.generateNewID();
|
||||
const locallyDisabled = upgraded.setActive(Maybe.some(company), license.id, false);
|
||||
assert.ok(locallyDisabled.isSuccess);
|
||||
assert.equal(locallyDisabled.data.active(license, Maybe.some(company)), false);
|
||||
assert.equal(locallyDisabled.data.active(license, Maybe.none()), true);
|
||||
assert.equal(seedResourceCatalog(locallyDisabled.data), locallyDisabled.data);
|
||||
});
|
||||
|
||||
@ -23,13 +23,21 @@ export const inventorySpanish = {
|
||||
snapshotNotice:
|
||||
"La ficha conserva los campos de la plantilla utilizados al dar de alta el recurso.",
|
||||
resourceDescription:
|
||||
"Registra un buzón, carpeta compartida, aplicación o certificado disponible en el contexto de esta compañía.",
|
||||
"Registra un buzón, carpeta compartida, aplicación, certificado o licencia de software disponible en el contexto de esta compañía.",
|
||||
resourceExamples:
|
||||
"Ejemplos: administracion@empresa.es, carpeta Administración, Zoom corporativo o certificado de firma.",
|
||||
"Ejemplos: administracion@empresa.es, carpeta Administración, Zoom corporativo, certificado de firma o suscripción Microsoft 365.",
|
||||
resourceGuidance:
|
||||
"Las características son opcionales salvo que la plantilla indique lo contrario. No registres contraseñas ni claves privadas.",
|
||||
resourceScope:
|
||||
"Este registro identifica el recurso de la compañía. Su disponibilidad y su configuración en los equipos se gestionan por separado.",
|
||||
licenseDescription:
|
||||
"Registra una licencia o suscripción de software contratada por esta compañía.",
|
||||
licenseExamples:
|
||||
"Ejemplos: Microsoft 365 Business Standard por usuario, ESET Endpoint por dispositivo o una licencia perpetua de ERP.",
|
||||
licenseGuidance:
|
||||
"Todos los campos son opcionales. Cantidad contratada indica las plazas o unidades del contrato. Caducidad y renovación pueden ser distintas; una licencia perpetua puede no tener caducidad. No registres claves de activación.",
|
||||
licenseScope:
|
||||
"La aplicación utilizada y la licencia contratada son recursos distintos. Este registro no asigna plazas ni calcula cuántas quedan disponibles.",
|
||||
relatedNotice: "La plantilla se guardará aunque después canceles el recurso.",
|
||||
inventoryErrors: {
|
||||
INVALID_REQUEST: "Revisa los datos del formulario.",
|
||||
@ -73,13 +81,20 @@ export const inventoryEnglish = {
|
||||
recordedBy: "Recorded by {{name}}",
|
||||
snapshotNotice: "This record retains the template fields captured when the resource was created.",
|
||||
resourceDescription:
|
||||
"Register a mailbox, shared folder, application or certificate used in this company's context.",
|
||||
"Register a mailbox, shared folder, application, certificate or software license used in this company's context.",
|
||||
resourceExamples:
|
||||
"Examples: admin@company.com, Administration folder, corporate Zoom or signing certificate.",
|
||||
"Examples: admin@company.com, Administration folder, corporate Zoom, signing certificate or Microsoft 365 subscription.",
|
||||
resourceGuidance:
|
||||
"Characteristics are optional unless the template requires them. Never store passwords or private keys.",
|
||||
resourceScope:
|
||||
"This record identifies the company resource. Availability and device configuration are managed separately.",
|
||||
licenseDescription: "Register a software license or subscription contracted by this company.",
|
||||
licenseExamples:
|
||||
"Examples: Microsoft 365 Business Standard per user, ESET Endpoint per device or a perpetual ERP license.",
|
||||
licenseGuidance:
|
||||
"All fields are optional. Contracted quantity records seats or units covered by the contract. Expiration and renewal dates may differ; perpetual licenses may have no expiration. Do not enter activation keys.",
|
||||
licenseScope:
|
||||
"The application used and its contracted license are separate resources. This record does not allocate seats or calculate remaining capacity.",
|
||||
relatedNotice: "The template remains saved if you later cancel the resource.",
|
||||
inventoryErrors: {
|
||||
INVALID_REQUEST: "Check the form data.",
|
||||
|
||||
@ -84,6 +84,10 @@ export function ResourceForm({
|
||||
);
|
||||
const template = templates.find((item) => item.id === values.templateId);
|
||||
const fields = resourceFields(resource, template, items);
|
||||
const isSoftwareLicense = items.some(
|
||||
(item) => item.id === values.templateId && item.builtinKey === "template:software_license"
|
||||
);
|
||||
const helpPrefix = isSoftwareLicense ? "license" : "resource";
|
||||
const mutation = useMutation({
|
||||
mutationFn: (input: ResourceInput) =>
|
||||
inventoryApi.save(companyId, input, csrfToken, resource?.id),
|
||||
@ -234,11 +238,11 @@ export function ResourceForm({
|
||||
)}
|
||||
<InventoryFeedback error={mutation.error ?? referenceQuery.error} />
|
||||
<ContextHelp
|
||||
description={t("resourceDescription")}
|
||||
examples={t("resourceExamples")}
|
||||
description={t(`${helpPrefix}Description`)}
|
||||
examples={t(`${helpPrefix}Examples`)}
|
||||
examplesLabel={t("examplesLabel")}
|
||||
guidance={t("resourceGuidance")}
|
||||
scopeNote={t("resourceScope")}
|
||||
guidance={t(`${helpPrefix}Guidance`)}
|
||||
scopeNote={t(`${helpPrefix}Scope`)}
|
||||
title={t("helpTitle")}
|
||||
/>
|
||||
<FormActions>
|
||||
|
||||
@ -75,3 +75,57 @@ test("recurso: ayuda inferior, plantilla con alta rápida y sin asignaciones de
|
||||
);
|
||||
assert.equal(child.includes('aria-label="Nueva plantilla"'), false);
|
||||
});
|
||||
|
||||
test("licencia: formulario reutilizable con campos opcionales y ayuda antes de guardar", async () => {
|
||||
const catalog = buildCatalogResponse(seedResourceCatalog(ResourceCatalog.create()), Maybe.none());
|
||||
const template = catalog.items.find((item) => item.builtinKey === "template:software_license");
|
||||
assert.ok(template && template.values.kind === "template");
|
||||
const fields = template.values.fields.map((binding) => {
|
||||
const field = catalog.items.find((item) => item.id === binding.fieldId);
|
||||
assert.ok(field && field.values.kind === "field");
|
||||
return {
|
||||
fieldId: field.id,
|
||||
name: field.values.name,
|
||||
type: field.values.type,
|
||||
unit: field.values.unit,
|
||||
choices: field.values.choices,
|
||||
required: binding.required,
|
||||
};
|
||||
});
|
||||
const resource = {
|
||||
id: UniqueID.generateNewID().toPrimitive(),
|
||||
companyId,
|
||||
specification: { templateId: template.id, templateName: template.values.name, fields },
|
||||
values: {
|
||||
name: "Microsoft 365",
|
||||
templateId: template.id,
|
||||
active: true,
|
||||
notes: "",
|
||||
characteristics: {},
|
||||
},
|
||||
history: [],
|
||||
};
|
||||
for (const [language, help] of [
|
||||
["es", "Registra una licencia o suscripción"],
|
||||
["en", "Register a software license or subscription"],
|
||||
]) {
|
||||
assert.ok(language && help);
|
||||
const html = await render(
|
||||
language,
|
||||
<ResourceForm
|
||||
companyId={companyId}
|
||||
csrfToken="test"
|
||||
items={catalog.items}
|
||||
onSaved={() => undefined}
|
||||
resource={resource}
|
||||
/>
|
||||
);
|
||||
assert.ok(html.includes("Cantidad contratada"));
|
||||
assert.ok(html.includes("Fecha de renovación"));
|
||||
assert.ok(html.includes("Renovación automática"));
|
||||
assert.ok(html.includes(help));
|
||||
assert.ok(html.indexOf(help) < html.indexOf('type="submit"'));
|
||||
assert.equal(html.includes("Cantidad contratada *"), false);
|
||||
assert.equal(html.includes("licenseDescription"), false);
|
||||
}
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user