diff --git a/AGENTS.md b/AGENTS.md index 268a14c..7803e4c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,7 +94,8 @@ These are current project assumptions and should not be casually changed. - Preview uses label-preview flags - Submit/create flow uses upsert apply (`/kitchen/items/upsert?mode=apply`) -- Auto-print is deferred and should not be assumed in current UI submit flow +- UI exposes a `Print` checkbox next to save (default on for current page session) +- If `Print` is enabled and save succeeds, label printing uses `/kitchen/items/{uuid_b64}/print-label` ### Item-definition search for label creation diff --git a/README.md b/README.md index e61e87b..437fb22 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,8 @@ Expected shapes today: Updates measured or descriptive stock state using `{ quantity }` or `{ level }`. - `POST /{database}/kitchen/items/{uuid_b64}/use` Marks an item used up (`gone`) via stock-event semantics. +- `POST /{database}/kitchen/items/{uuid_b64}/print-label` + Prints label for an existing item; called from the save flow when `Print` is enabled. - `DELETE /{database}/kitchen/items/{uuid_b64}` Compatibility fallback when `/use` is not available on the backend. - `GET /{database}/kitchen/locations` @@ -188,4 +190,4 @@ Expected shapes today: - Local storage only keeps non-sensitive app config, session payload, active kitchen, and label draft state. - Kitchen context now lives in the URL path instead of a custom header. - The API client now builds database-scoped kitchen routes by default; it always keeps bearer authentication handling separate from URL shaping. -- Label submit now uses upsert-first apply semantics; auto-print is intentionally deferred. +- Label submit uses upsert-first apply semantics and an optional `Print` checkbox (default on for the current page session). diff --git a/src/api/labels.js b/src/api/labels.js index fab528d..514635f 100644 --- a/src/api/labels.js +++ b/src/api/labels.js @@ -52,3 +52,57 @@ export async function previewLabel(store, body) { throw new Error('Label preview response did not include an image.'); } + +export async function printItemLabel(store, uuidB64) { + return apiRequest(store, `${getPath('items')}/${uuidB64}/print-label`, { + method: 'POST', + }); +} + +function flattenDetails(details) { + if (!details) { + return ''; + } + + if (typeof details === 'string') { + return details; + } + + if (Array.isArray(details)) { + return details + .map((entry) => (typeof entry === 'string' ? entry : JSON.stringify(entry))) + .join(' | '); + } + + if (typeof details === 'object') { + return Object.entries(details) + .map(([key, value]) => `${key}: ${value}`) + .join(' | '); + } + + return String(details); +} + +export function formatPrintErrorMessage(error) { + const status = error?.status || error?.cause?.status; + const payload = error?.payload || error?.cause?.payload || {}; + const code = String(payload?.code || '').toLowerCase(); + const detailsText = flattenDetails(payload?.details || error?.details || error?.cause?.details); + + let message; + if (code === 'printer_unavailable') { + message = 'Printer is unavailable.'; + } else if (code === 'print_failed') { + message = 'Label printing failed.'; + } else if (status === 503) { + message = 'Printer service is unavailable.'; + } else if (status === 404) { + message = 'Saved item could not be found for printing.'; + } else if (status === 400) { + message = 'Print request was invalid.'; + } else { + message = error?.message || 'Printing failed.'; + } + + return detailsText ? `${message} (${detailsText})` : message; +} diff --git a/src/features/labels/label-create-page.js b/src/features/labels/label-create-page.js index 4b08c7d..a7c09e6 100644 --- a/src/features/labels/label-create-page.js +++ b/src/features/labels/label-create-page.js @@ -4,7 +4,11 @@ import { searchItemDefinitions, } from '../../api/stock.js'; import { fetchLocations } from '../../api/locations.js'; -import { previewLabel } from '../../api/labels.js'; +import { + formatPrintErrorMessage, + previewLabel, + printItemLabel, +} from '../../api/labels.js'; import { STORAGE_KEYS } from '../../app/config.js'; import { debounce, normalizeValidationError } from '../shared/form-utils.js'; import { loadStoredValue, saveStoredValue } from '../shared/storage.js'; @@ -404,7 +408,7 @@ export function renderLabelCreatePage() {
- + @@ -412,18 +416,28 @@ export function renderLabelCreatePage() { -