feat(harmonia): Attachments / Snapshot files panel (function:Attachment UI) - #6373
Merged
Conversation
…d (end-to-end) A `function: Attachment` entity is a first-class composition child of its master (native 1:n, master-detail wiring unchanged), turning file attachments into a modeled, type-safe detail rather than a bespoke subsystem. Builds on the merged CMS Attachments SDK (#6370). Intent/EDM (engine-intent): - `attachment` added to IntentParser ENTITY_FUNCTIONS; EntityIntent.isAttachment(). - EdmIntentGenerator injects the standard file-metadata columns (FileName [major], ContentType, FileSize [BIGINT], StoragePath, Uuid — all read-only, upload-set) + implicit audit, and marks the entity attachmentEntity="true". The author declares no primary key on an attachment child, so a generated integer Id is synthesized (otherwise the entity/ controller would have no PK). Controller (template-application-rest-java): - Gated on attachmentEntity, the generated controller gains POST /upload (multipart → stores each file in the tenant CMS at /Attachments/<Master>/<yyyy>/<MM>/<uuid>/<file>, persists one row per file with the reference + metadata), GET /{id}/download (permission- scoped stream, never a raw documents?path=), and deleteById also removes the CMS file. SDK (api-modules-java): - Attachments.storeUploads(master) reads the servlet parts Spring's multipart resolver has already parsed (the controller is served by a Spring @PostMapping, which consumes the body before commons-fileupload could), so the generated controller only ever sees Attachment. Test: EdmIntentGeneratorTest asserts the injected metadata + Id PK; RecordAttachmentIT drives the full HTTP round-trip (multipart upload → verbatim download → delete → gone) through the real multipart servlet path and tenant CMS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…hment children Renders a composition child declared `function: Attachment` (or, forthcoming, `function: Snapshot`) as a purpose-built Files panel instead of a generic row table — the UI half of the record-attachments feature (backend in #6371, storage SDK in #6370). Shared runtime (application-core): - detailPanel gains a `files` mode (parallel to the existing `calendar` mode): lists the master-filtered files with per-file Download (a plain browser GET to the controller's `/{id}/download`), and — unless read-only — an Upload control (multipart POST to `/upload`) and per-file Remove (reuses the detail delete, which also drops the CMS file). Like a calendar, a files panel always renders (its empty state is meaningful). - api client: request() now sends a FormData body as-is (browser sets the multipart boundary) instead of JSON-encoding it, so the upload goes through the same client + auth/error path. Harmonia templates: - detail-register emits `files: { readOnly: <attachmentReadOnly> }` for an `attachmentEntity` child (read-only flips it to download-only — the generated Snapshot case). - form-view (editable), document-view (editable), master-view (read-only browse) each render the files branch: an x-h-info-page empty state + file cards (name/size/Download[/Remove]), gated on `def.files`; the row table + Add button are suppressed for a files detail. $refs is ${dollar}-escaped so Velocity leaves the Alpine ref alone. Editable path verified end-to-end on a generated CompanyAttachment (upload → master-filtered list → verbatim download → remove). Read-only (Snapshot) branch is in place, activated once the `attachmentReadOnly` marker lands with `function: Snapshot`. Depends on #6371 (function: Attachment backend + controller verbs). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
delchev
added a commit
that referenced
this pull request
Jul 22, 2026
…rd copies A sibling of function: Attachment (#6371) for SYSTEM-GENERATED copies (e.g. the printed invoice stored on issue) rather than user-uploaded files. Same composition-child + CMS + file-metadata machinery, but read-only and versioned. - IntentParser: `snapshot` added to ENTITY_FUNCTIONS. - EntityIntent: isSnapshot() + isFileChild() (attachment or snapshot — shared injection). - EdmIntentGenerator: a file-child gets the injected metadata columns + synthesized Id + audit (now keyed on isFileChild). A Snapshot additionally gets a read-only, major `Version` column carrying the new DOCUMENT_VERSION widget (the copy's sequence within its master, for special treatment in forms/print), and is marked attachmentReadOnly="true" alongside attachmentEntity. - EntityController template: the `/upload` verb is gated off when attachmentReadOnly — a Snapshot controller exposes download + list only (no upload; copies are created server-side by the forthcoming generateSnapshot serviceTask). The read-only Harmonia Files panel (#6373) already keys on attachmentReadOnly (download only, no upload/remove). Verified: EdmIntentGeneratorTest (metadata + Id + Version/DOCUMENT_VERSION + attachmentReadOnly); a generated CompanySnapshotController has @get /{id}/download and NO @post /upload, and the model carries Version(DOCUMENT_VERSION) + attachmentReadOnly=true. Part A of the Sales-Invoice snapshot flow. Follow-ups: C (server-side print render) and D (generateSnapshot BPM serviceTask + delegate, mint on issue). Depends on #6371. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
delchev
added a commit
that referenced
this pull request
Jul 22, 2026
…rd copies (#6374) A sibling of function: Attachment (#6371) for SYSTEM-GENERATED copies (e.g. the printed invoice stored on issue) rather than user-uploaded files. Same composition-child + CMS + file-metadata machinery, but read-only and versioned. - IntentParser: `snapshot` added to ENTITY_FUNCTIONS. - EntityIntent: isSnapshot() + isFileChild() (attachment or snapshot — shared injection). - EdmIntentGenerator: a file-child gets the injected metadata columns + synthesized Id + audit (now keyed on isFileChild). A Snapshot additionally gets a read-only, major `Version` column carrying the new DOCUMENT_VERSION widget (the copy's sequence within its master, for special treatment in forms/print), and is marked attachmentReadOnly="true" alongside attachmentEntity. - EntityController template: the `/upload` verb is gated off when attachmentReadOnly — a Snapshot controller exposes download + list only (no upload; copies are created server-side by the forthcoming generateSnapshot serviceTask). The read-only Harmonia Files panel (#6373) already keys on attachmentReadOnly (download only, no upload/remove). Verified: EdmIntentGeneratorTest (metadata + Id + Version/DOCUMENT_VERSION + attachmentReadOnly); a generated CompanySnapshotController has @get /{id}/download and NO @post /upload, and the model carries Version(DOCUMENT_VERSION) + attachmentReadOnly=true. Part A of the Sales-Invoice snapshot flow. Follow-ups: C (server-side print render) and D (generateSnapshot BPM serviceTask + delegate, mint on issue). Depends on #6371. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
delchev
added a commit
that referenced
this pull request
Jul 22, 2026
…opy on issue (#6377) * feat(intent): function: Snapshot — generated read-only versioned record copies A sibling of function: Attachment (#6371) for SYSTEM-GENERATED copies (e.g. the printed invoice stored on issue) rather than user-uploaded files. Same composition-child + CMS + file-metadata machinery, but read-only and versioned. - IntentParser: `snapshot` added to ENTITY_FUNCTIONS. - EntityIntent: isSnapshot() + isFileChild() (attachment or snapshot — shared injection). - EdmIntentGenerator: a file-child gets the injected metadata columns + synthesized Id + audit (now keyed on isFileChild). A Snapshot additionally gets a read-only, major `Version` column carrying the new DOCUMENT_VERSION widget (the copy's sequence within its master, for special treatment in forms/print), and is marked attachmentReadOnly="true" alongside attachmentEntity. - EntityController template: the `/upload` verb is gated off when attachmentReadOnly — a Snapshot controller exposes download + list only (no upload; copies are created server-side by the forthcoming generateSnapshot serviceTask). The read-only Harmonia Files panel (#6373) already keys on attachmentReadOnly (download only, no upload/remove). Verified: EdmIntentGeneratorTest (metadata + Id + Version/DOCUMENT_VERSION + attachmentReadOnly); a generated CompanySnapshotController has @get /{id}/download and NO @post /upload, and the model carries Version(DOCUMENT_VERSION) + attachmentReadOnly=true. Part A of the Sales-Invoice snapshot flow. Follow-ups: C (server-side print render) and D (generateSnapshot BPM serviceTask + delegate, mint on issue). Depends on #6371. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(intent): generateSnapshot delegate — render + store a document copy on issue Part D of the Sales-Invoice snapshot flow: generates the JavaDelegate a process wires as a `delegate:` service task to mint an immutable printed copy of a document on issue (the number stays across amendments; only the snapshot Version increments). - SnapshotSupport builds the `snapshots` glue collection — one descriptor per function: Snapshot child of a DOCUMENT master (which has a PrintFeeder to render from): master, master PK (the process variable), the snapshot entity/perspective/master-FK, language. - GlueIntentGenerator emits the `snapshots` collection into the .glue. - generateUtils adds the `snapshots` case (sanitizes the perspective → javaPerspective, its own loop like printFeeders/settlements). - Snapshot.java.template generates gen/events/<Master>SnapshotGenerator.java: reads the master id from the process variable, reuses the master's generated PrintFeeder (same gen.events package) for the {document, items} payload, renders server-side via sdk.print.Print (#6376), stores the PDF via sdk.cms.Attachments (#6370), and writes a <Snapshot> row with Version = max existing + 1 (gap-free — snapshots are immutable). Bound by the BPMN via the existing `delegate:` (flowable:class) path — no new parser/BPMN wiring, and ServiceTaskHandlerGenerator already skips a delegate: task. Verified: on the SalesInvoice document master with a function: Snapshot child + a `generateSnapshot` delegate step, the .glue carries the snapshots descriptor, gen/events/SalesInvoiceSnapshotGenerator .java is emitted, and it compiles cleanly into the client bean container (538 beans, no javac errors). Depends on: #6374 (function: Snapshot type — the PR base), #6376 (sdk.print.Print), #6371/#6370 (sdk.cms.Attachments). Follow-up: KF adoption wires generateSnapshot into the SalesInvoice process after markIssued + the Confirm/Amend loop; a full process-run e2e mints the first snapshot on issue. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
UI half of record attachments: a
function: Attachment(and forthcomingfunction: Snapshot) composition child renders as a Files panel instead of a row table. Backend #6371, SDK #6370 (merged).Shared runtime (application-core): detailPanel gains a
filesmode (parallel tocalendar) — master-filtered list + per-file Download; unless read-only, an Upload control (multipart POST /upload) + per-file Remove (reuses detail delete, which drops the CMS file). api client sends a FormData body as-is (browser sets the boundary) instead of JSON-encoding.Harmonia templates: detail-register emits$refs is $ {dollar}-escaped for Velocity.
files: { readOnly: <attachmentReadOnly> }; form-view/document-view (editable) + master-view (read-only browse) render an x-h-info-page empty state + file cards gated ondef.files(row table + Add suppressed).Verified end-to-end on a generated CompanyAttachment (upload → master-filtered list → verbatim download → remove). Read-only Snapshot branch in place, activates with the
attachmentReadOnlymarker.Depends on #6371 + #6370 (merged). Follow-up:
function: Snapshot+ DOCUMENT_VERSION widget + server-side print render.🤖 Generated with Claude Code