vai al contenuto principale

Modella query e filtri

Modelliamo attributi query e filtri di selezione sui dati per implementare requisiti complessi.

Nella lezione precedente #

Se hai seguito correttamente tutti i passi del tutorial fino a questo punto, il modello dovrebbe apparire come in figura:

Designer tutorial engine partial

Dal punto di vista dei requisiti abbiamo un sistema che consente di gestire informazioni su impiegati (dati personali, organizzazione in team) e progetti (nome, stato e committente). Impiegati e progetti sono collegati da alcune relazioni (gli impiegati hanno incarichi che li legano a diversi progetti, un progetto ha almeno un responsabile tecnico).

Dal punto di vista della modellazione abbiamo un modello abbastanza complesso: abbiamo introdotto nuove classi e le abbiamo collegate mediante associazioni e composizioni, caratterizzate da regole di navigabilità e molteplicità differenti; le classi fanno inoltre uso di attributi derivati di tipo math e di personalizzazioni dell’Application Schema, tra cui valori di default, class warning e personalizzazioni del form layout e dei widget.

Abbiamo raggiunto quindi una buona comprensione degli strumenti e delle tecniche di modellazione procedendo a piccole dosi sui requisiti. Tuttavia, il modello attuale è ancora troppo incentrato sulla classe Employee e mancano delle informazioni per completare lo scenario.

Completiamo il modello dati #

In questa lezione introduciamo due potenti strumenti di Livebase: gli attributi query e i filtri a livello applicativo, Il nostro obiettivo è corredare la classe Project di tutte le informazioni necessarie al controllo di gestione. Dovremo tener conto dei costi (tra cui quelli dello staff) e dei ricavi provenienti dai pagamenti dei clienti, informazioni attualmente non presenti nel modello.

Vedremo anche una nuova tipologia di classe: la classe Enum, molto utile per rappresentare un insieme di oggetti con proprietà fisse e note a priori.

Arresta Workforce. Come al solito, prima di aprire il Designer ti consigliamo di archiviare la versione corrente di TutorialEngine.

Gli attributi query #

Quando abbiamo rimosso gli attributi position e team, modellandoli come classi, abbiamo ottenuto l’effetto collaterale di perdere le colonne relative a essi dalla Livetable di Employee; questo è accaduto perché la vista tabellare consente di mostrare solo attributi definiti direttamente sulla classe.

La Livetable ha funzionalità molto utili (come il raggruppamento e filtraggio dei dati), quindi è di nostro interesse poter visualizzare nella tabella anche alcuni attributi delle classi associate a Employee. Per realizzare ciò possiamo far uso degli attributi derivati di tipo query.

Abbiamo già visto cos’è un attributo derivato; in particolare abbiamo lavorato con gli attributi math, definiti a partire da espressioni matematiche. Le espressioni possono prendere in input solo valori provenienti dagli attributi presenti nella classe su cui è dichiarato l’attributo derivato. Al contrario, un attributo query dipende, appunto, da una query che estrae il valore di un attributo presente in una classe target, collegata attraverso un cammino di relazioni. Si tratta quindi di un attributo derivato che può dipendere da attributi presenti in classi diverse da quella in cui è definito.

Un attributo query è definito quindi dal percorso che lo lega all’attributo target (analogo di una FROM). Inoltre, come vediamo tra poco, l’espressione della query può eventualmente includere una condizione di filtraggio (analogo di una clausola WHERE) e un operatore di aggregazione (analogo di una GROUP BY o COUNT).

1. Aggiungi un attributo query semplice #

Creiamo un attributo query che riporti in Employee il nome del team di cui l’impiegato fa parte: apri il Designer, e dal Class menu di Employee seleziona New derived attribute → Query per aprire il Query expression editor.

Sotto Path è presente una lista di tutti gli attributi raggiungibili a partire da Employee navigando i suoi ruoli . Da qui possiamo selezionare l’attributo target della query espandendone il percorso. Ovviamente, da questa posizione non avrebbe senso scegliere un attributo di Employee, pertanto questi ultimi appaiono disabilitati. Ciascun tipo di attributo è raffigurato con un’icona diversa:

  • attributi nativi: ;
  • attributi math: ;
  • attributi query: .

Espandi il ruolo team e seleziona l’attributo name. In basso viene mostrato il percorso della query (team.name). Per ora ignora le altre opzioni, perciò clicca su OK per chiudere l’editor, e rinomina l’attributo /team1 in /team_name.

Designer employee query expression editor

Analogamente a quanto accade per gli attributi math, passando il mouse su un attributo query viene mostrato un tooltip che riporta l’espressione che lo definisce. Selezionando l’attributo query, l’attributo target corrispondente viene evidenziato in rosso. Viceversa, selezionando un attributo qualsiasi, vengono cerchiati in blu anche tutti gli attributi query definiti a partire da esso.

Designer employee team query attribute red tooltip

Designer employee team query attribute blue

Questo meccanismo è importante perché ci consente di conoscere il cammino compiuto dalla query per raggiungere l’attributo. Questa informazione non è scontata perché ci possono essere più percorsi che collegano la classe su cui è definita la query e la classe puntata dalla query. La raggiungibilità delle classi non dipende dalla navigabilità dei ruoli; infatti, è sufficiente che esista una relazione tra due classi affinché essa sia percorribile in entrambi i versi.

2. Aggiungi una query su più valori #

/team_name è un attributo query che attraversa Employee.team, un ruolo con cardinalità Zero or one (01) (un impiegato fa parte al più di un team); pertanto il suo valore coinciderà col valore di Team.name. Vediamo cosa succede se proviamo a portare in Employee anche l’attributo Qualification.label.

Trascina label da Qualification a Employee: si apre di nuovo il Query expression editor con l’attributo label già selezionato. Stiamo attraversando un ruolo con cardinalità a molti, pertanto la query estrae un insieme di valori (tutte le qualifiche dell’impiegato) e non uno solo; dobbiamo quindi stabilire in che modo aggregare questi valori così da poterli rappresentare in un unico attributo. Osserva il menu a tendina di fianco ad Aggregation function: sono disponibili diversi operatori generici, più alcuni specifici in base al tipo di dato dell’attributo sorgente. Vediamoli brevemente:

FunzioneSignificato
Count allL’attributo contiene il conteggio dei record trovati.
Count distinctCome sopra, ma non include record con valori duplicati.
Concat (comma separated)L’attributo è una stringa contenente tutti i valori dei record trovati, separati tra loro da virgole.
Concat distinct (comma separated)Come sopra, ma non include record con valori duplicati.
Max / MinRestituisce il valore massimo/minimo tra i record trovati.

Se la sorgente è un attributo numerico sono disponibili ulteriori operatori:

FunzioneSignificato
SumRestituisce la somma dei valori dei record trovati.
AverageRestituisce il valore medio tra quelli dei record trovati.
Standard deviationRestituisce la deviazione standard tra i valori dei record trovati.

Per attributi booleani abbiamo inoltre:

FunzioneSignificato
Logical ANDRestituisce TRUE se tutti i record sono veri, FALSE altrimenti
Logical ORRestituisce TRUE se almeno un record è vero, FALSE altrimenti

Per Qualification ci interessa mostrare tutte le qualifiche dell’impiegato; perciò scegli Concat distinct (comma separated) e clicca su OK per chiudere l’editor.

È comparso un attributo query con un nome decisamente lungo! Questo perché il Designer sceglie il nome di default di una query in questo modo:

  • se il ruolo ha cardinalità a uno, il nome di default è nomeRuolo_nomeAttributo;
  • se il ruolo ha cardinalità a molti, il nome di default include in testa la funzione di aggregazione usata;
  • in entrambi i casi, se l’attributo Source è l’unico attributo di quella classe, nomeAttributo viene omesso.

Rinominiamo /concat_distinct_comma_separated_qualifications, ricordando sempre che attributi e ruoli condividono lo stesso spazio di nomi per una classe. Pertanto, in questo caso ci conviene ripristinare il nome di default per il ruolo qualifications: apri il suo Role menu e seleziona Reset default name: il suo nome tornerà a essere _qualification__. Dopodiché, rinomina la query in /qualifications.

Il tooltip ci permette di distinguere rapidamente le query a uno da quelle a molti. Per queste ultime esso riporta anche la funzione di aggregazione usata (nel nostro caso CONCAT_DS_CS, cioè Concat distinct – comma separated).

Designer employee qualification query attribute red tooltip

In questo modo abbiamo ripristinato nella Livetable di Employee la visione delle colonne relative a qualifiche e team, mantenendo allo stesso tempo queste informazioni in classi separate.

3. Aggiungi query con opzioni di filtraggio #

Ora che sappiamo usare le query possiamo sofisticare la relazione che lega Employee e Project_assignment. Un incarico può essere considerato “attivo” in due situazioni:

  • quando non ha una data di termine, cioè quando il valore di end_date è nullo;
  • quando end_date non è nullo, ma la data corrente è compresa nel periodo tra data di inizio e data di fine dell’incarico.

Rappresentiamo questa logica con un’espressione booleana: crea la math /is_active dentro Project_assignment digitando l’espressione isNull(end_date) || (__System.date >= start_date && __System.date <= end_date).

Fatto questo, definiamo la seguente query su Employee: “un impiegato è attivo se ha almeno un incarico attivo”. Innanzitutto, trascina /is_active da Project_assignment a Employee.

Designer employee task project query expression path selector

Come puoi vedere, dal momento che ci sono due cammini possibili che collegano Employee e Project_assignment, è apparso il Query expression path selector; passando su uno dei due percorsi, vengono evidenziate in rosso tutte le classi e le relazioni coinvolte. Ciò è molto utile, soprattutto in presenza di cammini multipli e complessi. Vogliamo attraversare la composizione, perciò scegli il primo path Employee.assignments.is_active.

Scegliamo una funzione di aggregazione: dato che /is_active è booleano e un impiegato ha più incarichi, possiamo scegliere il Logical OR; rinomina anche questo attributo su Employee in /is_active (possiamo farlo perché /is_active di Project_assignment non occupa lo spazio dei nomi di Employee).

Ora definiamo delle query su Team per fornire qualche informazione utile sui suoi membri.

Per prima cosa, possiamo contare il numero di membri di un team risalendo il ruolo members; ma quale attributo di Employee scegliamo per la query? Dato che abbiamo bisogno di un attributo univoco, possiamo sfruttare il Platform attribute __id; abilitalo su Employee, e poi trascinalo su Team. Scegli Count distinct come funzione di aggregazione (in questi casi anche Count va bene) e rinomina l’attributo in /num_members.

Aggiungiamo poi un altro attributo per contare il numero di membri attivi, cioè coloro che hanno almeno un incarico attivo. Per farlo, crea un’altra query trascinando __id da Employee a Team, ma stavolta aggiungi una condizione di filtraggio dal Query expression editor: clicca su Add filter e digita l’espressione Employee.is_active. Usa una Count distinct anche in questo caso, e rinomina l’attributo in /num_active_members.

La regola che abbiamo appena aggiunto ci ha consentito di rendere più specifica la query: il conteggio degli impiegati include solo gli oggetti che soddisfano la condizione – cioè quelli che hanno il booleano /is_active pari a TRUE – mentre tutti gli altri vengono filtrati.

Designer employee team query attribute red tooltip where clause 1

Ora proviamo qualcosa di più avanzato: vogliamo mostrare, per ciascun team, il nome dell’impiegato più attivo, cioè quello col maggior numero di incarichi attivi. Procediamo per tre passi:

  1. contiamo il numero di incarichi attivi per ciascun impiegato: abilita il Platform attribute __id su Project_assignment, trascina quest’ultimo su Employee (scegliendo il percorso Employee.assignments.__id), imponi la condizione di filtraggio Project_assignment.is_active e aggrega con Count distinct; infine rinomina l’attributo in /num_active_assignments. Così facendo abbiamo un conteggio simile a quello che abbiamo definito per /num_active_members su Team;
  2. memorizziamo in Team il valore massimo del numero di incarichi attivi: trascina /num_active_assignments da Employee a Team, scegli la funzione di aggregazione Max e rinomina l’attributo in /max_active_assignments. In questo modo, viene copiato in Team il numero di incarichi attivi del membro che ne ha più di tutti;
  3. a questo punto, trascina l’attributo /full_name di Employee su Team, e imponi una condizione con l’espressione Team.max_active_assignments = Employee.num_active_assignments; aggrega con Concat distinct (comma separated) e rinomina l’attributo in /busiest_member. Grazie al confronto con /max_active_assignments, vengono recuperate le altre informazioni del membro del team che ha quell’esatto numero di incarichi attivi, di cui memorizziamo nome e il cognome; nel caso in cui ci siano più impiegati che condividono il valore massimo, allora /busiest_member conterrà tutti i loro nomi, separati da una virgola.

Cliccando su /is_active di Project_assignment, vengono evidenziate tutte le query che dipendono da questo attributo, incluse quelle basate su derivati di derivati.

Designer employee task team is active queries

Abilita le query nella vista applicativa #

Prima di procedere oltre dobbiamo fare un piccolo appunto. Di default, i Platform attributes come __id sono disabilitati, cioè non compaiono nella vista applicativa (avevamo accennato l’argomento qui); la conseguenza è che anche tutte le query definite a partire da questi attributi saranno inizialmente disabilitate.

Ciò può andarci bene per le query “intermedie”, come /num_active_assignments o /max_active_assignments, dato che non abbiamo interesse a mostrarle in alcuna tabella; tuttavia ci sono query che vorremmo mostrare, come /num_members, /num_active_members e /busiest_member.

Designer employee team application enable attributes Per fare in modo che queste query compaiano nelle tabelle, spostati sull’Application Schema e riabilita solo questi attributi.

I selection filter #

Torniamo a ragionare sulle associazioni tra classi e consideriamo un aspetto che non avevamo trattato nella precedente lezione: per quanto sappiamo, è sufficiente che esista un’associazione tra due classi affinché gli oggetti di una siano associabili a quelli dell’altra. L’associabilità dipende dalla struttura della relazione, in termini di cardinalità e navigabilità dei ruoli; quindi, nelle relazioni in cui è possibile, due oggetti qualunque possono essere associati tra loro.

Prendiamo ora in considerazione tre scenari che il modello attualmente ci consente di rappresentare, ma che avrebbero poco senso nella realtà:

  • un impiegato può indicare sé stesso come supervisore (gli basta selezionare il suo nome);
  • un progetto può avere come direttore un impiegato qualsiasi, anche uno che non ha alcun incarico su di esso;
  • un impiegato può avere più incarichi per lo stesso progetto che si sovrappongono temporalmente;

È chiaro che al momento gli strumenti che padroneggiamo non ci consentono di imporre vincoli per evitare queste situazioni. In questi scenari vogliamo infatti che l’associabilità tra impiegati, progetti e incarichi dipenda dal valore che essi assumono, e non solo dalla cardinalità e navigabilità delle relazioni le loro classi. Possiamo raggiungere questo obiettivo grazie ai Selection Filter.

Il Designer ci consente di definire, in modo analogo alla filter condition della query, una o più condizioni sui ruoli di un’associazione; tali condizioni hanno lo scopo di restringere l’insieme di oggetti della classe target che possono essere associati attraverso quel ruolo.

1. Aggiungi un filtro semplice #

Dal momento che il filtraggio di record rappresenta un partizionamento orizzontale dei dati, possiamo definire i Selection Filter solamente a livello applicativo; perciò, spostiamoci sull’Application Schema .

Cominciamo aggiungendo la prima regola: apri il Role menu di Employee.supervisor e seleziona Set selection filters... per aprire il Role filters manager.

Designer role menu application

Clicca su Add e digita nell’editor l’espressione Employee.__id != Employee.supervisor.__id. Prima di confermare, puoi dare un nome simbolico alla regola: ad esempio, chiamala excludeSelf e clicca su OK per chiudere l’editor.

Designer employee supervisor role filters manager one filter

In questo modo abbiamo definito un filtro sulla Livetable degli associabili per il ruolo supervisor nella form di Employee. Da questa lista ora non compare infatti il record relativo dell’impiegato di cui è aperta la form, così da impedire di selezionare se stesso; quando gli __id coincidono, infatti, la condizione viene valutata FALSE e quell’oggetto non sarà associabile.

Chiudi il Role filters manager e osserva il diagramma: su supervisor è comparso un pallino nero, a indicare la presenza di un selection filter per quel ruolo.

Designer employee application supervisor selection filter

Come puoi notare, l’editor consente di definire condizioni multiple per un singolo ruolo. In questi casi il filtro valuterà sempre l’AND logico tra tutte le espressioni.

2. Combina filtri e attributi query #

Ora che abbiamo creato un filtro semplice, possiamo ragionare sugli altri due problemi:

Project → Employee #

Al momento un project può indicare un impiegato qualunque per il ruolo director; vogliamo invece che sia possibile sceglierlo solamente tra gli impiegati che hanno un incarico attivo per quel progetto.

Spostiamoci sul Database Schema . Per mostrare il nome del capo-progetto direttamente dalla tabella Project, creiamo una query a /full_name di Employee su questa classe: anche qui, rinomina il ruolo in director_ per rendere disponibile l’identificatore; dopodiché trascina /full_name passando per esso (Project.director_.full_name); questo percorso produce una query a un valore singolo, che chiamiamo /director.

Un progetto deve conoscere quali sono gli impiegati che ci stanno lavorando: crea una query su Project trascinando su questa classe l’__id di Employee, stavolta scegliendo il percorso che passa per assignments (Project.assignments.employee.__id); aggiungi la condizione Project_assignment.is_active, aggrega con una Concat distinct (comma separated) e rinomina l’attributo in /active_collaborators. In questo modo memorizziamo, per ciascun progetto, una stringa contenente tutti gli __id degli impiegati che hanno un incarico attivo per quel progetto. Se uno di questi incarichi termina, l’__id dell’impiegato a esso relativo scompare dalla lista dei collaboratori attivi

Designer employee project project assignment queries

Dall’Application Schema , aggiungi un filtro sul ruolo _Project.director__ chiamandolo onlyActiveCollaborators, e digitando l’espressione containsCS(Project.active_collaborators, Employee.__id). La funzione containsCS confronta due stringhe e verifica che la prima (una lista di valori separati da virgole) contenga la seconda.

In questo modo, quando dalla form di un project apriamo la Livetable degli associabili per director, la tabella mostra un sottoinsieme contenente i soli impiegati il cui __id compare nella lista dei collaboratori attivi per quel progetto. Tutti gli altri oggetti sono stati infatti filtrati perché non soddisfano il selection filter.

È importante ricordarsi che i progetti inizialmente non hanno collaboratori attivi; pertanto, quando creiamo un nuovo progetto, la Livetable degli associabili di director è sempre vuota, e siamo dunque inizialmente costretti a creare un progetto senza direttore (completando l’associazione in un secondo momento). Tuttavia, sul ruolo director abbiamo imposto un vincolo di cardinalità Exactly one (1) che, se non soddisfatto, non consente di confermare la creazione di alcun progetto! Per sbloccare la situazione dobbiamo necessariamente rilassare il vincolo: dal Database Schema modifica la cardinalità di _director__ in Zero or one (01).

Il filtro risultante appare come in figura:

Designer project employee application filters

Project_assignment → Project #

Al momento, un nuovo incarico di un impiegato può essere associato a un progetto qualsiasi, anche se esiste già un incarico associato a quel progetto nello stesso intervallo temporale (è un incarico attivo). Vogliamo quindi impedire a un impiegato di avere più incarichi contemporaneamente per lo stesso progetto, pur consentendogli di averne molteplici nel tempo.

Possiamo sfruttare la query /active_collaborators appena modellata per definire un filtro sul ruolo Project_assignment.project. Per prima cosa, dal Database Schema trascina l’__id di Employee su Project_assignment (passando per assignments) e rinomina la query in /employee_id. Così facendo otteniamo visibilità dell’__id dell’oggetto whole direttamente dentro i suoi oggetti part. Poi, dall’Application Schema , aggiungi un filtro sul ruolo project chiamato onlyOneActiveAssignmentPerProject, usando l’espressione !containsCS(Project.active_collaborators, Project_assignment.employee_id).

Il nostro obiettivo qui è permettere a un employee di associare un nuovo assignment ai progetti a cui non ha ancora lavorato, o a cui ha lavorato in precedenza, e nascondere invece i progetti per i quali è già un collaboratore attivo. Sappiamo che un filtro mostra sempre i record che soddisfano la sua condizione; in questo caso ci interessa però mostrare quelli che non la soddisfano, pertanto possiamo anteporre il prefisso ! (not) alla condizione in modo da negare il risultato calcolato da containsCS e invertire il significato logico del filtro.

In questo modo, quando dalla form di un nuovo assignment apriamo la Livetable degli associabili del ruolo project, la vista confronta l’__id dell’employee “padre” dell’assignment che stiamo associando con la lista dei collaboratori attivi di ciascun progetto, e mostra tutti i progetti in cui tale __id non compare.

Designer project assignment project application filters

Completa il modello dei dati #

Ora che sappiamo usare query e filtri, possiamo procedere nel modellare i requisiti rimanenti per la nostra applicazione.

Prima di aggiungere ulteriori elementi, potresti voler mettere ordine nel diagramma. Ecco alcune azioni che puoi compiere:

  • spostare una classe trascinandola per il suo header;
  • selezionare più classi: tenendo premuto Ctrl e cliccando sui loro header, oppure trascinando un rettangolo di selezione su di esse. Puoi spostare il gruppo cliccando sull’intestazione di uno qualsiasi degli elementi;
  • selezionare tutte le classi del diagramma premendo Ctrl+A;
  • allineare automaticamente un gruppo di classi selezionate, in verticale oppure in orizzontale aprendo il Class menu di una di esse e cliccando su Align classes;
  • aggiungere una piega su una relazione cliccando su un punto qualunque lungo essa, e successivamente trascinare i ruoli e le pieghe per darle la forma desiderata;
  • rimuovere una piega facendo click destro su di essa e selezionando Remove bend, oppure puoi rimuoverle tutte selezionando Remove all bends dal suo Relation menu.

1. Modella la gestione delle attività #

Dobbiamo implementare la gestione dei costi dello staff, cioè gli impiegati che collaborano a un progetto. Sappiamo finora che ciascun impiegato ha un costo orario (rappresentato da hourly_cost) ed è associato a un progetto tramite un incarico, che individua una finestra temporale tra due date.

Una prima strategia potrebbe consistere nel creare una query aggregata su Project a partire da Employee basandosi in qualche modo sulla durata dell’incarico. Tuttavia, dai requisiti, sappiamo di dover associare agli impiegati le attività che essi compiono relativamente ai progetti per cui hanno un incarico. Pertanto, dobbiamo suddividere un incarico (un periodo di più giorni) in entità più piccole che tengano traccia delle attività svolte nell’ambito di un singolo giorno di lavoro:

  • Spostati nel Database Schema e crea la classe Activity come in figura: aggiungi una data, due interi per indicare ore e minuti e un text per annotare la descrizione dell’attività. date è l’object title ed è required, insieme a ore e minuti.

Designer activity partial

  • Supponiamo esistano diverse tipologie di attività, come ad esempio assistenza tecnica, analisi, vendita o riunione, e che queste siano tutte note a priori. Possiamo modellare questo concetto utilizzando una classe Enum, la quale ammette come sue istanze un numero finito di oggetti con proprietà note e immutabili, identificati attraverso letterali (literal objects). Fai clic sul pulsanteper creare una nuova classe Enum e rinominala in Activity_type, quindi cliccaci sopra col destro e seleziona New literal per aggiungere un primo letterale da rinominare in TECHNICAL_ASSISTANCE. Ripeti l’operazione per aggiungere ANALYSIS, SALES e MEETING, fino ad avere una classe come quella indicata qui a fianco. Noterai che le classi Enum vengono sempre create con un attributo di default chiamato name, valorizzato con uno dei letterali definiti per ciascuna delle sue istanze.

Designer activity type

  • Aggiungi poi un’associazione unidirezionale da Activity ad Activity_type, rinomina i ruoli in activities e type_ e imposta la cardinalità di quest’ultimo a Exactly one (1). Infine, trascina name su Activity e rinomina la query in /type.

Designer relations activity activity type

  • Associamo Activity a Project_assignment: un’attività fa riferimento a un incarico preesistente, mentre un incarico consiste di diverse attività svolte in più giorni; pertanto, aggiungi un’associazione unidirezionale da Activity a Project_assignment, rinomina i ruoli in activities e assignment e imposta la cardinalità di quest’ultimo a Exactly one (1).

Designer relations activity task

Ora che Employee è collegata ad Activity, dobbiamo calcolare il costo di ciascuna attività in base al costo orario e al tempo totale trascorso dall’impiegato su di essa:

  • Prima di tutto, aggreghiamo ore e minuti: crea una math su Activity digitando l’espressione hours + (minutes/60) e chiamala /actual_hours.
  • Designer activity complete A questo punto, trascina hourly_cost di Employee su Activity, scegli il path Activity.assignment._employee.hourly_cost e rinomina l’attributo in /employee_hourly_cost: avremo una query a un valore singolo, perché ciascuna activity fa capo a un singolo project_assignment, che a sua volta fa capo a un singolo employee.
  • Adesso puoi usare l’espressione actual_hours * employee_hourly_cost per definire, su Activity, una math chiamata /cost che calcola il costo della singola attività.

Dal momento che ciascun incarico è riferito a un solo progetto, anche le sue attività lo sono. Perciò possiamo calcolare il costo complessivo delle risorse umane per un progetto sommando i costi di tutte le attività a esso associate. Trascina /cost da Activity a Project, scegli il path Project.assignments.activities.costs, aggrega con Sum e rinomina l’attributo in /total_staff_cost. Come puoi notare, questa query a molti è una somma a due livelli, che coinvolge tutti gli incarichi relativi a un progetto e tutte le attività relative a ciascuno di questi incarichi, da cui estrae i singoli costi.
Abbiamo soddisfatto il primo “obiettivo” per Project, cioè quello di avere un indicatore aggregato per il costo dello staff. Puoi vedere il risultato in figura:

Designer employee task project activity activity type query total staff cost

Prima di procedere oltre, aggiungiamo alcune query per arricchire la classe Project_assignment:

  • trascina Employee.full_name su Project_assignment passando per _employee__ e rinomina la query in /employee;
  • trascina Project.name su Project_assignment passando per _project__ e rinomina la query in /project.

Designer project assignment queries

In questo modo, quando associamo delle attività, abbiamo una chiara rappresentazione degli incarichi, in quanto vengono mostrati il nome dell’impiegato e il progetto a cui gli incarichi si riferiscono; quest’ultimo dettaglio è utile anche quando esaminiamo gli incarichi dalla form di un impiegato.

2. Modella la gestione della contabilità in entrata #

Prendiamo in considerazione la contabilità dei progetti, iniziando dalle entrate. Ci aspettiamo di emettere una fattura per il committente di un progetto (Customer); a sua volta il committente provvede a saldare il conto, suddividendolo eventualmente in più pagamenti.

Modelliamo una classe che rappresenti le nostre fatture.

  • Spostati sul diagramma Accounting, crea la classe Invoice con gli attributi in figura: ogni fattura ha un numero, una data e una somma da pagare (espressa con un numero reale). Restringi il dominio di amount ai soli reali positivi scegliendo un intervallo compreso tra 0 e unlimited, imposta a required tutti e tre gli attributi, e crea un object title composto che concateni number e date usando l’espressione concat(number," [",date,"]"). Infine, imponi un vincolo di unicità sulla coppia number-date.

Designer invoice

  • Associamo le fatture ai progetti: aggiungi un’associazione unidirezionale da Invoice a Project, rinomina il primo ruolo in invoices e imposta la cardinalità del secondo a Exactly one (1).

Designer relations invoice project customer

  • Creiamo due query per mostrare, all’interno della fattura, il nome del progetto e il nome dell’eventuale cliente: trascina entrambi i name rispettivamente da Project e Customer, e rinominali in /project e /customer.
  • Imponiamo una semplice regola sul ruolo _Invoice.project__ così da permette di associare una fattura a un progetto solo se quest’ultimo non è ancora concluso. Spostati nell’Application Schema e crea il filtro activeProjectsOnly con l’espressione !Project.completed.

Designer project invoice application filters

Come abbiamo detto, vogliamo tenere traccia del pagamento delle fatture, che può avvenire a più riprese per un singolo “conto”.

  • Crea la classe Payment come in figura: puoi copiare e incollare gli attributi date e amount da Invoice selezionandoli (tenendo premuto Ctrl), scegliendo la voce Copy dell’Attribute menu di uno dei due e cliccando su Paste dal Class menu di Payment. Spunta date come object title, e rinomina amount in payment_amount.

Designer payment

  • Aggiungi un’associazione unidirezionale da Payment a Invoice, rinomina il primo ruolo in payments e imposta la cardinalità del secondo a Exactly one (1). Dopodiché, trascina /title di Invoice su Payment e rinominalo in /invoice.

Designer relations payment invoice

Possiamo registrare più pagamenti per una fattura fino a estinguere la somma da pagare; pertanto, quando registriamo un pagamento per una fattura, dobbiamo tener conto dell’ammontare già pagato e di quello rimanente, e impedire all’utente di inserire un importo che ecceda l’ammontare rimanente. Procediamo come segue:

  • sommiamo l’ammontare dei pagamenti ricevuti: trascina payment_amount su Invoice, aggrega con Sum e rinomina la query in /amount_received;
  • contiamo l’ammontare rimanente: crea un attributo math su Invoice con l’espressione amount - amount_received e rinominalo in /amount_remaining;
  • trascina /amount_remaining da Invoice a Payment e chiamalo /invoice_amount_remaining.

Spostati sull’Application Schema e definisci un Class warning su Payment chiamato amountExceeded con l’espressione invoice_amount_remaining < 0, che viene calcolata durante una SaveNew e una SaveExisting; digita il messaggio “The declared amount exceeds the remaining amount to pay.” e rendi il warning bloccante.

Designer warning amount exceeded

Impediamo di associare nuovi pagamenti a fatture che sono state estinte, ovvero il cui ammontare rimanente è zero:

  • sul Database Schema , crea un attributo math su Invoice con l’espressione amount_remaining = 0 e rinominalo in /is_fully_paid;
  • dall’ Application Schema , crea un filtro sul ruolo Payment.invoice chiamato notPaidOnly con la condizione negata !Invoice.is_fully_paid.

Designer payment invoice application filters

Le due classi risultanti ora appaiono come in figura.

Designer relations payment invoice queries

Finalmente possiamo calcolare le entrate per ciascun progetto, per il quale vogliamo conoscere sia il totale delle fatture emesse sia lo stato effettivo del pagamento, sotto forma di percentuale:

  • trascina amount da Invoice a Project, aggrega con Sum e chiamalo /total_invoiced;
  • trascina payment_amount da Payment a Project (passando ovviamente per Invoice), aggrega di nuovo con Sum e chiamalo /total_payments. Dopodiché, crea un attributo math su Project digitando l’espressione (total_payments * 100) / total_invoiced, e chiamalo /payment_percentage.

Abbiamo soddisfatto il secondo obiettivo per Project, e ora disponiamo un resoconto anche del fatturato in entrata. Puoi vedere il risultato in figura:

Designer invoice project customer payment query total invoiced payments

3. Modella la gestione della contabilità in uscita #

L’ultimo fattore da considerare riguarda tutte le spese esterne che possiamo associare direttamente a un progetto, come quelle relative all’acquisto di materiali e risorse, al noleggio di strumenti, oppure ancora all’assunzione di stagisti e consulenti.

Immaginiamo quindi di voler registrare un certo numero di fatture passive – relative a un generico “fornitore” – e di associarle direttamente al progetto per cui sono state emesse. In questo modo potremo disporre, per ciascun progetto, anche di un dato relativo al totale delle spese vive.

Osserva Invoice: la classe rappresenta in realtà una fattura in uscita (o attiva): è infatti relativa a un cliente e ha dei pagamenti associati; non possiamo riutilizzarla nel nuovo contesto e dobbiamo invece introdurre una nuova classe. Consideriamo però una cosa: una generica fattura è pur sempre caratterizzata almeno da un codice, una data e un ammontare. Questi attributi sono già presenti su Invoice, perciò può essere una buona idea riutilizzare questi elementi.

Sfruttiamo quindi le composizioni a uno e definiamo una struttura comune a cui poi associamo due classi che rappresentano le due tipologie di fattura:

  • rinomina l’attuale classe Invoice: fai doppio click sul suo header e digita Outgoing_invoice; crea poi una nuova classe Invoice, che sarà la nostra struttura comune; aggiungi una composizione da Outgoing_invoice a Invoice, rinomina il ruolo part in details e imposta la sua cardinalità a Exactly one (1);
  • sposta gli attributi number, date e amount sulla nuova classe in modo tale che compaiano nella form annidata “details”, sia per Outgoing_invoice che per la nuova classe che aggiungeremo tra poco: tenendo premuto Ctrl, seleziona i tre attributi, trascinali su Invoice e clicca su Move here.

Come puoi notare dal warning, questo spostamento non è gratuito, dato che gli attributi coinvolti sono sorgenti di alcune math e query. Niente paura, possiamo risolvere questi problemi con facilità!

Designer relations invoice active invoice payment invalid attributes

Clicca su OK per confermare lo spostamento; procediamo poi come segue:

  • Per prima cosa abbiamo bisogno di un nuovo object title per le due classi: elimina /title da Outgoing_invoice e crea un nuovo object title composto su Invoice, concatenando code e date come prima, con l’espressione concat(number," [",date,"]"). Trascina poi /title da Invoice a Outgoing_invoice, seleziona Link here, rinomina la query in /title e spuntala come object title.
  • Aggiungiamo informazioni a Invoice, memorizzando anche una descrizione della fattura: aggiungi un nuovo attributo description: text.
  • Designer relations invoice outgoing invoice La math /amount_remaining richiede che su Outgoing_invoice vi sia un di un attributo di nome amount: trascina amount da Invoice a Outgoing_invoice, anche qui seleziona Link here e rinomina la query in /amount. Allo stesso modo, rendiamo di nuovo visibili i dettagli della fattura aggiungendo le query per /number e /date e per il nuovo attributo /description.
  • A questo punto dovresti non vedere alcun warning () sulle tre classi.

Ora possiamo creare una nuova classe Incoming_invoice, collegando anch’essa a Invoice con una composizione a uno come abbiamo fatto sopra. Anche qui, rinomina il ruolo della classe part in details e trascina gli attributi di Invoice dentro Incoming_invoice, spuntando la query /title come object title.

Designer relations composition invoice active invoice passive invoice

Aggiungiamo una classe per rappresentare un generico fornitore a cui associare questo tipo di fatture. Crea una classe Supplier con un solo attributo name (che è anche l’object title) che indica la ragione sociale.

Designer supplier

Collega direttamente Incoming_invoice a Supplier con un’associazione unidirezionale, rinomina il primo ruolo in invoices e imposta la cardinalità del secondo a Exactly one (1). Poi, trascina name su Incoming_invoice e rinominalo in /supplier.

Designer relations incoming invoice supplier

Colleghiamo Incoming_invoice con Project con un’associazione a uno, allo stesso modo di Outgoing_invoice. A tal proposito, rinomina il ruolo di Outgoing_invoice in outgoing_invoices, e chiama incoming_invoices quello di Incoming_invoice. Anche qui, trascina name da Project a Incoming_invoice e rinominalo in /project.

Designer relations incoming invoice supplier project

Definiamo un filtro activeProjectsOnly sul ruolo Incoming_invoice.project, analogo a quello già presente su Outgoing_invoice.project.

Designer project active invoice passive invoice application filters

Infine, colleghiamo Supplier a Project con un’associazione bidirezionale molti-a-molti, chiamando i rispettivi ruoli suppliers e projects.

Designer relations supplier project

Supponendo di poter associare più fornitori a più progetti, dobbiamo fare in modo di associare una fattura in entrata solamente ai fornitori registrati per quel progetto. A tale scopo introduciamo un nuovo strumento: il Selection path editor. Dall’Application Schema, apri il Role menu di Incoming_invoice.supplier e seleziona Set selection path...; apparirà un menu simile al Query expression path selector visto in precedenza. Da qui, seleziona la prima voce Incoming_invoice.project.suppliers.Supplier per chiudere il menu e far comparire anche qui un pallino sul ruolo.

Designer incoming invoice supplier selection path selector

Ciò che abbiamo appena fatto è stato restringere l’insieme dei fornitori associabili a una fattura in entrata ai soli fornitori associati al progetto a cui tale fattura è associata. In pratica, quando inseriamo una nuova incoming_invoice, inizialmente la Livetable degli associabili per supplier è vuota, e si popola solo dopo che abbiamo associato un progetto nel ruolo project, permettendoci di scegliere a quale fornitore fare riferimento.

Un Selection path è quindi una modo rapido per dichiarare un filtro su un ruolo in base a un percorso di relazioni; in questo caso abbiamo potuto utilizzarlo perché non dovevamo basare il filtro su condizioni particolari, a differenza di quanto abbiamo fatto per i filtri combinati definiti in precedenza.

Ora possiamo calcolare le spese di ciascun progetto: trascina amount da Invoice a Project, facendo attenzione a scegliere il path Project.incoming_invoices.details.amount in modo da considerare solamente l’ammontare delle fatture passive. Aggrega con Sum e rinomina la query in /total_expenses. A tal proposito, modifichiamo l’attributo puntato da /total_invoiced in modo che anch’esso faccia riferimento a Invoice.amount: seleziona Edit expression... dal suo Attribute menu e modifica il path in outgoing_invoice.details.amount.

I percorsi delle due query appaiono come in figura:

Designer project queries total invoiced total expenses

4. Calcola il bilancio totale #

Abbiamo soddisfatto il terzo e ultimo “obiettivo” per Project, e possiamo finalmente calcolare il bilancio totale: crea un attributo math su Project con l’espressione total_invoiced - total_expenses - total_staff_cost e chiamalo /total_balance.

Ora il nostro diagramma Accounting è completo e appare come in figura:

Designer complete diagrams accounting

Il diagramma Accounting.

Per avere una visione d’insieme su una classe centrale come Project possiamo creare un diagramma con tutte le classi del modello: per farlo rapidamente, fai click destro su uno dei due diagrammi e seleziona Clone; dopodiché, mostra tutte le classi nascoste, trascinandole dal tab Classes oppure dalle relazioni nascoste. Rinomina il nuovo diagramma in Administration.

Il nostro modello dei dati è completo, ed è rappresentato nei tre seguenti diagrammi:

Designer complete diagrams

I diagrammi Administration, Accounting e Staff.

Designer Administration diagram

Il diagramma Administration

Designer Accounting diagram

Il diagramma Accounting

Designer Staff diagram

Il diagramma Staff

Aggiorna e controlla l’applicazione #

Come di consueto, controlliamo che tutto funzioni correttamente nell’applicazione. Salva il modello e chiudi il Designer, dopodiché avvia la Cloudlet Workforce lasciando che Livebase risolva tutte le issue di allineamento del database (tutte di tipo low severity).

Accedi all’applicazione generata: sulla vista Application sono comparse ulteriori voci di menù per le nuove classi, fatta eccezione per la classe Enum Activity_type le cui istanze sono definite in fase di modellazione e non possono essere visionate o modificate direttamente. Possiamo però associare una di queste istanze in fase di creazione di una nuova Activity, come vedremo più avanti.

Sulle Livetable compaiono inoltre tutti gli attributi query che abbiamo definito e abilitato per questa vista applicativa. Puoi verificare questo, ad esempio, per le tabelle Projects, Teams e Active Invoices.

Verifichiamo il corretto funzionamento dei selection filter e delle query che abbiamo definito, nonché della classe Enum:

  • crea un progetto, e verifica che non ci siano impiegati associabili come director;
  • crea un incarico attivo per un impiegato e assegnalo a un progetto; verifica che, creando un secondo incarico, quel progetto non sia più associabile e non compaia nella Livetable di selezione per il ruolo project;
  • verifica che gli impiegati con almeno un incarico attivo abbiano una spunta su Is active;
  • crea un’attività: verifica che nella form di creazione sia presente un dropdown in corrispondenza dell’attributo activity_type, e che questo permetta di scegliere quale oggetto associare per mezzo dei letterali che abbiamo definito nel Designer;
  • imposta un progetto come completed, poi crea una fattura di qualsiasi tipo; verifica che quel progetto non sia associabile;
  • crea una fattura attiva, poi crea diversi pagamenti da associare a essa; verifica che amount remaining indichi correttamente la somma rimanente da pagare.

Conclusioni #

Con questa lezione abbiamo esaurito gli argomenti di modellazione relativi al Database Schema. Inoltre, abbiamo tradotto in codice gran parte dei requisiti descritti nello scenario, preparando il terreno per lo step finale del corso: il partizionamento dell’accesso ai dati.

Clicca sul bottone per scaricare il modello che abbiamo realizzato in questa lezione:

TutorialEngine_queries_filters.xml

Nella prossima lezione… #

Definiremo Application Schema e Profile Schema multipli per partizionare il modello in base ai diversi profili utente descritti nei requisiti: Definisci viste applicative e profili utente.