Introduzione

L’applicazione POS è stata creata con l’utilizzo della tecnologia Windows Presentation Foundation. Lo scheletro dell’applicazione è diviso in più sezioni. La sezione di base è il contenuto, cioè l’area principale dell’applicazione in cui viene presentato il contenuto sotto forma delle viste.  Il secondo elemento è la sezione dell’intestazione, inseparabilmente collegata con il contenuto, che costituisce una sorta di titolo per gli elementi presentati nel contenuto. Inoltre, si può anche distinguere la sezione dello stato in cui si trovano i pulsanti di accesso rapido (per es. chiudi o minimizza) e viene presentata l’informazione riguardante l’utente attualmente connesso.

Nella sezione del contenuto vengono visualizzate le viste. Esistono tre tipi di viste:

  • vista di base (primary view)
  • vista modale (modal view): vista visualizzata sulla vista principale. La vista copre la vista di base e blocca le interazioni dell’utente in questa vista. Tuttavia, non viene bloccata la logica. Dopo la chiusura si ritorna alla vista di base.
  • vista del messaggio (message view): vista visualizzata sulla vista di base o modale. La vista copre le viste che si trovano sotto e blocca le interazioni dell’utente nelle viste che si trovano sotto. Tuttavia, non viene bloccata la logica. La vista si distingue per la possibilità di estenderla su tutta la larghezza dello schermo per lo sfondo del colore uguale a quello del font. Utilizzo tipico: messaggio di errore, domanda durante la chiusura dell’applicazione ecc.

Ogni vista viene creata nello stesso modo (vedi: <<Creazione delle viste>>) Il metodo in cui la vista viene presentata dipende da quello come è stato aperto (vedi <<Navigazione tra le finestre>>).




Creazione delle viste

Nuovo modulo

LA creazione di una nuova vista comincia con la creazione di un nuovo modulo POS. In Visual Studio bisogna selezionare nuovo progetto WPF Custom Control Library. Questo tipo di progetto genera automaticamente la struttura necessaria e consente di aggiungere nuovi elementi. All’inizio il progetto sarà composto dalla cartella Themes in cui si troverà il file Generic.xaml e del file CustomControl1.cs. I file possono essere eliminati.

Il passo successivo consiste nel creare la classe Module.cs che sarà responsabile per la registrazione del modulo nell’applicazione POS. Questa classe deve ereditare dalla classe di base ModuleBase (Comarch.POS.Presentation.Core) e appropriatamente implementare il metodo Initialize. All’interno di questo metodo registriamo i nostri servizi, viste e viewmodels, registrare i pulsanti del menu principale POS, registrare i nostri campi di controllo, gestire la visibilità delle proprietà dei campi di controllo nella gestione dell’interfaccia, attaccare (estendere) i contenitori e datagrid esistenti nel sistema.

Perché l’utente abbia la possibilità di gestire la vista durante il funzionamento dell’applicazione POS, nella cartella Themes bisogna aggiungere un nuovo elemento del tipo Resource Dictionary (cliccare con il tasto destro del mouse sulla cartella Themes e poi, in ordine: Add, Resource Dictionary…). Assegnargli il nome ModernUI.xaml e salvarlo. In questo file saranno definite le proprietà predefinite per gli elementi gestiti dell’interfaccia (viste, campi di  controllo). Le informazioni più dettagliate si trovano nell’articolo <<Gestione della vista e dei suoi elementi>>. Alla fine bisogna registrare la risorsa. Questo passo va eseguito nel costruttore della classe Module tramite l’aggiunta della seguente riga:

LayoutService.RegisterResources(typeof(Module));

(LayoutService è una classe statistica che si trova nell’area Comarch.POS.Presentation.Core.Service).

Creazione delle viste

La creazione di una nuova vista comincia con la creazione della cartella Views in cui verranno mantenute tutte le viste del progetto e della cartella ViewModels per i viewmodels. Poi, nella cartella Views, bisogna aggiungere un nuovo elemento del tipo User Control (WPF), denominato, per esempio, OrdersView.xaml.

Il passo successivo consiste nel cambiare il tipo UserControl in View nell’area Comarch.POS.Presentation.Core.

<core:View x:Class="Comarch.POS.Presentation.Sales.Views.OrdersView"

           xmlns:core="clr-namespace:Comarch.POS.Presentation.Core;assembly=Comarch.POS.Presentation.Core"

In code-behind bisogna eliminare l’ereditarietà dal UserControl e implementare l’interfaccia richiesta View.

LA proprietà Header è la stringa visualizzata nella sezione della testata dell’applicazione. La proprietà deve essere impostata soltanto per le viste che saranno aperte come viste di base perché solo tali viste hanno la presentazione della testata. Negli altri casi (cioè per la vista modale e per il messaggio) basta impostare String.Empty. Esiste anche la possibilità di creare la propria testata della sezione con qualsiasi contenuto. Più informazioni si possono trovare nella sezione <<Aggiunta della propria testata>>.

La proprietà HeaderLayoutId dovrebbe invece contenere il nome unico per l’ID layout id che è indispensabile per il corretto funzionamento della gestione della vista da parte dell’utente POS. Se la vista non deve essere possibile da gestire, si può impostare la proprietà come String.Empty. Per gli scopi di questa documentazione specifichiamo il valore come “OrdersViewId”.

Il costruttore di base richiede il trasferimento dell’oggetto del tipo IviewModel e per questo motivo adesso passiamo alla creazione del viewmodel. Prima, nella cartella ViewModels, bisogna creare la cartella denominata Orders. Poi, all’interno della cartella bisogna aggiungere la nuova classe OrdersViewModel e l’interfaccia IOrdersViewModel. L’interfaccia dovrebbe essere pubblica ed ereditare da IViewModel. Anche la classe OrdersViewModel deve essere pubblica e implementare la classi di base ViewModelBase e l’interfaccia IOrdersViewModel.

Visto che si eredita da ViewModelBase, bisogna implementare il metodo IsTarget. Il metodo viene chiamato durante la navigazione e consente all’istanza del viewmodel di verificare se proprio essa dovrebbe essere attivata o no (informazioni più dettagliate: <<Navigazione tra le viste>>); per allora impostiamo la restituzione di true.

In aggiunta, se la vista deve essere possibile da gestire dall’utente POS, bisogna aggiungere un’altra classe viewmodel denominata DesignOrdersViewModel che erediterà da DesignViewModelBase e implementare l’interfaccia IOrdersViewModel. L’intera struttura viene presentata nel disegno accanto.

Adesso bisogna tornare al code-behind della classe OrdersView (file OrdersView.xaml.cs) e parametrizzare il costruttore perché assumi il parametro del tipo IOrdersViewModel denominato viewModel (attenzione: questo nome è importante, il parametro deve chiamarsi proprio così). Poi completare il costruttore di base, trasferendogli la variabile viewModel.

L’ultimo passo consiste nel registrare la nuova vista. Il processo è descritto nel capitolo Registrazione delle viste per la navigazione e per la gestione dell’aspetto.

Pronto modello di creazione di un modulo con vista vuota è disponibile nell’esempio <<Modulo semplice con nuova vista vuota>>.

Aggiunta della propria testata

Per ogni vista di base è possibile impostare il testo della testata che verrà presentato nella parte superiore dell’applicazione POS. Per impostazione predefinita, è solo possibile collocarci una stringa di caratteri definita nella proprietà Header della vista. Nel caso in cui sia necessario mettere nella testata una struttura più complessa, è possibile creare la propria vista di testata (custom header).

La definizione della propria testa comincia con l’aggiunta della cartella con la vista del nuovo elemento User Control (WPF). Per migliorare la visibilità l’elemento avrà lo stesso nome della vista, ma con il prefisso Header (per la vista OrdersView il nome sarà OrdersViewHeader).

Alla fine bisogna ritornare a code-behind della vista OrdersView e impostare nel costruttore la proprietà CustomHeaderType sul tipo della nostra testata personalizzata. Alla vista della testata viene automaticamente assegnato il DataContext che indicherà ViewModel della vista. Nel nostro caso questo sarà OrdersViewModel. Grazie a tutto ciò, nella nostra testata sarà possibile utilizzare il binding direttamente verso la proprietà sul viewmodel della vista.

public partial class OrdersView 

{

        public OrdersView(IOrdersViewModel viewModel) : base(viewModel)

        {

            CustomHeaderType = typeof (OrdersViewHeader);

            InitializeComponent();

        }

…

…

 

Registrazione delle viste per la navigazione e gestione dell’aspetto

Dopo la creazione della vista (cioè l’implementazione corretta della logica di funzionamento in view-model e dell’UI in xaml) bisogna registrare la vista perché essa possa funzionare. Per farlo apriamo la classe Module creata durante la creazione del progetto (vedi: <<Nuovo modulo>>). Nel metodo Initialize aggiungiamo le seguenti righe:

Register<IOrderViewModel, OrderViewModel>();RegisterViews(new ViewStructure<OrdersView, DesignOrdersViewModel>("OrdersView", Resources.ResourceManager);

La prima riga registra il viewmodel nel contenitore, grazie a ciò nel costruttore della vista viene automaticamente inserita l’istanza dell’interfaccia IOrdersViewModel.

Il secondo metodo consente di registrare la vista nella modalità di gestione della sua interfaccia da parte dell’utente POS durante il funzionamento dell’applicazione. Grazie a ciò, dopo l’apertura della vista di gestione dell’interfaccia, nell’elenco delle viste l’utente vedrà la nostra vista. Potrà anche aprirlo per la modifica e cambiare l’aspetto di tutti gli elementi che definiremo come possibili da gestire (più informazioni su quello come farlo si trovano nel’articolo <<Gestione della vista e dei suoi elementi>>. Il metodo richiede il trasferimento di due parametri diversi: Il primo metodo è il nome della chiave nelle risorse, invece il secondo è l’istanza del manager delle risorse. Questi parametri permettono di presentare il nome della vista nell’albero delle viste nella gestione delle viste dell’applicazione POS.

Alternativamente, se non vogliamo registrare la vista nella modalità di gestione dell’interfaccia, dobbiamo utilizzare il metodo RegisterForNavigation<TView >() invece di RegisterViews.

RegisterForNavigation<OrdersView>();

Aggiunta della vista al menu principale

Ogni vista può essere aggiunta al menu principale sotto forma di una piastrella (TileButton) per rendere possibile l’apertura della vista nella modalità di base. Dopo l’aggiunta della vista al menu principale questa vista verrà automaticamente aggiunta anche all’estendibile menu laterale che può essere aperto dal livello di qualsiasi vista di base.

Per aggiungere la vista creata prima sotto forma di una piastrella al menu bisogna, nel metodo Initialize della classe Module registrare la vista con l’utilizzo del metodo RegisterMenuTile<TView>, dove TView è il nome della classe della vista. I parametri di richiamo sono invece, in ordine, il nome della chiave nelle risorse a il manager della risorse. Opzionalmente è anche possibile definire il delegato canExecute. In caso di restituzione del false la piastrella sarà grigia e inattiva. È anche possibile indicare il delegato per le azioni che vanno eseguite prima dell’apertura della vista e/o definire le autorizzazioni che saranno necessarie per aprire la vista insieme alla chiave nelle risorse dove sarà salvato il messaggio che apparirà nella finestra modale di estensione delle autorizzazioni per i casi in cui le autorizzazioni non saranno soddisfatte (più informazioni sulle autorizzazioni: <<Verifica delle autorizzazioni>>). Per gli scopi di questo esempio registriamo la vista precedentemente creata, cioè OrdersView.

RegisterMenuTile<OrdersView>("OrdersView", Resources.ResourceManager);

Dopo la registrazione della vista nel menu principale sarà possibile aprirla tramite la piastrella adeguata visibile nel menu principale. Per impostazione predefinita la piastrella sarà così come è stata definita nella configurazione dell’interfaccia POS. Per cambiare le sue proprietà visuali bisogna aggiungere le immissioni adeguate nel file ModernUI.xaml che si trova nella cartella Themes. Per esempio, per impostare un nuovo colore predefinito per la nostra nuova piastrella che apre la vista OrdersView, bisogna aggiungere la seguente inserzione:

<SolidColorBrush x:Key="OrdersView.Default.Background" Color="Red" />

Ogni chiave in MordernUI avrà il formato seguente:

[LayoutId o Nome del tipo del campo di controllo].Default.[Nome della proprietà]

Invece l’intera definizione sarà come segue:

<[Nome del tipo della proprietà] x:Key=”[chiave secondo il suddetto formato]” [attributi]>[valore]</[Nome del tipo della proprietà>

Gli attributi e il valore non sono necessari e dipendono strettamente dal tipo della proprietà in questione.

Il LayoutId per la piastrella della vista OrdersView viene definito nel primo parametro del metodo RegisterMenuTile. Nel nostro caso il LayoutId utilizzato nella chiave che definisce il colore della piastrella è OrdersView, perché tale valore è stato impostato prima durante il richiamo del metodo che registra la vista sotto forma della piastrella nel menu principale. Le proprietà, supportate dalla gestione dell’interfaccia, dipendono dal tipo del campo di controllo. Per le piastrelle questo è il campo di controllo TileButton. L’elenco delle proprietà supportate per particolari campi di controllo può essere trovato qui: <<Elenco delle proprietà supportate>>

Inserzione del modulo nell’applicazione POS

L’ultima tappa, dopo la compilazione del modulo creato, consiste nell’avviarlo nell’ambiente di destinazione, cioè nell’applicazione POS. Per farlo, bisogna copiare il modello costruito sotto forma di una libreria (o librerie) nella directory di installazione dell’applicazione POS. Poi aprire per la modifica il file POS.exe.config e trovarci la sezione <modules> e aggiungere il nuovo modulo alla fine della sezione secondo il modello:

<module assemblyFile=”[nome_modulo].dll” moduleType=”[namespace_della_classe_module].Module, [namespace_classe_module]” moduleName=”[ nome_modulo]” />

Nel caso in cui ci siano più librerie, registriamo soltanto quelle che hanno la classe Module implementata.

 




Navigazione tra le viste

Nell’applicazione POS è possibile aprire qualsiasi numero di viste in modalità diverse (vista di base, modale o messaggio), ma solo una di ogni gruppo può essere attiva in un dato momento. Le altre viste aperte sono definite come inattive. In ogni momento, quando è aperta qualsiasi vista di base, è possibile chiamare la finestra di navigazione tra le viste di base (menu di navigazione) per attivare un’altra vista attivata che è stata aperta prima e non chiusa (è diventata inattiva). Il menu di navigazione non può essere chiamato nella vista modale né nella vista di messaggio.

Menu di navigazione

Il menu di navigazione può essere chiamato con l’utilizzo della combinazione di tasti CTRL+TAB o con l’utilizzo del pulsante  disponibile nella sezione dello stato, accanto all’orologio. In questa finestra, le viste aperte vengono presentate sotto forma di piastrelle colorate con il titolo, l’icona e/o la descrizione addizionale. Il titolo della piastrella può essere impostato tramite la proprietà Header nella classe View o tramite la proprietà SwitchHeader sul viewmodel (quando vogliamo che sia diverso dalla testata della vista). L’icona e il colore vengono impostati con l’utilizzo degli stili che si trovano nel file ModernUI.xaml. L’impostazione del colore e delle icone avviene esattamente nello stesso modo che la definizione del colore e dell’icona per la piastrella registrata nel menu principale. Per questo motivo, se la vista è già registrata nel menu principale, la piastrella nel menu di navigazione assumerà il colore e la piastrella uguali a quelli che si trovano sulla piastrella nel menu principale, a patto che il valore della chiave delle risorse (il primo argomento) utilizzata nel metodo RegisterMenuTile sia uguale al valore della proprietà HeaderLayoutId nel code-behind della vista. Nel nostro caso i nomi sono diversi perciò bisogna aggiungere inserzioni addizionali in ModernUI.xaml.

Definizione in ModerUI.xaml del colore e della icona della piastrella nella finestra del menu di navigazione per la vista di esempio OrdersView:

<SolidColorBrush x:Key="OrdersViewId.Default.Background" Color="Red" />

<models:ImageKey x:Key="OrdersViewId.Default.ImageKey" SvgValue="ListIcon" />

 

Bisogna ricordare che nel suddetto caso OrdersViewId è il valore assegnato a HeaderLayoutId nella classe della vista.

In aggiunta, esiste la possibilità di aggiungere la descrizione addizionale nella piastrella (così come si può vedere nella piastrella Nuovo ordine nella schermata presentata sopra). Per questo testo è responsabile la proprietà SwitchHeader2 nella classe del view-model.

Creazione delle viste

Le viste possono essere in più modalità (come vista di base, vista modale oppure visa del messaggio). Possiamo decidere se una vista deve essere aperta come vista indipendente da altre viste (voce separata nella finestra del menu di navigazione) o deve essere il figlio della vista attualmente attiva (mancanza di una nuova voce nel menu di navigazione). Aprendo la vista possiamo impostare il parametro IsPreviewMode (apre la vista nella modalità di sola lettura, la maggioranza dei campi di controllo è automaticamente bloccata per la modifica) e qualsiasi numero dei parametri propri.

Le viste possono essere aperte con l’utilizzo dei metodi disponibili nella classe ViewModelBase e nel servizio  IViewManager. Il metodo più semplice per aprire la vista di base consiste nel chiamare il metodo OpenView<TView>(), dove TView è il nome della classe della vista che vogliamo aprire o attivare se è già aperta. Parametri aggiuntivi:

  • isChild (bool): parametro che indica se la vista aperta deve essere il figlio della vista attualmente aperta,
  • parameters (NavigationParameters): parametro che consente di trasferire qualsiasi numero del parametri propri al viewmodel aperto nella vista,
  • isPreviewMode (bool): parametro disponibile già nel costruttore del viewmodel della vista aperta (contrariamente a parameters che è disponibile soltanto nei metodi di inizializzazione del viewmodel descritti nel capitolo Ordine di chiamata dei metodi del ViewModel durante la navigazione). Il parametro serve per aprire la vista nella modalità di anteprima (con funzionamento limitato). L’impostazione di questo parametro su true causerà l’impostazione della proprietà per sola lettura IsPreviewMode nella casse ViewModelBase. In aggiunta, i campi di controllo extBox, SwitchBox, RadioButton, NumericTextBox, ComboBox, CheckBox dell’area Comarch.POS.Presentation.Core passano alla modalità di sola lettura, se almeno una di loro si troverà in questa vista.

Per aprire la vista in modalità di vista modale bisogna utilizzare il metodo:

OpenModalView<TView>(), dove TView è il nome della classe della vista che vogliamo aprire o attivare se è già aperta. Parametri aggiuntivi:

  • parameters (NavigationParameters): analogici come per OpenView
  • isPreviewMode (bool): analogici come per OpenView

Il metodo non possiede il parametro isChild perché tutte le viste modali vengono aperte in modalità gerarchica (sono figli delle viste che li hanno aperte). L’eccezione è costituita dalla mancanza di relazioni tra diversi tipi di viste. Per esempio, dopo l’apertura di una vista modale da una vista di base attiva non viene creata la dipendenza del tipo parent-child.

Per aprire la vista nella modalità di vista del messaggio bisogna eseguire due passi. Prima di loro consiste nell’impostare in xaml la vista di allineamento orizzontale:

<core:View x:Class="Comarch.POS.Presentation.Sales.Views.OrdersView"

                      HorizontalAlignment="Stretch"

…

 

Invece il secondo è il chiamo del metodo:

OpenModalView<TView>(), dove TView è il nome della classe della vista che vogliamo aprire o attivare se è già aperta. Analogicamente come per la vista modale, la relazione parent-child occorre sempre senza la relazione tra diversi tipi di viste. Parametri aggiuntivi:

  • parameters (NavigationParameters): analogici come per OpenView e OpenModalView,
  • isPreviewMode (bool): analogici come per OpenView e OpenModalView

Se vogliamo visualizzare soltanto un messaggio semplice o una domanda con pulsanti qualsiasi, basta utilizzare il servizio MonitService. Ci sono disponibili metodi come:

  • ShowInformation: metodo che visualizza la vista di messaggio con qualsiasi contenuto e il pulsante OK,
  • ShowError: metodo che visualizza la vista di messaggio con il contenuto di un’eccezione e il pulsante OK,
  • ShowQuestion: metodo che visualizza la vista di messaggio con qualsiasi contenuto e i pulsanti SI e NO,
  • Show: metodo che visualizza la vista di messaggio con qualsiasi contenuto e pulsanti qualsiasi delle azioni predefinite (OK, SI/NO) o qualsiasi numero di pulsanti propri.

Più informazioni sui messaggi sono disponibili nell’articolo <<Messaggi>>.

Chiusura delle viste

Per quanto riguarda la chiusura delle viste, abbiamo a disposizione dei metodi che consentono di chiudere la vista attualmente aperta, il metodo che serve a chiudere una vista selezionata (con l’opzione di chiuderla assieme a tutti i suoi figli). Chiudendo una vista possiamo, analogicamente come in caso di apertura, aggiungere parametri propri che verranno trasferiti alla vista che sarà aperta dopo la chiusura della vista attuale.

Dopo la chiusura della vista attiva viene sempre aperta la vista da cui la vista chiusa è stata aperta. Nel caso in cui le viste siano in relazione parent-child (durante l’apertura della vista IsChild=true), durante la chiusura della vista-figlio si ritorna alla vista-padre.

Per chiudere la vista attiva bisogna utilizzare il metodo:

Essendo sul viewmodel bisogna chiamare il metodo Close() che chiama direttamente il metodo CloseView nel ViewManager e trasferisce la sua vista come parametro. Il metodo Close sempre chiude la vista associata al viewmodel attuale. Parametri aggiuntivi:

  • parameters (NavigationParameters): parametro che consente di trasferire le informazioni dalla vista chiusa alla vista che verrà attivata

 Per chiudere una vista selezionata (attiva o inattiva) bisogna utilizzare il metodo:

CloseView() che si trova nel servizio IViewManager. Parametri aggiuntivi del metodo:

  • view (IView): parametro che specifica la vista che deve essere chiusa,
  • closeParents (bool): parametro che indica se bisogna chiudere anche tutte le viste dipendenti nella relazione child-parent (viste aperte con il parametro IsChild=true). Per impostazione predefinita, il valore è impostato su

Ordine di chiamata dei metodi di navigazione

Durante la navigazione te le viste (apertura, chiusura, commutazione della vista attiva) vengono chiamati i metodi speciali i view-models che partecipano nella navigazione. Questi metodi permettono di eseguire azioni appropriate, a seconda che la vista venga aperta per la prima volta, venga attivata di nuovo, venga disattivata o chiusa. I view-models forniscono anche i parametri che noi inviamo durante il chiamo dei metodi di apertura e di chiusura.

I metodi di navigazione vengono forniti dalla classe di base di ogni viewmodel, cioè ViewModelBase. Questi sono i metodi OnInitialization, OnActivation, OnActivated, OnDeactivated, Dispose and IsTarget. I metodi automatici adeguati sono chiamati durante il processo di apertura sia sulla vista da cui viene aperta la nuova vista, che sulla vista di destinazione. Nel caso del processo di chiusura i metodi specifici vengono chiamati sulla vista chiusa e sulla vista che verrà attivata dopo la chiusura della vista attuale. La stessa regola riguarda la navigazione tra le finestre aperte. Un’altra questione importante è quello quali metodi vengono chiamati durante la navigazione specifica e il loro ordine.

Il processo di apertura, chiusura e la navigazione tra le finestre viene presentato sul seguente diagramma:

Descrizione dei metodi:

  • IsTarget (ViewModelBase)

Viene chiamato durante l’apertura della vista, prima del costruttore della vista, su tutte le istanze dei viewmodel delle viste conformi al tipo aperto. Se non esiste alcun’istanza della vista del tipo aperto, viene creata una nuova vista. Se esistono già viste aperte di tale tipo, il metodo sarà chiamato su ogni viewmodel della vista e nell’ordine della loro apertura. Se uno di essi torna a true, verrà attivato. Se invece nessuna delle viste esistenti di questo tipo torna a true, viene creata una nuova istanza.

  • OnInitialization (ViewModelBase)

viene chiamato in ViewModel solo durante l’apertura di una nuova vista (nuova istanza, nuova scheda). Mancanza di accesso al parente (la chiamata di ViewManager.GetParentViewModel(this) restituirà null).
Attenzione: in questo metodo non si deve aprire o chiudere le viste!

  • OnActivation (ViewModelBase)

viene chiamato durante ogni apertura della vista o sola attivazione. Mancanza di accesso al parente (la chiamata di ViewManager.GetParentViewModel(this) restituirà null).
Attenzione: in questo metodo non si deve aprire o chiudere le viste!

  • OnActivated (ViewModelBase)

viene chiamato durante ogni apertura della vista o sola attivazione.

  • OnDeactivated (ViewModelBase)

viene chiamato durante la disattivazione o la chiusura della vista.

  • Dispose (ViewModelBase)

viene chiamato soltanto durante la chiusura della vista. Consente di rilasciare le risorse che non possono essere gestite da Garbage Collector o fermare i thread figli.

  • OnApplyViewTemplate (View)

Viene chiamato nel code-behind della classe della vista una sola volta, dopo la creazione dell’istanza della vista e caricamento di tutti i componenti (campi di controllo) della vista.

 




Verifica delle autorizzazioni

Autenticazione dell’utente

Per l’autenticazione degli utenti POS viene utilizzato il servizio ISecurityService. Questo servizio contiene i metodi che consentono sia di connettere l’utente al sistema, disconnetterlo che bloccare lo schermo e verificare che l’utente sia in possesso delle autorizzazioni richieste.

Metodi del servizio ISecurityService:

  • SignIn(string login, SecureString password)
    Il metodo consente di connettere l’utente al sistema.
  • SignOut()
    Il metodo disconnette l’utente. In conseguenza vengono chiuse tutte le viste precedentemente aperte e l’utente viene spostato alla vista di accesso.
  • Lock()
    Il metodo blocca lo schermo. Dopo la chiamata di questo metodo viene aperta la vista di a accesso e non sarà possibile passare ad altre viste aperte finché non verrà confermata la sua identità tramite l’inserzione della password.

Autorizzazione dell’utente

In POS, ogni utente connesso può possedere o non possedere autorizzazioni ai luoghi dell’applicazione predefiniti. Le autorizzazioni vengono definite nel sistema ERP tramite l’assegnazione al gruppo di utente delle autorizzazioni alle azioni e agli oggetti business. Per controllare se l’utente connesso possiede autorizzazioni adeguate bisogna utilizzare il servizio  IAuthorizationService e il suo metodo ValidatePermissions o, essendo nella classe di viewmodel, utilizzare direttamente la chiamata del metodo estendente con lo stesso nome. La chiamata di questo metodo causerà la verifica delle autorizzazioni. Se la verifica non sarà riuscita, verrà aperta la vista modale con la possibilità di indicare l’utente per cui il processo della verifica verrà eseguito ancora una volta (a patto che sia stato inserito il suo nome utente e la password adeguata). Tale processo non causa l’effettuazione di accesso sull’utente indicato ma soltanto il passaggio attraverso una tappa di verifica delle autorizzazioni.
Parametri del metodo:

  • accessDeniedMessage(string): testo che deve apparire quando l’utente non possiede le autorizzazioni verificate,
  • autorization (IAuthorization): autorizzazione da verificare,
  • successAction (Action): azione che deve essere eseguita dopo la riuscita della verifica,
  • cancelAction (Action): azione opzionale che deve essere eseguita quando l’utente annullerà la possibilità di aumentare la autorizzazioni tramite l’accesso a un altro account per la durata della verifica delle autorizzazioni.
  • login (string) (per impostazione predefinita: null): nome utente opzionale dell’utente per cui verranno verificate le autorizzazioni,
  • password (SecureString) (per impostazione predefinita: null): password opzionale dell’utente per cui verranno verificate le autorizzazioni (obbligatoria se viene inserito il nome utente)
  • logByCard (bool) (per impostazione predefinita: false): specifica se l’utente ha effettuato l’accesso tramite la scheda magnetica

Esempio

Controlliamo se l’utente connesso possiede le autorizzazioni ad aggiungere lo scontrino:

this.ValidatePermissions(“Mancanza di autorizzazioni per creare lo scontrino", Authorization.Check.To(PermissionName.Receipt).WithLevels(PermissionLevel.Add),

() =>

{

NotificationService.Show(“Autorizzazione verificata con successo", NotifyIcon.Information);

});

 

 




Estensibilità della sincronizzazione dei dati con DataService

Aggiunta e aggiornamento dei dati

Attività in ERP Standard

Nel database del sistema ERP Standard esiste la procedura POS.ExportCustomObjects. Bisogna sovrascriverla perché essa scarichi dal database i dati che devono trovarsi in un dato POS e perché li restituisca nel formato XML.

Alla procedura vengono trasferiti i parametri che consentono di limitare e adattare i set di dati inviati a un POS specifico:

  • @rowVersion (bigint): valore che consente di eseguire la sincronizzazione differenziale. Questo è un valore che si trova in XML generato da questa procedura durante l’ultima sincronizzazione riuscita (nell’attributo RowVersion).
  • @companyUnitId (int): ID del centro in cui è definito POS (CompanyStructure.CompanyUnits)
  • @pointOfSaleId (int): ID di POS (Synchronization.PointsOfSales)
  • @languageId (int): ID della lingua dei dati (Dictionaries.Languages)

Durante l’esportazione dei dati dalle colonne del tipo bit e datetime bisogna utilizzare la funzione GetBitStringPOS.GetDatetimeString.

Attività in POS

Nel database POS esiste la procedura Synchronization.ImportCustomObjects. Bisogna sovrascriverla perché questa aggiorni nel database POS le tabelle in base ai dati ricevuti da ERP Standard in formato XML, cioè i dati generati dalla procedura POS.ExportCustomObjects.

Esempio

L’esempio dimostra la sincronizzazione differenziale dei dati da due tabelle: esportazione dal database ERP Standard e importazione verso il database POS:

  • Dic_PaymentForms → Configuration.CustomPaymentForms
  • Dic_Country → Configuration

a)  Procedura di esportazione

ALTER PROCEDURE [POS].[ExportCustomObjects]
@rowVersion bigint,
@companyUnitId int,
@pointOfSaleId int,
@languageId int
AS
BEG+IN
SET NOCOUNT ON;
declare @dbts bigint = cast(@@DBTS as bigint)


select
@dbts as [@RowVersion],


(select
pf.Id as [@Id],
pf.Name as [@Name],
pf.CategoryId as [@Type],
POS.GetBitString(pf.Active)
as [@IsActive]
from SecDictionaries.Dic_PaymentForms pf
where pf.Timestamp > @rowVersion
for xml path('PaymentForm'), type),
(select
c.Id as [@Id],
c.Code as [@Code],
c.Name as [@Name],
POS.GetBitString(c.Active)
as [@IsActive]
from dbo.Dic_Country c
where c.Timestamp > @rowVersion


for xml path('Country'), type)
for xml path('CustomObjects')
END

 

b) Procedura di importazione

ALTER PROCEDURE [Synchronization].[ImportCustomObjects]

@XML xml

AS

BEGIN

SET NOCOUNT ON;

-- Countries --

select

doc.col.value('@Id', 'int') Id,

doc.col.value('@Code', 'nvarchar(50)') Code,

doc.col.value('@Name', 'nvarchar(100)') Name,

doc.col.value('@IsActive', 'bit') IsActive

into #Countries

from @XML.nodes('/DataFromERP/CustomObjects/Country') doc(col)

update pos

set

Code = erp.Code,

Name = erp.Name,

IsActive = erp.IsActive

from Configuration.CustomCountries pos

join #Countries erp on erp.Id = pos.Id

insert into Configuration.CustomCountries

(

Id,

Code,

Name,

IsActive

)

select

Id,

Code,

Name,

IsActive

from #Countries erp

where not exists (select 1 from Configuration.CustomCountries where Id = erp.Id)

-- Payment forms --

select

doc.col.value('@Id', 'int') Id,

doc.col.value('@Name', 'nvarchar(50)') Name,

doc.col.value('@Type', 'tinyint') [Type],

doc.col.value('@IsActive', 'bit') IsActive

into #PaymentForms

from @XML.nodes('/DataFromERP/CustomObjects/PaymentForm') doc(col)

update pos

set

Name = erp.Name,

Type = erp.Type,

IsActive = erp.IsActive

from Configuration.CustomPaymentForms pos

join #PaymentForms erp on erp.Id = pos.Id

insert into Configuration.CustomPaymentForms

(

Id,

Name,

Type,

IsActive

)

select

Id,

Name,

Type,

IsActive

from #PaymentForms erp

where not exists (select 1 from Configuration.CustomPaymentForms where Id = erp.Id)

END

 

Gestione dei dizionari generali

Aggiornamento della procedura di esportazione

Nella procedura POS.ExportGenericDirectories, nella clausola WHERE bisogna includere InternalName del dizionario che anche va sincronizzato.

Nella procedura POS.ExportGenericDirectoryValues, nella clausola WHERE bisogna includere InternalName del dizionario che anche va sincronizzato.

Chiavi esterne per lo schema Standard

Per le tabelle sincronizzate

La disponibilità degli oggetti, sincronizzazioni ecc. possono, al livello della riga in una data tabella, influire i dati che saranno sincronizzato. L’esempio costituiscono i clienti, i gruppi di clienti, i magazzini, i registri, le modalità di pagamento ecc.

Per evitare la valutazione multipla riguardante i dati da sincronizzare nelle tabelle derivanti, bisogna ricorrere alla tabella POS.SentObjects.

Esempio: Esportazione della definizione delle tasse associate a un cliente

(select

ven.Id as [@Id],

ven.ActivityId as [@ActivityId],

ven.VendorId as [@VendorId]

from Implementations.VendorActivityConnectionsEcoTax ven

inner join Implementations.ActivityEcoTax ac on ven.ActivityId = ac.Id

inner join Implementations.SettingsEcoTax sett on ac.Id = sett.CompanyActivityId and sett.CompanyId = @companyUnitId

inner join POS.SentObjects so on so.ObjectId = ven.VendorId and so.SyncTypeId = 14 and so.POSId = @pointOfSaleId

where ven.Timestamp > @rowVersion

for xml path('VendorActivityConnectionsEcoTax'), type)

il valore del tipo dell’oggetto sincronizzato può essere trovato nella tabella POS.SyncTypes.

Per le tabelle non sincronizzate

Bisogna implementare da soli il processo di sincronizzazione, come se la tabella provenisse da una parte aggiunta del sistema aggiunta.

Eliminazione dei dati

Attività in ERP Standard

a) Alla tabella DeletionTypes bisogna aggiungere l’inserzione con Id >= 1000 e il nome unico del tipo degli oggetti eliminati.
b) Nel trigger AFTER DELETE della tabella dalla quale i dati eliminati vanno sincronizzati verso POS bisogna aggiungere alla tabella POS.DeletedObjects le inserzioni riguardanti gli oggetti eliminati. Colonne da completare:

  • DeletionTypeId: identificatore del tipo definito nel punto a)
  • Ident: identificatore dell’oggetto eliminato. Questo può essere un numero (int), GUID (uniqueidentifier), nvarchar o l’insieme di valori separati dal simbolo “|”, per es. „3428|654”. Vedi anche i punti riguardanti le attività in POS (colonne IdentColumnName, IdentColumnType).
  • POSId: identificatore di POS (Synchronization.PointsOfSales): bisogna completarlo se l’oggetto va eliminato soltanto da una postazione POS specifica o lasciare NULL se dovrebbe essere eliminato da tutte le postazioni.

Attività in POS

Ala tabella Synchronization.DeletionTypes bisogna aggiungere la riga che definisce il metodo di gestione dal meccanismo di sincronizzazione dell’informazione riguardante l’eliminazione degli oggetti provenienti dal sistema ERP.

Sono disponibili due modi di eliminazione: automatico e personalizzato.

  • Nella modalità automatica il meccanismo di sincronizzazione eliminerà automaticamente i dati da una tabella specifica, identificando le righe in base ai nomi delle colonne inseriti (i valori nelle colonne TableName, IdentColumnName, IdentColumnType devono essere completate e anche NULL nella colonna CustomProcName). Utilizzato per l’eliminazione in POS di quasi tutti i tipi di oggetti possibil da eliminare in ERP (vedi: inserzioni standard nella tabella Synchronization.DeletionTypes).
  • Invece la modalità personalizzata richiede la specificazione della procedura che gestirà l’eliminazione degli oggetti di un dato tipo (la colonna CustomProcName deve essere completata). La modalità viene utilizzata quando la condizione di eliminazione è più complicata e non è possibile utilizzare il meccanismo automatico o quando durante l’eliminazione bisogna eseguire operazioni aggiuntive.

La tabella Synchronization.DeletionTypes contiene le seguenti colonne:

  • DelType: lo stesso nome che nel database ERP Standard, nella tabella DeletionTypes.
  • Order: numero che indica l’ordine di eliminazione dei tipi di oggetti.
  • TableName [soltanto la modalità automatica]: nome della tabella da cui gli oggetti vanno eliminati automaticamente nell’ambito di un dato tipo DelType.
  • IdentColumnName [soltanto la modalità automatica]: nomi delle colonne (separate dal simbolo “|”) secondo cui va eseguita l’identificazione automatica delle righe eliminate. „Id”, „GUID”, „PriceTypeId|CustomerGroupId”. Il loro numero e l’ordine deve essere conforme al valore inserito nel database ERP Standard, nella colonna POS.DeletedObjects.Ident.
  • IdentColumnType [soltanto la modalità automatica]: tipi di colonne definite come IdentColumnName, nella stessa quantità e nello stesso ordine, per es. „int”, „uniqueidentifier”, „int|int”. Nel caso del tipo nvarchar(x) bisogna inserire “nvarchar”.
  • CustomProcName [soltanto la modalità personalizzata]: nome della procedure responsabile per l’eliminazione degli oggetti di un dato tipo. Dovrebbe utilizzare i dati provenienti dalla tabella temporanea #DeletedObjects. Vedi: procedure esistenti Synchronization.DeleteCustomerPriceTypes, Synchronization.DeleteWarehouseDocuments.

 




Notifiche e messaggi

Il metodo di comunicazione principale dell’applicazione POS con l’utente è rappresentato dalle notifiche e messaggi. Le notifiche consentono di fornire brevi informazioni che non bloccano il lavoro, sotto forma di un rettangolo con il testo che appare nella parte destra dello schermo, sotto lo stato. I messaggi, invece, servono per fornire informazioni più lunghe o importanti e non è possibile che l’utente li ignori perché vengono visualizzate sotto forma della vista di messaggio. Vengono utilizzati in caso di errori.

Notifiche

Ogni notifica è composta dal contenuto presentato (testo troppo lungo, composto da più di 6 righe, verrà tagliato) e dall’icona. Esistono tre tipi di notifiche che differiscono tra di loro per quanto riguarda l’icona:

  1. Informazione
  2. Avviso
  3. Errore

Dopo la chiamata la notifica appare nella parte destra dello schermo e sparisce, per impostazione predefinita, dopo tre secondi. Il tempo di presentazione della notifica può essere cambiato nel file di configurazione dell’applicazione, modificando il valore della chiave NotificationTimeout. Quando l’utente sposta il cursore del mouse sulla notifica, questa non sparisce e verrà presentata per tutto il tempo per cui il cursore del mouse sarà posizionato nella sua area. Se durante la presentazione della notifica appare un’altra notifica, la prima notifica verrà spostata giù. Cinque notifiche possono essere visualizzate contemporaneamente sullo schermo. Quando ci saranno più notifiche, le notifiche successive verranno memorizzate nel buffer e saranno presentate al momento della scomparsa delle notifiche visualizzate.

Per visualizzare le notifiche bisogna utilizzare il servizio INotificationService (ogni viewmodel ha l’accesso a questo servizio tramite la proprietà NotificationService) e chiamare il metodo Show(string msg, NotifyIcon icon), dove il parametro msg è il contenuto del messaggio e icon è l’enum che specifica il tipo di notifica (cioè l’icona).

Messaggi

I messaggi vengono presentati tramite la vista del messaggio (le caratteristiche di questa vista sono descritte nell’articolo Introduzione). Ogni messaggio è composto dalla testata (breve titolo presentato con caratteri più grandi rispetto a quelli del messaggio stesso), dal testo del messaggio (non è limitato per quanto riguarda il numero di caratteri, quando il testo è più lungo viene fornita la casella di scorrimento che permette di scorrerlo), opzionalmente dall’icona (scelta dal gruppo di icone predefinite) e da almeno un pulsante. La configurazione dei pulsanti dipende dal tipo di messaggio ma è anche possibile definire la propria configurazione.

Per visualizzare il messaggio bisogna utilizzare il servizio IMonitService (ogni viewmodel ha l’accesso a questo servizio tramite la proprietà MonitService) e chiamare uno dei metodi, a seconda delle esigenze:

  • ShowInformation

Il metodo visualizza il messaggio di informazione. Icona di informazione e pulsante OK.

  • ShowError

Il metodo visualizza il messaggio di errore. Icona di errore e pulsante OK.

  • ShowQuestion

Il metodo visualizza la domanda. Icona di domande e pulsanti: SI, NO.

  • Show

Il metodo visualizza il messaggio di qualsiasi tipo definito. È possibile definire l’icona (selezionarla dalle icone predefinite: informazione, domanda, avviso, errore o nessuna). Indicare pulsanti di azione disponibili (OK, SI/NO) o definire i propri pulsanti.

 




Servizi

L’applicazione POS utilizza il meccanismo dei contenitori Unity. Questo rende possibile un accesso veloce a tutti i servizi precedentemente registrati in qualsiasi classe del ViewModel e View. Risolve anche tutte le dipendenze, a patto che anche queste siano state registrate prima. Un esempio può essere ogni vista ottenuta dal contenitore con il contemporaneo inserimento del view-model dipendente nel suo costruttore. Questo è possibile perché ogni view-model viene prima registrato nel contenitore (vedi: <<Registrazione delle viste per la navigazione e per la gestione dell’aspetto>>). Invece in questo viewmodel vengono inseriti altri servizi richiesti che sono necessari per questo viewmodel (a patto che siano stati registrati prima).

Scaricamento dei servizi dal contenitore

Lo scaricamento dell’istanza del servizio selezionato può essere effettuato in tre modi:

  • Tramite la definizione della proprietà pubblica con aggiunto attributo Dependency dall’area Practices.Unity.
[Dependency]

public IProductsService ProductsService { get; set; }

 

  • Tramite l’inserimento dell’istanza nel costruttore del viewmodel tramite la dichiarazione semplice del parametro.
public OrdersViewModel(IProductService productService) { … }

 

  • Chiamata del metodo Resolve sull’oggetto IUnityContainer disponibile nella classe ViewModelBase sotto la proprietà
var productsService = Container.Resolve<IProductsService>();

Tramite la creazione dell’istanza del servizio che avviene con ogni suo scaricamento dal contenitore. L’eccezione costituisce il caso del servizio registrato come singleton la cui istanza viene creata soltanto una volta durante il primo tentativo di scaricamento.

Registrazione dei propri servizi nel contenitore

La registrazione del servizio può essere effettuata in qualsiasi momento, dappertutto dove abbiamo l’accesso all’oggetto IUnityContainer. Se vogliamo essere sicuri che il servizio sarà disponibile dall’inizio, bisogna registrarlo nella classe Module. Questa regola si applica per tutti i view-model. Il servizio può essere registrato in base alla sua interfaccia. Se la classe del servizio non ha l’interfaccia, non bisogna registrarla, a meno che non vogliamo che sia disponibile come singleton.

Per registrare il servizio nella classe Module bisogna utilizzare il metodo

Register<TInterface,TClass>(), dove TInterface è il nome dell’interfaccia e  TuClass è il nome della classe che implementa l’interfaccia. Parametri aggiuntivi:

  • singleton (bool): parametro che indica se l’istanza scaricata dal contenitore deve essere ogni volta creata di nuovo o sempre restituita come la stessa istanza. Per impostazione predefinita, il valore di questo parametro è impostato su

Di sotto viene presentato un esempio di registrazione nella classe Module del servizio della classe CustomService che implementa l’interfaccia ICustomService come singleton:

Register<ICustomService, CustomService>(true);

 

Se vogliamo perché il servizio registrato sia immediatamente istanziato, ma contiene dipendenze che saranno inserite automaticamente, dobbiamo accertarci che questo sarà possibile. Nel caso in cui il servizio dipenda da un altro servizio che esiste in un altro modulo, dobbiamo assicurarci che esso sia già stato registrato. Perché questo sia garantito, l’istanziazione (scaricamento dal contenitore) deve essere eseguita all’interno del metodo AfterAllModulesLoaded  (che si trova in ModuleBase).

Elenco dei servizi POS disponibili

I servizi possono essere divisi in quelli di base, riguardanti il core dell’applicazione POS e i servizi business che riguardano la logica di funzionamento dell’applicazione dal punto di vista del business.

Tra i servizi di base si annoverano:

  • IMonitService (Comarch.POS.Presentation.Core.Services)

Servizio che consente di visualizzare i messaggi (informazioni e domande). Più informazioni disponibili nel capitolo <<Messaggi>>.

  • INotificationService (Comarch.POS.Presentation.Core.Services)

Servizio che consente di visualizzare le notifiche. Più informazioni disponibili nel capitolo <<Notifiche (Messaggi)>>.

  • IViewManager (Comarch.POS.Presentation.Core.Services)

Classe che consente di gestire le viste (apertura e chiusura). Più informazioni disponibili nel capitolo <<Navigazione tra le viste>>.

  • ILoggingService (Comarch.POS.Library.Logging)

Servizio che consente di salvare le informazioni nel file di log

  • ISecurityService (Comarch.POS.BusinessLogic.Interfaces.Security)

Servizio responsabile per l’autenticazione e autorizzazione dell’utente POS. Più informazioni disponibili nel capitolo <<Verifica delle autorizzazioni>>.

  • IAutorizationService (Comarch.POS.Presentation.Base.Services)

Servizio responsabile per la convalida delle autorizzazione degli utenti POS connessi. Più informazioni disponibili nel capitolo <<Verifica delle autorizzazioni>>.

Tra i servizi business si annoverano:

  • IConfigurationService (Comarch.POS.Library.Settings)

Servizio che consente di ottenere l’accesso alla configurazione dell’applicazione

  • IFiscalizationService (Comarch.POS.Presentation.Fiscalization.Services)

Servizio di stampa sulla stampante fiscale

  • ISynchronizationService (Comarch.POS.Synchronization.Interfaces)

Servizio di sincronizzazione

  • IPrintoutManager (Comarch.POS.Presentation.Core.Services)

Servizio di stampa

  • E tutti gli altri servizi che si trovano nell’area Comarch.POS.BusinessLogic.Interfaces

 




Estensibilità della sincronizzazione dei dati da POS: opzione 1

Esempio della procedura di esportazione

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Synchronization].[GetCustomData]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))

DROP FUNCTION [Synchronization].[GetCustomData]

GO

CREATE FUNCTION [Synchronization].[GetCustomData]

(

@syncType int,

@documentId int

)

RETURNS XML

AS

BEGIN

declare @data XML;

set @data = (select [Implementations].[GetSpecificData](@syncType, @documentId)

for xml path('SpecificElements'), root('CustomData'), type)

return @data

END

GO

IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Implementations].[GetSpecificData]') AND type in (N'FN', N'IF', N'TF', N'FS', N'FT'))

DROP FUNCTION [Implementations].[GetSpecificData]

GO

CREATE FUNCTION [Implementations].[GetSpecificData]

(

@syncType int,

@documentId int

)

RETURNS XML

AS

BEGIN

declare @specifics XML;

set @specifics = (select

el.OrdinalNumber as [@OrdinalNumber],

el.SpecificCode as [@SpecificCode],

SpecificTypeId as [@SpecificTypeId]

from ExtensionSchema.SpecificDataTable el

inner join Documents.TradeDocuments doc on el.DocumentId = doc.Id

where el.DocumentId = @documentId and @syncType = 45

for xml path('row'))

return @specifics

END

GO

Esportazione dei dati da POS

 

Esportazione dei dati da POS

È anche possibile aggiungere i propri dati agli oggetti creati in POS e sincronizzati verso il sistema ERP. Per farlo bisogna sovrascrivere nel database POS la funzione Synchronization.GetCustomData. La funzione restituisce XML e come argomenti assume il tipo dell’oggetto sincronizzato (int) e il suo identificatore (int). La funzione viene avviata separatamente pe rogni ogetto che va inviato al sistema ERP.

Importazione in DataService

L’importazione dei dati viene eseguita in codice C#. Ogni oggetto elaborato possiede la proprietà CustomData, del tipo XEelement. I dati vanno deserializzati e elaborati manualmente.
Le informazioni utili possono essere scaricate dalla classe statica WebServiceHelper. Per ciascuna chiamata del metodo del contratto di DataService è possibile ottenere le seguenti informazioni sull’istanza POS:

– codice POS

– GUID POS

– codice profilo

– versione

Esempio di importazione su DataService

[DataServiceBusinessModule]

public static class Module

{

[MethodInitializer]

public static void Initialize()

{

var customerService = IoC.Container.Resolve<IDataCustomerExtensionPointService>();

customerService.AfterSaveCustomerEvent += CustomerServiceEx_AfterSaveCustomerEvent;

}

private static void CustomerServiceEx_AfterSaveCustomerEvent(object sender, DTOResultEventArgs<Queue.DTO.CustomerDTO, string, int> e)

{

Console.WriteLine("{0}: {1}", e.Argument, e.EntityRow.CustomData.Name);

var xe = e.EntityRow.CustomData; // XElement

}

}


 




Estensibilità della sincronizzazione dei dati da POS: opzione 2

Esportazione in POS

Esiste la possibilità di sincronizzare i dati non associati agli oggetti creati in POS e sincronizzati verso il sistema ERP. Per farlo bisogna utilizzare la procedura Synchronzation.ExportCustoms. Bisogna ricordare di mantenere la struttura nel corpo della procedura <Customs><Custom>, invece la struttura sotto il nodo <Custom> è pienamente libera.

Si raccomanda che la struttura delle tabelle da cui vengono selezionati i dati da trasferire sia un albero e la tabella principale possieda le colonne:

  • GUID: assicurerà l’identificazione dell’oggetto tra POS e ERP
  • Id: per la relazione nella struttura
  • Type: se l’utente vuole differenziare i dati su DS, soprattutto se esistessero parti create separatamente dell’applicazione
  • WasSentToERP: per filtrare i dati già inviati

Contrassegno dei dati inviati

Esiste la possibilità di utilizzare il metodo standard per contrassegnare gli oggetti inviati per contrassegnare gli oggetti non standard.
Per farlo bisogna sovraccaricare il metodo del servizio ISynchronizationRepository (quest’area non possiede punti di estensioni)

Contrassegno dei dati inviati

Esiste la possibilità di utilizzare il metodo standard per contrassegnare gli oggetti inviati per contrassegnare gli oggetti non standard.
Per farlo bisogna sovraccaricare il metodo del servizio ISynchronizationRepository (quest’area non possiede punti di estensioni)

public class SynchronizationRepositoryExt : SynchronizationRepository

{

public override void MarkIfWasSentToERP(string xml)

{

base.MarkIfWasSentToERP(xml);

using (var context = new POSDBContext())

{

context.ExecuteProcedure("Synchronization.MarkSentCustomData", xml);

}

}

}

All’interno è possibile, per esempio, chiamare la procedura che contrassegnerà gli elementi inviati.

CREATE PROCEDURE [Synchronization].[MarkSentCustomData]

@p0 xml

AS

BEGIN

UPDATE CustomSchema.CustomRootTable

SET WasSentToERP = 1

Where GUID in (SELECT xmlData.Col.value('@GUID','varchar(max)') FROM @p0.nodes('/Customs/Custom') xmlData(Col))

END

GO

 

Esempio di esportazione

CREATE PROCEDURE [Synchronization].[ExportCustoms]

AS

BEGIN

SET NOCOUNT ON;

select

pad.GUID as [@GUID],

pad.Type as [@Type],

pad.Data1 as [@Data1],

Synchronization.GetDatetimeString(pad.ChangeDate) as [@ChangeDate],

(

select

td.Number as [@NumberString],

td.Status as [@Status],

td.Value as [@Value]

from CustomSchema.Table1 td where pad.Id = td.RootId

for xml path('Table1'), root('Tables1'), type

),

(

select

ti.ToPayNet as [@ToPayNet],

ti.Points as [@Points],

ti.ToPay as [@ToPay]

from CustomSchema.Table2 ti where pad.Id = ti.RootId

for xml path('Table2'), root('Tables2'), type

)

from CustomSchema.RootData pad

where pad.WasSentToERP = 0

for xml path('Custom'), root('Customs')

END

GO

 

Importazione in DataService

L’importazione viene eseguita con l’utilizzo del servizio IdataCustomService e sovraccarico del metodo SaveCustom. Il metodo, come argomento, riceve ogni riga Custom sotto forma di un oggetto XElement.

Per gestire più elementi aggiunti all’applicazione POS bisogna utilizzare i punti di estensione per DataService.

Snippet per l’importazione

 

[DataServiceBusinessModule]

public static class Module

{

[MethodInitializer]

public static void Initialize()

{

var dataCustomService = IoC.Container.Resolve<IDataCustomExtensionPointService>();

dataCustomService.OnSaveCustomEvent += DataCustomService_OnSaveCustomEvent;

}

private static void DataCustomService_OnSaveCustomEvent (object sender, XEEventArgs e)

{

//deserializzazione + salvataggio dei dati

}

}


 




Azioni Data Service non standard

Il contatto di DataService contiene due metodi che consentono di chiamare un’azione universale:

byte[] CustomGet(string operationCode, byte[] args)

void CustomExecute(string operationCode, byte[] args)

 

Sia l’argomento che il tipo restituito (solo CustomGet) è una scheda di byte che rende possibile di inviare e ricevere qualsiasi struttura.

Chiamata su POS

I metodi per chiamare le azioni universali si trovano nel servizio ISynchronizationService. Basta inserire nel proprio modulo un’istanza del servizio suddetto e chiamare l’operazione richiesta.

Gestione su DataService

L’allegamento della gestione di un’azione universale consiste nel registrare la sua gestione nel modulo di estensione.

Nota
La classe con l’attributo deve essere statica.

[DataServiceBusinessModule]

public static class Module

{

[MethodInitializer]

public static void Initialize()

{

var customOpsService = IoC.Container.Resolve<ICustomOperationsService>();

customOpsService.RegisterCustomGet("my_op", MyCustomGet);

}

private static byte[] MyCustomGet(byte[] data)

{

//codice

}

}

 




Modulo fiscale

Esistono due interfacce di base grazie a cui è possibile adattare il funzionamento del modulo fiscale alle proprie necessità.

IFiscalizationService: contiene tutti i metodi che fanno parte della comunicazione con la stampante fiscale. In aggiunta, è possibile controllare i metodi che preparano i dati per la stampa sulla stampante fiscale (per es. gli elementi del documento, pagamenti, indirizzo per la fattura)

ItemCustomizationService: consente di modificare qualsiasi campo che viene inviato sulla stampante fiscale

Un aspetto importante dell’estensione del modulo fiscale costituisce il fatto che la classe di base Module deve ereditare dalla classe Module del modulo fiscale e non da Comarch.POS.Presentation.Core. In tal caso è possibile sovraccaricare i metodi aggiuntivi (RegisterServices, TriggerEventBinding, RegisterViewModels, RegisterViews, AddContainerElements)

Modifica del driver Comarch.B2.Printer2

In aggiunta, se esiste tale necessità, è possibile, ereditando dalla classe PrintManager, sovraccaricare ogni metodo.

Bisogna ricordare che la classe che eredita anche erediti dall’interfaccia IPrinterService. Questo risulta dal fatto che i driver vengono caricati dinamicamente e l’istanziazione avviene in base all’interfaccia adeguata.

public class MyPrinterManager : PrinterManager, IPrinterService { … }

Stampa del documento proprio

Esiste la possibilità di stampare il proprio documento. Sul viewModel adeguato bisogna utilizzare il metodo PrintCustomDocument del servizio IDocumentEventService.

Poi è possibile:

o, ereditando dalla classe FiscalizationService, chiamare nell’istanza interna IPrinterService il metodo per la stampa delle righe in modalità non fiscale (NonFiscalOpen, NonFiscalLinePrint, NonFiscalClose)

o scrivere il proprie driver (per es. in base a Comarch.B2.Printer2), sovraccaricare il metodo PrintCustomDocument e, ereditando dalla classe FiscalizationService, sovraccaricare il metodo PrintCustomDocument

Flag aggiuntivi

Nella classe TradeDocument è stata aggiunta la proprietà FiscalParams che contiene le informazioni aggiuntive per il modulo fiscale.

 

 




Altri: buoni acquisto

Buoni acquisto

Protocollo di comunicazione per i buoni acquisto.
Metodi Rest utilizzati, contratto: Comarch.B2.DataService.Contracts.dll

VoucherEntity[] GetInternalVouchers(string numer)
Il metodo dovrebbe restituire l’elenco dei buoni acquisto aggiornato, in accordo con il numero indicato (di solito questo è un buono acquisto)

VoucherResult UpdateVouchers(VoucherEntity[] vouchers);
Il metodo dovrebbe eseguire l’azione di aggiunta/attivazione/disattivazione/aggiornamento di particolari buoni sconto trasferiti come parametro.

L’attività da eseguire per buoni acquisto particolari dipende dai dati inclusi nell’entità VoucherEntity. Dati importanti inviati da POS:

VoucherEntity
Id : int Id del buono acquisto (0 se va creato)
TypeId : VoucherKindEnum Tipo di buono acquisto

(Unknown, InternalSold, InternalReleased, External, GiftCard)

SortId : int Genere di buono acquisto
CurrencyId : int Valuta del buono acquisto
IsActive : bool Stato del buono acquisto

True: buono acquisto interno (venduto, rilasciato, carda), da utilizzare (disattivazione) o nel caso della carta da aggiornare lo stato Amount

Number : string Numero del buono acquisto
Amount : decimal Valore del buono acquisto

Azione prevista a seconda della configurazione dei parametri VoucherEntity inviati al servizio dei buoni acquisto (DataService) con l’utilizzo del metodo UpdateVouchers:

Id SortId IsActive Azione
External Utilizzo del buono esterno

Salvataggio nel database come utilizzato e non attivo ha il valore specificato in Amount con la valuta CurrencyId e il numero Number

0 InternalReleased Generazione di un buono sconto interno rilasciato

Salvataggio nel database del buono acquisto come attivo e non utilizzato con il valore Amount, con il numero Number e con la valuta CurrencyId

>0 InternalReleased

InternalSold

GiftCard

true Utilizzo del buono sconto o aggiornamento del valore della carta regalo

 

>0 InternalReleased false Attivazione di un buono acquisto esistente e rilasciato internamente

Bool IsExternalVoucherExists(string numer, int sortId)
Il metodo dovrebbe verificare se nel database esiste già un buono acquisto esterno con il numero e genere indicato.

 




Esempi

Il completamento di questo documento costituisce la soluzione Visual Studio con l’elenco completo degli esempi divisi in progetti separati. Per avviare gli esempi è possibile costruire l’intera soluzione e, in seguito, copiare i file di risultato nella cartella di installazione POS e registrare soltanto un modulo di inizializzazione POSUsageExample.dll (l’istruzione della registrazione si trova nel file README.txt della soluzione). Alternativamente è possibile costruire ogni progetto separatamente (a seconda dell’esempio che si vuole analizzare) e registrare nell’applicazione POS soltanto la libreria selezionata. I progetti sono divisi in tre categorie: esempi di utilizzo dei campi di controllo, esempi delle viste intere ed esempi di estensibilità delle viste esistenti.

Esempi di utilizzo dei campi di controllo POS

Esempio di utilizzo del campo di controllo ComboBox2

Esempi di utilizzo del campo di controllo con la vista modale standard dell’elenco di selezione e con la presentazione non standard. Disponibili nel progetto ComboBox2Example.

Esempio di utilizzo del campo di controllo ButtonSpinner

Esempio di utilizzo del campo di controllo insieme a TextBox per il controllo dei valori numerici inseriti dall’utente. Disponibili nel progetto ButtonSpinnerExample.

Esempio di utilizzo del campo di controllo ComboBoxButton

Disponibile nel progetto ComboBoxButtonExample.

Esempio di utilizzo del campo di controllo MultiButton

Esempio di utilizzo del campo di controllo insieme alla gestibilità nell’applicazione. Disponibile nel progetto MultiButtonExample.

Esempio di utilizzo del campo di controllo ItemsContainer

Esempi di utilizzo del campo di controllo insieme alla gestibilità nell’applicazione. Primo con la definizione degli elementi in xaml, secondo con il contenuto costruito nel codice in modo dinamico asincronico. Disponibili nel progetto ItemsContainerExample.

Esempio di utilizzo del campo di controllo Grid

Esempio di costruzione di una vista completamente gestibile in base a Grid. Disponibile nel progetto GridExample.

Esempio di utilizzo del campo di controllo FieldControl

Esempi di utilizzo del campo di controllo insieme alla gestibilità completa e gestione della convalida. Disponibili nel progetto FieldControlExample.

Esempio di utilizzo dei campi di controllo Tab Control e TabControl e TabControlItem

Esempi di utilizzo del campo di controllo TabControl e TabControlItem per la creazione delle schede sulla vista. Disponibili nel progetto TabControlExample.

Esempio di utilizzo del campo di controllo DatePicker2

Nel progetto DatePicker2Example si trova il codice di un utilizzo di esempio del campo di controllo insieme alla convalida.

Esempio di creazione delle viste

Modulo semplice con una nuova vista vuota

L’esempio presenta il modo in cui bisogna creare i moduli di estensione per POS. Disponibile nel progetto EmptyViewExample. È composto dalla classe Module che consente di registrare il modulo e da una vista vuota (SimpleView, SimpleViewModel) insieme alla modalità di gestione dell’interfaccia (DesignSimpleViewModel). La vista è stata registrata sotto forma della piastrella nel menu principale dell’applicazione POS.

Vista tipica dell’elenco dei documenti

L’esempio presenta il modello di una vista tipica con l’elenco che scarica i dati in modo asincronico e che supporta l’ordinamento e impaginazione e con il motore di ricerca e filtri. La vista è composta dalla classe Module (che è responsabile per la registrazione del modulo di estensione e della vista sotto forma della piastrella nel menu principale dell’applicazione POS) e della classe della vista e di view-model dell’elenco; SimpleListView, SimpleListViewModel e DesignSimpleListViewModel. L’esempio è disponibile nel progetto DataGridCompleteExample.

Vista tipica di un documento commerciale

L’esempio presenta il modello di una vista tipica di documento commerciale che contiene DataGride e il motore di ricerca SearchBox. Disponibile nel progetto DocumentExample.

Vista tipica di un documento commerciale con la gestione degli attributi

Esempio del modello della vista di un documento commerciale con la gestione degli attributi per l’elenco e sotto forma di campi di controllo generati in modo dinamico nel contenitore della vista. Disponibile nel progetto DocumentAttributesExample.

Esempi di estensibilità per viste esistenti di POS.

Aggiunta del campo di controllo al contenitore di una vista esistente

Il progetto ControlExtensionsExamples contiene l’esempio descritto in questo documento e un altro esempio che dimostra l’aggiunta dei pulsanti sia al contenitore ItemsContainer che su Grid in una vista esistente creata nello stesso progetto.

Aggiunta della colonna a DataGrid su una vista esistente

Esempio completo della estensibilità delle colonne DataGrid nel progetto DataGridColumnExtensionExample descritta in questo documento.

Esempio di implementazione del proprio metodo di aggregazione dei dati in DataGrid

Nel progetto DataGridAggregationExample è stata presentato un esempio dell’implementazione di aggregazione sotto forma di una mediana.

Esempio di estensione dell’area dello stato

Il progetto StatusBarExtensionExample aggiunge due pulsanti all’area dello stato. Uno dei pulsanti è una scorciatoia per il nuovo documento e il secondo pulsante utilizza il campo di controllo ComboBoxButton.