26 KiB
Dividend Section Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Aggiungere il parametro ?dividend=true che sostituisce la tabella Sottostanti nelle sezioni Anagrafica con una pagina landscape dedicata Sottostanti+Dividendi.
Architecture: Il flag ShowDividend viene aggiunto a CertificateReportData e propagato attraverso controller → orchestratore → renderer. DividendSectionRenderer è un renderer diretto (non IPdfSectionRenderer) iniettato nell'orchestratore come già avviene per ExpiredAnagraficaSectionRenderer. Entrambi i flussi (attivo + expired) ricevono la pagina landscape subito dopo la Sezione 1. Le chiavi cache vengono estese a 4 combinazioni.
Tech Stack: ASP.NET Core 8, Syncfusion PDF Net.Core v33, SkiaSharp, Microsoft.Data.SqlClient
File Map
| File | Azione |
|---|---|
Models/CertificateModels.cs |
Modify — aggiungere ShowDividend |
Services/Interfaces/IServices.cs |
Modify — aggiungere showDividend a IReportOrchestrator |
Controllers/ReportController.cs |
Modify — aggiungere param dividend a tutti e 3 gli endpoint |
Services/Implementations/ReportOrchestrator.cs |
Modify — signature, cache keys, inject renderer, inserire sezione |
Services/Implementations/AnagraficaSectionRenderer.cs |
Modify — skip SEZIONE C se ShowDividend=true |
Services/Implementations/ExpiredAnagraficaSectionRenderer.cs |
Modify — skip SEZIONE C se ShowDividend=true |
Services/Implementations/DividendSectionRenderer.cs |
Create — nuova pagina landscape con tabella a 2 livelli |
Program.cs |
Modify — registrare DividendSectionRenderer |
Task 1: Aggiungere ShowDividend al modello
Files:
-
Modify:
Models/CertificateModels.cs -
Step 1: Leggere il file corrente
Aprire
Models/CertificateModels.cse individuare la classeCertificateReportData. Il file contiene giàpublic bool ShowBranding { get; set; } = false;. -
Step 2: Aggiungere la proprietà
Subito dopo
ShowBranding:public bool ShowBranding { get; set; } = false; public bool ShowDividend { get; set; } = false; -
Step 3: Build
dotnet build CertReports.SyncfusionExpected: Build succeeded, 0 errors.
-
Step 4: Commit
git add CertReports.Syncfusion/Models/CertificateModels.cs git commit -m "feat: add ShowDividend flag to CertificateReportData"
Task 2: Aggiornare l'interfaccia IReportOrchestrator
Files:
-
Modify:
Services/Interfaces/IServices.cs -
Step 1: Leggere il file
Trovare
IReportOrchestrator. Attualmente:public interface IReportOrchestrator { Task<byte[]> GenerateReportAsync(string isin, bool showBranding = false); } -
Step 2: Aggiungere il parametro
public interface IReportOrchestrator { Task<byte[]> GenerateReportAsync(string isin, bool showBranding = false, bool showDividend = false); } -
Step 3: Build
dotnet build CertReports.SyncfusionExpected: errore di compilazione su
ReportOrchestrator(firma non corrisponde) — normale, fix al Task 4. -
Step 4: Commit
git add CertReports.Syncfusion/Services/Interfaces/IServices.cs git commit -m "feat: add showDividend param to IReportOrchestrator interface"
Task 3: Aggiornare ReportController — parametro dividend
Files:
-
Modify:
Controllers/ReportController.cs -
Step 1: Leggere il file
Ci sono 3 endpoint che chiamano
_orchestrator.GenerateReportAsync(isin, showBranding):GenerateReport(GET/api/report?p=...)GenerateReportByIsin(GET/api/report/by-isin/{isin})DownloadReport(GET/api/report/download?p=...)
-
Step 2: Modificare
GenerateReportAggiungere il parametro
dividende passarlo all'orchestratore:public async Task<IActionResult> GenerateReport( [FromQuery(Name = "p")] string? encryptedIsin = null, [FromQuery(Name = "alias")] string? aliasId = null, [FromQuery(Name = "branding")] bool showBranding = false, [FromQuery(Name = "dividend")] bool showDividend = false)E nella chiamata:
return await GenerateAndReturnPdf(isin, showBranding, showDividend); -
Step 3: Modificare
GenerateReportByIsinpublic async Task<IActionResult> GenerateReportByIsin( string isin, [FromQuery(Name = "branding")] bool showBranding = false, [FromQuery(Name = "dividend")] bool showDividend = false)E nella chiamata:
return await GenerateAndReturnPdf(isin, showBranding, showDividend); -
Step 4: Modificare
DownloadReportpublic async Task<IActionResult> DownloadReport( [FromQuery(Name = "p")] string? encryptedIsin = null, [FromQuery(Name = "alias")] string? aliasId = null, [FromQuery(Name = "branding")] bool showBranding = false, [FromQuery(Name = "dividend")] bool showDividend = false)Nella chiamata interna:
var pdfBytes = await _orchestrator.GenerateReportAsync(isin, showBranding, showDividend); -
Step 5: Aggiornare il metodo privato
GenerateAndReturnPdfTrovare la firma di
GenerateAndReturnPdfe aggiungerebool showDividend = false, poi passarla aGenerateReportAsync:private async Task<IActionResult> GenerateAndReturnPdf(string isin, bool showBranding = false, bool showDividend = false) { // ... var pdfBytes = await _orchestrator.GenerateReportAsync(isin, showBranding, showDividend); // ... } -
Step 6: Build
dotnet build CertReports.SyncfusionExpected: errore su
ReportOrchestrator(non ancora aggiornato) — normale. -
Step 7: Commit
git add CertReports.Syncfusion/Controllers/ReportController.cs git commit -m "feat: add ?dividend query param to all report endpoints"
Task 4: Aggiornare AnagraficaSectionRenderer — skip SEZIONE C
Files:
-
Modify:
Services/Implementations/AnagraficaSectionRenderer.cs -
Step 1: Leggere il file
Trovare il blocco SEZIONE C. Attualmente:
// ── SEZIONE C: SOTTOSTANTI ──────────────────────────────────── if (info.Sottostanti.Count > 0) { // Se lo spazio rimanente è meno di 80pt, nuova pagina if (y > PageH - 80f) { PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding); page = doc.Pages.Add(); g = page.Graphics; y = 0f; } DrawSottostanti(g, info.Sottostanti, PageW, y); } -
Step 2: Wrappare il blocco con la condizione
// ── SEZIONE C: SOTTOSTANTI ──────────────────────────────────── if (!data.ShowDividend && info.Sottostanti.Count > 0) { // Se lo spazio rimanente è meno di 80pt, nuova pagina if (y > PageH - 80f) { PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding); page = doc.Pages.Add(); g = page.Graphics; y = 0f; } DrawSottostanti(g, info.Sottostanti, PageW, y); } -
Step 3: Build
dotnet build CertReports.SyncfusionExpected: stesso errore su
ReportOrchestrator— normale. -
Step 4: Commit
git add CertReports.Syncfusion/Services/Implementations/AnagraficaSectionRenderer.cs git commit -m "feat: skip sottostanti table in anagrafica when ShowDividend=true"
Task 5: Aggiornare ExpiredAnagraficaSectionRenderer — skip SEZIONE C
Files:
-
Modify:
Services/Implementations/ExpiredAnagraficaSectionRenderer.cs -
Step 1: Leggere il file
Trovare il blocco SEZIONE C (struttura identica ad
AnagraficaSectionRenderer):// ── SEZIONE C: SOTTOSTANTI ──────────────────────────────────── if (info.Sottostanti.Count > 0) { if (y > PageH - 80f) { PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding); page = doc.Pages.Add(); g = page.Graphics; ... } DrawSottostanti(g, info.Sottostanti, PageW, y); } -
Step 2: Stessa modifica del Task 4
// ── SEZIONE C: SOTTOSTANTI ──────────────────────────────────── if (!data.ShowDividend && info.Sottostanti.Count > 0) { if (y > PageH - 80f) { PdfTheme.DrawFooter(g, PageW, PageH, 1, data.ShowBranding); page = doc.Pages.Add(); g = page.Graphics; ... } DrawSottostanti(g, info.Sottostanti, PageW, y); } -
Step 3: Build
dotnet build CertReports.Syncfusion -
Step 4: Commit
git add CertReports.Syncfusion/Services/Implementations/ExpiredAnagraficaSectionRenderer.cs git commit -m "feat: skip sottostanti table in expired anagrafica when ShowDividend=true"
Task 6: Creare DividendSectionRenderer
Files:
-
Create:
Services/Implementations/DividendSectionRenderer.cs -
Step 1: Creare il file con la struttura base
using CertReports.Syncfusion.Models; using Syncfusion.Pdf; using Syncfusion.Pdf.Graphics; using Syncfusion.Pdf.Grid; using System.Drawing; namespace CertReports.Syncfusion.Services.Implementations; /// <summary> /// Genera la pagina landscape con la tabella unificata Sottostanti + Dividendi. /// Non implementa IPdfSectionRenderer — iniettato direttamente nell'orchestratore. /// </summary> public class DividendSectionRenderer { // Larghezze colonne indicative (totale 742pt) — scalate proporzionalmente a runtime private static readonly float[] ColWidths = [ 90f, // Nome 50f, // Strike 50f, // Last 46f, // % Perf. 50f, // Barr.Cap. 46f, // Buf.Cap. 50f, // Trig.CPN 46f, // Buf.CPN 46f, // Trig.AC 54f, // Data Stacco 54f, // Data Pag. 40f, // Importo 40f, // Rend. 40f, // Imp.Fut. 40f, // Rend.Fut. ]; private static readonly string[] Col2Headers = [ "", "Strike", "Last", "% Perf.", "Barr.Cap.", "Buf.Cap.", "Trig.CPN", "Buf.CPN", "Trig.AC", "Data Stacco", "Data Pag.", "Importo", "Rend.", "Imp.Fut.", "Rend.Fut.", ]; // Indici colonne soggette a colore performance (0-based) private static readonly HashSet<int> PerfCols = [3, 5, 7, 12, 14]; public PdfDocument Render(CertificateReportData data) { var doc = new PdfDocument(); doc.PageSettings.Orientation = PdfPageOrientation.Landscape; doc.PageSettings.Size = PdfPageSize.A4; var page = doc.Pages.Add(); var g = page.Graphics; var size = page.GetClientSize(); float w = size.Width - 2 * PdfTheme.PageMargin; float h = size.Height - 2 * PdfTheme.PageMargin - PdfTheme.FooterHeight; float x0 = PdfTheme.PageMargin; float y = PdfTheme.PageMargin; // ── Titolo ──────────────────────────────────────────────────── var titleFont = PdfTheme.TitleFont; g.DrawString("Sottostanti e Dividendi", titleFont, PdfTheme.AccentBlueBrush, new RectangleF(x0, y, w, 20f)); y += 20f + 4f; // ── Linea separatrice ───────────────────────────────────────── g.DrawLine(PdfTheme.AccentBluePen, new PointF(x0, y), new PointF(x0 + w, y)); y += 6f; // ── Calcolo scale fattore su larghezza reale ────────────────── float totalNominal = ColWidths.Sum(); float scale = w / totalNominal; float[] cw = ColWidths.Select(c => c * scale).ToArray(); // ── Disegno header a 2 livelli ──────────────────────────────── float rh = PdfTheme.RowHeight; DrawHeader(g, x0, y, cw, rh); y += rh * 2; // ── Tabella dati ────────────────────────────────────────────── DrawDataGrid(g, data, x0, y, cw, w); // ── Footer ──────────────────────────────────────────────────── PdfTheme.DrawFooter(g, w, h, 1, data.ShowBranding); return doc; } private static void DrawHeader(PdfGraphics g, float x0, float y, float[] cw, float rh) { var headerFont = PdfTheme.TableFont; var whiteBrush = PdfBrushes.White; var accentBrush = PdfTheme.AccentBlueBrush; var darkBlueBrush = new PdfSolidBrush(Color.FromArgb(255, 10, 56, 128)); // #0A3880 // ── Riga 1: Gruppi ──────────────────────────────────────────── // "Nome" (col 0) float cx = x0; DrawHeaderCell(g, cx, y, cw[0], rh, "", accentBrush, whiteBrush, headerFont); cx += cw[0]; // "SOTTOSTANTE" (cols 1-3) float sottostanteW = cw[1] + cw[2] + cw[3]; DrawHeaderCell(g, cx, y, sottostanteW, rh, "SOTTOSTANTE", accentBrush, whiteBrush, headerFont); cx += sottostanteW; // "BARRIERE" (cols 4-8) float barriereW = cw[4] + cw[5] + cw[6] + cw[7] + cw[8]; DrawHeaderCell(g, cx, y, barriereW, rh, "BARRIERE", accentBrush, whiteBrush, headerFont); cx += barriereW; // "DIVIDENDI" (cols 9-14) — blu scuro float dividendiW = cw[9] + cw[10] + cw[11] + cw[12] + cw[13] + cw[14]; DrawHeaderCell(g, cx, y, dividendiW, rh, "DIVIDENDI", darkBlueBrush, whiteBrush, headerFont); // ── Riga 2: Sottocolonne ────────────────────────────────────── cx = x0; for (int i = 0; i < cw.Length; i++) { var bg = i >= 9 ? darkBlueBrush : accentBrush; DrawHeaderCell(g, cx, y + rh, cw[i], rh, Col2Headers[i], bg, whiteBrush, headerFont); cx += cw[i]; } // ── Separatore verticale tra col 8 e col 9 ─────────────────── float sepX = x0 + cw.Take(9).Sum(); var sepPen = new PdfPen(Color.FromArgb(255, 100, 181, 246), 1.5f); // #64B5F6 g.DrawLine(sepPen, new PointF(sepX, y), new PointF(sepX, y + rh * 2)); } private static void DrawHeaderCell(PdfGraphics g, float x, float y, float w, float h, string text, PdfBrush bg, PdfBrush fg, PdfFont font) { g.DrawRectangle(bg, new RectangleF(x, y, w, h)); if (!string.IsNullOrEmpty(text)) { var fmt = new PdfStringFormat { Alignment = PdfTextAlignment.Center, LineAlignment = PdfVerticalAlignment.Middle, }; g.DrawString(text, font, fg, new RectangleF(x + 1, y, w - 2, h), fmt); } } private static void DrawDataGrid(PdfGraphics g, CertificateReportData data, float x0, float y, float[] cw, float pageW) { var grid = new PdfGrid(); grid.Style.CellPadding = new PdfPaddings(2, 2, 2, 2); grid.Style.Font = PdfTheme.TableFont; // 15 colonne grid.Columns.Add(15); for (int i = 0; i < 15; i++) grid.Columns[i].Width = cw[i]; var sottostanti = data.Info.Sottostanti; for (int i = 0; i < sottostanti.Count; i++) { var s = sottostanti[i]; var row = grid.Rows.Add(); row.Cells[0].Value = s.Nome; row.Cells[1].Value = s.Strike; row.Cells[2].Value = s.LastPrice; row.Cells[3].Value = s.Performance ?? "—"; row.Cells[4].Value = s.CapitalBarrier ?? "—"; row.Cells[5].Value = s.ULCapitalBarrierBuffer ?? "—"; row.Cells[6].Value = s.CouponBarrier ?? "—"; row.Cells[7].Value = s.ULCouponBarrierBuffer ?? "—"; row.Cells[8].Value = s.TriggerAutocall ?? "—"; row.Cells[9].Value = s.DividendExDate ?? "—"; row.Cells[10].Value = s.DividendPayDate ?? "—"; row.Cells[11].Value = s.DividendAmount ?? "—"; row.Cells[12].Value = s.DividendYield ?? "—"; row.Cells[13].Value = s.DividendFutAmount ?? "—"; row.Cells[14].Value = s.DividendFutYield ?? "—"; // Righe alternate if (i % 2 == 1) { for (int c = 0; c < 15; c++) row.Cells[c].Style.BackgroundBrush = PdfTheme.TableAltRowBrush; } // Colore performance (negativi rosso, positivi verde) foreach (int c in PerfCols) { var val = row.Cells[c].Value?.ToString(); if (string.IsNullOrEmpty(val) || val == "—") continue; // Le stringhe arrivano già formattate dalla SP, es. "-12,34%" bool isNegative = val.TrimStart().StartsWith('-'); row.Cells[c].Style.TextBrush = isNegative ? PdfTheme.NegativeRedBrush : PdfTheme.PositiveGreenBrush; } // Allineamento centrato per tutte le celle tranne Nome for (int c = 1; c < 15; c++) { row.Cells[c].StringFormat = new PdfStringFormat { Alignment = PdfTextAlignment.Center, LineAlignment = PdfVerticalAlignment.Middle, }; } } grid.Draw(g, new PointF(x0, y)); } } -
Step 2: Build
dotnet build CertReports.SyncfusionExpected: errore su
ReportOrchestrator— normale. -
Step 3: Commit
git add CertReports.Syncfusion/Services/Implementations/DividendSectionRenderer.cs git commit -m "feat: add DividendSectionRenderer with landscape two-level header table"
Task 7: Registrare DividendSectionRenderer in Program.cs
Files:
-
Modify:
Program.cs -
Step 1: Leggere
Program.csTrovare il blocco delle registrazioni renderer. Attualmente c'è:
builder.Services.AddScoped<ExpiredAnagraficaSectionRenderer>(); -
Step 2: Aggiungere la registrazione subito sotto
builder.Services.AddScoped<ExpiredAnagraficaSectionRenderer>(); builder.Services.AddScoped<DividendSectionRenderer>(); -
Step 3: Build
dotnet build CertReports.SyncfusionExpected: solo errore su
ReportOrchestrator— normale. -
Step 4: Commit
git add CertReports.Syncfusion/Program.cs git commit -m "feat: register DividendSectionRenderer in DI container"
Task 8: Aggiornare ReportOrchestrator
Files:
-
Modify:
Services/Implementations/ReportOrchestrator.cs -
Step 1: Leggere il file completo
Leggere l'intero file per avere contesto preciso prima di modificare.
-
Step 2: Aggiungere il campo e il parametro costruttore
Aggiungere dopo
_expiredAnagraficaRenderer:private readonly ExpiredAnagraficaSectionRenderer _expiredAnagraficaRenderer; private readonly DividendSectionRenderer _dividendRenderer;Nel costruttore aggiungere il parametro dopo
expiredAnagraficaRenderer:ExpiredAnagraficaSectionRenderer expiredAnagraficaRenderer, DividendSectionRenderer dividendRenderer)E l'assegnazione:
_expiredAnagraficaRenderer = expiredAnagraficaRenderer; _dividendRenderer = dividendRenderer; -
Step 3: Aggiornare la signature del metodo e le chiavi cache
Cambiare la firma:
public async Task<byte[]> GenerateReportAsync(string isin, bool showBranding = false, bool showDividend = false)Aggiornare le chiavi cache (le 4 combinazioni):
var dividendSuffix = showDividend ? ":dividend" : ""; var baseCacheKey = showBranding ? $"{isin}:branded{dividendSuffix}" : $"{isin}{dividendSuffix}"; var expiredCacheKey = showBranding ? $"{isin}:expired:branded{dividendSuffix}" : $"{isin}:expired{dividendSuffix}";Nota: questo produce le 4 chiavi:
{isin}— no branding, no dividend{isin}:branded— branding, no dividend{isin}:dividend— no branding, dividend{isin}:branded:dividend— branding + dividend- (+ varianti expired)
-
Step 4: Impostare
ShowDividendnelCertificateReportDataNel blocco di inizializzazione:
var reportData = new CertificateReportData { Info = await _dataService.GetCertificateInfoAsync(isin), Eventi = await _dataService.GetCertificateEventsAsync(isin), Scenario = await _dataService.GetScenarioAnalysisAsync(isin), ShowBranding = showBranding, ShowDividend = showDividend, }; -
Step 5: Inserire la sezione dividend nel flusso expired
Dopo
pdfSections.Add(_expiredAnagraficaRenderer.Render(reportData));, aggiungere:pdfSections.Add(_expiredAnagraficaRenderer.Render(reportData)); if (reportData.ShowDividend) { try { pdfSections.Add(_dividendRenderer.Render(reportData)); _logger.LogInformation("Sezione 'Dividend' generata per {Isin}", isin); } catch (Exception ex) { _logger.LogError(ex, "Errore nella sezione 'Dividend' per {Isin}", isin); throw; } } -
Step 6: Inserire la sezione dividend nel flusso attivo (attuale)
Il flusso attivo usa un
foreachsu_sectionRenderers.OrderBy(r => r.Order).AnagraficaSectionRendererhaOrder = 1(Sezione 1). Dobbiamo inserire la pagina dividend subito dopo Anagrafica.Sostituire il
foreachcon:foreach (var renderer in _sectionRenderers.OrderBy(r => r.Order)) { if (renderer.SectionName == "Scenario" && !isScenarioAllowed) { _logger.LogInformation("Sezione Scenario saltata per {Isin}", isin); continue; } try { pdfSections.Add(renderer.Render(reportData)); _logger.LogInformation("Sezione '{Section}' generata per {Isin}", renderer.SectionName, isin); } catch (Exception ex) { _logger.LogError(ex, "Errore nella sezione '{Section}' per {Isin}", renderer.SectionName, isin); throw; } // Inserire pagina dividend subito dopo Anagrafica (Sezione 1) if (renderer.SectionName == "Anagrafica" && reportData.ShowDividend) { try { pdfSections.Add(_dividendRenderer.Render(reportData)); _logger.LogInformation("Sezione 'Dividend' generata per {Isin}", isin); } catch (Exception ex) { _logger.LogError(ex, "Errore nella sezione 'Dividend' per {Isin}", isin); throw; } } } -
Step 7: Build
dotnet build CertReports.SyncfusionExpected: Build succeeded, 0 errors.
-
Step 8: Commit
git add CertReports.Syncfusion/Services/Implementations/ReportOrchestrator.cs git commit -m "feat: wire DividendSectionRenderer into orchestrator — both active and expired flows"
Task 9: Verifica manuale
Non esistono test automatici nel progetto. Verificare manualmente:
-
Step 1: Avviare l'applicazione
dotnet run --project CertReports.Syncfusion -
Step 2: Test baseline — comportamento invariato
Aprire nel browser:
https://localhost:{porta}/api/report/by-isin/{ISIN_VALIDO}Expected: report normale con tabella Sottostanti visibile in Sezione 1. Nessuna pagina landscape.
-
Step 3: Test con
?dividend=truehttps://localhost:{porta}/api/report/by-isin/{ISIN_VALIDO}?dividend=trueExpected:
- Sezione 1 (Anagrafica): nessuna tabella Sottostanti
- Dopo Sezione 1: pagina landscape con titolo "Sottostanti e Dividendi"
- Header a 2 livelli: "SOTTOSTANTE" (blu) | "BARRIERE" (blu) | "DIVIDENDI" (blu scuro)
- Separatore verticale tra colonna Trig.AC e Data Stacco
- Righe dati con colori performance su % Perf., Buf.Cap., Buf.CPN, Rend., Rend.Fut.
- Colonne dividendo vuote mostrano "—"
- Footer con numero pagina
-
Step 4: Test combinato
?dividend=true&branding=trueExpected: come sopra + footer con "Powered by Smart Roots" su tutte le pagine.
-
Step 5: Test su certificato expired
Usare un ISIN con
Stato != "Quotazione". Verificare lo stesso comportamento nei flusso expired. -
Step 6: Commit finale
git add -A git commit -m "feat: dividend section complete — landscape page with two-level header"
Note implementative
Gotcha Syncfusion v33 da rispettare
| Problema | Soluzione applicata nel piano |
|---|---|
Color(r,g,b) rimosso |
Color.FromArgb(255, r, g, b) usato ovunque |
grid.Draw() restituisce void |
Usato senza assegnazione |
RectangleF no named args |
new RectangleF(x, y, w, h) — no named parameters |
| Namespace conflict | Aggiungere using Syncfusion.Pdf; se necessario |
Dimensioni pagina landscape
A4 landscape in Syncfusion: Width ≈ 841pt, Height ≈ 595pt.
Con PageMargin = 30pt (verificare in PdfTheme), la larghezza utile w ≈ 781pt.
Le colonne vengono scalate proporzionalmente da 742pt a w → scale ≈ 1.05.
SectionName == "Anagrafica"
Il valore esatto confermato in AnagraficaSectionRenderer.cs è "Anagrafica" — la stringa di confronto nel Task 8 Step 6 è corretta.