From c3d71bdf7c7568812ea09f425d9d21206410a443 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Mon, 29 Jun 2026 21:00:42 +0100 Subject: [PATCH] refactor(templates): drop orphaned classic scaffolding and deprecated shims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These template types have no remaining consumers and were already slated for removal: the orphaned CoverLetterTemplate and WeeklyScheduleTemplate contracts, the deprecated-for-removal CV HeadlineRenderer/ContactRenderer/ BannerRenderer shims, the WeeklyScheduleTemplateV1 builtin, and the never-shipped classic invoice/proposal stub scaffolding (presets/builder/ spec) that only its own smoke tests exercised. The shipping invoice and proposal surface is the layered v2 preset stack. Remove those types and the two classic smoke tests. Repoint the invoice/ proposal package docs at the v2 stack, point DocumentTemplate's @param example at the v2 InvoiceDocumentSpec/ProposalDocumentSpec, and drop the two stability-ledger rows that tracked the now-removed types. Tests: ./mvnw verify javadoc:javadoc -pl . — 1621 tests, 0 failures, javadoc gate clean; examples and benchmarks compile against the new jar. --- docs/api-stability.md | 2 - .../templates/api/CoverLetterTemplate.java | 53 ---- .../templates/api/DocumentTemplate.java | 2 +- .../templates/api/WeeklyScheduleTemplate.java | 62 ---- .../builtins/WeeklyScheduleTemplateV1.java | 62 ---- .../cv/v2/components/BannerRenderer.java | 32 -- .../cv/v2/components/ContactRenderer.java | 32 -- .../cv/v2/components/HeadlineRenderer.java | 32 -- .../invoice/builder/InvoiceBuilder.java | 254 ---------------- .../invoice/builder/package-info.java | 6 - .../templates/invoice/package-info.java | 25 +- .../invoice/presets/ModernInvoice.java | 68 ----- .../invoice/presets/package-info.java | 6 - .../templates/invoice/spec/InvoiceSpec.java | 282 ------------------ .../templates/invoice/spec/package-info.java | 11 - .../proposal/builder/ProposalBuilder.java | 233 --------------- .../proposal/builder/package-info.java | 6 - .../templates/proposal/package-info.java | 22 +- .../proposal/presets/ModernProposal.java | 77 ----- .../proposal/presets/package-info.java | 6 - .../templates/proposal/spec/ProposalSpec.java | 231 -------------- .../templates/proposal/spec/package-info.java | 6 - .../presets/ModernInvoiceSmokeTest.java | 68 ----- .../presets/ModernProposalSmokeTest.java | 63 ---- 24 files changed, 11 insertions(+), 1630 deletions(-) delete mode 100644 src/main/java/com/demcha/compose/document/templates/api/CoverLetterTemplate.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/api/WeeklyScheduleTemplate.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/builtins/WeeklyScheduleTemplateV1.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/cv/v2/components/BannerRenderer.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/cv/v2/components/ContactRenderer.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/cv/v2/components/HeadlineRenderer.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/invoice/builder/InvoiceBuilder.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/invoice/builder/package-info.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/invoice/presets/ModernInvoice.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/invoice/presets/package-info.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/invoice/spec/InvoiceSpec.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/invoice/spec/package-info.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/proposal/builder/ProposalBuilder.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/proposal/builder/package-info.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/proposal/presets/ModernProposal.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/proposal/presets/package-info.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/proposal/spec/ProposalSpec.java delete mode 100644 src/main/java/com/demcha/compose/document/templates/proposal/spec/package-info.java delete mode 100644 src/test/java/com/demcha/compose/document/templates/invoice/presets/ModernInvoiceSmokeTest.java delete mode 100644 src/test/java/com/demcha/compose/document/templates/proposal/presets/ModernProposalSmokeTest.java diff --git a/docs/api-stability.md b/docs/api-stability.md index 4332454a3..225e81cf8 100644 --- a/docs/api-stability.md +++ b/docs/api-stability.md @@ -177,8 +177,6 @@ window starts, and its `Status` flips to `deprecated 1.x`. | Element | Tier now | Status | Why the 1.x shape is a compromise | 2.0 action | ADR | Issue | |---|---|---|---|---|---|---| | `DocumentSession.pageMargins(List)` / `PageMarginRule` | Stable | planned | Per-page margins resolve a block's content width by the page it *begins* on (the engine measures each block once, before pagination). A margin that changes the content width therefore does not re-wrap a block mid-flow across a page boundary. | Revisit a page-aware per-line/per-fragment width model so a block can re-wrap when it crosses a margin boundary, if demand warrants. | — | — | -| `templates.api.CoverLetterTemplate` | Stable | deprecated 1.9 | Orphan interface — nothing implements it; cover-letter presets implement the generic `DocumentTemplate` seam. | Remove; callers implement `DocumentTemplate`. | — | — | -| `templates.cv.v2.components.HeadlineRenderer` / `ContactRenderer` / `BannerRenderer` | Stable | deprecated 1.9 | Pre-widgets delegating shims superseded by the `Headline` / `ContactLine` / `SectionHeader` widgets; no callers. | Remove; use the widgets. | — | — | --- diff --git a/src/main/java/com/demcha/compose/document/templates/api/CoverLetterTemplate.java b/src/main/java/com/demcha/compose/document/templates/api/CoverLetterTemplate.java deleted file mode 100644 index 1e6abe515..000000000 --- a/src/main/java/com/demcha/compose/document/templates/api/CoverLetterTemplate.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.demcha.compose.document.templates.api; - -import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.templates.data.coverletter.CoverLetterDocumentSpec; - -/** - * Canonical compose contract for reusable cover-letter templates. - * - *

Responsibility: define one reusable cover-letter scene that writes - * semantic blocks into a caller-owned {@link DocumentSession}. Implementations - * are usually immutable value objects configured by a theme.

- * - * @deprecated since 1.9.0; removed in 2.0. No type implements this interface — - * the layered cover-letter presets implement the generic - * {@link DocumentTemplate} seam - * ({@code DocumentTemplate}) instead. - * Implement {@code DocumentTemplate}. - */ -@Deprecated(since = "1.9.0", forRemoval = true) -public interface CoverLetterTemplate { - - /** - * Stable public template identifier. - * - * @return unique template id used by registries and integrations - */ - String getTemplateId(); - - /** - * Human-readable display name. - * - * @return template display name - */ - String getTemplateName(); - - /** - * Optional human-readable description. - * - * @return template description, or an empty string when omitted - */ - default String getDescription() { - return ""; - } - - /** - * Composes a cover letter into a live document session. - * - * @param document active mutable document session receiving template nodes - * @param spec cover-letter document spec - * @throws NullPointerException if an implementation requires non-null inputs - */ - void compose(DocumentSession document, CoverLetterDocumentSpec spec); -} diff --git a/src/main/java/com/demcha/compose/document/templates/api/DocumentTemplate.java b/src/main/java/com/demcha/compose/document/templates/api/DocumentTemplate.java index 11d872d5b..a837112f6 100644 --- a/src/main/java/com/demcha/compose/document/templates/api/DocumentTemplate.java +++ b/src/main/java/com/demcha/compose/document/templates/api/DocumentTemplate.java @@ -26,7 +26,7 @@ * * * @param the spec type rendered by this template (e.g. {@code CvDocument}, - * {@code InvoiceSpec}, {@code ProposalSpec}) + * {@code InvoiceDocumentSpec}, {@code ProposalDocumentSpec}) */ public interface DocumentTemplate { diff --git a/src/main/java/com/demcha/compose/document/templates/api/WeeklyScheduleTemplate.java b/src/main/java/com/demcha/compose/document/templates/api/WeeklyScheduleTemplate.java deleted file mode 100644 index bc2ba03e9..000000000 --- a/src/main/java/com/demcha/compose/document/templates/api/WeeklyScheduleTemplate.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.demcha.compose.document.templates.api; - -import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.templates.data.schedule.WeeklyScheduleDocumentSpec; - -/** - * Canonical compose contract for reusable weekly schedule templates. - * - *

Responsibility: define one reusable planning/schedule scene that - * composes against a live {@link DocumentSession}.

- * - *
{@code
- * WeeklyScheduleTemplate template = new WeeklyScheduleTemplateV1();
- * WeeklyScheduleDocumentSpec schedule = WeeklyScheduleDocumentSpec.builder()
- *         .title("Engineering Roster")
- *         .weekLabel("Week Of 20 Apr - 26 Apr 2026")
- *         .day("mon", "Monday", "Release prep", "delivery")
- *         .category("delivery", "DELIVERY", new Color(0, 173, 76), new Color(0, 110, 49))
- *         .person("artem", "ARTEM", 10)
- *         .assignment("artem", "mon", "delivery", ScheduleSlot.of("09:00", "17:00"))
- *         .build();
- *
- * try (DocumentSession document = GraphCompose.document(Path.of("schedule.pdf")).create()) {
- *     template.compose(document, schedule);
- *     document.buildPdf();
- * }
- * }
- */ -public interface WeeklyScheduleTemplate { - - /** - * Stable public template identifier. - * - * @return unique template id used by registries and integrations - */ - String getTemplateId(); - - /** - * Human-readable display name. - * - * @return template display name - */ - String getTemplateName(); - - /** - * Optional human-readable description. - * - * @return template description, or an empty string when omitted - */ - default String getDescription() { - return ""; - } - - /** - * Composes a weekly schedule into a live document session. - * - * @param document active mutable document session receiving template nodes - * @param spec weekly schedule document spec - * @throws NullPointerException if an implementation requires non-null inputs - */ - void compose(DocumentSession document, WeeklyScheduleDocumentSpec spec); -} diff --git a/src/main/java/com/demcha/compose/document/templates/builtins/WeeklyScheduleTemplateV1.java b/src/main/java/com/demcha/compose/document/templates/builtins/WeeklyScheduleTemplateV1.java deleted file mode 100644 index b8bb05a78..000000000 --- a/src/main/java/com/demcha/compose/document/templates/builtins/WeeklyScheduleTemplateV1.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.demcha.compose.document.templates.builtins; - -import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.templates.api.WeeklyScheduleTemplate; -import com.demcha.compose.document.templates.data.schedule.WeeklyScheduleDocumentSpec; -import com.demcha.compose.document.templates.support.common.SessionTemplateComposeTarget; -import com.demcha.compose.document.templates.support.common.TemplateLifecycleLog; -import com.demcha.compose.document.templates.support.schedule.WeeklyScheduleTemplateComposer; -import com.demcha.compose.document.templates.theme.WeeklyScheduleTheme; - -import java.util.Objects; - -/** - * Canonical implementation of the weekly schedule template. - */ -public final class WeeklyScheduleTemplateV1 implements WeeklyScheduleTemplate { - private final WeeklyScheduleTemplateComposer composer; - - /** - * Creates the canonical weekly schedule template with the default theme. - */ - public WeeklyScheduleTemplateV1() { - this(null); - } - - /** - * Creates the canonical weekly schedule template with a custom theme. - * - * @param theme custom theme, or null for the default theme - */ - public WeeklyScheduleTemplateV1(WeeklyScheduleTheme theme) { - this.composer = new WeeklyScheduleTemplateComposer( - Objects.requireNonNullElseGet(theme, WeeklyScheduleTheme::defaultTheme)); - } - - @Override - public String getTemplateId() { - return "weekly-schedule-v1"; - } - - @Override - public String getTemplateName() { - return "Weekly Schedule V1"; - } - - @Override - public String getDescription() { - return "A reusable landscape weekly roster with aligned day bands, metric rows, and a schedule matrix."; - } - - @Override - public void compose(DocumentSession document, WeeklyScheduleDocumentSpec spec) { - long startNanos = TemplateLifecycleLog.start(getTemplateId(), spec); - try { - composer.compose(new SessionTemplateComposeTarget(document), spec); - TemplateLifecycleLog.success(getTemplateId(), spec, startNanos); - } catch (RuntimeException | Error ex) { - TemplateLifecycleLog.failure(getTemplateId(), spec, startNanos, ex); - throw ex; - } - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/BannerRenderer.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/BannerRenderer.java deleted file mode 100644 index 0d1c4beca..000000000 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/BannerRenderer.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.demcha.compose.document.templates.cv.v2.components; - -import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.templates.core.theme.BrandTheme; -import com.demcha.compose.document.templates.cv.v2.widgets.SectionHeader; - -/** - * @deprecated since 1.9.0; removed in 2.0. Use - * {@link com.demcha.compose.document.templates.cv.v2.widgets.SectionHeader#banner} - * instead — the widget groups the banner alongside its sibling - * variants ({@code underlined}, {@code flat}) so picking a section- - * title style becomes one choice in one place. Kept as a thin - * delegating shim so v2 code written before the widgets layer keeps - * compiling unchanged. - */ -@Deprecated(since = "1.9.0", forRemoval = true) -public final class BannerRenderer { - - private BannerRenderer() { - } - - /** - * @param section the section builder being populated - * @param title the section title text - * @param theme the active theme supplying palette, typography, and spacing - * @deprecated delegates to {@link SectionHeader#banner}. - */ - @Deprecated - public static void render(SectionBuilder section, String title, BrandTheme theme) { - SectionHeader.banner(section, title, theme); - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/ContactRenderer.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/ContactRenderer.java deleted file mode 100644 index 4e74b7a3f..000000000 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/ContactRenderer.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.demcha.compose.document.templates.cv.v2.components; - -import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.templates.cv.v2.data.CvIdentity; -import com.demcha.compose.document.templates.core.theme.BrandTheme; -import com.demcha.compose.document.templates.core.identity.ContactLine; - -/** - * @deprecated since 1.9.0; removed in 2.0. Use - * {@link com.demcha.compose.document.templates.core.identity.ContactLine#centered} - * instead — the widget gives you named centred/right-aligned - * variants plus a configurable field order. Kept as a thin - * delegating shim so v2 code written before the widgets layer keeps - * compiling unchanged. - */ -@Deprecated(since = "1.9.0", forRemoval = true) -public final class ContactRenderer { - - private ContactRenderer() { - } - - /** - * @param section the section builder being populated - * @param identity the contact identity supplying the field values - * @param theme the active theme supplying palette, typography, and spacing - * @deprecated delegates to {@link ContactLine#centered}. - */ - @Deprecated - public static void render(SectionBuilder section, CvIdentity identity, BrandTheme theme) { - ContactLine.centered(section, identity, theme); - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/HeadlineRenderer.java b/src/main/java/com/demcha/compose/document/templates/cv/v2/components/HeadlineRenderer.java deleted file mode 100644 index a25dc85a6..000000000 --- a/src/main/java/com/demcha/compose/document/templates/cv/v2/components/HeadlineRenderer.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.demcha.compose.document.templates.cv.v2.components; - -import com.demcha.compose.document.dsl.SectionBuilder; -import com.demcha.compose.document.templates.cv.v2.data.CvName; -import com.demcha.compose.document.templates.core.theme.BrandTheme; -import com.demcha.compose.document.templates.core.identity.Headline; - -/** - * @deprecated since 1.9.0; removed in 2.0. Use - * {@link com.demcha.compose.document.templates.core.identity.Headline#spacedCentered} - * instead — the widget gives you a named API plus alignment + - * spaced-caps variants, while this class only ever did the - * centred-spaced-caps form. Kept as a thin delegating shim so v2 - * code written before the widgets layer keeps compiling unchanged. - */ -@Deprecated(since = "1.9.0", forRemoval = true) -public final class HeadlineRenderer { - - private HeadlineRenderer() { - } - - /** - * @param section the section builder being populated - * @param name the candidate name to render as the headline - * @param theme the active theme supplying palette, typography, and spacing - * @deprecated delegates to {@link Headline#spacedCentered}. - */ - @Deprecated - public static void render(SectionBuilder section, CvName name, BrandTheme theme) { - Headline.spacedCentered(section, name.full(), theme); - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/invoice/builder/InvoiceBuilder.java b/src/main/java/com/demcha/compose/document/templates/invoice/builder/InvoiceBuilder.java deleted file mode 100644 index 78c75895f..000000000 --- a/src/main/java/com/demcha/compose/document/templates/invoice/builder/InvoiceBuilder.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.demcha.compose.document.templates.invoice.builder; - -import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.node.ContainerNode; -import com.demcha.compose.document.node.DocumentNode; -import com.demcha.compose.document.node.ParagraphNode; -import com.demcha.compose.document.node.TextAlign; -import com.demcha.compose.document.style.DocumentInsets; -import com.demcha.compose.document.style.DocumentTextStyle; -import com.demcha.compose.document.templates.api.DocumentTemplate; -import com.demcha.compose.document.templates.core.text.MarkdownText; -import com.demcha.compose.document.templates.invoice.spec.InvoiceSpec; -import com.demcha.compose.document.templates.themes.Spacing; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Fluent builder for assembling a Templates v2 invoice - * {@link DocumentTemplate}. - * - *

Produces a single-column invoice with the following sections: - * invoice number heading, issue/due date row, From / Bill-To parties - * side by side, a line-item table (rendered as paragraphs in this - * minimal v2 surface — a fully styled table layout will land in a - * follow-up that adds Templates v2 table styles), summary rows, and - * footer notes.

- * - *

This is a deliberately minimal invoice surface for v1.6: the - * legacy {@code InvoiceTemplateV2} continues to provide the cinematic - * presentation, and the v2 surface here covers the canonical data - * shape and composition seam that a custom preset can build on. The - * full visual feature parity (cinematic chrome, line-item table - * styling, party panels) lands in a follow-up release.

- */ -public final class InvoiceBuilder { - - private String id; - private String displayName; - private DocumentTextStyle headingStyle; - private DocumentTextStyle bodyStyle; - private Spacing spacing; - - private InvoiceBuilder() { - } - - /** - * Returns a fresh builder. - * - * @return new builder - */ - public static InvoiceBuilder builder() { - return new InvoiceBuilder(); - } - - /** - * Sets the stable identifier exposed via - * {@link DocumentTemplate#id()}. - * - * @param value non-null identifier - * @return this builder - */ - public InvoiceBuilder id(String value) { - this.id = Objects.requireNonNull(value, "id"); - return this; - } - - /** - * Sets the human-readable display name. - * - * @param value non-null display name - * @return this builder - */ - public InvoiceBuilder displayName(String value) { - this.displayName = Objects.requireNonNull(value, "displayName"); - return this; - } - - /** - * Sets the text style applied to invoice number / section - * headings. - * - * @param value non-null heading text style - * @return this builder - */ - public InvoiceBuilder headingStyle(DocumentTextStyle value) { - this.headingStyle = Objects.requireNonNull(value, "headingStyle"); - return this; - } - - /** - * Sets the text style applied to body paragraphs (parties, - * line items, summary rows, notes). - * - * @param value non-null body text style - * @return this builder - */ - public InvoiceBuilder bodyStyle(DocumentTextStyle value) { - this.bodyStyle = Objects.requireNonNull(value, "bodyStyle"); - return this; - } - - /** - * Sets the active spacing tokens. - * - * @param value non-null spacing tokens - * @return this builder - */ - public InvoiceBuilder spacing(Spacing value) { - this.spacing = Objects.requireNonNull(value, "spacing"); - return this; - } - - /** - * Validates configuration and returns the assembled - * {@link DocumentTemplate}. - * - * @return ready-to-use template instance - * @throws NullPointerException if any required setter has not been - * called - */ - public DocumentTemplate build() { - Objects.requireNonNull(id, "id"); - Objects.requireNonNull(displayName, "displayName"); - Objects.requireNonNull(headingStyle, "headingStyle"); - Objects.requireNonNull(bodyStyle, "bodyStyle"); - Objects.requireNonNull(spacing, "spacing"); - - final String capturedId = id; - final String capturedDisplay = displayName; - final DocumentTextStyle capturedHeading = headingStyle; - final DocumentTextStyle capturedBody = bodyStyle; - final Spacing capturedSpacing = spacing; - - return new DocumentTemplate() { - @Override - public String id() { - return capturedId; - } - - @Override - public String displayName() { - return capturedDisplay; - } - - @Override - public void compose(DocumentSession session, InvoiceSpec spec) { - Objects.requireNonNull(session, "session"); - Objects.requireNonNull(spec, "spec"); - List children = new ArrayList<>(); - children.add(headingRow("Invoice " + spec.invoiceNumber())); - children.add(bodyParagraph("Issued: " + spec.issueDate() - + (spec.dueDate().isBlank() ? "" : " Due: " + spec.dueDate()))); - children.add(partyBlock("From", spec.fromParty())); - children.add(partyBlock("Bill To", spec.billToParty())); - children.add(lineItemsBlock(spec.lineItems())); - if (!spec.summaryRows().isEmpty()) { - children.add(summaryBlock(spec.summaryRows())); - } - for (String note : spec.notes()) { - children.add(bodyParagraph(note)); - } - session.add(new ContainerNode( - "invoice." + capturedId, - children, - capturedSpacing.moduleGap(), - DocumentInsets.zero(), - DocumentInsets.zero(), - null, null, null, null)); - } - - private ParagraphNode headingRow(String text) { - return new ParagraphNode( - "invoice.heading", "", - MarkdownText.parse(text, capturedHeading), - capturedHeading, TextAlign.LEFT, - capturedSpacing.lineSpacing(), "", null, null, null, - DocumentInsets.zero(), - new DocumentInsets(0, 0, capturedSpacing.sectionTitleBelow(), 0), - null); - } - - private ParagraphNode bodyParagraph(String text) { - return new ParagraphNode( - "invoice.line", "", - MarkdownText.parse(text, capturedBody), - capturedBody, TextAlign.LEFT, - capturedSpacing.lineSpacing(), "", null, null, null, - DocumentInsets.zero(), DocumentInsets.zero(), null); - } - - private DocumentNode partyBlock(String label, InvoiceSpec.Party party) { - List rows = new ArrayList<>(); - rows.add(bodyParagraph("**" + label + "**")); - rows.add(bodyParagraph(party.name())); - for (String line : party.addressLines()) { - rows.add(bodyParagraph(line)); - } - if (!party.email().isBlank()) { - rows.add(bodyParagraph(party.email())); - } - if (!party.phone().isBlank()) { - rows.add(bodyParagraph(party.phone())); - } - if (!party.taxId().isBlank()) { - rows.add(bodyParagraph("Tax ID: " + party.taxId())); - } - return new ContainerNode( - "invoice." + label.toLowerCase().replace(' ', '_'), - rows, - capturedSpacing.lineSpacing(), - DocumentInsets.zero(), DocumentInsets.zero(), - null, null, null, null); - } - - private DocumentNode lineItemsBlock(List items) { - List rows = new ArrayList<>(); - rows.add(bodyParagraph("**Description | Qty | Unit | Amount**")); - for (InvoiceSpec.LineItem item : items) { - String row = String.format("%s | %s | %s | %s", - item.description(), item.quantity(), - item.unitPrice(), item.amount()); - rows.add(bodyParagraph(row)); - if (!item.details().isBlank()) { - rows.add(bodyParagraph(" " + item.details())); - } - } - return new ContainerNode( - "invoice.lineItems", - rows, - capturedSpacing.listItemSpacing(), - DocumentInsets.zero(), DocumentInsets.zero(), - null, null, null, null); - } - - private DocumentNode summaryBlock(List rows) { - List nodes = new ArrayList<>(); - for (InvoiceSpec.SummaryRow row : rows) { - String text = row.isTotal() - ? "**" + row.label() + ": " + row.value() + "**" - : row.label() + ": " + row.value(); - nodes.add(bodyParagraph(text)); - } - return new ContainerNode( - "invoice.summary", - nodes, - capturedSpacing.lineSpacing(), - DocumentInsets.zero(), DocumentInsets.zero(), - null, null, null, null); - } - }; - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/invoice/builder/package-info.java b/src/main/java/com/demcha/compose/document/templates/invoice/builder/package-info.java deleted file mode 100644 index 11a8854bb..000000000 --- a/src/main/java/com/demcha/compose/document/templates/invoice/builder/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Templates v2 invoice builder. - * - * @since 1.6.0 - */ -package com.demcha.compose.document.templates.invoice.builder; diff --git a/src/main/java/com/demcha/compose/document/templates/invoice/package-info.java b/src/main/java/com/demcha/compose/document/templates/invoice/package-info.java index 15d717b36..3fd32e2a1 100644 --- a/src/main/java/com/demcha/compose/document/templates/invoice/package-info.java +++ b/src/main/java/com/demcha/compose/document/templates/invoice/package-info.java @@ -1,25 +1,10 @@ /** - * Templates v2 invoice domain — layouts, presets, builder, and spec data types. + * Invoice template family. * - *

This package is the home of all invoice templates in the v2 - * architecture. Sub-packages partition the domain by concern:

- * - *
    - *
  • {@code invoice.layouts} — slot caркасы (Standard, - * DetailedLineItems, SummaryAtTop).
  • - *
  • {@code invoice.presets} — flat copy-and-tweak preset classes - * (ModernInvoice, ClassicInvoice). Presets accept a - * {@code TableStyle} parameter so the line-item table presentation - * is interchangeable.
  • - *
  • {@code invoice.builder} — {@code InvoiceBuilder} for users - * composing their own preset.
  • - *
  • {@code invoice.spec} — data records ({@code InvoiceSpec}, - * {@code InvoiceHeader}, {@code LineItem}) describing the user's - * invoice content.
  • - *
- * - *

Sub-packages will be populated during Phase F of the Templates v2 - * migration.

+ *

The shipping invoice surface is the layered preset stack under + * {@code invoice.v2}, built on the shared {@code templates.core} layer and a + * {@code BrandTheme}. {@code invoice.v2.presets.ModernInvoice} is the + * reference preset.

* * @since 1.6.0 */ diff --git a/src/main/java/com/demcha/compose/document/templates/invoice/presets/ModernInvoice.java b/src/main/java/com/demcha/compose/document/templates/invoice/presets/ModernInvoice.java deleted file mode 100644 index d879175ab..000000000 --- a/src/main/java/com/demcha/compose/document/templates/invoice/presets/ModernInvoice.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.demcha.compose.document.templates.invoice.presets; - -import com.demcha.compose.document.style.DocumentColor; -import com.demcha.compose.document.style.DocumentTextDecoration; -import com.demcha.compose.document.style.DocumentTextStyle; -import com.demcha.compose.document.templates.api.DocumentTemplate; -import com.demcha.compose.document.templates.invoice.builder.InvoiceBuilder; -import com.demcha.compose.document.templates.invoice.spec.InvoiceSpec; -import com.demcha.compose.document.templates.themes.Spacing; -import com.demcha.compose.document.theme.BusinessTheme; -import com.demcha.compose.font.FontName; - -/** - * Templates v2 "Modern Invoice" preset — minimal v2 invoice surface. - * - *

Provides a clean, single-column invoice rendering through the new - * {@link InvoiceBuilder} pipeline. The legacy {@code InvoiceTemplateV2} - * continues to ship the cinematic invoice experience; this v2 preset - * is the canonical seam for builder-driven custom invoices.

- */ -public final class ModernInvoice { - - /** - * Stable template identifier. - */ - public static final String ID = "modern-invoice"; - - /** - * Human-readable display name. - */ - public static final String DISPLAY_NAME = "Modern Invoice"; - - private ModernInvoice() { - } - - /** - * Builds a fresh {@code Modern Invoice} template configured for - * the given business theme. - * - * @param theme active business theme - * @return ready-to-use template - * @throws NullPointerException if {@code theme} is null - */ - public static DocumentTemplate create(BusinessTheme theme) { - Spacing spacing = Spacing.comfortable(); - - DocumentTextStyle headingStyle = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) - .size(20.0) - .decoration(DocumentTextDecoration.BOLD) - .color(DocumentColor.rgb(41, 128, 185)) - .build(); - - DocumentTextStyle bodyStyle = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA) - .size(10.0) - .color(DocumentColor.rgb(40, 50, 70)) - .build(); - - return InvoiceBuilder.builder() - .id(ID) - .displayName(DISPLAY_NAME) - .headingStyle(headingStyle) - .bodyStyle(bodyStyle) - .spacing(spacing) - .build(); - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/invoice/presets/package-info.java b/src/main/java/com/demcha/compose/document/templates/invoice/presets/package-info.java deleted file mode 100644 index 14dea7683..000000000 --- a/src/main/java/com/demcha/compose/document/templates/invoice/presets/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Templates v2 invoice preset classes. - * - * @since 1.6.0 - */ -package com.demcha.compose.document.templates.invoice.presets; diff --git a/src/main/java/com/demcha/compose/document/templates/invoice/spec/InvoiceSpec.java b/src/main/java/com/demcha/compose/document/templates/invoice/spec/InvoiceSpec.java deleted file mode 100644 index 866fc594b..000000000 --- a/src/main/java/com/demcha/compose/document/templates/invoice/spec/InvoiceSpec.java +++ /dev/null @@ -1,282 +0,0 @@ -package com.demcha.compose.document.templates.invoice.spec; - -import java.util.List; -import java.util.Objects; - -/** - * User-facing data record for an invoice in Templates v2. - * - *

Simplified compared to the legacy {@code InvoiceData} — - * captures the essentials (header info, parties, line items, summary - * rows, footer notes) without the cinematic-presentation flags. Each - * field is immutable and validates at construction.

- * - * @param invoiceNumber stable invoice identifier (required) - * @param issueDate human-readable issue date (required) - * @param dueDate human-readable due date (may be empty) - * @param fromParty billing party (required) - * @param billToParty recipient party (required) - * @param lineItems ordered line items (must not be empty) - * @param summaryRows subtotal / tax / total rows in source order - * @param notes optional footer notes - */ -public record InvoiceSpec( - String invoiceNumber, - String issueDate, - String dueDate, - Party fromParty, - Party billToParty, - List lineItems, - List summaryRows, - List notes) { - - /** - * Compact constructor that defensively copies lists and - * normalises null strings. - * - * @throws NullPointerException if a required field is null - * @throws IllegalArgumentException if invoiceNumber, issueDate, or - * lineItems are blank/empty - */ - public InvoiceSpec { - Objects.requireNonNull(invoiceNumber, "invoiceNumber"); - Objects.requireNonNull(issueDate, "issueDate"); - Objects.requireNonNull(fromParty, "fromParty"); - Objects.requireNonNull(billToParty, "billToParty"); - Objects.requireNonNull(lineItems, "lineItems"); - if (invoiceNumber.isBlank()) { - throw new IllegalArgumentException("invoiceNumber must not be blank"); - } - if (issueDate.isBlank()) { - throw new IllegalArgumentException("issueDate must not be blank"); - } - if (lineItems.isEmpty()) { - throw new IllegalArgumentException("lineItems must not be empty"); - } - dueDate = dueDate == null ? "" : dueDate; - lineItems = List.copyOf(lineItems); - summaryRows = summaryRows == null ? List.of() : List.copyOf(summaryRows); - notes = notes == null ? List.of() : List.copyOf(notes); - } - - /** - * One billing party (sender or recipient). - * - * @param name legal / display name - * @param addressLines address lines in source order - * @param email contact email - * @param phone contact phone - * @param taxId tax identifier (may be empty) - */ - public record Party(String name, List addressLines, - String email, String phone, String taxId) { - - /** - * Compact constructor that normalises null strings and - * defensively copies the address list. - * - * @throws NullPointerException if {@code name} is null - * @throws IllegalArgumentException if {@code name} is blank - */ - public Party { - Objects.requireNonNull(name, "name"); - if (name.isBlank()) { - throw new IllegalArgumentException("name must not be blank"); - } - addressLines = addressLines == null ? List.of() : List.copyOf(addressLines); - email = email == null ? "" : email; - phone = phone == null ? "" : phone; - taxId = taxId == null ? "" : taxId; - } - } - - /** - * One line item in the invoice table. - * - * @param description short description column - * @param details extended description column (may be empty) - * @param quantity numeric quantity column - * @param unitPrice per-unit price column - * @param amount total amount column - */ - public record LineItem(String description, String details, - String quantity, String unitPrice, String amount) { - - /** - * Compact constructor that normalises null strings. - */ - public LineItem { - description = description == null ? "" : description; - details = details == null ? "" : details; - quantity = quantity == null ? "" : quantity; - unitPrice = unitPrice == null ? "" : unitPrice; - amount = amount == null ? "" : amount; - } - } - - /** - * One summary row (subtotal, tax, total, etc.) appearing beneath - * the line item table. - * - * @param label row label - * @param value row value - * @param isTotal whether this row is the headline total (rendered - * emphasised by the preset) - */ - public record SummaryRow(String label, String value, boolean isTotal) { - - /** - * Compact constructor that normalises null strings. - */ - public SummaryRow { - label = label == null ? "" : label; - value = value == null ? "" : value; - } - - /** - * Convenience factory for a non-total row. - * - * @param label row label - * @param value row value - * @return summary row with isTotal = false - */ - public static SummaryRow of(String label, String value) { - return new SummaryRow(label, value, false); - } - - /** - * Convenience factory for the headline total row. - * - * @param label row label - * @param value row value - * @return summary row with isTotal = true - */ - public static SummaryRow total(String label, String value) { - return new SummaryRow(label, value, true); - } - } - - /** - * Returns a fluent builder. - * - * @return new builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Mutable builder for {@link InvoiceSpec}. - */ - public static final class Builder { - private String invoiceNumber; - private String issueDate; - private String dueDate = ""; - private Party fromParty; - private Party billToParty; - private final java.util.List lineItems = new java.util.ArrayList<>(); - private final java.util.List summaryRows = new java.util.ArrayList<>(); - private final java.util.List notes = new java.util.ArrayList<>(); - - private Builder() { - } - - /** - * Sets the invoice number. - * - * @param value invoice number - * @return this builder - */ - public Builder invoiceNumber(String value) { - this.invoiceNumber = value; - return this; - } - - /** - * Sets the issue date. - * - * @param value issue date - * @return this builder - */ - public Builder issueDate(String value) { - this.issueDate = value; - return this; - } - - /** - * Sets the due date. - * - * @param value due date - * @return this builder - */ - public Builder dueDate(String value) { - this.dueDate = value == null ? "" : value; - return this; - } - - /** - * Sets the sender party. - * - * @param value sender party - * @return this builder - */ - public Builder fromParty(Party value) { - this.fromParty = value; - return this; - } - - /** - * Sets the recipient party. - * - * @param value recipient party - * @return this builder - */ - public Builder billToParty(Party value) { - this.billToParty = value; - return this; - } - - /** - * Adds a line item. - * - * @param item line item - * @return this builder - */ - public Builder lineItem(LineItem item) { - this.lineItems.add(item); - return this; - } - - /** - * Adds a summary row. - * - * @param row summary row - * @return this builder - */ - public Builder summaryRow(SummaryRow row) { - this.summaryRows.add(row); - return this; - } - - /** - * Adds a footer note. - * - * @param note footer note - * @return this builder - */ - public Builder note(String note) { - if (note != null) this.notes.add(note); - return this; - } - - /** - * Builds an immutable {@link InvoiceSpec}. - * - * @return new invoice spec - */ - public InvoiceSpec build() { - return new InvoiceSpec(invoiceNumber, issueDate, dueDate, - fromParty, billToParty, lineItems, summaryRows, notes); - } - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/invoice/spec/package-info.java b/src/main/java/com/demcha/compose/document/templates/invoice/spec/package-info.java deleted file mode 100644 index da5da5324..000000000 --- a/src/main/java/com/demcha/compose/document/templates/invoice/spec/package-info.java +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Templates v2 invoice specification records. - * - *

Holds the immutable records used by the v2 invoice pipeline: - * {@link com.demcha.compose.document.templates.invoice.spec.InvoiceSpec} - * with nested {@code Party}, {@code LineItem}, and {@code SummaryRow} - * value records.

- * - * @since 1.6.0 - */ -package com.demcha.compose.document.templates.invoice.spec; diff --git a/src/main/java/com/demcha/compose/document/templates/proposal/builder/ProposalBuilder.java b/src/main/java/com/demcha/compose/document/templates/proposal/builder/ProposalBuilder.java deleted file mode 100644 index eff8d3249..000000000 --- a/src/main/java/com/demcha/compose/document/templates/proposal/builder/ProposalBuilder.java +++ /dev/null @@ -1,233 +0,0 @@ -package com.demcha.compose.document.templates.proposal.builder; - -import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.node.ContainerNode; -import com.demcha.compose.document.node.DocumentNode; -import com.demcha.compose.document.node.ParagraphNode; -import com.demcha.compose.document.node.TextAlign; -import com.demcha.compose.document.style.DocumentInsets; -import com.demcha.compose.document.style.DocumentTextStyle; -import com.demcha.compose.document.templates.api.DocumentTemplate; -import com.demcha.compose.document.templates.core.text.MarkdownText; -import com.demcha.compose.document.templates.proposal.spec.ProposalSpec; -import com.demcha.compose.document.templates.themes.Spacing; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -/** - * Fluent builder for assembling a Templates v2 proposal - * {@link DocumentTemplate}. - * - *

Produces a single-column proposal with the following sections: - * title, From / To parties, content sections (heading + body, in - * source order), pricing summary, footer note. Markdown markers - * ({@code **bold**}, {@code *italic*}) in body text are routed - * through {@link MarkdownText}.

- * - *

Like {@link com.demcha.compose.document.templates.invoice.builder.InvoiceBuilder}, - * this is the minimal v2 proposal surface — the legacy - * {@code ProposalTemplateV2} continues to ship the cinematic - * presentation. Visual feature parity for the v2 proposal lands in a - * follow-up release.

- */ -public final class ProposalBuilder { - - private String id; - private String displayName; - private DocumentTextStyle titleStyle; - private DocumentTextStyle headingStyle; - private DocumentTextStyle bodyStyle; - private Spacing spacing; - - private ProposalBuilder() { - } - - /** - * Returns a fresh builder. - * - * @return new builder - */ - public static ProposalBuilder builder() { - return new ProposalBuilder(); - } - - /** - * Sets the stable identifier exposed via - * {@link DocumentTemplate#id()}. - * - * @param value non-null identifier - * @return this builder - */ - public ProposalBuilder id(String value) { - this.id = Objects.requireNonNull(value, "id"); - return this; - } - - /** - * Sets the human-readable display name. - * - * @param value non-null display name - * @return this builder - */ - public ProposalBuilder displayName(String value) { - this.displayName = Objects.requireNonNull(value, "displayName"); - return this; - } - - /** - * Sets the text style applied to the proposal title. - * - * @param value non-null title text style - * @return this builder - */ - public ProposalBuilder titleStyle(DocumentTextStyle value) { - this.titleStyle = Objects.requireNonNull(value, "titleStyle"); - return this; - } - - /** - * Sets the text style applied to section headings. - * - * @param value non-null heading text style - * @return this builder - */ - public ProposalBuilder headingStyle(DocumentTextStyle value) { - this.headingStyle = Objects.requireNonNull(value, "headingStyle"); - return this; - } - - /** - * Sets the text style applied to body paragraphs (parties, - * section bodies, pricing rows, notes). - * - * @param value non-null body text style - * @return this builder - */ - public ProposalBuilder bodyStyle(DocumentTextStyle value) { - this.bodyStyle = Objects.requireNonNull(value, "bodyStyle"); - return this; - } - - /** - * Sets the active spacing tokens. - * - * @param value non-null spacing tokens - * @return this builder - */ - public ProposalBuilder spacing(Spacing value) { - this.spacing = Objects.requireNonNull(value, "spacing"); - return this; - } - - /** - * Validates configuration and returns the assembled - * {@link DocumentTemplate}. - * - * @return ready-to-use template instance - * @throws NullPointerException if any required setter has not been - * called - */ - public DocumentTemplate build() { - Objects.requireNonNull(id, "id"); - Objects.requireNonNull(displayName, "displayName"); - Objects.requireNonNull(titleStyle, "titleStyle"); - Objects.requireNonNull(headingStyle, "headingStyle"); - Objects.requireNonNull(bodyStyle, "bodyStyle"); - Objects.requireNonNull(spacing, "spacing"); - - final String capturedId = id; - final String capturedDisplay = displayName; - final DocumentTextStyle capturedTitle = titleStyle; - final DocumentTextStyle capturedHeading = headingStyle; - final DocumentTextStyle capturedBody = bodyStyle; - final Spacing capturedSpacing = spacing; - - return new DocumentTemplate() { - @Override - public String id() { - return capturedId; - } - - @Override - public String displayName() { - return capturedDisplay; - } - - @Override - public void compose(DocumentSession session, ProposalSpec spec) { - Objects.requireNonNull(session, "session"); - Objects.requireNonNull(spec, "spec"); - List children = new ArrayList<>(); - children.add(titleParagraph(spec.title())); - children.add(partyLine("From: " + spec.fromParty().name())); - children.add(partyLine("To: " + spec.toParty().name())); - for (ProposalSpec.Section section : spec.sections()) { - if (!section.heading().isBlank()) { - children.add(headingParagraph(section.heading())); - } - if (!section.body().isBlank()) { - children.add(bodyParagraph(section.body())); - } - } - if (!spec.pricingRows().isEmpty()) { - children.add(headingParagraph("Pricing")); - for (ProposalSpec.PricingRow row : spec.pricingRows()) { - String text = row.isHeadline() - ? "**" + row.label() + ": " + row.value() + "**" - : row.label() + ": " + row.value(); - children.add(bodyParagraph(text)); - } - } - if (!spec.footerNote().isBlank()) { - children.add(bodyParagraph(spec.footerNote())); - } - session.add(new ContainerNode( - "proposal." + capturedId, - children, - capturedSpacing.moduleGap(), - DocumentInsets.zero(), - DocumentInsets.zero(), - null, null, null, null)); - } - - private ParagraphNode titleParagraph(String text) { - return new ParagraphNode( - "proposal.title", "", - MarkdownText.parse(text, capturedTitle), - capturedTitle, TextAlign.LEFT, - capturedSpacing.lineSpacing(), "", null, null, null, - DocumentInsets.zero(), - new DocumentInsets(0, 0, capturedSpacing.sectionTitleBelow(), 0), - null); - } - - private ParagraphNode headingParagraph(String text) { - return new ParagraphNode( - "proposal.heading", "", - MarkdownText.parse(text, capturedHeading), - capturedHeading, TextAlign.LEFT, - capturedSpacing.lineSpacing(), "", null, null, null, - DocumentInsets.zero(), - new DocumentInsets( - capturedSpacing.sectionTitleAbove(), 0, - capturedSpacing.sectionTitleBelow(), 0), - null); - } - - private ParagraphNode bodyParagraph(String text) { - return new ParagraphNode( - "proposal.line", "", - MarkdownText.parse(text, capturedBody), - capturedBody, TextAlign.LEFT, - capturedSpacing.lineSpacing(), "", null, null, null, - DocumentInsets.zero(), DocumentInsets.zero(), null); - } - - private ParagraphNode partyLine(String text) { - return bodyParagraph(text); - } - }; - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/proposal/builder/package-info.java b/src/main/java/com/demcha/compose/document/templates/proposal/builder/package-info.java deleted file mode 100644 index c6068c2c5..000000000 --- a/src/main/java/com/demcha/compose/document/templates/proposal/builder/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Templates v2 proposal builder. - * - * @since 1.6.0 - */ -package com.demcha.compose.document.templates.proposal.builder; diff --git a/src/main/java/com/demcha/compose/document/templates/proposal/package-info.java b/src/main/java/com/demcha/compose/document/templates/proposal/package-info.java index 047e37899..8f61d2ce9 100644 --- a/src/main/java/com/demcha/compose/document/templates/proposal/package-info.java +++ b/src/main/java/com/demcha/compose/document/templates/proposal/package-info.java @@ -1,22 +1,10 @@ /** - * Templates v2 proposal domain — layouts, presets, builder, and spec data types. + * Proposal / statement-of-work template family. * - *

This package is the home of all proposal / statement-of-work - * templates in the v2 architecture. Sub-packages partition the domain - * by concern:

- * - *
    - *
  • {@code proposal.layouts} — slot caркасы.
  • - *
  • {@code proposal.presets} — flat copy-and-tweak preset classes - * (ModernProposal, MinimalProposal).
  • - *
  • {@code proposal.builder} — {@code ProposalBuilder} for users - * composing their own preset.
  • - *
  • {@code proposal.spec} — data records describing the proposal - * content.
  • - *
- * - *

Sub-packages will be populated during Phase F of the Templates v2 - * migration.

+ *

The shipping proposal surface is the layered preset stack under + * {@code proposal.v2}, built on the shared {@code templates.core} layer and a + * {@code BrandTheme}. {@code proposal.v2.presets.ModernProposal} is the + * reference preset.

* * @since 1.6.0 */ diff --git a/src/main/java/com/demcha/compose/document/templates/proposal/presets/ModernProposal.java b/src/main/java/com/demcha/compose/document/templates/proposal/presets/ModernProposal.java deleted file mode 100644 index 91e077bf2..000000000 --- a/src/main/java/com/demcha/compose/document/templates/proposal/presets/ModernProposal.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.demcha.compose.document.templates.proposal.presets; - -import com.demcha.compose.document.style.DocumentColor; -import com.demcha.compose.document.style.DocumentTextDecoration; -import com.demcha.compose.document.style.DocumentTextStyle; -import com.demcha.compose.document.templates.api.DocumentTemplate; -import com.demcha.compose.document.templates.proposal.builder.ProposalBuilder; -import com.demcha.compose.document.templates.proposal.spec.ProposalSpec; -import com.demcha.compose.document.templates.themes.Spacing; -import com.demcha.compose.document.theme.BusinessTheme; -import com.demcha.compose.font.FontName; - -/** - * Templates v2 "Modern Proposal" preset — minimal v2 proposal surface. - * - *

Provides a clean, single-column proposal rendering through the - * new {@link ProposalBuilder} pipeline. The legacy - * {@code ProposalTemplateV2} continues to ship the cinematic proposal - * experience; this v2 preset is the canonical seam for builder-driven - * custom proposals.

- */ -public final class ModernProposal { - - /** - * Stable template identifier. - */ - public static final String ID = "modern-proposal"; - - /** - * Human-readable display name. - */ - public static final String DISPLAY_NAME = "Modern Proposal"; - - private ModernProposal() { - } - - /** - * Builds a fresh {@code Modern Proposal} template configured for - * the given business theme. - * - * @param theme active business theme - * @return ready-to-use template - * @throws NullPointerException if {@code theme} is null - */ - public static DocumentTemplate create(BusinessTheme theme) { - Spacing spacing = Spacing.airy(); - - DocumentTextStyle titleStyle = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) - .size(24.0) - .decoration(DocumentTextDecoration.BOLD) - .color(DocumentColor.rgb(41, 128, 185)) - .build(); - - DocumentTextStyle headingStyle = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA_BOLD) - .size(14.0) - .decoration(DocumentTextDecoration.BOLD) - .color(DocumentColor.rgb(44, 62, 80)) - .build(); - - DocumentTextStyle bodyStyle = DocumentTextStyle.builder() - .fontName(FontName.HELVETICA) - .size(10.5) - .color(DocumentColor.rgb(40, 50, 70)) - .build(); - - return ProposalBuilder.builder() - .id(ID) - .displayName(DISPLAY_NAME) - .titleStyle(titleStyle) - .headingStyle(headingStyle) - .bodyStyle(bodyStyle) - .spacing(spacing) - .build(); - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/proposal/presets/package-info.java b/src/main/java/com/demcha/compose/document/templates/proposal/presets/package-info.java deleted file mode 100644 index e8c871c6e..000000000 --- a/src/main/java/com/demcha/compose/document/templates/proposal/presets/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Templates v2 proposal preset classes. - * - * @since 1.6.0 - */ -package com.demcha.compose.document.templates.proposal.presets; diff --git a/src/main/java/com/demcha/compose/document/templates/proposal/spec/ProposalSpec.java b/src/main/java/com/demcha/compose/document/templates/proposal/spec/ProposalSpec.java deleted file mode 100644 index d6199f80f..000000000 --- a/src/main/java/com/demcha/compose/document/templates/proposal/spec/ProposalSpec.java +++ /dev/null @@ -1,231 +0,0 @@ -package com.demcha.compose.document.templates.proposal.spec; - -import java.util.List; -import java.util.Objects; - -/** - * User-facing data record for a proposal in Templates v2. - * - *

Simplified compared to the legacy {@code ProposalData} — - * captures the essentials (title, parties, content sections, pricing - * rows) without the cinematic-presentation flags.

- * - * @param title proposal title (required) - * @param fromParty issuing party (required) - * @param toParty receiving party (required) - * @param sections ordered content sections (heading + body) - * @param pricingRows ordered pricing rows (label + value) - * @param footerNote optional closing line (may be empty) - */ -public record ProposalSpec( - String title, - Party fromParty, - Party toParty, - List
sections, - List pricingRows, - String footerNote) { - - /** - * Compact constructor that defensively copies lists and - * normalises null strings. - * - * @throws NullPointerException if a required field is null - * @throws IllegalArgumentException if {@code title} is blank - */ - public ProposalSpec { - Objects.requireNonNull(title, "title"); - Objects.requireNonNull(fromParty, "fromParty"); - Objects.requireNonNull(toParty, "toParty"); - if (title.isBlank()) { - throw new IllegalArgumentException("title must not be blank"); - } - sections = sections == null ? List.of() : List.copyOf(sections); - pricingRows = pricingRows == null ? List.of() : List.copyOf(pricingRows); - footerNote = footerNote == null ? "" : footerNote; - } - - /** - * One proposal party (sender or recipient). - * - * @param name legal / display name - * @param email contact email (may be empty) - * @param phone contact phone (may be empty) - */ - public record Party(String name, String email, String phone) { - - /** - * Compact constructor that normalises null strings and - * rejects a blank name. - * - * @throws NullPointerException if {@code name} is null - * @throws IllegalArgumentException if {@code name} is blank - */ - public Party { - Objects.requireNonNull(name, "name"); - if (name.isBlank()) { - throw new IllegalArgumentException("name must not be blank"); - } - email = email == null ? "" : email; - phone = phone == null ? "" : phone; - } - } - - /** - * One content section in the proposal. - * - * @param heading section heading - * @param body body text (may carry markdown markers) - */ - public record Section(String heading, String body) { - - /** - * Compact constructor that normalises null strings. - */ - public Section { - heading = heading == null ? "" : heading; - body = body == null ? "" : body; - } - } - - /** - * One pricing row — label + value pair appearing in the pricing - * summary block. - * - * @param label row label (e.g. "Discovery workshop") - * @param value row value (e.g. "GBP 1,450") - * @param isHeadline whether this row is the headline total - * (rendered emphasised by the preset) - */ - public record PricingRow(String label, String value, boolean isHeadline) { - - /** - * Compact constructor that normalises null strings. - */ - public PricingRow { - label = label == null ? "" : label; - value = value == null ? "" : value; - } - - /** - * Convenience factory for a non-headline row. - * - * @param label row label - * @param value row value - * @return pricing row with isHeadline = false - */ - public static PricingRow of(String label, String value) { - return new PricingRow(label, value, false); - } - - /** - * Convenience factory for the headline total row. - * - * @param label row label - * @param value row value - * @return pricing row with isHeadline = true - */ - public static PricingRow headline(String label, String value) { - return new PricingRow(label, value, true); - } - } - - /** - * Returns a fluent builder. - * - * @return new builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Mutable builder for {@link ProposalSpec}. - */ - public static final class Builder { - private String title; - private Party fromParty; - private Party toParty; - private final java.util.List
sections = new java.util.ArrayList<>(); - private final java.util.List pricingRows = new java.util.ArrayList<>(); - private String footerNote = ""; - - private Builder() { - } - - /** - * Sets the proposal title. - * - * @param value title - * @return this builder - */ - public Builder title(String value) { - this.title = value; - return this; - } - - /** - * Sets the sender party. - * - * @param value sender party - * @return this builder - */ - public Builder fromParty(Party value) { - this.fromParty = value; - return this; - } - - /** - * Sets the receiving party. - * - * @param value receiving party - * @return this builder - */ - public Builder toParty(Party value) { - this.toParty = value; - return this; - } - - /** - * Adds a content section. - * - * @param section content section - * @return this builder - */ - public Builder section(Section section) { - this.sections.add(section); - return this; - } - - /** - * Adds a pricing row. - * - * @param row pricing row - * @return this builder - */ - public Builder pricingRow(PricingRow row) { - this.pricingRows.add(row); - return this; - } - - /** - * Sets the footer note. - * - * @param note footer note - * @return this builder - */ - public Builder footerNote(String note) { - this.footerNote = note == null ? "" : note; - return this; - } - - /** - * Builds an immutable {@link ProposalSpec}. - * - * @return new proposal spec - */ - public ProposalSpec build() { - return new ProposalSpec(title, fromParty, toParty, - sections, pricingRows, footerNote); - } - } -} diff --git a/src/main/java/com/demcha/compose/document/templates/proposal/spec/package-info.java b/src/main/java/com/demcha/compose/document/templates/proposal/spec/package-info.java deleted file mode 100644 index a4d1c68c1..000000000 --- a/src/main/java/com/demcha/compose/document/templates/proposal/spec/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Templates v2 proposal specification records. - * - * @since 1.6.0 - */ -package com.demcha.compose.document.templates.proposal.spec; diff --git a/src/test/java/com/demcha/compose/document/templates/invoice/presets/ModernInvoiceSmokeTest.java b/src/test/java/com/demcha/compose/document/templates/invoice/presets/ModernInvoiceSmokeTest.java deleted file mode 100644 index 77c67cb03..000000000 --- a/src/test/java/com/demcha/compose/document/templates/invoice/presets/ModernInvoiceSmokeTest.java +++ /dev/null @@ -1,68 +0,0 @@ -package com.demcha.compose.document.templates.invoice.presets; - -import com.demcha.compose.GraphCompose; -import com.demcha.compose.document.api.DocumentPageSize; -import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.style.DocumentInsets; -import com.demcha.compose.document.templates.api.DocumentTemplate; -import com.demcha.compose.document.templates.invoice.spec.InvoiceSpec; -import com.demcha.compose.document.theme.BusinessTheme; -import org.junit.jupiter.api.Test; - -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Smoke test for the v2 invoice pipeline through - * {@link ModernInvoice}. - */ -class ModernInvoiceSmokeTest { - - private static final BusinessTheme THEME = BusinessTheme.modern(); - - private static InvoiceSpec sampleSpec() { - return InvoiceSpec.builder() - .invoiceNumber("GC-2026-001") - .issueDate("01 May 2026") - .dueDate("15 May 2026") - .fromParty(new InvoiceSpec.Party("GraphCompose Studio", - List.of("18 Layout Street", "London, UK"), - "billing@graphcompose.dev", - "+44 20 5555 1000", - "GB-99887766")) - .billToParty(new InvoiceSpec.Party("Northwind Systems", - List.of("Attn: Finance", "410 Market Avenue"), - "ap@northwind.example", "+44 161 555 2200", "")) - .lineItem(new InvoiceSpec.LineItem( - "Discovery workshop", "Stakeholder interviews", - "1", "GBP 1,450", "GBP 1,450")) - .lineItem(new InvoiceSpec.LineItem( - "Template architecture", "Reusable document flows", - "2", "GBP 980", "GBP 1,960")) - .summaryRow(InvoiceSpec.SummaryRow.of("Subtotal", "GBP 3,410")) - .summaryRow(InvoiceSpec.SummaryRow.of("VAT (20%)", "GBP 682")) - .summaryRow(InvoiceSpec.SummaryRow.total("Total", "GBP 4,092")) - .note("Payment due within 14 days.") - .build(); - } - - @Test - void exposesStableIdentity() { - DocumentTemplate template = ModernInvoice.create(THEME); - assertThat(template.id()).isEqualTo(ModernInvoice.ID); - assertThat(template.displayName()).isEqualTo(ModernInvoice.DISPLAY_NAME); - } - - @Test - void composeAddsRootDocumentNode() throws Exception { - DocumentTemplate template = ModernInvoice.create(THEME); - try (DocumentSession session = GraphCompose.document() - .pageSize(DocumentPageSize.A4) - .margin(DocumentInsets.of(28)) - .create()) { - template.compose(session, sampleSpec()); - assertThat(session.roots()).isNotEmpty(); - } - } -} diff --git a/src/test/java/com/demcha/compose/document/templates/proposal/presets/ModernProposalSmokeTest.java b/src/test/java/com/demcha/compose/document/templates/proposal/presets/ModernProposalSmokeTest.java deleted file mode 100644 index f767f8364..000000000 --- a/src/test/java/com/demcha/compose/document/templates/proposal/presets/ModernProposalSmokeTest.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.demcha.compose.document.templates.proposal.presets; - -import com.demcha.compose.GraphCompose; -import com.demcha.compose.document.api.DocumentPageSize; -import com.demcha.compose.document.api.DocumentSession; -import com.demcha.compose.document.style.DocumentInsets; -import com.demcha.compose.document.templates.api.DocumentTemplate; -import com.demcha.compose.document.templates.proposal.spec.ProposalSpec; -import com.demcha.compose.document.theme.BusinessTheme; -import org.junit.jupiter.api.Test; - -import static org.assertj.core.api.Assertions.assertThat; - -/** - * Smoke test for the v2 proposal pipeline through - * {@link ModernProposal}. - */ -class ModernProposalSmokeTest { - - private static final BusinessTheme THEME = BusinessTheme.modern(); - - private static ProposalSpec sampleSpec() { - return ProposalSpec.builder() - .title("Platform Refresh Sprint") - .fromParty(new ProposalSpec.Party("GraphCompose Studio", - "billing@graphcompose.dev", "+44 20 5555 1000")) - .toParty(new ProposalSpec.Party("Northwind Systems", - "platform@northwind.example", "+44 161 555 2200")) - .section(new ProposalSpec.Section("Overview", - "We propose a focused refresh of your **internal " - + "platform** documents — invoice, proposal, and CV — " - + "using the GraphCompose v1.6 architecture.")) - .section(new ProposalSpec.Section("Scope", - "Discovery, template architecture, render QA, and " - + "developer enablement, delivered across a " - + "4-week sprint.")) - .pricingRow(ProposalSpec.PricingRow.of("Discovery workshop", "GBP 1,450")) - .pricingRow(ProposalSpec.PricingRow.of("Template architecture", "GBP 1,960")) - .pricingRow(ProposalSpec.PricingRow.of("Render QA", "GBP 960")) - .pricingRow(ProposalSpec.PricingRow.headline("Total", "GBP 4,370")) - .footerNote("*Thank you for considering GraphCompose Studio.*") - .build(); - } - - @Test - void exposesStableIdentity() { - DocumentTemplate template = ModernProposal.create(THEME); - assertThat(template.id()).isEqualTo(ModernProposal.ID); - assertThat(template.displayName()).isEqualTo(ModernProposal.DISPLAY_NAME); - } - - @Test - void composeAddsRootDocumentNode() throws Exception { - DocumentTemplate template = ModernProposal.create(THEME); - try (DocumentSession session = GraphCompose.document() - .pageSize(DocumentPageSize.A4) - .margin(DocumentInsets.of(28)) - .create()) { - template.compose(session, sampleSpec()); - assertThat(session.roots()).isNotEmpty(); - } - } -}