Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -265,8 +265,8 @@ La nota *"se i numeri sono questi cosa devo fare per crescere?"* suggerisce un l
| **3** | Motore di riclassificazione + indici (da `docs/MODELLO_FINANZIARIO.md`), **import Excel §11.1** | Import di un vero file cliente → Conto Economico riclassificato corretto | 🟡 **Quasi**: motore, indici e import fatti e verificati. Manca il file di un cliente **con i saldi**: quello consegnato è un modello vuoto (vedi `docs/MODELLO_FINANZIARIO.md` §8-bis) |
| **4** | UI Business: Panoramica + Conto Economico + Stato Patrimoniale (§10.2-10.4) | Le 3 schermate con dati reali importati | ✅ **Fatta** (sessione 1) — menu laterale e grafici compresi |
| **5** | UI Capitale Circolante + Tesoreria/Cash Flow + Scadenziario (§10.5-10.6) | Previsione di cassa funzionante su dati reali | ✅ **Fatta** (sessione 2) |
| **6** | UI Banche e Finanziamenti (§10.7) + collegamento rate→Cash Flow | Fidi/finanziamenti con impatto visibile in Tesoreria | ⬜ Prossima |
| **7** | Analisi & Simulazioni (§10.8) | Scenario what-if salvabile e confrontabile | ⬜ |
| **6** | UI Banche e Finanziamenti (§10.7) + collegamento rate→Cash Flow | Fidi/finanziamenti con impatto visibile in Tesoreria | ✅ **Fatta** (sessione 2) |
| **7** | Analisi & Simulazioni (§10.8) | Scenario what-if salvabile e confrontabile | ⬜ Prossima |
| **8** | Sync Consulente↔Azienda via Tailscale (§6) + status bar (§7) | Due installazioni reali che si scambiano dati | ⬜ |
| **9** | Import Excel avanzato: tolleranza a varianti di formato tra clienti/periodi (§11.1) | Import robusto su più file Excel reali diversi tra loro | ⬜ |
| **10** | Installer offline (electron-builder) per Consulente e Azienda | `.exe` funzionanti, Tailscale bundled | 🟡 **Parziale**: `.exe` installabile, portable e demo funzionanti. Mancano le due varianti separate e Tailscale bundled, che hanno senso solo dopo la Fase 8. **Decisione del cliente (2026-09-16): nessuna release fino a codice finito; alla fine una sola release con tre eseguibili — installer, portable, demo** |
Expand Down Expand Up @@ -470,7 +470,41 @@ La **Panoramica** ora mostra liquidità di oggi, cash flow a 30 giorni e il graf

**Verifiche**: 54 test (`npm run test`), fra cui la previsione di cassa calcolata a mano giorno per giorno; `npm run verify:schema` passa da 22 a 35 controlli con i vincoli della 004; percorso completo provato nell'app: incasso parziale che aggiorna liquidità e previsione, fattura "60 giorni fine mese" con scadenza calcolata, aggiornamento di un database demo della 0.0.4.

**Da fare in Fase 6**: Banche e Finanziamenti (§10.7), con le rate che entrano nella previsione come `source = 'finanziamento'`, gli affidamenti disponibili nella Tesoreria e il DSCR finalmente calcolabile.
**Fase 6 — Banche e Finanziamenti (§10.7)**

Migrazione `005_banks`:

| Tabella | Cosa tiene |
|---|---|
| `banks` | Gli istituti dell'azienda, società di leasing comprese. Nome univoco per azienda, senza distinguere le maiuscole |
| `credit_lines` | Linee a revoca: fido di cassa, anticipo fatture/SBF, carte, altre. Accordato e utilizzato a una data; l'utilizzato può superare l'accordato (uno sconfinamento è un dato, non un errore) |
| `loans` | Mutui, finanziamenti e leasing: **i parametri del piano, non le rate** |

**Le rate non si copiano da nessuna parte.** Il piano di ammortamento si ricalcola sempre dagli stessi parametri (`src/shared/engine/loans.ts`) e le rate entrano nella previsione di cassa come movimenti `source = 'finanziamento'` generati al volo. Una sola fonte: modificare un mutuo aggiorna subito la tesoreria, senza righe orfane. Per lo stesso motivo la demo non ha più la "rata del mutuo" come previsione manuale: un database demo della Fase 5 la perde al primo avvio, altrimenti la rata si conterebbe due volte.

Il modello del consulente non tratta i piani di ammortamento: la matematica è quella standard, con queste scelte dichiarate.

1. **Francese** (rata costante) o **italiano** (quota capitale costante), con **preammortamento** di soli interessi.
2. **Tasso nominale annuo** diviso per il numero di rate dell'anno, come nei piani bancari italiani.
3. **Leasing**: rata costante con **riscatto** pagato una rata dopo l'ultima; il valore attuale di rate e riscatto dà il capitale. Un maxi-canone iniziale si registra come movimento a parte.
4. **Arrotondamento al centesimo** rata per rata; l'ultima assorbe i residui e chiude il debito esattamente (a zero, o al riscatto).
5. **Le rate già scadute si considerano pagate**: in Italia sono quasi sempre addebitate in automatico. Il debito residuo è quello dopo l'ultima rata scaduta.

Verificato con i numeri da manuale: 12.000 € al 6% in 12 rate mensili danno la rata francese di 1.032,80 €.

**DSCR finalmente calcolato** (§5): EBITDA degli ultimi 12 mesi ÷ rate dei 12 mesi successivi alla **fine del periodo analizzato**, riscatti compresi. Legarlo alla fine del periodo e non a oggi rende il valore di un periodo stabile, qualunque giorno lo si guardi. Senza finanziamenti resta un trattino.

**Situazione per istituto**: per i finanziamenti accordato e utilizzato coincidono col debito residuo, come nella Centrale Rischi. **Disponibile e percentuale di utilizzo guardano solo le linee a revoca**: un finanziamento è utilizzato al 100% per definizione, e una società di leasing sarebbe sempre "in allarme". Il disponibile si somma linea per linea: lo sconfinamento di una carta non toglie disponibilità al fido di un'altra linea.

**Collegamenti con le altre viste**: la Tesoreria mostra gli affidamenti disponibili (senza contarli come liquidità) e le rate nella categoria "Rate finanziamenti"; la Panoramica avvisa quando gli affidamenti sono utilizzati **dall'80%** in su (rosso dal 95%) — l'avviso "Affidamenti utilizzati oltre soglia" dei mockup, con una soglia che il modello non fissa; lo Stato Patrimoniale mostra il DSCR con la soglia di 1,25x.

**Il modulo di inserimento mostra la rata mentre lo si compila**, calcolata dallo stesso motore del server: è il modo più rapido per accorgersi di un tasso o di un numero di rate sbagliato. Ogni finanziamento ha il suo piano completo consultabile.

**Dati della demo**: tre istituti, quattro linee (una quasi esaurita, da rinegoziare), un mutuo francese, un leasing col riscatto e un finanziamento trimestrale all'italiana ancora in preammortamento, tutti con date relative al primo avvio.

**Verifiche**: 68 test (14 sul motore dei finanziamenti); `npm run verify:schema` a 48 controlli; nell'app: finanziamento creato e modificato dal modulo con la rata ricalcolata (leasing 24.000 € al 6,1% con riscatto 2.400 € → 520,47 €), rate dentro la previsione di cassa, DSCR sullo stato patrimoniale, aggiornamento di un database demo della Fase 5.

**Da fare in Fase 7**: Analisi & Simulazioni (§10.8), che ora ha tutti i motori sotto: conto economico, circolante, tesoreria e finanziamenti.

---

Expand Down
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Un gestionale desktop per consulenti finanziari aziendali: riclassifica il bilan
calcola gli indici che contano, previene le tensioni di cassa prima che arrivino,
e risponde a "cosa succede se" senza toccare un foglio Excel.

![stato](https://img.shields.io/badge/stato-in%20sviluppo%20%C2%B7%20fase%205%2F10-3ddbff)
![stato](https://img.shields.io/badge/stato-in%20sviluppo%20%C2%B7%20fase%206%2F10-3ddbff)
![piattaforma](https://img.shields.io/badge/Windows-x64-3ddbff)
[![licenza](https://img.shields.io/badge/licenza-MIT-5cff9d)](LICENSE)

Expand Down Expand Up @@ -157,6 +157,11 @@ la scadenza si calcola da sola), e le voci che si ripetono — stipendi, affitto
di cassa. Quando una fattura viene pagata, anche solo in parte, lo si registra con un
clic, e la liquidità di oggi si aggiorna.

**Banche e finanziamenti, a mano.** Per ogni istituto si registrano i fidi (quanto è
accordato, quanto è usato) e i finanziamenti: importo, tasso, numero di rate. Il piano
di ammortamento si calcola da solo, e le rate finiscono nella previsione di cassa senza
doverle scrivere una per una.

---

## A che punto siamo
Expand All @@ -166,7 +171,9 @@ loro aziende, importa il piano dei conti da Excel e ne calcola il bilancio
riclassificato con tutti gli indici, e lo mostra in cinque schermate: panoramica con
avvisi automatici, conto economico, stato patrimoniale, capitale circolante con le sue
note automatiche, e tesoreria con scadenziario e previsione di cassa a sei mesi.
Mancano banche e finanziamenti, le simulazioni e il collegamento fra i due programmi.
C'è anche la parte bancaria: fidi, mutui e leasing con il loro piano di ammortamento,
le rate che entrano da sole nella previsione di cassa. Mancano le simulazioni e il
collegamento fra i due programmi.

| | Fase | Stato |
|---|---|---|
Expand All @@ -176,8 +183,8 @@ Mancano banche e finanziamenti, le simulazioni e il collegamento fra i due progr
| 3 | Riclassificazione, indici, import Excel | 🟡 Quasi — serve un file cliente **con i saldi** per la verifica finale |
| 4 | Le prime tre schermate di analisi | ✅ Fatta |
| 5 | Capitale circolante, tesoreria, scadenziario | ✅ Fatta |
| 6 | Banche e finanziamenti | ⬜ Prossima |
| 7 | Analisi e simulazioni | ⬜ |
| 6 | Banche e finanziamenti | ✅ Fatta |
| 7 | Analisi e simulazioni | ⬜ Prossima |
| 8-9 | Collegamento fra i due programmi | ⬜ |
| 10 | I tre eseguibili finali: installer, portable, demo | 🟡 Già provati, si pubblicano a codice finito |

Expand All @@ -188,8 +195,8 @@ La roadmap completa, con il dettaglio di cosa c'è dentro ogni fase, è in

Il modo più rapido per vederlo all'opera è la **versione demo**:
`DaProdFinanza-Demo-x.y.z-portable.exe`. Si lancia senza installare niente e parte
già con una pizzeria di esempio, due anni di bilanci, lo scadenziario e le previsioni
di cassa. Si entra come
già con una pizzeria di esempio, due anni di bilanci, lo scadenziario, le previsioni
di cassa, le banche e i finanziamenti. Si entra come
consulente con **`cammo` / `1234`**, oppure come azienda con **`Pizzeria DaProd` /
`1234`**. I suoi dati restano in una cartella a parte e non si mescolano mai con
quelli di un'installazione vera.
Expand Down
2 changes: 1 addition & 1 deletion docs/MODELLO_FINANZIARIO.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ L'implementazione usa `Gross Profit = (Ricavi Operativi + Rimanenze Finali) −
- **Acquisti**, che serve al DPO (§6): nel file esiste la colonna ma nessuna sezione la marca. L'implementazione usa `Costi Materie Prime + Costi Produzione`.
- **Debiti finanziari**, che servono a PFN e Debt/Equity (§5): non esiste un tag che li distingua. L'implementazione li ricava per differenza, togliendo dai debiti totali le voci che finanziarie non sono (fornitori, enti previdenziali, TFR). Un tag esplicito sarebbe più solido.

Il **DSCR** resta non calcolabile finché non esiste il modulo Banche (`AGENTS.md` §10.7): richiede le rate attese nei 12 mesi successivi. L'app mostra un trattino, non uno zero.
Il **DSCR** richiede le rate attese nei 12 mesi successivi: dalla Fase 6 le calcola il modulo Banche dai piani di ammortamento, a partire dalla fine del periodo analizzato (`AGENTS.md` §13-ter). Senza finanziamenti registrati l'app mostra un trattino, non uno zero.

### d) Il file contiene più informazione di questo documento

Expand Down
56 changes: 56 additions & 0 deletions scripts/verify-schema.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,62 @@ app.whenReady().then(() => {
check('soglia minima negativa', () => insertSettings(null, null, -1), 'rifiutato')
check('impostazioni valide', () => insertSettings(5000, `${ANNO}-01-01`), 'accettato')
check('seconde impostazioni per la stessa azienda', () => insertSettings(null, null), 'rifiutato')

console.log('\n Banche e finanziamenti')
const bankUuid = randomUUID()
const insertBank = (uuid, name) =>
db
.prepare(
`INSERT INTO banks (uuid, company_uuid, name, created_at, updated_at, synced, deleted)
VALUES (?, ?, ?, ?, ?, 0, 0)`
)
.run(uuid, tempCompany, name, now, now)
check('istituto', () => insertBank(bankUuid, 'Banca di prova'), 'accettato')
check('stesso istituto con le maiuscole diverse', () => insertBank(randomUUID(), 'BANCA DI PROVA'), 'rifiutato')

const insertLine = (kind, granted, used, bank = bankUuid) =>
db
.prepare(
`INSERT INTO credit_lines (uuid, company_uuid, bank_uuid, kind, label, granted_cents, used_cents,
created_at, updated_at, synced, deleted)
VALUES (?, ?, ?, ?, 'linea', ?, ?, ?, ?, 0, 0)`
)
.run(randomUUID(), tempCompany, bank, kind, granted, used, now, now)
check('fido di cassa', () => insertLine('fido_cassa', 3000000, 500000), 'accettato')
check('sconfinamento (utilizzato oltre l’accordato)', () => insertLine('carta', 100000, 150000), 'accettato')
check('tipo di linea inventato', () => insertLine('mutuo', 100000, 0), 'rifiutato')
check('linea su un istituto inesistente', () => insertLine('fido_cassa', 100000, 0, randomUUID()), 'rifiutato')

const insertLoan = (fields) => {
const row = {
kind: 'mutuo',
principal: 10000000,
rate: 4.2,
installments: 96,
frequency: 'monthly',
grace: 0,
amortization: 'francese',
balloon: 0,
...fields
}
db.prepare(
`INSERT INTO loans (uuid, company_uuid, bank_uuid, kind, label, principal_cents,
annual_rate_percent, first_due_date, installments, frequency,
grace_installments, amortization, balloon_cents,
created_at, updated_at, synced, deleted)
VALUES (?, ?, ?, ?, 'prestito', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, 0)`
).run(
randomUUID(), tempCompany, bankUuid, row.kind, row.principal, row.rate, `${ANNO}-01-31`,
row.installments, row.frequency, row.grace, row.amortization, row.balloon, now, now
)
}
check('mutuo', () => insertLoan({}), 'accettato')
check('leasing con riscatto', () => insertLoan({ kind: 'leasing', balloon: 1000000 }), 'accettato')
check('preammortamento lungo quanto tutto il piano', () => insertLoan({ grace: 96 }), 'rifiutato')
check('riscatto pari al capitale', () => insertLoan({ balloon: 10000000 }), 'rifiutato')
check('periodicità inventata', () => insertLoan({ frequency: 'weekly' }), 'rifiutato')
check('tasso negativo', () => insertLoan({ rate: -1 }), 'rifiutato')
check('ammortamento inventato', () => insertLoan({ amortization: 'tedesco' }), 'rifiutato')
} finally {
db.exec('ROLLBACK')
db.close()
Expand Down
71 changes: 71 additions & 0 deletions src/main/db/demo-banks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { addDays, addMonths, formatDate, parseDate } from '@shared/engine'
import { saveBank, saveLine, saveLoan } from '../server/services/banks.service'
import { todayLocal } from '../server/services/treasury.service'

/**
* Banche e finanziamenti della Pizzeria DaProd dimostrativa (Fase 6).
*
* Come per la tesoreria, le date sono relative al primo avvio: un mutuo con
* la prima rata fissa nel 2024 sarebbe finito, in una demo aperta fra qualche
* anno. Tre istituti, le linee tipiche di un ristorante (fido, anticipo delle
* fatture di catering, carta aziendale) e tre piani diversi: un mutuo
* francese, un leasing col riscatto e un finanziamento con preammortamento.
*/

const euro = (value: number): number => Math.round(value * 100)

/** Il 28 del mese, `months` mesi prima (o dopo) di oggi. */
function il28(today: string, months: number): string {
const t = new Date(parseDate(addMonths(today, months)))
return formatDate(Date.UTC(t.getUTCFullYear(), t.getUTCMonth(), 28))
}

export function seedDemoBanks(companyUuid: string, today = todayLocal()): void {
const vesuvio = saveBank(companyUuid, {
name: 'Banca del Vesuvio',
branch: 'Filiale di Pizzolandia centro',
contact: 'Dott.ssa Esposito'
})
const partenopeo = saveBank(companyUuid, { name: 'Credito Partenopeo', branch: 'Agenzia 12' })
const leasing = saveBank(companyUuid, { name: 'Sud Leasing', notes: 'Società di leasing strumentale' })

saveLine(companyUuid, {
bank_uuid: vesuvio.uuid, kind: 'fido_cassa', label: 'Fido di cassa c/c',
granted_cents: euro(30_000), used_cents: euro(8_500), used_as_of: addDays(today, -2),
annual_rate_percent: 6.9, expiry_date: addMonths(today, 7)
})
saveLine(companyUuid, {
bank_uuid: vesuvio.uuid, kind: 'anticipo_fatture', label: 'Anticipo fatture catering',
granted_cents: euro(20_000), used_cents: euro(4_000), used_as_of: addDays(today, -2),
annual_rate_percent: 5.4, expiry_date: addMonths(today, 7)
})
saveLine(companyUuid, {
bank_uuid: partenopeo.uuid, kind: 'carta', label: 'Carta aziendale',
granted_cents: euro(5_000), used_cents: euro(1_200), used_as_of: addDays(today, -2)
})
saveLine(companyUuid, {
bank_uuid: partenopeo.uuid, kind: 'fido_cassa', label: 'Fido di cassa c/c',
granted_cents: euro(15_000), used_cents: euro(12_900), used_as_of: addDays(today, -2),
annual_rate_percent: 7.4, expiry_date: addMonths(today, 3),
notes: 'Utilizzato quasi per intero: da rinegoziare.'
})

saveLoan(companyUuid, {
bank_uuid: vesuvio.uuid, kind: 'mutuo', label: 'Mutuo ristrutturazione del locale',
principal_cents: euro(130_000), annual_rate_percent: 4.2,
first_due_date: il28(today, -32), installments: 96, frequency: 'monthly',
grace_installments: 0, amortization: 'francese', balloon_cents: 0
})
saveLoan(companyUuid, {
bank_uuid: leasing.uuid, kind: 'leasing', label: 'Leasing forno a legna e abbattitore',
principal_cents: euro(38_000), annual_rate_percent: 5.5,
first_due_date: il28(today, -14), installments: 60, frequency: 'monthly',
grace_installments: 0, amortization: 'francese', balloon_cents: euro(3_800)
})
saveLoan(companyUuid, {
bank_uuid: partenopeo.uuid, kind: 'finanziamento', label: 'Finanziamento liquidità garantito',
principal_cents: euro(40_000), annual_rate_percent: 3.9,
first_due_date: il28(today, -5), installments: 20, frequency: 'quarterly',
grace_installments: 4, amortization: 'italiano', balloon_cents: 0
})
}
7 changes: 2 additions & 5 deletions src/main/db/demo-treasury.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { createItem, openingCash, todayLocal, updateSettings } from '../server/s
* aperta fra sei mesi con tutte le scadenze nel passato mostrerebbe solo
* arretrati. Ci sono di proposito un credito scaduto da oltre 60 giorni, un
* incasso parziale e un pagamento scaduto: sono i casi che la vista deve saper
* raccontare.
* raccontare. Le rate dei finanziamenti non stanno qui: le genera il piano di
* ammortamento (vedi demo-banks.ts).
*/

const euro = (value: number): number => Math.round(value * 100)
Expand Down Expand Up @@ -136,10 +137,6 @@ export function seedDemoTreasury(companyUuid: string, today = todayLocal()): voi
description: 'Altri acquisti di ingredienti non ancora fatturati (stima)',
due_date: giornoDelMese(today, 28), amount_cents: euro(14_000), recurrence: 'monthly'
},
{
direction: 'out', category: 'Rate finanziamenti', description: 'Rata mutuo ristrutturazione',
due_date: giornoDelMese(today, 28), amount_cents: euro(1_850), recurrence: 'monthly'
},
// Una tantum.
{
direction: 'in', category: 'Altre entrate', description: 'Rimborso assicurazione per il guasto al forno',
Expand Down
Loading