Skip to content

The import carries the dish, not only its price - #827

Merged
sridharkalaibala merged 5 commits into
developfrom
feat/the-import-carries-the-dish
Sep 16, 2026
Merged

sridharkalaibala merged 5 commits into
developfrom
feat/the-import-carries-the-dish

Conversation

@sridharkalaibala

Copy link
Copy Markdown
Contributor

Stage 1 of the Azure Coastal Kitchen menu roadmap, and the one piece of it that needs nothing from you.

"you need to fill the details of menu. description and nutrition, veg or non veg, other all details needs to be filled one by one. very detailed."

Every one of those fields already exists on an item and already has a box on the item screen. What did not exist was a way to fill in ninety of them without opening ninety forms: the item import carries the price-and-stock columns and nothing else, and sets description to empty on the way past.

api/src/utils/dish-columns.js is the translation between a column in a file and a field on a dish. It is its own file because the rules it enforces are the item form's own rules - an import that can write what the form would have refused is not an import, it is a back door, and the form filters food tags against the tickable list precisely so that a client asking for heart_healthy stores nothing.

Three rules

A column the file does not carry changes nothing. This is the one that matters to the other 89 shops. Everybody re-imports the 18-column export to change prices; if a missing column meant "make it empty", the first price update after a menu was written would erase the menu. So the dish detail is spread in from what the row actually has, rather than listed - the opposite of how the rest of the import behaves.

A column that is there and empty clears the field, because otherwise there is no way to take a value back out through the door it went in.

Nothing is dropped in silence. A tag that is not a tag, a diet word nobody knows, a pairing naming a dish that is not on the menu: each comes back in the message the import already shows. Three, then a count - a shop that mistyped one column produces one note per row, and a hundred of them is a wall, not a message.

Nutrition arrives as an estimate unless the file says kitchen

An empty nutrition_source is what turns the calorie figure and the health claims ON. It cannot be what a file gets by default: a spreadsheet of nutrition drafted from standard recipes, imported with no source column, would otherwise arrive claiming a kitchen stood behind every figure, on a live menu. Writing the word kitchen in the column is a person asserting it, which is the same act as the box on the item screen.

Nutrition merges per nutrient, so a file with only a calories column leaves the protein somebody typed last month alone, and an empty cell removes that one figure.

Cross-selling, by name

"chickent briyani link to chicken 65 or mojito or coke"

Nobody types an ObjectId into a spreadsheet, so pairings arrive as dish names and resolve after every row has been written - the file is usually the whole menu, so a row pairs with dishes that did not exist when it was read. A name the file itself carries matches whatever the case; a name only in the database is looked up once, through safe-search, so "Chicken 65 (Boneless)" reaches Mongo as a name and not as a pattern. Same three caps as the form: never itself, never twice, six at most.

(The price rule that stops a coke suggesting a biryani back is #824, separately.)

Columns

description, diet, icon, food_tags, menu_marks, spice_choice, prep_note, prep_minutes, goes_with, nutrition_source, calories, protein_g, carbs_g, fat_g, sat_fat_g, fibre_g, sugar_g, sodium_mg.

Headers read however they are capitalised or spaced, with the obvious alternative spellings (kcal, protein, fiber_g, food_type, ready_in_minutes). Diet accepts what a kitchen would actually type: "Non Veg", "non-veg", "N".

What is deliberately NOT here

The export is not widened. It would put 18 more columns in front of every retail shop that will never set a spice level, to solve a problem they do not have. The round trip works without it: export the 18 columns, add the ones you need, import - nothing is blanked, because of the first rule.

Tests

69 new (53 on the columns, 16 on the import). The ones worth reading are "an ordinary price re-import DOES NOT TOUCH THE MENU SOMEBODY WROTE", "NUMBERS IN A FILE ARE AN ESTIMATE", and "A HEALTH CLAIM ASKED FOR BY NAME STORES NOTHING". Full api suite green: 389 suites / 11,036 tests.

The diet list now has one definition instead of two - item.repository had a private copy, and a second place writes the field now.

Branched on top of #826 so CI is green; merge that first and this reduces to its own commit.

Two files arrived wrapped at 80 columns. The api package sets 100, so
its format:check fails on develop and every branch cut from it inherits
a red run that has nothing to do with what it changed - the same thing
that happened once already.

Whitespace only: two statements rejoined onto one line.
Owner, on Azure Coastal Kitchen: "you need to fill the details of menu.
description and nutrition, veg or non veg, other all details needs to be
filled one by one. very detailed."

Every one of those fields already exists on an item and already has a box
on the item screen. What did not exist was a way to fill in ninety of
them without opening ninety forms: the item import carries the
price-and-stock columns and nothing else, and sets description to empty
on the way past. So a menu can only be detailed by hand.

utils/dish-columns is the translation between a column in a file and a
field on a dish. It is its own file because the rules it enforces are the
item form's rules - an import that can write what the form would have
refused is not an import, it is a back door, and the form filters food
tags against the tickable list precisely so that a client asking for
heart_healthy stores nothing.

THREE RULES.

A column the file does not carry changes nothing. This is the one that
matters to the other 89 shops: everybody re-imports the 18-column export
to change prices, and if a missing column meant "make it empty", the
first price update after a menu was written would erase the menu. So the
dish detail is spread in from what the row actually has, not listed.

A column that is there and empty clears the field, because otherwise
there is no way to take a value back out through the door it went in.

Nothing is dropped in silence. A tag that is not a tag, a diet word
nobody knows, a pairing naming a dish that is not on the menu: each comes
back in the message the import already shows, three then a count.

NUMBERS IN A FILE ARE AN ESTIMATE UNLESS THE FILE SAYS KITCHEN. An empty
nutrition_source is what turns the calorie figure and the health claims
on, so it cannot be a default: a spreadsheet drafted from standard
recipes would otherwise arrive claiming a kitchen had checked every
figure, on a live menu. Nutrition merges per nutrient, so a file with
only a calories column leaves the protein somebody typed alone.

Pairings arrive as NAMES and resolve after every row is written, because
the file is usually the whole menu and a row pairs with dishes that did
not exist yet when it was read. A name the file carries matches whatever
the case; one that is only in the database is looked up once, through
safe-search, so "Chicken 65 (Boneless)" is a name and not a pattern.
Same three caps as the form: never itself, never twice, six at most.

The export is deliberately not widened. It would put 18 more columns in
front of every retail shop that will never set a spice level. The round
trip works without it, because of the first rule.

69 tests. The diet list now has one definition rather than two.
Three files wrapped at 80 where the api package sets 100, and one
generated list that a new server message never got added to.

`languages/server/_english.json` is written by
tests/tools/i18n-server-text.js and is what every translation pack is
checked against. A message added to the server without regenerating it
makes the i18n suite fail on develop and on every branch cut from it.
`order_changed` arrived with the order-conflict work.

Whitespace and one generated line. No behaviour.

The two causes are worth naming because both are silent locally: prettier
run from the repo root skips api/ entirely (the root .prettierignore), so
`cd api && npm run format` is the one that catches the first, and the
second has a tool that writes the answer.
@sridharkalaibala
sridharkalaibala merged commit 7bb41b9 into develop Sep 16, 2026
10 checks passed
@github-actions github-actions Bot added the ready for QA Merged to develop and live on develop.posnic.io - anyone can test it label Sep 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Merged to develop. Anyone can test this - you do not need write access.

Try it at https://develop.posnic.io, or run it yourself:

git fetch origin develop && git checkout develop
npm install && npm --prefix api install
npm run dev   # then http://localhost:3000

When you have tested it, say what you did and what happened, and set
QA passed or QA failed. If you cannot set labels, just comment -
a maintainer will.

Reporting that something is broken is as useful as fixing it. It is
better found here than by a shopkeeper.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for QA Merged to develop and live on develop.posnic.io - anyone can test it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant