Un deseo tan antiguo como Navision: un cambio de cliente general a través del código de programa. Y desgraciadamente se ha quedado en un deseo hasta hoy... ¿o no?
Changecompany - Problema con los activadores
Una tarea típica en Navision es, por ejemplo, la importación de datos de un único archivo a diferentes clientes. Por ejemplo, se leen existencias de un SGA (sistema de gestión de almacenes), que se asignan a distintos clientes en el ERP.
Además, si desea sustituir una aplicación mainframe de los años 70/80, por ejemplo Trampolin o Quattro Pro en Siemens Nixdorf, es posible que usted/su aplicación ni siquiera conozca el término "cliente".
En principio, esta lectura es muy sencilla. La sustitución del ordenador central no es... La sustitución del ordenador central no es... La tabla receptora se establece como DataPerCompany = NO, y así todos los registros de datos se pueden leer directamente de una sola vez,
o bien se deja la tabla receptora en el valor por defecto DataPerCompany = , y se cambia el mandante con ImportTable.changecompany(mandante de destino del registro de datos a importar) para cada registro de datos a importar.
Ninguno de los dos es técnicamente difícil.
Sin embargo, resulta difícil a) introducir correctamente estos datos en las filas de la hoja del libro de artículos del cliente de destino correspondiente y b) contabilizar correctamente esta tabla.
¿Por qué? Todos los que han trabajado con ChangeCompany han tropezado con ella.
Veamos un ejemplo sencillo:
CLEAR(item); //Importante: ¡debe colocarse ANTES del ChangeCompany! Clear reinicia ChangeCompany, a diferencia de Init. item.CHANGECOMPANY(cliente2) Item.INSERT(true); /¡El TRUE es el mal!
Presumiblemente, este código funcionará exactamente así.
¿Pero qué pasa?
El artículo se crea en el cliente2 ¡pero con un número de artículo del cliente actual!
¿Por qué? Veamos el InsertTrigger de ItemTable 27 en el contexto general:
CLEAR(item); item.CHANGECOMPANY(cliente2) Item.INSERT(true); -> IF "No." = '' THEN BEGIN GetInvtSetup; InvtSetup.TESTFIELD("Item Nos."); //¡InvtSetup viene de cliente1! NoSeriesMgt.InitSeries(InvtSetup."Item Nos.",xRec."No. Series",0D,"No.","No. Series"); //¡La unidad de código de la serie numérica se ejecuta con todos los registros en ella en el cliente 1! "Costing Method" := InvtSetup."Default Costing Method"; //¡InvtSetup viene de cliente1! END;
No hay forma de cambiar este comportamiento. Cualquiera que trabaje con ChangeCompany por primera vez está casi obligado a tropezar con este comportamiento. Lo normal (en mi opinión) es que la tabla "sepa" que está trabajando en el cliente2, y por tanto toda la lógica del programa en ella trabaje automática e implícitamente con registros del cliente2.
Mhm... Spoiler alert: No es así. El cliente global activo se aplica a la sesión, no al objeto. Así que para el contexto de usuario actual.
Otro ejemplo gracioso es éste:
Customer.get('Número de cliente');
Customer.CHANGECOMPANY(cliente2);
Customer.calcfields(Balance);
SI el cliente con el "número de cliente" existe en el cliente 2, calcfields(Saldo) calculará el saldo para... ¡este número de cliente en el cliente 1 actual! ¡No importa de que cliente se trate! Porque también el necesario Detailed Cust. Cust. (Tabla 339) se toman del cliente del contexto de usuario, es decir, ¡del cliente activo! Si este número de cliente (mejor: la Detailed Cust. Ledg. Entry (table 339) entries) para este cliente no existe en el cliente actual, el resultado es simplemente 0, incluso si este cliente tiene saldo en el cliente 2.
La solución para el problema de los CalcFields es bastante sencilla: el Calcfield debe emularse "simplemente" a través de la tabla correspondiente, p. ej.
CustLedgEntry.setrange("Nº de cliente");
CustLedgEntry.Calcsums(Amount);
Pero, ¿qué hacemos con la hoja de entrada de artículos o con cualquier otra hoja de entrada? ¿O, en general, al insertar registros? Los registros se pueden inicializar con plantillas, por ejemplo. Dado que la lógica estándar de Navision no realiza un cambio de cliente para los triggers, habría que reprogramar TODA la lógica relevante con el cambio de cliente necesario. Un simple "Table.ChangeCompany(NewClient, Global=True)" nos haría la vida mucho más fácil...
Bueno... eso no existe, por desgracia. Bueno...
Cambio de cliente completo con todos los activadores
StartSession(SessionID,CodeunitNo,Company,Record) permite iniciar cualquier unidad de código como una nueva sesión - en cualquier cliente. Dado que el cliente (=Empresa) es válido para una sesión, el cliente dado como parámetro es válido para todo el tiempo de ejecución de esta unidad de código - y todos los objetos, disparadores, etc. llamados en o por esta unidad de código.
Por lo tanto, el truco consiste en leer los archivos que se van a importar en las respectivas tablas temporales por cliente en el ejemplo anterior para la importación completamente sin triggers, y luego pasar esta tabla temporal a una sesión de inicio.
Atención. ¡Si esto debe suceder de una sola vez, entonces también necesita tablas temporales (por ejemplo, como una matriz), o también debe guardar el cliente válido en la tabla, y luego reaccionar correctamente en la unidad de código llamada!
Pues
Case ActualLíneaCliente of
'1' : TempTableCliente1.FieldValue = Algo;
'2' : TempTableCliente2.FieldValue = Algo;
end;
o
TempTableCliente[ActualLíneaCliente].FieldValue = Algo;
Sin embargo, es mucho más elegante trasladar toda la lógica de lectura / procesamiento directamente a la nueva sesión:
SessionStartetAt := CREATEDATETIME(WORKDATE,TIME); STARTSESSION(SessionEvent."Session ID",50010,pCompanyCode,ImportAS400); Log.Log(ObjRef,'SessionStart'+ pCompanyCode,SessionEvent."Session ID",''); SLEEP(100); ActiveSession.SETRANGE("Session ID",SessionEvent."Session ID"); WHILE ActiveSession.FINDFIRST DO BEGIN SessionDuration := ROUND((CREATEDATETIME(WORKDATE,TIME) - SessionStartetAt) / 1000,1,'<'); SessionWindow.UPDATE(2,ROUND(SessionDuration / pEstDurationS * 10000,1,'<')); SessionEvents.SETRANGE("Session Unique ID",ActiveSession."Session Unique ID"); SLEEP(1000); END; Log.Log(ObjRef,'Session Duration '+pCompanyCode,SessionDuration,pFileName); SessionEvents.SETCURRENTKEY("Session Unique ID"); IF SessionEvents.FINDLAST THEN Log.Log(ObjRef,'SessionEnd '+ pCompanyCode,SessionEvents.Comment,pFileName);
Por supuesto, esto es sólo el marco básico. Si usted tiene una necesidad aquí, por favor póngase en contacto conmigo.
Especialmente en el área de transferencias de datos AS/400 , a menudo existe la necesidad de leer tablas que se emiten a través de clientes en clientes separados en Navision Financials o Business Central. Tenga en cuenta que a) nunca debe configurar una tabla Navision estándar como "Cliente cruzado = Sí". Estaría abriendo la caja de Pandora. Y b) que desde BC 15 ya no se puede hacer esto... afortunadamente.
Pero... ¿cómo saber cuándo está lista la sesión?
Este es el siguiente problema automático de las sesiones. Son autosuficientes. Trabajan de forma independiente. Cuando usted termina, ellos terminan.
Lo más sencillo es construir las tablas a procesar de tal forma que puedas lanzarlas con una sesión de inicio, y en algún momento estarán listas, y ya está. Por supuesto, esto no funciona si los datos a importar se construyen unos sobre otros. Por ejemplo, primero los datos maestros del artículo y luego los datos de la transacción correspondiente.
Pero si realmente quiere saber cuándo han terminado, necesita algo como un valor de retorno. StartSession incluso proporciona esto: OK := StartSession(...
Sin embargo, OK sólo devuelve si se ha podido iniciar la sesión, no si se ha terminado correctamente. Por tanto, no es comparable con el conocido OK=Codeunit.run..., que a) espera a ver si la unidad de código se terminó, y b) también devuelve un resultado: TRUE: Se ha procesado correctamente FALSE: Se ha producido un error durante el procesamiento, que habría provocado un error. No existe tal cosa con StartSession.
Bueno... Afortunadamente, Navision / Business Central todavía tiene suficientes raíces de Navision que todavía se puede formar una solución en torno a la parte cada vez más complicada de Microsoft.
La unidad de código iniciada con la sesión de inicio en Navision o Business Central introduce un nuevo valor en la tabla de sesiones. Y en la terminación una entrada en la SessionEvents, si está disponible con mensaje de error de terminación.
Aquí un truco: Introduzca las dos páginas existentes 670 & 9506 así como una nueva página en la tabla 2000000111 Evento de sesión directamente en el sitio del menú en Herramientas de aplicación:
En el ejemplo anterior, la espera a que finalice la sesión específica del cliente recién iniciada ya está incorporada.