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(); - } - } -}