Commit 56f10e15 authored by bsarmiento31's avatar bsarmiento31
Browse files

Initial commit

parents
using EvaPosSrvDTO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace EvaPOS_API_FRAME.DTO
{
///<summary>
/// DTO que obtiene los valores del XML "Cerrar Sesion"
/// </summary>
public class SignOffRequestDTO : DTOBase
{
public string OperadorID { get; set; }
public SignOffRequestDTO(int sessionid, TipoMensaje messagetype) : base(sessionid, messagetype) { }
}
//[XmlRoot(ElementName = "SignOffRequest")]
//public class SignOffRequest : DTOBase
//{
// public SignOffRequest(int idSesion, TipoMensaje tipo) : base(idSesion, tipo)
// {
// }
// public SignOffRequest()
// {
// }
// [XmlElement(ElementName = "OperatorID")]
// public string OperatorID { get; set; }
//}
}
using EvaPosSrvDTO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace EvaPOS_API_FRAME.DTO
{
///<summary>
/// DTO para suspender transaccion en CHEC
/// </summary>
[XmlRoot(ElementName = "SuspendTransactionRequest")]
public class SuspendTransactionRequest : DTOBase
{
public SuspendTransactionRequest(int idSesion, TipoMensaje tipo) : base(idSesion, tipo) { }
public SuspendTransactionRequest() { }
[XmlElement(ElementName = "ParameterExtension")]
public object ParameterExtension { get; set; }
[XmlElement(ElementName = "RequestID")]
public int RequestID { get; set; }
[XmlElement(ElementName = "OperatorID")]
public object OperatorID { get; set; }
[XmlElement(ElementName = "EnableCallback")]
public bool EnableCallback { get; set; }
[XmlElement(ElementName = "VoidTenders")]
public bool VoidTenders { get; set; }
}
}
using EvaPosSrvDTO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace EvaPOS_API_FRAME.DTO
{
///<summary>
/// DTO que obtiene los valores del XML "Cerrar Chec"
/// </summary>
[XmlRoot(ElementName = "TerminateRequest")]
public class TerminateRequestDTO : DTOBase
{
public TerminateRequestDTO(int idSesion, TipoMensaje tipo) : base(idSesion, tipo)
{
}
public TerminateRequestDTO()
{
}
[XmlElement(ElementName = "DestroySession")]
public bool DestroySession { get; set; }
}
}
using EvaPosSrvDTO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
namespace EvaPOS_API_FRAME.DTO
{
/// <summary>
/// DTO para leer los valores de cancelar una transaccción o cancelar artículo
/// </summary>
[XmlRoot(ElementName = "VoidTransactionRequest")]
public class VoidTransactionDTO : DTOBase
{
public VoidTransactionDTO(int idSesion, TipoMensaje tipo) : base(idSesion, tipo)
{
}
public VoidTransactionDTO()
{
}
[XmlElement(ElementName = "ParameterExtension")]
public object ParameterExtension { get; set; }
[XmlElement(ElementName = "RequestID")]
public int RequestID { get; set; }
[XmlElement(ElementName = "OperatorID")]
public double OperatorID { get; set; }
[XmlElement(ElementName = "EnableCallback")]
public bool EnableCallback { get; set; }
[XmlElement(ElementName = "VoidTenders")]
public bool VoidTenders { get; set; }
}
}
# SCO Gateway - Guía de programación
## Entorno global
La clase `Entorno` mantiene variables y objetos de acceso global que mantienen información para acceder en diversos puntos del programa.
Es un "singleton".
Agregar a `Entorno` cualquier variable u objeto visible a todo el programa.
## Información de configuración
Archivo configuración: `appsettings.json`
La aplicación espera el archivo de configuación con los parámetros de operación. Archivo ubicado en la raíz de carpeta con programa ejecutable.
### Parámetros de configuración
En la siguiente lista se presentan los parámetros de configuración definidos. En la siguiente lista, los parámetros y sus valores.
- Indicar tipos de POS, según el tipo se activan los comandos adecuados.
- 'POS' valores: 'pruebas', 'gk', 'evapos'.
- Ubicación del SCO.
- 'IpSCO': string, dirección IP del SCO.
- 'PortSCO': string, puerto IP del SCO.
### Programación de la configuración
La clase `Config.cs` mantienen los valores permitidos a usar como parámetros predefinidos en el archivo de configuración de la aplicación.
Otras secciones del programa pueden requerir validar los valores recibidos por la configuración y según estos valores tomar acción. La clase `Config` mantiene los valores permitidos.
Un uso interesante de esta clase es generar un lista o copia de estructura de archivo configuración de ejemplo para ayudar en la configuración de uno de estos y en tener la lista de valores permitidos en sus parámetros.
## Procesar solicitudes del SCO
La siguiente es la secuencia de pasos que sigue el programa para procesar una solicitud (mensaje) remitido por el SCO.
- Inicialización de adaptadores y comandos, según el tipo de POS.
- El servidor SCO inicia y queda en modo escucha.
- Para cada mensaje que ingresa, el ciclo es:
- Se determina el tipo de mensaje.
- Según el tipo de mensaje se obtiene el comando adecuado y su adaptador. El factory de creación del directorio de comandos se encargga de inicializar los comandos adecuados al tipo de POS.
- Se ejecuta el comando.
## Agregar soporte a una nueva POS
Para soportar un nuevo tipo de POS, los pasos son:
1. Definir parámetro de configuración que indica el tipo de POS. En la clase `Constantes.cs` crear una constante con el valor esperado en el archivo de configuración `appsettings.json`, por ejemplo `gk_test`.
2. Editar el archivo `appsettings.json` y asignar al parámetro `GatewayConfig`:`POS` el valor que identifica el tipo de POS, por ejemplo `gk_test`.
3. Crear una clase factory de directorio de comandos implementando `IDispensaDirectorioCmds`. Nombrar la clase `DispensaDirectorioCmds<tipo_pos>.cs`.
4. Editar la clase factory `DirectorioCmdsFactory` y agregar el nuevo dispensador al método `CreaDirectorioCmds` para que según el valor del parámetro de configuración seleccione el factory adecuado. A las clases factory de directorio de comandos se pasa como parámetro el objeto tipo `Config` para brindar acceso a las clases factory a parámetros en el archivo de configuración que puedan ser requeridos.
5. Crear una carpeta para almacenar las clases de comando. Llamar la carpeta `Comandos<tipo_pos>`, por ejemplo `ComandosGkPruebas`.
6. Crear los comandos en la carpeta. Usar un espacio de nombres dedicado para cada tipo de POS, con el patrón `SCOGateway.Comandos.<tipo_pos>`, por ejemplo `SCOGateway.Comandos.GkPruebas`.
7. Instanciar los comandos en el método adecuado de la case factory.
8. Si se requiere almacenar valores que deben ser manetenidos entres comandos o entre interacciónes, se usa el singleton `Entorno<T>`. Este singleton es una clase que maneja tipos genéricos y mantiene un atributo tipo `T` genérico al cual la implementación de POS puede asociar un objeto con los datos que debe mantener. La implementación de POS particular puede crear una clase con los datos de su necesidad y vincularla al `Entorno<T>` usando los métodos `setDatos` / `getDatos`. Un buen punto para inicializar el entorno con el contenedor de datos apropiado para el tipo de POS es la clase dispensadora de comandos, la cual es invocada por el factory de dispensadores al inicio del gateway. Por ejemplo, para almacenar datos a un tipo_pos se crea una clase que almacen los datos particulares, sea por ejemplo `TipoPOS`, se instancia el entorno con `Entorno<TipoPOS>.Instancia.setDatos(new TiposPOS())`, se puede fijar y leer datos particulares de este tipo de POS con `Entorno<TipoPOS>.Instancia.getDatos().DatoParticularDelTipoPOS = valor;`
## Integración con GK Smart POS
Para integrar GK Smart POS se tienen dos implementaciones en el código:
- Una maqueta de servicios web de Smart POS, esta estará identificada por el sufijo `GkPruebas`. S
- La integración con Smart POS propiamente dicha, estará indentificada por el sufijo `Gk`.
Espacios de nombres:
- `SCOGateway.POSGk` contiene el código para la integración oficial con Smart POS Gk.
- `SCOGateway.POSGkPruebas` contiene el código para la integración con el servidor maqueta de los servicios web de Smart POS Gk.
Carpetas de código asociado con GK Smart POS:
- `ComandosGkPruebas` y `ComandosGk` contiene los comandos para ambos tipos de ambientes de Gk.
- `POSGk` contiene código especializado para GK Smart POS.
## Notas
- Para el comando `SubtotalCmdGK` y `AddCustomerCmdGK`, la respuesta trae muchos campos, hay que anotar cual es el campo que nesecitamos
/// <summary>
/// Clase que representa los parámetros de configuración de la aplicación
/// registrados en el archivo appsettings.json (o equivalente definido).
/// </summary>
public sealed class Config
{
public required string POS { get; set; } = "pruebas";
public required string IpSCO { get; set; } = "127.0.0.1";
public required int PortSCO { get; set; } = 6697;
public required string Language { get; set; } = "en";
public override string ToString()
{
return base.ToString() + " - " + $"POS: '{POS}', IpSCO: '{IpSCO}', PortSCO: '{PortSCO}', Language: '{Language}'";
}
}
\ No newline at end of file
/// <summary>
/// Esta clase reune la definición de constantes globales
/// requeridas por el programa. Particularmente, valores
/// predefinidos de configuración.
/// </summary>
public static class Const
{
/// <summary>
/// Configuración. Indica tipo de pos de pruebas, para instanciar
/// comandos de prueba internos (dummy).
/// </summary>
public const string PosPruebas = "pruebas";
/// <summary>
/// Configuración. Indica tipo de pos GK, para instanciar
/// comandos para acceder Smart POS de GK.
/// </summary>
public const string PosGK = "gk";
/// <summary>
/// Configuración. Indica tipo de pos GK pruebas, para instanciar
/// comandos de prueba Smart POS de GK simulada.
/// </summary>
public const string PosGKPruebas = "gk_test";
/// <summary>
/// Configuración. Indica tipo de pos EvaPOS, para instanciar
/// comandos para acceder EvaPOS.
/// </summary>
public const string PosEvaPOS = "evapos";
}
\ No newline at end of file
/// <summary>
/// Directorio de comandos.
/// Almacena instancia de comandos indexadas por su Referencia.
/// Usado para identificar el comando que procesa un mensaje particular
/// asociado por la propiedad Referencia del comando.
/// </summary>
public class CreaDirectorioCmds
{
private Dictionary<string, IComando> dir = new Dictionary<string, IComando>();
/// <summary>
/// Agrega un comando al directorio.
/// </summary>
public void AgregaComando(IComando cmd)
{
dir.Add(cmd.Referencia, cmd);
}
/// <summary>
/// Recupera una "shallow copy" del comando asociado al parámmetro.
/// </summary>
public IComando ObtieneComando(string referencia)
{
IComando cmd;
try
{
cmd = dir[referencia];
}
catch (KeyNotFoundException)
{
throw new Exception($"No se encuentra comando con referencia '{referencia}'.");
}
return cmd.CreaCopia();
}
}
\ No newline at end of file
using GatewaySCO;
/// <summary>
/// Factory que retorna un directorio de comandos instanciado
/// con los comandos apropiados según tipo de POS.
/// </summary>
public class DirectorioCmdsFactory
{
public static CreaDirectorioCmds CreaDirectorio(Config config)
{
switch (config.POS)
{
case "pruebas":
return DispensaDirectorioCmdsPruebas.Dispensa(config);
case "evapos":
return DispensaDirectorioCmdsEvaPOS.Dispensa(config);
case "gk":
return DispensaDirectorioCmdsGK.Dispensa(config);
case "gk_test":
return DispensaDirectorioCmdsGKPruebas.Dispensa(config);
default:
throw new ArgumentException("TipoPOS no válido", "tipoPOS");
}
}
}
using Serilog;
/// <summary>
/// Instancia directorio de comandos de EvaPOS con cada
/// uno de los comandos.
/// </summary>
public class DispensaDirectorioCmdsEvaPOS : IDispensaDirectorioCmds
{
/// <summary>
/// Retorna directorio de comandos instanciado y poblado de
/// comandos.
/// </summary>
public static CreaDirectorioCmds Dispensa(Config config)
{
Log.Information("Instancia comandos EvaPOS.");
throw new NotImplementedException();
}
}
\ No newline at end of file
using Serilog;
/// <summary>
/// Instancia directorio de comandos de GK Smart POS con cada
/// uno de los comandos.
/// </summary>
public class DispensaDirectorioCmdsGK : IDispensaDirectorioCmds
{
/// <summary>
/// Retorna directorio de comandos instanciado y poblado de
/// comandos.
/// </summary>
public static CreaDirectorioCmds Dispensa(Config config)
{
Log.Information("Instancia comandos de GK Smart POS.");
return new IniciaDirectorioCmds()
//.AgregaCmd(new Gk.InitializeRequestCmd())
.DirectorioCmds;
}
}
\ No newline at end of file
using GatewaySCO;
using Serilog;
using Gk = SCOGateway.POSGkPruebas;
/// <summary>
/// Instancia directorio de comandos de prueba de GK con cada
/// uno de los comandos.
/// </summary>
public class DispensaDirectorioCmdsGKPruebas : IDispensaDirectorioCmds
{
/// <summary>
/// Retorna directorio de comandos instanciado y poblado de
/// comandos.
/// </summary>
public static CreaDirectorioCmds Dispensa(Config config)
{
Log.Information("Instancia comandos de GK de prueba.");
// Inicializa el entorno para los comandos.
EntornoGK e = new EntornoGK();
e.Language = config.Language;
Entorno<EntornoGK>.Instancia.setDatos(e);
return new IniciaDirectorioCmds()
.AgregaCmd(new Gk.InitializeRequestCmd())
.DirectorioCmds;
}
}
\ No newline at end of file
using EvaPOS_API_FRAME.Comandos;
using Serilog;
/// <summary>
/// Instancia directorio de comandos de prueba con cada
/// uno de los comandos.
/// </summary>
public class DispensaDirectorioCmdsPruebas : IDispensaDirectorioCmds
{
/// <summary>
/// Retorna directorio de comandos instanciado y poblado de
/// comandos.
/// </summary>
public static CreaDirectorioCmds Dispensa(Config config)
{
Log.Information("Instancia comandos de prueba.");
return new IniciaDirectorioCmds()
.AgregaCmd(new ErrorCmd())
.AgregaCmd(new AddItemRequestCmd())
.AgregaCmd(new InitializeRequestCmd())
.AgregaCmd(new QueryStatusRequestCmd())
.AgregaCmd(new ReportStatusEventsRequestCmd())
.AgregaCmd(new SignOffResponseCmd())
.AgregaCmd(new AddReceiptLinesRequestCmd())
.AgregaCmd(new GetTotalsRequestCmd())
.AgregaCmd(new AddTenderDebitCmd())
.AgregaCmd(new PrintCurrentReceiptsRequestCmd())
.AgregaCmd(new ReprintReceiptsCmd())
.AgregaCmd(new TerminateRequestCmd())
.AgregaCmd(new VoidTransactionCmd())
.AgregaCmd(new RemoveReceiptLinesCmd())
.AgregaCmd(new CancelActionCmd())
.AgregaCmd(new AddCustomerBirthdateCmd())
.AgregaCmd(new AddCustomerCmd())
.AgregaCmd(new SuspendTransactionRequestCmd())
.DirectorioCmds;
}
}
\ No newline at end of file
using Serilog;
namespace GatewaySCO
{
/// <summary>
/// Clase singleton que mantiene valores y objetos de interes global.
/// </summary>
public sealed class Entorno<T>
{
/// <summary>
/// Instanciador singleton.
/// </summary>
private static readonly Lazy<Entorno<T>> lazy = new Lazy<Entorno<T>>(() => new Entorno<T>());
/// <summary>
/// Acceso a instancia del singleton.
/// </summary>
public static Entorno<T> Instancia { get { return lazy.Value; } }
/// <summary>
/// Datos particulares de cada implementación de POS.
/// </summary>
private static T? _datos;
/// <summary>
/// Constructor privado.
/// </summary>
private Entorno()
{
Log.Debug("Entorno inicializado.");
}
/// <summary>
/// Obtener datos de entorno.
/// </summary>
public T getDatos()
{
if (_datos == null)
{
throw new ApplicationException("Datos de entorno no inicializados.");
}
return _datos;
}
/// <summary>
/// Fijar datos de entorno.
/// </summary>
public void setDatos(T datos)
{
_datos = datos;
}
}
}
using System.Xml;
namespace GatewaySCO
{
/// <summary>
/// Clase Utilistarios para operaciones sobre mensajes.
/// </summary>
public class Util
{
/// <summary>
/// Utilitario para convertir en string contenido XML de objeto XmlNode.
/// </summary>
public static string ContenidoXmlComoString(XmlNode docXml)
{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.OmitXmlDeclaration = true;
settings.NewLineOnAttributes = true;
using (var stringWriter = new StringWriter())
using (var xmlTextWriter = XmlWriter.Create(stringWriter, settings))
{
docXml.WriteTo(xmlTextWriter);
xmlTextWriter.Flush();
return stringWriter.GetStringBuilder().ToString();
}
}
/// <summary>
/// Lee valor int de entidad XML indicada por xpath..
/// </summary>
public static int LeeIntEnNodo(XmlNode nodo, string xpath)
{
int valor = 0;
XmlNode? xnodo = nodo.SelectSingleNode(xpath);
if (xnodo == null)
throw new Exception($"Nodo <{nodo.Name}> no contiene a <{xpath}>.");
string? xvalor = xnodo.InnerText;
if (xvalor == null)
throw new Exception($"Nodo <{xpath}> con contenido nulo.");
try
{
valor = int.Parse(xvalor);
}
catch (Exception e)
{
throw new Exception($"Error '{e.Message}' convirtiendo valor <{xvalor}> de <{xpath}> en entero.");
}
return valor;
}
/// <summary>
/// Lee valor long de entidad XML indicada por xpath..
/// </summary>
public static long LeeLongEnNodo(XmlNode nodo,string xpath)
{
long valor = 0;
XmlNode? xnodo = nodo.SelectSingleNode(xpath);
if (xnodo == null)
throw new Exception($"Nodo <{nodo.Name}> no contiene a <{xpath}>.");
string? xvalor = xnodo.InnerText;
if (xvalor == null)
throw new Exception($"Nodo <{xpath}> con contenido nulo.");
try
{
valor = long.Parse(xvalor);
}
catch (Exception e)
{
throw new Exception($"Error '{e.Message}' convirtiendo valor <{xvalor}> de <{xpath}> en entero.");
}
return valor;
}
/// <summary>
/// Lee valor bool de entidad XML indicada por xpath.
/// </summary>
public static bool LeeBoolEnNodo(XmlNode nodo, string xpath)
{
bool valor = false;
XmlNode? xnodo = nodo.SelectSingleNode(xpath);
if (xnodo == null)
throw new Exception($"Nodo '{nodo.Name}' no contiene a '{xpath}'.");
string? xvalor = xnodo.InnerText;
if (xvalor == null)
throw new Exception($"Nodo '{xpath}' con contenido nulo.");
else if (xvalor.ToLower() == "false")
valor = false;
else if (xvalor.ToLower() == "true")
valor = true;
else
throw new Exception($"Valor '{xvalor}' de '{xpath}' no se puede convertir a Boolean.");
return valor;
}
/// <summary>
/// Lee valor string de entidad XML indicada por xpath.
/// </summary>
public static string LeeStringEnNodo(XmlNode nodo, string xpath)
{
XmlNode? xnode = nodo.SelectSingleNode(xpath);
if (xnode == null)
throw new Exception($"Nodo '{nodo.Name}' no contiene a '{xpath}'.");
string? valor = xnode.InnerText;
if (valor == null)
throw new Exception($"Nodo '{xpath}' con contenido nulo.");
return valor;
}
}
}
\ No newline at end of file
using EvaPosSrvDTO;
using EvaPosSrvResp;
/// <summary>
/// Interface de definición de comandos.
/// </summary>
public interface IComando
{
/// <summary>
/// Identifica el mensaje al cual el comando responde.
/// La referencia es usada por el servidor de mensajes para asociar
/// un tipo de mensaje con el comando respectivo.
/// La referencia corresponde a un string contenido en el mensaje
/// que permite identificar su tipo.
/// </summary>
public string Referencia { get; set; }
/// <summary>
/// Carga el DTO con los datos a procesar.
/// Este método debe ser invocado antes de ejecutar el comando,
/// para inicializarlo con los datos necesarios para ejecutar.
/// </summary>
public void CargaDTO(DTOBase dto);
/// <summary>
/// Ejecuta el comando, retorna objeto DTO de respuesta.
/// </summary>
public Respuestas Ejecutar();
/// <summary>
/// Retorna una "shallow copy" del objeto.
/// </summary>
public IComando CreaCopia();
}
\ No newline at end of file
/// <summary>
/// Interface para definir las clases que instancian
/// directorios de comandos según el tipo de POS.
/// </summary>
public interface IDispensaDirectorioCmds
{
/// <summary>
/// Dispensa un directorio de comandos inicializados.
/// </summary>
static abstract CreaDirectorioCmds Dispensa(Config config);
}
\ No newline at end of file
/// <summary>
/// Clase para inicializar el directorio de comandos.
/// Se crear una instancia de cada comando según el tipo de POS.
/// </summary>
/// <remarks>
/// Evaluar modificar la definición de comandos para que el mismo
/// comando se instancie.
/// </remarks
public class IniciaDirectorioCmds
{
public CreaDirectorioCmds DirectorioCmds { get; private set; } = new CreaDirectorioCmds();
public IniciaDirectorioCmds AgregaCmd(IComando cmd)
{
DirectorioCmds.AgregaComando(cmd);
return this;
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace gatewayGK.POSGk
{
/// <summary>
/// Registro para agregar un cliente a la venta
/// </summary>
record AddCustomerReq
{
[JsonPropertyName("identifier")] public string Identifier { get; set; } = "";
[JsonPropertyName("customerGroupId")] public string CustomerGroupId { get; set; } = "";
[JsonPropertyName("customerCardType")] public string CustomerCardType { get; set; } = "";
[JsonPropertyName("customerCardNumber")] public string CustomerCardNumber { get; set; } = "";
[JsonPropertyName("searchDatabase")] public bool SearchDatabase { get; set; } = true;
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace gatewayGK.POSGk
{
record AddCustomerResp
{
[JsonPropertyName("operatorID")] public string OperatorID { get; set; } = "";
[JsonPropertyName("workstationID")] public string WorkstationID { get; set; } = "";
[JsonPropertyName("businessUnitID")] public string BusinessUnitID { get; set; } = "";
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment