Requerimiento: Trazabilidad de Actividades de Servicio Técnico hacia la orden origen y el ítem de mano de obra¶
Módulo: Actividades / Service Calls / OMS
Integración: SAP Business One — Service Layer (OData) · objeto Activities
Stack: NestJS (backend) · Next.js (frontend)
Estado: Modelado para desarrollo
Decisión clave: Hoy la relación de la actividad con SAP se modela exclusivamente con la tripleta
DocType/DocNum/DocEntry, y esa tripleta ya está ocupada apuntando al ítem de mano de obra (DocType = "4"). Como consecuencia se pierde la relación con la orden que originó la solicitud de servicio técnico.La solución adoptada es liberar la tripleta para que apunte a la orden origen e introducir un campo de usuario (UDF) en el objeto
Activitiesde SAP, llamadoU_ItemLaborCode, que referencia la tabla de ítems (OITM) y almacena el código del ítem de mano de obra. Así la actividad conserva ambas relaciones simultáneamente: (1) la orden origen y (2) el ítem labor.
1. Resumen del requerimiento¶
El OMS permite que un vendedor solicite un servicio técnico desde una orden (botón en el formulario de la orden). Esa solicitud se materializa como una Actividad de SAP de tipo Servicio Técnico (ActivityType = 6). El negocio necesita que esa actividad mantenga trazabilidad hacia:
- La orden que la origina — para poder navegar orden → actividad y actividad → orden.
- El ítem de mano de obra (labor) — el ítem SAP de tipo
itLabor(códigos tipoST00xxx) que representa el trabajo/servicio a facturar.
El modelo actual de SAP solo dispone de una vía nativa de vinculación de la actividad a un documento (la tripleta DocType/DocNum/DocEntry), por lo que ambas relaciones no caben y hoy se privilegia la del ítem labor, perdiéndose la de la orden.
Objetivo: conservar las dos relaciones agregando un UDF dedicado al ítem labor.
2. Glosario¶
| Término | Significado |
|---|---|
| Actividad ST | Actividad de SAP con ActivityType = 6 (Servicio Técnico). |
| Ítem labor | Ítem de SAP de tipo itLabor (mano de obra/servicio, códigos ST00xxx). Se lista con GET /items/labour. |
| Orden origen | Documento de venta (pedido) desde el cual el vendedor solicita el servicio técnico. |
| Tripleta de documento | Los tres campos DocType + DocNum + DocEntry con que la Activity de SAP referencia a un documento. |
| UDF | User Defined Field. En SAP se expone en Service Layer con prefijo U_ (p. ej. U_ItemLaborCode). |
| Service Call | Llamada de servicio de SAP. Agrupa actividades en su array ServiceCallActivities. |
3. Modelo de datos actual (SAP Activities)¶
Ejemplo real de una actividad ST:
{
"ActivityCode": 636,
"CardCode": "C32852",
"ActivityType": 6, // Servicio Técnico
"Subject": 24, // Ejecución
"Details": "Inicialización",
"Activity": "cn_Note",
"Status": 11,
"HandledBy": 103,
// --- vinculación a documento: hoy apunta al ÍTEM LABOR ---
"DocType": "4", // 4 = Ítem
"DocNum": "ST00025", // = ItemCode del labor
"DocEntry": "ST00025", // = ItemCode del labor
"ParentObjectId": 6422, // (no lo usa el backend)
"ParentObjectType": "191"
}
3.1. Cómo se usa hoy la tripleta en el backend¶
DocType |
Significado | Uso en el código |
|---|---|---|
"4" |
Ítem (labor) | DocNum/DocEntry = ItemCode del labor. Constante DOC_TYPE_ITEM = '4' en activities.service.ts:43. |
"17" |
Pedido de venta | Al leer la actividad, si DocType === '17' y hay DocEntry, se resuelve el pedido y se adjunta como RelatedOrder — activities.service.ts:535-558. |
El conflicto: un mismo registro de Activity no puede tener
DocType = "4"(ítem) yDocType = "17"(orden) al mismo tiempo. Al usarla para el ítem labor, se pierde la de la orden.
Referencias de creación relevantes:
- Creación genérica:
POST /activities→ activities.service.ts:351-376 → repoPOST /Activitiesactivities.repository.ts:204-207. - Creación dentro de Service Call:
POST /service-calls/:id/activities→ service-calls.service.ts:2763-2861. La tripleta se fija a ítem labor en service-calls.service.ts:2808-2818. - Carga masiva:
POST /service-calls/:id/activities/batch→ service-calls.service.ts:2875-2975, fijaDocType: '4'yDocNum/DocEntry = task.itemCodeen service-calls.service.ts:2898-2909.
4. Modelo de datos propuesto¶
Se reparte la información en dos vías:
| Relación | Vía de almacenamiento | Contenido |
|---|---|---|
| Actividad → Orden origen | Tripleta DocType / DocNum / DocEntry |
DocType = "17" (pedido de venta) · DocNum/DocEntry = DocEntry de la orden. |
| Actividad → Ítem labor | UDF nuevo U_ItemLaborCode |
ItemCode del ítem itLabor (p. ej. ST00025). |
Actividad resultante (ejemplo):
{
"ActivityCode": 636,
"ActivityType": 6,
"DocType": "17", // ahora: PEDIDO (orden origen)
"DocNum": "1042", // DocEntry / número de la orden
"DocEntry": "1042",
"U_ItemLaborCode": "ST00025" // NUEVO: ítem de mano de obra
}
Nomenclatura SAP: el campo se define en SAP como
ItemLaborCodey el Service Layer lo expone comoU_ItemLaborCode. En el backend siempre se lee/escribe con el prefijoU_.
4.1. Definición del UDF en SAP Business One¶
El campo ya fue creado en el objeto Actividades (OCLG) vía Service Layer (POST /b1s/v2/UserFieldsMD) con esta configuración:
| Propiedad | Valor |
|---|---|
| Objeto / tabla | Actividades (OCLG) |
| Nombre del campo | ItemLaborCode (Service Layer: U_ItemLaborCode) |
| Descripción | Ítem de mano de obra (labor) asociado a la actividad ST |
Tipo (Type) |
db_Alpha (Alfanumérico) |
Longitud (EditSize) |
50 |
Objeto vinculado (LinkedSystemObject) |
ulItems — asiste el choose-from-list del cliente SAP. |
Obligatorio (Mandatory) |
tNO |
Payload de creación (funcional):
{
"TableName": "OCLG",
"Name": "ItemLaborCode",
"Description": "Item de mano de obra (labor) asociado a la actividad ST",
"Type": "db_Alpha",
"EditSize": 50,
"LinkedSystemObject": "ulItems",
"Mandatory": "tNO"
}
⚠️ El vínculo NO valida integridad referencial.
LinkedSystemObject: "ulItems"(al igual queLinkedTable) es solo una ayuda de UI en el cliente SAP: habilita el choose-from-list, pero ni el Service Layer ni la DI API rechazan un valor inexistente. Se verificó que unPOST /ActivitiesconU_ItemLaborCode: "lkljart"(ítem inexistente) se guarda sin error. Por lo tanto, la validación de que el valor sea unItemCodede tipoitLaborexistente corre 100% por cuenta del backend (ver §6).Nota sobre edición: las propiedades de vínculo de un UDF no se pueden modificar de forma fiable con
PATCHuna vez creado el campo; si hubiera que corregirlas, se debe borrar y recrear el UDF (DELETE /b1s/v2/UserFieldsMD(TableName='OCLG',FieldID=<id>)).
Al ser un UDF, no requiere serialización especial: el sap-client es un proxy transparente al Service Layer y los campos U_* viajan como propiedades normales del JSON en POST/PATCH y se leen con $select estándar de OData. Ver ejemplos vigentes en service-calls.service.ts:2370-2382 (U_Sucursal, U_Warehouse, etc.).
5. Impacto en el backend¶
5.1. DTOs¶
CreateActivityDto — dtos/create-activity.dto.ts
- Agregar campo opcional ItemLaborCode?: string (validado como ItemCode de labor).
- Actualizar la descripción de DocType/DocEntry: cuando la actividad es ST, la tripleta pasa a representar la orden origen, no el ítem.
ActivityResponseDto — dtos/activity-response.dto.ts:26-125
- Exponer ItemLaborCode? y, si aplica, resolver LabourItemName? a partir de U_ItemLaborCode (hoy LabourItemName se deriva de la tripleta; debe pasar a derivarse del UDF).
- RelatedOrder? pasa a resolverse desde DocType = "17" + DocEntry (ya soportado en la lectura).
DTOs de Service Call — service-calls.service.ts:231-291
- CreateServiceCallActivityDto y BatchActivityTaskInput: el itemCode del labor debe mapearse a U_ItemLaborCode en lugar de a la tripleta.
5.2. Mapeo actividad → payload SAP¶
En los tres puntos de creación, mover el ItemCode labor de la tripleta al UDF y dejar la tripleta para la orden:
- Genérico: activities.service.ts:351-376.
- Service Call (una): service-calls.service.ts:2808-2818 — sustituir el bloque que fija
DocType='4'/DocNum/DocEntryporU_ItemLaborCode = itemCode, y usar la tripleta para la orden si viene informada. - Service Call (batch): service-calls.service.ts:2898-2909 —
U_ItemLaborCode = task.itemCode.
5.3. Lectura / $select¶
Añadir U_ItemLaborCode a los $select de las consultas de actividades (tablero ST, detalle, listados) para no perder el campo:
- Tablero ST y detalle: activities-servicio-tecnico.controller.ts y el service correspondiente.
- Resolución de LabourItemName: hoy se hace vía tripleta; repuntar a U_ItemLaborCode.
5.4. Resolución inversa (orden → actividades)¶
El filtro GET /activities?docEntry=&docType= (activities.controller.ts:44-45) ya permite listar actividades por documento. Con la orden en la tripleta, GET /activities?docType=17&docEntry=<orden> devolverá las actividades ST originadas por esa orden — funcionalidad nueva habilitada por el cambio.
6. Reglas de validación¶
ItemLaborCode, si viene informado, debe existir enOITMy ser de tipoitLabor. El backend ya lista labores conGET /items/labour(items.controller.ts:103-124); reutilizar esa fuente para validar.- Para actividades ST creadas desde una orden, la tripleta debe llevar
DocType = "17"y elDocEntryde la orden origen. - Compatibilidad hacia atrás: las actividades históricas tienen el ítem labor en la tripleta (
DocType = "4"). Ver §7.
7. Migración y compatibilidad¶
⚠️ Contexto crítico: en producción hoy el ítem labor se muestra leyendo la tripleta (
DocType = "4",DocNum/DocEntry=ItemCode). Esos son exactamente los mismos campos que a partir de este cambio pasarán a usarse para la orden origen (DocType = "17"). Por lo tanto la convivencia de ambos modelos es obligatoria, no opcional: si la capa de lectura no distingue el modelo, las actividades históricas dejarían de mostrar su ítem labor (o lo mostrarían como si fuera una orden).
7.1. Soporte legacy en visualización — OBLIGATORIO¶
La capa de lectura debe discriminar por modelo usando el DocType y la presencia del UDF:
| Escenario | Detección | Ítem labor | Orden origen |
|---|---|---|---|
| Nuevo (post-cambio) | U_ItemLaborCode informado |
U_ItemLaborCode |
tripleta con DocType = "17" |
| Legacy (histórico) | U_ItemLaborCode vacío y DocType = "4" |
DocEntry (tripleta) |
no disponible |
Regla efectiva de resolución en lectura:
// Ítem labor efectivo
const itemLaborCode = activity.U_ItemLaborCode
?? (activity.DocType === '4' ? activity.DocEntry : null);
// Orden origen efectiva (solo modelo nuevo)
const relatedOrderDocEntry = activity.DocType === '17' ? activity.DocEntry : null;
Esto aplica a todos los puntos donde hoy se deriva LabourItemName/RelatedOrder (detalle, tablero ST, listados). Mientras existan actividades sin U_ItemLaborCode, este branch legacy no se puede eliminar.
7.2. Backfill de históricos — RECOMENDADO¶
El requerimiento es ejecutar un backfill para unificar el modelo y poder retirar el branch legacy a futuro:
- Alcance: actividades ST (
ActivityType = 6) conDocType = "4"yU_ItemLaborCodevacío. - Acción: copiar
DocEntry → U_ItemLaborCodemediantePATCH /Activities(<code>). - Tripleta: en el modelo legacy no hay orden origen conocida, por lo que la tripleta debe limpiarse (o dejarse informada como labor) — no se puede inventar un
DocEntry = "17". Es decir, el backfill completa la relación con el labor pero no crea retroactivamente la relación con la orden (esa información no existe en los históricos). - Forma sugerida: un comando CLI en
src/commands/(patrónreindex:*) idempotente y reejecutable, que registre cuántas actividades migró.
Conclusión del punto: aunque se corra el backfill, la lectura legacy de §7.1 debe estar en producción antes o junto con el despliegue, porque el backfill no es instantáneo y no puede reconstruir la orden origen de los históricos. El backfill reduce la deuda a futuro; la lectura legacy evita la regresión inmediata.
7.3. Orden de despliegue¶
- Crear el UDF
U_ItemLaborCodeen SAP (ver §4.1). Debe existir antes de desplegar el backend que lo escribe, o losPOST/PATCHfallarán. - Desplegar el backend con lectura dual (§7.1) y escritura al nuevo modelo.
- Ejecutar el backfill (§7.2).
- (Futuro, opcional) Una vez el backfill al 100% y verificado, retirar el branch legacy.
8. Impacto en frontend (resumen)¶
- Formulario de solicitud de ST desde la orden: enviar el
DocEntryde la orden (tripleta) y elItemLaborCode(labor) por separado. - Formulario de actividades y "Cargar Tareas" en Service Call: el selector de ítem labor pasa a poblar
ItemLaborCodeen vez de la tripleta. - Vista de detalle de actividad: mostrar tanto la orden relacionada (
RelatedOrder) como el ítem labor (LabourItemName), ahora provenientes de fuentes distintas.
9. Decisiones y puntos abiertos¶
Decidido¶
- Tipo de documento origen: pedido de venta (
DocType = "17"). ✅ Confirmado. La tripleta de la actividad ST apunta alDocEntrydel pedido de venta origen. - Backfill de históricos: se ejecuta (recomendado) y, además, la lectura legacy es obligatoria. ✅ Ver §7. Motivo: en producción hoy el ítem labor se visualiza desde los mismos campos (tripleta) que ahora se reasignan a la orden; sin lectura dual habría regresión inmediata. El backfill completa el labor en el UDF pero no puede reconstruir la orden origen de los históricos.
Abierto¶
- Longitud del UDF: confirmar longitud máxima real de los
ItemCodede labor (se definióEditSize = 50). - Integridad referencial: el vínculo del UDF (
LinkedSystemObject: "ulItems") no valida en Service Layer/DI API (solo asiste el choose-from-list del cliente SAP). La validación de queU_ItemLaborCodesea un ítemitLaborexistente corre 100% por cuenta del backend (ver §6).