fix(recipes): read ingredient_type/ingredient_id so components keep their name - #14
Merged
Conversation
…heir name The API now types each recipe ingredient and returns an id that resolves against the matching endpoint, because a component ingredient's material_id pointed at the component's paired stock record and could never be looked up. material_id and material_name became deprecated aliases, null for components. RecipeIngredient.MaterialID was a non-pointer int, so those nulls unmarshalled to the zero value and nameOrID fell through to strconv.Itoa(0): every component ingredient rendered as a literal "0" instead of its name. Reads the new fields, keeping the deprecated aliases as a fallback so the CLI still renders correctly against an API that predates ingredient_type. The ingredient table gains a TYPE column and its first column is now INGREDIENT rather than MATERIAL, since components were never materials.
Deploying with
|
| Status | Name | Latest Commit | Updated (UTC) |
|---|---|---|---|
| ❌ Deployment failed View logs |
stocksmith-cli | 78b05c6 | Aug 15 2026, 07:27 AM |
obitum
added a commit
that referenced
this pull request
Aug 28, 2026
) The API (CU-868kwy85j) now types purchase receipt three ways and reports inbound stock. Previously an expense carried only a header `received` boolean, which the app sets as an AND across the lines — so a purchase with 3 of 5 lines arrived and one where nothing arrived both serialised `received: false`. Integrations could not tell them apart, which is why customers close a part-arrived purchase and re-enter the remainder as a new one. There was also no route to inbound quantities at all: /expenses is gated on the material-costs permission, so an operations integration that cannot see supplier pricing could not call it. - expenses: read `received_status` and render it as the RECEIPT column, replacing the RECEIVED yes/no. The rollup is a superset of the boolean, so a separate column would only ever restate it. - expenses: read the per-line `received` and add a RECEIVED column to the line-items sub-table in `expenses show` - expenses: add --received-status, passed through verbatim (comma-separate for more than one); the API validates and 400s on an unknown value - materials: read `on_order` and add an ON ORDER column. It is reported in stock units, so it carries the same unit of measure as ON HAND — never the purchase unit, which is the easy mistake here. - output: add FormatBoolPtr for a nullable boolean Three back-compat guards, since --api-url can point at any deployment. ExpenseLineItem.Received is a *bool so an API that predates per-line receipt renders "—" rather than reporting every line as not yet arrived; receiptStatus() falls back to the header boolean and yields only "received" or "outstanding", never inventing "partial"; and an absent on_order renders "—" rather than an invented zero. This is the same failure #14 fixed, where a null unmarshalled to a zero value and rendered as a literal "0". Contract tests now assert on_order is a string and stays a top-level material field rather than moving inside the gated unit_cost block, that received_status is one of the three strings, and that the header received boolean survives alongside it. Verified: go build, go vet, gofmt, go test ./... -race, and the rendered output of expenses list/show and materials list against a mock API. golangci-lint is not installed locally; CI covers it. Co-authored-by: Claude Opus 5 (1M context) <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.
What / Why
stocksmith recipes show <id>printed a literal0in place of every component ingredient's name.The API (craftybase-app#5709, CU-868kqwr7q) now types each recipe ingredient and returns an id that resolves against the matching endpoint. That change was needed because a component ingredient's
material_idpointed at the component's paired stock record, which the materials endpoint excludes — so it could never be looked up.material_id/material_namebecame deprecated aliases,nullfor components:{ "id": 6001, "ingredient_type": "component", "ingredient_id": 2167942, "ingredient_name": "Punk IPA", "material_id": null, "material_name": null, "quantity": "0.5", "unit": "litres" }RecipeIngredient.MaterialIDwas a non-pointerint, so those nulls unmarshalled silently to the zero value (no error), and the renderernameOrID(ing.MaterialName, ing.MaterialID)fell through tostrconv.Itoa(0). Any recipe containing a component — i.e. any multi-level BOM — lost the ingredient's name.The change
RecipeIngredientreadsingredient_type,ingredient_id,ingredient_name;MaterialIDbecomes*intto match howExpenseLineItemalready models a nullable material.label()prefers the new fields and falls back to the deprecated aliases, so the CLI still renders correctly against an API that predatesingredient_type(relevant during rollout).Before / after for a bottling recipe:
Testing
go test ./... -racepasses. Added:label()falls back tomaterial_name, thenmaterial_id, then—, covering pre-ingredient_typeresponses.gofmtandgo vetare clean. golangci-lint isn't installed locally, so CI is the first run of it.