MercatorSelfScan

0000003167     -      07/08/2023

MercatorSelfScan est une application offrant les fonctionnalité de caisse autonome pour la saisie des ventes et la perception du paiement effectuées par le client (self-checkout). Il s'agit d'un programme autonome, qui se connecte à Mercator ERP. Il est construit sur base d'une application hybride Blazor, ce qui facilite le paramétrage et la personnalisation de son interface.

Installation

MercatorSelfScan requiert l'option SELFSCAN dans le voucher, suivie d'un ou plusieurs ID d'utilisateurs présents dans la table USERS. Par exemple :

SELFSCAN=A12345678X+Z87654321T

Il est compatible avec la version 11.0 ou ultérieure.

Dans un premier temps, il est nécessaire de disposer sur le poste d'un Mercator Core fonctionnel et à jour. (MercatorSelfScan n'est pas compatible avec la version classique de Mercator).

Ensuite, il est nécessaire d'installer le WebView2 Runtime.

Enfin, le contenu du fichier MercatorSelfScan.zip doit être dézippé dans un répertoire vide. Le fichier MercatorSelfScan.appsettings.json doit être modifié tel qu'indiqué plus bas, et notamment reprendre le chemin d'accès version le Mercator Core dans la clé MainDir. L'assembly MercatorDatabase.dll du Mercator lié doit être copiée dans le répertoire de MercatorSelfScan. L'application est démarrée via MercatorSelfScan.exe.

Utilisation

Lorsque l'application démarre, elle se met sur un mode "pas en service". Il est nécessaire qu'un membre du personnel active la caisse. Comme tous les logins proposés par le programme, cela peut s'effectuer :

  • par la saisie d'un mot de passe valide dans Mercator
  • par le scan depuis MercatorPenguin du code QR de login
  • par la lecture d'un code-barres (sur une carte personnelle) qui contient ce mot de passe

Le self-checkout peut commencer.

Si le programme est paramétré pour gérer plusieurs langues, alors l'utilisateur peut choisir une langue parmi celles-ci. L'utilisateur démarre le scanning en touchant ce bouton :

selfscan_start

Si le programme est paramétré pour demander la saisie d'une carte de fidélité, l'utilisateur pourra scanner cette carte ou choisir de continuer en tant que client anonyme.

Le client scanne alors tous les articles de son panier et le contenu de celui-ci s'affiche dans une grille. Un bouton "Article sans code-barres" permet de sélectionner les articles qui ne peuvent être scannés (voir ci-dessous). Quand l'utilisateur a scanné tous les articles, il doit toucher ce bouton :

Le client reçoit un écran l'invitant à choisir un mode de paiement (par exemple, cash via un monnayeur et un terminal de paiement par carte).

En fonction du paramétrage :

  • le client reçoit un écran demandant s'il souhaite un ticket
  • le client, identifié avec sa carte de fidélité et en fonction de sa fiche client, est informé que le ticket est disponible sous forme électronique dans une application, dans un site web, ...
  • le ticket est imprimé automatiquement
  • le ticket n'est jamais imprimé

Le cycle de vente du self-checkout se termine par un écran remerciant le client pour son achat.

Dans le haut de chaque écran, ces boutons sont disponibles :

selfscan_buttons

Ils permettent de changer la langue de l'interface et d'appeler un membre le personnel à l'aide. Celui-ci reçoit une notification via MercatorSelfScanMonitor.

Tout ce qui se passe dans la caisse peut être monitoré à distance via une application autonome MercatorSelfScanMonitor.

Paramétrage

Le paramétrage s'effectue en modifiant la valeur des clés dans le fichier MercatorSelfScan.appsettings.json. Celui-ci est lu au démarrage de l'application. Cela signifie qu'après toute modification, il faut redémarrer MercatorSelfScan.

  • MainDir : répertoire du Mercator lié à MercatorSelfScan. Ce répertoire doit être un chemin local et non mappé. Attention : les backslashes doivent être doublés.
  • IdDossier : pour les configurations multi-dossiers, l'ID du dossier. Sinon, vide.
  • UserLogin : utilisateur Mercator sous lequel le SelfScan va fonctionner. Il est recommandé d'utiliser un super-user afin de s'assurer qu'une session soit toujours disponible.
  • Password : mot de passe dans Mercator pour cet utilisateur.
  • DeveloperTools : indiquez 1 pour activer les outils développeurs permettant d'accéder à la console et d'inspecter les éléments HTML (via CTRL-SHIFT-I). En production, cette valeur doit être 0.
  • DefaultLanguage : langue pour le démarrage de l'application. Choix possibles : F N E D.
  • AvailableLanguages : liste des langues disponibles pour les utilisateurs. Si cette valeur contient une seule langue, le sélecteur de langue n'est jamais affiché.
  • CustomerField : colonne de la table CLI pour la recherche de la carte de fidélité. Si cette valeur est vide, l'étape de scan de la carte de fidélité n'est pas affichée.
  • DefaultCustomer : id du client par défaut si le client ne s'identifie pas avec sa carte de fidélité.
  • Sequence : séquence de vente utilisée.
  • PriceUpdatable : indiquez si le prix unitaire de vente est modifiable. Cette modification a toujours lieu moyennant le login d'un assistant. Valeurs possibles : 0 et 1.
  • MaxRowsCart : le nombre maximum de lignes qui s'affichent dans la liste des articles déjà scannés. Une fois ce nombre dépassé, une pagination est affichée.
  • MaxLengthCart : le nombre maximum de caractères pour l'affichage de la désignation dans la liste des articles déjà scannés.
  • FreeRemoveInCart : la suppression d'une ligne scannée est-elle libre ou requiert elle la validation d'un assistant ? Valeurs possibles : 0 et 1.
  • PayTerms : terminaux de paiement disponibles. Liste séparée par une virgule. Valeurs possibles :
    • Czam
    • DevTest (terminal de test pour le développement ou le débuggage)
  • PayTypes : n° des modes de paiements associés aux terminaux repris ci-dessus. Le nombre d'éléments dans cette liste séparée par une virgule doit être égal au nombre d'éléments dans PayTerms. (Le n° est celui de "Outils / Paramètres / Modes de Paiement".)
  • PrintTicket : la dernière étape du processus de vente doit-elle imprimer un ticket et afficher un message indiquant que le ticket a été imprimé ? Valeurs possibles :
    • 0 : ne pas imprimer de ticket
    • 1 : toujours imprimer le ticket
    • le nom d'une colonne bit de la table CLI (par ex. C_PRINT_TICKET) dont la valeur indique le comportement souhaité
  • SecondsFinish : nombre de secondes après lesquelles l'interface affiche automatiquement la page de remerciement lorsque les processus de paiement et d'impression du ticket sont terminés.
  • SecondsThankYou : nombre de secondes après lesquelles l'interface est réinitialisée (retour à l'étape n°1) lorsque le processus de vente est terminé.
  • Name : le nom de cette caisse. Il apparaîtra dans MercatorSelfScanMonitor. Si cette valeur est vide, le nom de la machine est utilisé.
  • MonitorHost : les coordonnées TCP de MercatorSelfScanMonitor sous la forme "adresse ip:port". Si MercatorSelfScanMonitor n'est pas utilisé, cette valeur doit être vide.

 

Le look de l'application peut être largement modifié en éditant ces fichiers se trouvant dans le sous-répertoire wwwroot :

  • index.html
  • css/style.css
  • css/mercator.css
  • les images

Articles sans code-barres

 

MercatorSelfScan peut gérer les articles sans code-barres. Pour cela, il utilise en écran nommé ItemSelector qui permet à l'utilisateur de sélectionner un article sur base de sa photo et de son nom et de spécifier, le cas échéant, la quantité.

Pour que cet écran soit visible, il faut que la clé ItemSelectorRecordsPerPage soit supérieure à zéro. Cette page prend en compte les clés suivantes :

  • ItemSelectorColumns : nombre de colonnes. Valeurs possible 1, 2, 3, 4, 6, 12
  • ItemSelectorRecordsPerPage : nombre d'éléments par page
  • ItemSelectorImageSize : la taille de l'image associée, en pixels
  • ItemSelectorMaxLength : le nombre maximal de caractères pour que la désignation soit toujours affichée sur une seule ligne

La page ItemSelect peut afficher :

  • Des rubriques : la sélection d'une rubrique permet d'accéder à une liste "enfant" contenant d'autres rubriques et/ou d'autres articles
  • Des articles permettant la sélection directe

Ce système permet de présenter les articles selon n'importe quelle arborescence, par exemple les rayons, familles et sous-familles ou une autre classification au choix. Il est possible de mélanger les articles et les rubriques. Cela permet d'afficher les éléments dans un ordre jugé optimal pour l'utilisateur. (Par exemple, on affiche d'abord des rubriques mais déjà quelques articles les plus fréquemment achetés). Cet affichage est déterminé par une procédure stockée SQL libre. Cette procédure doit être nommée SP_SELFSCAN_ITEMSELECTOR et accepte ces paramètres :

  • @langue char(1) : la langue sous la forme F, N, E, D
  • @first int : le n° du premier record à renvoyer selon la pagination
  • @last int : le n° du dernier record à renvoyer selon la pagination
  • @level char(10) : un code libre permettant de reconnaître la rubrique. Le niveau supérieur est reconnu par @level = ''
  • @id char(10) : l'identifiant de la rubrique ou de l'article

Le code repris ci-dessous donne un exemple d'une telle procédure stockée. Les noms des colonnes dans la table @t ainsi que la colonne RowCount sont attendus par MercatorSelfScan.

CREATE PROCEDURE [dbo].[SP_SELFSCAN_ITEMSELECTOR] 
@langue char(1),@first int,@last int,@level char(10),@id char(10)
AS
BEGIN
declare @t table (recno int identity(1,1), [level] char(1),id char(10),name varchar(250),img varbinary(MAX))

if @level = ''
begin
insert into @t ([level],id,name,img)
select 'R' as level,id,nom as name,img from RAYONS where (img is not null) and (DATALENGTH(img) > 0)
union
select null as level,s_id as id,s_modele as name,s_image1 as img from STOCK where s_id in ('100058','100059','100060')
end

if @level = 'R'
begin
insert into @t ([level],id,name,img)
select 'F' as level,id,nom as name,img from FAMILLES where (id_rayon = @id) and (IMG is not null)
union
select null as level,s_id as id,s_modele as name,s_image1 as img from STOCK where (s_id_rayon = @id) and (s_image1 is not null) and (DATALENGTH(s_image1) > 0)
end

if @level = 'F'
begin
insert into @t ([level],id,name,img)
select null as level,s_id as id,s_modele as name,s_image1 as img from STOCK where (s_id_famil = @id) and (s_image1 is not null) and (DATALENGTH(s_image1) > 0)
end

declare @rowcount int
select @rowcount = count(*) from @t
select *,@rowcount as [rowcount] from @t where recno between @first and @last

END

 

Personnalisation par code

Le comportement de MercatorSelfScan peut être personnalisé à l'aide d'un customizer SelfScan présent dans le Mercator lié. Ce customizer doit implémenter l'interface MercatorUi.ICustomizers.ISelfScanStarted, qui va exiger cette méthode :

Zoom
public void SelfScanStarted(MercatorUi.SelfScan.SelfScan selfScan)

 

A partir de là, il est possible de s'inscrire sur des événements documentés ci-dessous. Cette méthode est appelée lors du démarrage de MercatorSelfScan.

La classe MercatorUi.SelfScan dispose d'une propriété ConfigurationManagerAppSettings qui permet d'accéder au contenu du fichier MercatorSelfScan.appsettings.json, via le nom de clé :

Zoom
string s = selfScan.ConfigurationManagerAppSettings["CustomerField"];
int= selfScan.ConfigurationManagerAppSettings.GetValue<int>("CartMaxRows");

Cet accès est en lecture seulement.

Il est permis d'ajouter des clés supplémentaires dans le fichier json, pour les exploiter ici.

 

Personnaliser les chaînes de caractères

Toutes les chaînes de caractères affichées dans l'interface sont personnalisables. Pour cela, il faut intercepter l'événement ChangeStringResource de l'objet MercatorUi.SelfScan.

Zoom
namespace SelfScan
{
    public class Customizer : MercatorUi.ICustomizers.ISelfScanStarted
    {
        public void SelfScanStarted(MercatorUi.SelfScan.SelfScan selfScan)
        {
            selfScan.ChangeStringResource += SelfScan_ChangeStringResource;
        }

        private void SelfScan_ChangeStringResource(object sender, MercatorUi.SelfScan.SelfScan.ChangeStringResourceEventArgs e)
        {
            if ((e.ResourceName == "I don't have a loyalty card") && (MercatorUi.Globals.Langue == ApiLangues.F))
                e.ResourceValue = "Je ne dispose malheureusement pas de cette avantageuse carte de fidélité !";
        }
    }
}

 

Si on souhaite connaître le nom et la valeur des ressources contenues dans MercatorSelfScan, il suffit de placer temporairement ce code, qui produira un fichier contenant les ressources affichées durant la session. Le fichier est produit lors de la fermeture de MercatorSelfScan.

Zoom
namespace SelfScan
{
    public class Customizer : MercatorUi.ICustomizers.ISelfScanStarted
    {
        private Dictionary<string, string> dicoResources = new Dictionary<string, string>();

        public void SelfScanStarted(MercatorUi.SelfScan.SelfScan selfScan)
        {
            selfScan.ChangeStringResource += SelfScan_ChangeStringResource;    
            selfScan.Disposing += SelfScan_Disposing;
        }

        private void SelfScan_ChangeStringResource(object sender, MercatorUi.SelfScan.SelfScan.ChangeStringResourceEventArgs e)
        {
            if (!dicoResources.ContainsKey(Globals.Langue + "\t" + e.ResourceName))
                dicoResources.Add(Globals.Langue + "\t" + e.ResourceName, e.ResourceValue);
        }

        private void SelfScan_Disposing(object sender, EventArgs e)
        {
            StringBuilder sb = new StringBuilder();
            foreach (KeyValuePair<string, string> kvp in dicoResources)
                sb.AppendLine(kvp.Key + "\t" + kvp.Value);
            Api.StrToFile(sb.ToString(), @"C:\Test\resources.txt", System.Text.Encoding.UTF8);
        }
    }
}

 

Personnaliser les colonnes de la grille des articles scannés

Dans cette grille, toutes les données présentes dans LIGNES_V sont disponibles. MercatorSelfScan propose une liste de colonnes par défaut. Celle-ci peut être adaptée en ajoutant, en modifiant ou en retirant des colonnes, via l'événement ChangeCartColumns. Dans l'exemple ci-dessous, on montre comment

  • ajouter en seconde position une colonne avec le code-barres qui est contenu dans S_CLE1.
  • supprimer la colonne avec la croix permettant de supprimer une ligne
Zoom
namespace SelfScan
{
    public class Customizer : MercatorUi.ICustomizers.ISelfScanStarted
    {
        public void SelfScanStarted(MercatorUi.SelfScan.SelfScan selfScan)
        {
            selfScan.ChangeCartColumns += SelfScan_ChangeCartColumns;
        }

        private void SelfScan_ChangeCartColumns(object sender, MercatorUi.SelfScan.SelfScan.ChangeCartColumnsEventArgs e)
        {
            MercatorBlazor.GridColumn newCol = new MercatorBlazor.GridColumn
            {
                HeaderText = MercatorUi._Divers.Iif_langue(MercatorUi.Globals.Langue, "Barcode", "Barcode", "Code-barres"),
                DataMember = "s_cle1",
                Sortable = false,
                CssClass = "order-status"
            };
            e.Columns.Insert(1, newCol);

            e.Columns.Remove(e.Columns.Last());
        }
    }
}

 

La classe MercatorBlazor.GridColumn dispose d'une méthode Clone() qui permet de dupliquer facilement une colonne existante.

 

Modification des requêtes SQL

Toutes les requêtes SQL peuvent être interceptées et modifiées si le customizer implémente l'interface IStringUpdater. Chaque requête est identifiable par son ID.

Dans l'exemple ci-dessous, nous montrons comment modifier la requête de recherche du client sur base de sa carte de fidélité, pour exclure les clients en sommeil.

Zoom
namespace SelfScan
{
    public class Customizer : MercatorUi.ICustomizers.IStringUpdater
    {
        public string StringUpdate(string stringToModify)
        {
            string id = Api.StrExtract(stringToModify, "<ID>", "</ID>");
            if (id == "SELECT_CLI")
                stringToModify = stringToModify.Replace("=@barcode)", "=@barcode) and (c_sommeil=0)");
            return stringToModify;
        }
    }
}

 

Personnalisation par rapport aux spécificités du lecteur de code-barres

La classe MercatorUi.SelfScan.SelfScan passée à la méthode SelfScanStarted contient une propriété BarcodeReader de type MercatorUi.SelfScan.BarcodeReader. Cette propriété doit être populée avec une instance d'une classe concrète qui est dérivée de cette classe abstraite.

Zoom
public abstract class BarcodeReader
{
    public abstract void SetEnabled(bool enabled);
 
    public void OnScanSuccessful(string barcode)
    { ... }
}

 

Il est nécessaire dans la classe concrète de surcharger la méthode SetEnabled qui permet d'activer et de désactiver le scanner. (Il est essentiel que le scanner soit désactivé quand l'utilisateur est dans un écran qui n'attend pas de scan).

Lorsque la classe concrète reçoit un code-barres (événement code-barres lu), il faut appeler la méthode OnScanSuccessful en lui passant le code-barres qui vient d'être lu.

Remarque : si cette mise en place n'est pas effectuée, MercatorSelfScan présente un émulateur de lecteur de code-barres qui permet d'utiliser le logiciel en mode "démo".

Conseil : ce customizer est centralisé dans la base de données de Mercator et s'applique donc à toutes les caisses. Nous recommandons d'ajouter une clé dans MercatorSelfScan.appsettings.json qui comportera les informations de connexion relatives au scanner installé sur ce poste. (Par exemple, le port COM ou le port USB utilisé). Cette clé sera lue tel que décrit ci-dessus.

Ci-dessous le code pour un scanner Datalogic MAGELLAN™ 3410VSi configuré pour émuler un port COM :

Zoom
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Linq;
using MercatorApi;
using MercatorExtensions;
using MercatorUi;
using MercatorDatabase;
using System.IO.Ports;
using MercatorUi.SelfScan;

// <ReferenceInclude >"System.IO.Ports.dll"</ReferenceInclude>

namespace SelfScan
{
    public class Customizer : MercatorUi.ICustomizers.ISelfScanStarted
    {
        public void SelfScanStarted(MercatorUi.SelfScan.SelfScan selfScan)
        {
            selfScan.Disposing += SelfScan_Disposing;
            string barCodePort = selfScan.ConfigurationManagerAppSettings["BarCodePort"];
            if (!string.IsNullOrEmpty(barCodePort))
                selfScan.BarcodeReader = new DataLogicScan(selfScan, barCodePort);
        }

        private void SelfScan_Disposing(object sender, EventArgs e)
        {
            MercatorUi.SelfScan.SelfScan selfScan = (MercatorUi.SelfScan.SelfScan)sender;
            if (selfScan.BarcodeReader is DataLogicScan dataLogicScan)
                dataLogicScan.Close();
        }
    }

    public class DataLogicScan : BarcodeReader
    {
        private SerialPort serialPort = null;

        public DataLogicScan(MercatorUi.SelfScan.SelfScan selfScan, string barCodePort)
            : base(selfScan)
        {
            try
            {
                serialPort = new SerialPort(barCodePort)
                {
                    BaudRate = 9600,
                    Parity = Parity.None,
                    StopBits = StopBits.One,
                    DataBits = 8
                };
                serialPort.Open();
                if (!serialPort.IsOpen)
                    return;
                if (barCodePort.StartsWith("COM", StringComparison.InvariantCultureIgnoreCase))
                {
                    serialPort.ReadTimeout = 1000;
                    serialPort.WriteTimeout = 1000;
                }
                serialPort.DataReceived += serialPort_DataReceived;
            }
            catch (Exception ex)
            {
                MercatorUi.Globals.ApiLogDelegate("DataLogicScan Init : " + ex.ToString());
            }
            return;
        }

        public void Close()
        {
            if (serialPort != null)
            {
                SetEnabled(false);
                serialPort.Dispose();
                serialPort = null;
            }
        }

        public override void SetEnabled(bool enabled)
        {
            if (serialPort != null)
            {
                byte[] buffer = Api.StringToBytes(enabled ? "E" : "D");
                try { serialPort.Write(buffer, 0, buffer.Length); }
                catch (Exception ex)
                {
                    MercatorUi.Globals.ApiLogDelegate("DataLogicScan SetEnabled : " + ex.ToString());
                }
            }
        }

        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            string result = "";
            try
            {
                byte[] bb = new Byte[serialPort.BytesToRead];
                int n = serialPort.Read(bb, 0, serialPort.BytesToRead);
                Array.Resize(ref bb, n);
                result = Api.BytesToString(bb);
                OnScanSuccessful(result.Substring(1).TrimEnd());
            }
            catch (Exception ex)
            {
                MercatorUi.Globals.ApiLogDelegate("DataLogicScan DataReceived : " + ex.ToString());
            }
        }
    }
}

 

Personnalisation d'un éventuel indicateur lumineux

Certains postes de scanning sont pourvus d'un indicateur lumineux (led) permettant d'afficher, selon la couleur, le statut de la caisse automatique. En général, ce dispositif est connecté via un port USB ou un port COM. Ce port peut être ajouté dans le fichier MercatorSelfScan.appsettings.json tel qu'indiqué ci-dessus. Un moyen simple est d'utiliser l'événement SendingToMonitor qui est levé en diverses circonstances pour envoyer des messages de statut à l'application MercatorSelfScanMonitor.

Ci-dessous un exemple de code compatible avec une caisse Toshiba permettant de spécifier la couleur de la led. Celle-ci présentera ces couleurs ;

  • vert : caisse active
  • orange : avertissement sur la caisse
  • rouge : caisse nécessitant une intervention du personnel
  • éteint : caisse non active

Zoom
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Linq;
using MercatorApi;
using MercatorExtensions;
using MercatorUi;
using MercatorDatabase;
using System.IO.Ports;
using MercatorUi.SelfScan;

// <ReferenceInclude >"System.IO.Ports.dll"</ReferenceInclude>

namespace SelfScan
{
    public class Customizer : MercatorUi.ICustomizers.ISelfScanStarted
    {
        private ManageLed manageLed;

        public void SelfScanStarted(MercatorUi.SelfScan.SelfScan selfScan)
        {
            selfScan.Disposing += SelfScan_Disposing;
            string ledPort = selfScan.ConfigurationManagerAppSettings["LedPort"];
            if (!string.IsNullOrWhiteSpace(ledPort))
                manageLed = new ManageLed(ledPort);
        }

        private void SelfScan_Disposing(object sender, EventArgs e)
        {
            manageLed?.Close();
        }

        private void SelfScan_SendingToMonitor(object sender, MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs e)
        {
            MercatorUi.SelfScan.SelfScan selfScan = (MercatorUi.SelfScan.SelfScan)sender;
            if (manageLed != null)
            {
                switch (e.Level)
                {
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Inactive:
                        MercatorPos.Pos.SendToPort(manageLed.LedPort, MercatorPos.Pos.Evaluate("chr(204)+chr(218)+chr(0)+chr(0)+chr(0)+chr(0)+chr(6)+chr(0)"), "1"); //Off
                        break;
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Normal:
                        MercatorPos.Pos.SendToPort(manageLed.LedPort, MercatorPos.Pos.Evaluate("chr(204)+chr(218)+chr(0)+chr(255)+chr(0)+chr(0)+chr(6)+chr(0)"), "1"); //Green
                        break;
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Closed:
                        MercatorPos.Pos.SendToPort(manageLed.LedPort, MercatorPos.Pos.Evaluate("chr(204)+chr(218)+chr(0)+chr(0)+chr(0)+chr(0)+chr(6)+chr(0)"), "1"); //Off
                        break;
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Alert:
                        MercatorPos.Pos.SendToPort(manageLed.LedPort, MercatorPos.Pos.Evaluate("chr(204)+chr(218)+chr(255)+chr(0)+chr(0)+chr(0)+chr(6)+chr(0)"), "1"); //Red
                        break;
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Warning:
                        MercatorPos.Pos.SendToPort(manageLed.LedPort, MercatorPos.Pos.Evaluate("chr(204)+chr(218)+chr(255)+chr(128)+chr(0)+chr(0)+chr(6)+chr(0)"), "1");//Orange
                        break;
                }
            }
        }
    }

    public class ManageLed
    {
        public string LedPort { get; private set; }
        private SerialPort serialPort = null;
        
        public ManageLed(string ledPort)
        {
            LedPort = ledPort;
            try
            {
                serialPort = new SerialPort(ledPort)
                {
                    BaudRate = 9600,
                    Parity = Parity.None,
                    StopBits = StopBits.One,
                    DataBits = 8
                };
                serialPort.Open();
                if (!serialPort.IsOpen)
                    MercatorUi.Globals.ApiLogDelegate("ManageLed Init : not open !");
                else if (ledPort.StartsWith("COM", StringComparison.InvariantCultureIgnoreCase))
                    serialPort.WriteTimeout = serialPort.ReadTimeout = 1000;
            }
            catch (Exception ex)
            {
                MercatorUi.Globals.ApiLogDelegate("ManageLed Init : " + ex.ToString());
            }
            return;
        }

        public void Close()
        {
            if (serialPort != null)
            {
                serialPort.Dispose();
                serialPort = null;
            }
        }
    }
}

Ci-dessous un exemple de code compatible avec une caisse permettant seulement d'allumer et d'éteindre la led.

Zoom
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Linq;
using MercatorApi;
using MercatorExtensions;
using MercatorUi;
using MercatorDatabase;
using System.IO.Ports;
using MercatorUi.SelfScan;

// <ReferenceInclude >"System.IO.Ports.dll"</ReferenceInclude>

namespace SelfScan
{
    public class Customizer : MercatorUi.ICustomizers.ISelfScanStarted
    {
        private ManageLed manageLed;

        public void SelfScanStarted(MercatorUi.SelfScan.SelfScan selfScan)
        {
            selfScan.Disposing += SelfScan_Disposing;
            string ledPort = selfScan.ConfigurationManagerAppSettings["LedPort"];
            if (!string.IsNullOrWhiteSpace(ledPort))
                manageLed = new ManageLed(ledPort);
        }

        private void SelfScan_Disposing(object sender, EventArgs e)
        {
            manageLed?.Close();
        }

        private void SelfScan_SendingToMonitor(object sender, MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs e)
        {
            MercatorUi.SelfScan.SelfScan selfScan = (MercatorUi.SelfScan.SelfScan)sender;
            if (manageLed != null)
            {
                switch (e.Level)
                {
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Inactive:
                        manageLed.SetEnabled(false);
                        break;
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Normal:
                        manageLed.SetEnabled(true);
                        break;
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Closed:
                        manageLed.SetEnabled(false);
                        break;
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Alert:
                        manageLed.SetEnabled(true);
                        break;
                    case MercatorUi.SelfScan.SelfScan.SendingToMonitorEventArgs.LevelEnum.Warning:
                        manageLed.SetEnabled(true);
                        break;
                }
            }
        }
    }

    public class ManageLed
    {
        public string LedPort { get; private set; }
        private SerialPort serialPort = null;
        
        public ManageLed(string ledPort)
        {
            LedPort = ledPort;
            try
            {
                serialPort = new SerialPort(ledPort)
                {
                    BaudRate = 9600,
                    Parity = Parity.None,
                    StopBits = StopBits.One,
                    DataBits = 8
                };
                serialPort.Open();
                if (!serialPort.IsOpen)
                    MercatorUi.Globals.ApiLogDelegate("ManageLed Init : not open !");
                else if (ledPort.StartsWith("COM", StringComparison.InvariantCultureIgnoreCase))
                    serialPort.WriteTimeout = serialPort.ReadTimeout = 1000;
            }
            catch (Exception ex)
            {
                MercatorUi.Globals.ApiLogDelegate("ManageLed Init : " + ex.ToString());
            }
            return;
        }

        public void SetEnabled(bool enabled)
        {
            if (serialPort != null)
            {
                byte[] buffer = Api.StringToBytes(enabled ? "d" : "n");
                try { serialPort.Write(buffer, 0, buffer.Length); }
                catch (Exception ex)
                {
                    MercatorUi.Globals.ApiLogDelegate("ManageLed SetEnabled : " + ex.ToString());
                }
            }
        }

        public void Close()
        {
            if (serialPort != null)
            {
                SetEnabled(false);
                serialPort.Dispose();
                serialPort = null;
            }
        }
    }
}

 

Exclure certains articles de la vente

Il s'avère souvent nécessaire ou obligatoire d'exclure certains articles du scanning autonome (par exemple l'alcool). Cette exclusion peut s'effectuer via un customizer placé sur la séquence de vente et qui répond à l'événement BeforeInsertItem. Via e.CancelInsertItem = true, il est possible d'empêcher l'insertion de cet article. La raison de cette exclusion pourra être placée dans la propriété billingEngine.TagString et sera affichée tant au client que via l'application MercatorSelfscanMonitor.

Ci-dessous un exemple de code. Il faut y noter le test sur cette condition 

if (Globals.IsMercatorSelfScan)

qui permet de déterminer si l'encodage a lieu dans MercatorSelfScan ou pas.

Zoom
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using MercatorApi;
using MercatorUi;
using MercatorExtensions;
using MercatorDatabase;

namespace Billing
{
    public class Customizer : MercatorUi.ICustomizers.IBillingEngineCreated, MercatorUi.ICustomizers.IBillingEngineClosed
    {

        public void BillingEngineCreated(MercatorUi.Engine.Gescom.BillingEngine billingEngine)
        {
            billingEngine.BeforeInsertItem += billingEngine_BeforeInsertItem;
        }

        public void BillingEngineClosed(MercatorUi.Engine.Gescom.BillingEngine billingEngine)
        {
            billingEngine.BeforeInsertItem -= billingEngine_BeforeInsertItem;
        }

        private void billingEngine_BeforeInsertItem(object sender, MercatorUi.Engine.Gescom.BillingEngine.BeforeInsertItemEventArgs e)
        {
            MercatorUi.Engine.Gescom.BillingEngine billingEngine = (MercatorUi.Engine.Gescom.BillingEngine)sender;
            if (Globals.IsMercatorSelfScan && (e.StockRecord.S_ID_RAYON == "id_rayon_alcool"))
            {
                billingEngine.TagString = "Cet article ne peut être vendu via cette caisse autonome !";
                e.CancelInsertItem = true;
            }
        }
    }
}