Mejora del modelo de datos de precios
4 Tareas
45 minutos
Escenario
FSG inicialmente usó las tablas de decisión y Rule-Declare-Expression para fijar un precio y computar el costo de diferentes elementos cotizables, como el evento y el servicio de hotel opcional, para respaldar el proceso de cotización del evento. El número de Rule-Declare-Expression fue considerado excesivo y puede aumentar cuando se agregan otros elementos cotizables, como el cobro del estacionamiento de automóviles y la oferta potencial del servicio de transporte.
FSG decidió implementar un enfoque de modelo de precios similar al usado en eCommerce o en el software del “carrito de compras”. En este enfoque, se definió un tipo de datos Pricing (Precios), y este contiene las propiedades fundamentales para computar los precios en función del modelo de precios cotizable. Los dos modelos de precios más básicos son la unidad y el volumen.
FSG implementó reglas Rule-Declare-OnChange que reaccionan a los cambios en las entradas en el tipo de datos Pricing (Precios), eliminando Rule-Declare-Expression. La solución todavía dependía de las tablas de decisión. Antes de eliminar Rule-Declare-Expression, persistía el problema de tener reglas específicas del modelo de precios al nivel de clase FSG-Data-Pricing. El diseño se mejoró usando la herencia de patrones y el polimorfismo para mover las reglas específicas del modelo de precios a su clase (por ejemplo, FSG-Data-Pricing-Volume). Anteriormente, se rechazó el uso de la puesta en circunstancia como un enfoque de especialización basado en el modelo de precios. Quedó abierta la opción de usar la puesta en circunstancia para un propósito detallado en el nivel de FSG-Data-Pricing, como la diferencia entre cómo se computa un precio y un costo.
Un motivo por el que se quisieron eliminar las tablas de decisión fue que, para agregar un nuevo elemento cotizable, la tabla de decisión que especifica un modelo de precios cotizable requiere modificación. La tabla de decisión que define cómo fijar el precio de los elementos cotizables también requiere modificación; esto infringió el principio abierto/cerrado. Tampoco existía una manera de especificar que se usaba un modelo de precios diferente para el costo en comparación con los precios. El Centro de excelencia (COE) del FSG no quería agregar otra tabla de decisión; por el contrario, quería eliminar las tablas de decisión.
También estaba el problema de pasar las entradas al data transform de recálculo de FSG-Data-Pricing, definiendo el valor de una propiedad de visualización correspondiente y actualizando un total en ejecución para el precio total y el costo total para, luego, computar la ganancia potencial. Había una actividad Recalculate (Recalcular) única para cada elemento cotizable (por ejemplo, RecalculateHotel), lo que también infringía el principio abierto/cerrado.
El COE de FSG sabe que pasar del estado actual del “motor de precios” a uno que cumpla el principio abierto/cerrado requiere análisis. Aquí comienza el reto.
La siguiente tabla incluye las credenciales que necesita para completar el reto.
Función | Nombre de usuario | Contraseña |
---|---|---|
Admin | COE@FSG | reglas |
Admin | Admin@FSGPricingTest | reglas |
Tareas detalladas
1 Transición a Pega Live Data
El COE de FSG sabía que, para evitar que la solución general se complicara demasiado, debía mantener cada parte de la solución lo más simple posible. La complejidad aumenta con cada variable que se agrega. El primer paso era convertir la tabla de decisión PricingModel a un tipo de datos Priceable (Cotizable). “Pricing Model” (Modelo de precios) no se usó como el nombre de tipo de datos. El propósito del tipo de datos no es encapsular la definición de un modelo de precios. En cambio, el propósito del tipo de datos es encapsular el Id. del elemento que se cotice y el modelo de precios que se use para derivar su precio.
La mejor manera de comenzar fue mantener la definición de la clase FSG-Data-Priceable al mínimo posible. La clase tendría una propiedad PricingType, donde el valor de PricingType es “Price” (Precio) o “Cost” (Costo). Hacer esto puede resultar en múltiples filas con el mismo ItemID, lo que agrega complejidad. PricingType debe transferirse como un parámetro para cada regla donde se transfiere ItemID. Tiene más sentido tener el tipo de datos Priceable (Cotizable) que tener una sola clave.
El valor de ItemID puede indicar si representa un costo anexando “Cost” (Costo) a su contraparte de ItemID de precio. La propiedad VolumeIncludesQty pertenece a la clase Priceable (Cotizable), aunque solo se aplica al modelo de precios “Volume” (Volumen). La propiedad dice que, si es verdadero, los precios son los que se visualizan. De otro modo, si es falso, el precio debería multiplicarse por la cantidad usada para derivar el precio.
Cotizable
ItemID (clave) |
PricingModel |
VolumeIncludesQty |
La tabla de precios ya existe. Cada columna en esta tabla es histórica. La tabla registra qué caso posee el registro (Reference), cuál es el elemento cotizable, el precio computado y las entradas que se usan para computar el precio: Quantity, Bit, DiscountFactor, PricingModel y, si corresponde, VolumeIncludesQty.
Precios
ItemID (clave) |
Referencia (clave) |
Precio |
Cantidad |
Bit |
DiscountFactor |
PricingModel (clave) |
VolumeIncludesQty |
La tabla de decisión originalmente llamada Pricing (Precio) se desglosó en una tabla de decisión UnitPricing dentro de la claseSG-Data-Pricing-Unit F y una tabla de decisión VolumePricing dentro de la clase FSG-Data-Pricing-Value . El polimorfismo se usa contra un data transform llamado DerivePricing, que termina en FSG-Data-Pricing y, posteriormente, se anula en cada clase derivada.
El primer paso es mover las columnas en ambas tablas de decisión hacia una tabla de datos en vivo. El nombre de la tabla de datos en vivo no puede ser Pricing (Precios) porque ese nombre ya está en uso. Un mejor nombre para la tabla es Price (Precio), porque esa es la información que contiene la tabla.
Precio
ItemID |
Cantidad desde |
Cantidad hasta |
PricingModel |
Precio |
Es posible decir que un registro Price (Precio) tiene cuatro claves, a saber, las cuatro columnas a la izquierda. No tiene sentido usar solo dos claves (ItemID y PricingModel) que dicen “Todos los elementos cotizables pueden tener solo una fila por PricingModel”, porque el precio de volumen en sí requiere múltiples filas, una para cada rango de cantidad desde/hasta. No es posible hacer valer ese único conjunto contiguo (que no se superpone) de filas de rango de cantidad de volumen definido. Potential (Potencial) existe para agregar una columna AsOfDate a esta tabla, y permitir que los precios transicionen a nuevos valores en el tiempo, sin problemas. Debido a la complejidad de la aplicación de una clave única, se toma la decisión de generar automáticamente pyGUID como la clave principal seleccionando el cuadro en la regla de clase de tipo de datosFSG-Data-Price .
2 Diseño de una solución abierta/cerrada
En la Etapa 1, el diseño de una solución abierta/cerrada es simple: convertir dos tablas de decisión en dos tablas de datos en vivo. La siguiente etapa es más difícil.
La tarea más simple es encontrar una alternativa para la actividad InitPricing al comienzo de la etapa de cotización. La actividad es específica para los elementos cotizables que cotiza la aplicación Booking (Reserva). Una alternativa superior es encontrar una manera de iniciar los elementos cotizables para cualquier aplicación.
Antes de analizar esto, uno podría preguntarse: “¿Por qué es necesario inicializar un conjunto de registros de precios antes de la cotización? En cambio, ¿por qué no forzar al usuario a crear una lista de registros de precios usando un layout de tablas y agregando una fila a la vez?”. La respuesta es que eso no es necesario, aunque sería mucho más productivo y fácil para el usuario si los registros de precios se inicializaran. Supongamos que obligamos al usuario a crear la lista inicial de registros de precios por su cuenta, cada vez. En ese caso, debe recordar con qué elementos cotizables debe comenzar y si son obligatorios u opcionales. Esto podría derivar en errores. El vendedor debería concentrarse en comunicarse con el cliente, no en hacer clics con el mouse que no llevan a nada. Es algo similar a pedir comida en un restaurante. Es mucho más fácil y rápido que el cliente seleccione desde un menú, que pedirle al camarero que recite de memoria todo lo que hay en el menú.
Las entradas necesarias para calcular la propiedad Price dentro de un registro Pricing se registran. Al principio, DiscountFactor está configurado en 1, es decir, sin descuento. Esto puede configurarse en el FSG-Data-Pricing data transform pyDefault. Si un elemento es siempre obligatorio, el registro Integer Bit está preestablecido en 1, y Quantity es distinto de cero. Si VolumeIncludesQty es verdadero, Quantity es 1. Sin embargo, si un elemento es opcional, el registro Integer Bit está preestablecido en 0. Cuando el valor de Bit se define en 0, Quantity es irrelevante.
Más de una aplicación puede inicializar el mismo elemento. Un nombre más genérico es “Owner” (Propietario). Esto no limita qué inicia un registro Pricing para una aplicación; cualquiera podría hacerlo. Esa “cosa”, sea lo que sea, es “propietaria” de esa manera de inicializar el registro Pricing del elemento. Por lo tanto, el nombre “Owner” se adapta bien.
PricingInit
Owner |
ItemID |
Bit |
Cantidad |
La siguiente tarea es fundamental en relación con la posibilidad de implementar una solución abierta/cerrada. Dentro de la UI de cotización, las entradas que pueden afectar a múltiples registros Pricing pueden modificarse. Ante un cambio, ese valor de entrada puede publicarse en el portapapeles, seguido inmediatamente por un pedido de recálculo de toda la cotización.
Pueden existir múltiples registros Pricing, cada uno con múltiples entradas que no son necesariamente iguales. Un checkbox que indica si desea el servicio de hotel es específico para los elementos con valores de HotelService y HotelServiceCost. En contraste, puede usarse un campo de Porcentaje de descuento como entrada para cada registro Pricing donde se compute el precio, pero no el costo.
En la Etapa 1, se realizó una búsqueda de una solución existente para derivar precios de manera consistente pero diferente, y esto condujo al uso del enfoque de modelo de precios. La solución existe, pero debe saber dónde buscar. No es intuitivo buscar “modelo de precios” en internet.
En la Etapa 2, puede realizarse una búsqueda de una forma genérica existente por la que se pueda recalcular un conjunto de valores en función de entradas independientemente configurables. El término “recalcular” conduce directamente a la respuesta, que es una hoja de cálculo. ¿Puede implementarse algo como una hoja de cálculo en Pega Platform? ¿Por qué no?
Un valor para una celda de una hoja de cálculo se computa usando expresiones que pueden incluir funciones. Una entrada de una función en una hoja de cálculo puede ser literal o una referencia a una celda de otra hoja de cálculo diferente, por ejemplo, A1. Una función de una celda de hoja de cálculo no puede referirse a sí misma, ya que eso sería un bucle infinito repetitivo.
Una abstracción de una celda de hoja de cálculo es decir que “isA” (es una) MemoryLocation. En Pega Platform™, una propiedad isA (es una) MemoryLocation. Un conjunto de columnas paralelas en una fila de hoja de cálculo es semejante a una página en Pega Platform (se denomina también grupo de campos). Un área rectangular en una hoja de cálculo es semejante a una lista de páginas/lista de grupos de campos en Pega Platform. Una propiedad en Pega Platform puede identificarse con su nombre único dentro de una página con nombre. En un data transform, si no se proporciona un nombre de página del paso, se presume que la página es Principal.
Un nombre adecuado para un tipo de datos que captura esta información es Pricing Property Source (Origen de propiedad de precios).
PricingPropertySource
Owner |
ItemID |
Página |
Propiedad |
SourceProperty |
La columna Page (Página) puede quedar en blanco. Si se invoca Recalculate data transform (Recalcular data transform) dentro de un bucle de repetición de registros Pricing mientras la página principal es un caso, el valor de un SourceProperty existe dentro de una propiedad en el nivel de caso. Un valor de columna Property (Propiedad) dentro de la tabla PricingPropertySource debe ser el nombre de una propiedad FSG-Data-Pricing, ya sea Bit, Quantity o DiscountFactor. Cualquier otra entrada usada por Recalculate deriva del registro de datos en vivo, concretamente, PricingModel y VolumeIncludesQty. La descripción anterior es lo que ocurre dentro de @baseclass data transform RecalculatePrices dentro del componente Pricing.
3 Traspaso de páginas a la transformación de refactorización (PricingSummary y PricingDisplay)
PricingSummary
Pasar a la Etapa 2 es un importante obstáculo a superar antes de lograr una solución abierta/cerrada. Sin embargo, todavía faltan unos toques finales. Una de esas tareas es encontrar una manera de computar el precio total, el costo total y la ganancia durante una iniciativa de recálculo. La manera más simple es definir un tipo de datos abstracto que contenga esas tres propiedades. Un nombre adecuado para ese tipo de datos es PricingSummary.
El patrón de diseño de visitante es cuando el objeto A se ofrece a un objeto B diferente para ser procesado por B, en oposición a que el objeto A esté en control del objeto B. En otras palabras, el objeto B está en control, ya que el objeto A “visita” diferentes áreas de la lógica de procesamiento del objeto B.
Un tipo de página PricingSummary puede ser el objeto A. Un data transform que establece los parámetros para transmitir al data transform RecalculatePrices @baseclass puede considerarse como el objeto B. Un parámetro puede ser de tipo Page Name
(Nombre de página). Un caso que tiene una página embebida de tipo PricingSummary puede obtener la ruta absoluta para esa página embebida invocando @pxGetStepPageReference(), cuando esa página del paso de la función es esa página embebida. El valor de retorno de esa invocación de la función puede configurarse en un parámetro llamado SummaryPage. Ese parámetro puede transmitirse al data transform Work- WorkRecalculatePrices, que declara a SummaryPage como un parámetro de nombre de página. En Pages & Classes (Páginas y clases), la clase de SummaryPage se define igual que FSG-Data-PricingSummary.
Ahora que un “objeto” de PricingSummary se ha definido, puede ser visitado por el data transform RecalculatePrices @baseclass. Esta visita ocurre después de que el data transform DerivePricing se ha aplicado a la instancia FSG-Data-Pricing actualmente iterada. Se requiere una pequeña cantidad de lógica para detectar que el elemento es un costo, en lugar de un precio; es decir, param.ItemID termina con Cost (Costo). De ser así, el elemento se maneja de forma diferente, es decir, se asume que DiscountFactor no se aplica. Finalmente, se agrega un costo a TotalCost, se agrega un precio a TotalPrice, y Profit (Ganancia) es la diferencia entre TotalPrice y TotalCost.
PricingDisplay
Un problema más complejo a resolver es configurar el valor de una “propiedad de visualización de precios”, es decir, una propiedad que exista solo para mostrar un precio o costo. Debemos debatir la pregunta: “¿Por qué no mostrar simplemente cada instancia de precios en un layout de tabla?”.
En primer lugar, una propiedad de visualización de precios es menos restrictiva. Una propiedad puede posicionarse donde quiera dentro de una vista de plantilla usando App Studio. Hay menos flexibilidad en App Studio cuando se trata de modificar el aspecto de una lista.
En segundo lugar, mostrar una lista requiere el reinicio de una página de datos de lista desde su origen (por ejemplo, la base de datos), o la lista debe embeberse (por ejemplo, una lista de grupo de campos). Volver a consultar un conjunto de filas consultado inicialmente desde la base de datos, inmediatamente después de volver a persistir esas filas, puede resultar problemático. Mantener una lista dentro de la memoria de portapapeles de un caso que se persiste potencialmente dentro de un BLOB de caso es un despilfarro, una vez que el caso avanza más allá de la etapa de cotización. Por último, configurar y actualizar la pantalla de un conjunto de propiedades de visualización de precios es notablemente más rápido.
Un nombre evidente para un tipo de datos abstracto que alberga propiedades de solo visualización es PricingDisplay. Es posible que haya menos propiedades de visualización que registros Pricing inicializados por PricingInit, o puede haber una por cada uno. De nuevo, estas propiedades se usan solamente para visualización. No tienen ningún otro propósito.
El patrón de visitante puede usarse de nuevo, pero es un desafío hacerlo conforme al principio abierto/cerrado. Un caso puede tener una página embebida de tipo PricingDisplay. La ruta absoluta a esa página embebida puede obtenerse invocando @pxGetStepPageReference(), cuando la página de ese paso de la función es esa página embebida. El valor de retorno desde esa llamada de función puede definirse en un parámetro llamado PricingDisplayPage. Ese parámetro puede entonces transmitirse al data transform Work- WorkRecalculatePrices, que declara que PricingDisplayPage es un parámetro de nombre de página. En Pages & Classes (Páginas y clases), la clase PricingDisplayPage es igual a ORG-APP-Data-PricingDisplay.
Ya se presume que el Id. del elemento que es un costo debe finalizar con Cost (Costo). Suena lógico que el nombre de una propiedad de visualización de precios que representa un precio termine con Price (Precio). Igualmente no hay motivo para agregar esto como restricción. Se recomienda evitar restricciones innecesarias. Lo que se puede hacer es definir un parámetro llamado ItemIDSuffix. Si un Id. de elemento no termina en Cost (Costo), entonces se anexa ItemIDSuffix a ese Id. ItemIDSuffix puede ser cualquier valor, incluyendo una cadena vacía.
Tras detectar que una PricingDisplayPage ha “visitado” el data transform RecalculatePrices, solo resta definir el valor una vez que se haya decidido el supuesto nombre de la propiedad de visualización de precios de esa propiedad que usa@setPropertyValue (myStepPage,Param.DisplayProperty,PricingSavable.Price).
4 Simplificación de la configuración de UI
Forzar a la UI a invocar el data transform Work- WorkRecalculatePrices directamente ante el cambio, o ante el clic de cada campo usado como entrada de cálculo, implicaría una carga sobre los responsables del mantenimiento de la aplicación. WorkRecalculatePrices tiene seis parámetros. Un parámetro es el booleano “Commit” (Confirmar), los otros cinco parámetros se transmitieron a @baseclass RecalculatePrices.
Como todos los registros Pricing igualmente se vuelven a computar, un campo de entrada que finalmente se usa para definir el valor de “Pricing Property Source” (Origen de propiedad de precios) de Bit, Quantity o DiscountFactor solo necesita publicar su valor en el portapapeles y decirle al servidor que vuelva a calcularlo. Después de recalcularlo, la vista se actualiza para mostrar los resultados del recálculo.
Cuando se publica un valor de campo de entrada, puede usarse un Rule-Declare-Expression para derivar el valor de “Pricing Property Source” (Origen de propiedad de precios) real. Por ejemplo, cuando se cambia el valor de propiedad DiscountPercentage entero, siendo el valor entre 0 y 100, puede definirse una propiedad DiscountFactor decimal correspondiente, donde el valor es entre 1.0 y 0, respectivamente.
El único parámetro que necesita comunicar el campo de entrada al servidor es si debería emitirse Commit (Confirmar). No es recomendable que un data transform asuma que siempre debe ejecutarse una confirmación. El data transform puede invocarse durante el procesamiento de caso.
La aplicación de FSGPricingTest contiene un data transform FSGSampleRecalculatePrices de FSG-PricingTest-Work con un solo parámetro booleano llamado “Commit” (Confirmar). A continuación, se usa el patrón proxy. Un proxy es algo que un objeto invoca para simplificar la interfaz hacia algo más complejo. Como se muestra, el data transform de FSGSampleRecalculatePrices define los valores para los cinco parámetros que después se transmiten a Work- WorkRecalculatePrices (que a su vez se transmiten a @baseclass RecalculatePrices). El parámetro Reference (Referencia) está definido como .pyID, el parámetro Owner (Propietario) está definido igual que el nombre de la aplicación (por ejemplo, Booking [Reserva]), el parámetro ItemIDSuffix está definido como Price (Precio), y los dos parámetros Page Name (Nombre de página) están definidos como se describió anteriormente.
Disponible en la siguiente misión:
¿Quiere ayudarnos a mejorar este contenido?